@oniroproject/core 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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);
@@ -1570,7 +1612,7 @@ function runProcess(opts) {
1570
1612
  const { command, args, cwd, env, onOutput } = opts;
1571
1613
  const timeoutMs = opts.timeoutMs ?? 6e4;
1572
1614
  const { abortSignal } = opts;
1573
- return new Promise((resolve6, reject) => {
1615
+ return new Promise((resolve7, reject) => {
1574
1616
  if (abortSignal?.aborted) {
1575
1617
  reject(new CancelledError(`Command aborted before start: ${command}`));
1576
1618
  return;
@@ -1635,21 +1677,41 @@ ${stderr.trim()}` : "")
1635
1677
  );
1636
1678
  return;
1637
1679
  }
1638
- resolve6({ code: code ?? -1, stdout, stderr });
1680
+ resolve7({ code: code ?? -1, stdout, stderr });
1639
1681
  });
1640
1682
  });
1641
1683
  }
1642
- function hdcExec(opts) {
1684
+ function resolveDeviceSerial(explicit, env = process.env) {
1685
+ if (explicit && explicit !== "auto") return explicit;
1686
+ const fromEnv = env.ONIRO_DEVICE_SERIAL || env.DEVICE_SERIAL;
1687
+ return fromEnv && fromEnv !== "auto" ? fromEnv : void 0;
1688
+ }
1689
+ function hdcTargetArgs(explicit) {
1690
+ const serial = resolveDeviceSerial(explicit);
1691
+ return serial ? ["-t", serial] : [];
1692
+ }
1693
+ var HOST_COMMANDS = /* @__PURE__ */ new Set(["list", "tconn", "start", "kill", "version", "checkserver"]);
1694
+ function hdcTargetArgsFor(args, explicit) {
1695
+ if (HOST_COMMANDS.has(args[0] ?? "")) return explicit ? ["-t", explicit] : [];
1696
+ return hdcTargetArgs(explicit);
1697
+ }
1698
+ function hdcClientFailure(output) {
1699
+ const first = output.split(/\r?\n/).find((l) => l.trim() !== "")?.trim() ?? "";
1700
+ return first.startsWith("[Fail]") ? first : null;
1701
+ }
1702
+ async function hdcExec(opts) {
1643
1703
  const hdc = getHdcPath(opts.config);
1644
- const argv = opts.deviceSerial ? ["-t", opts.deviceSerial, ...opts.args] : [...opts.args];
1645
- return runProcess({
1704
+ const res = await runProcess({
1646
1705
  command: hdc,
1647
- args: argv,
1706
+ args: [...hdcTargetArgsFor(opts.args, opts.deviceSerial), ...opts.args],
1648
1707
  timeoutMs: opts.timeoutMs,
1649
1708
  abortSignal: opts.abortSignal,
1650
1709
  onOutput: opts.onOutput,
1651
1710
  logger: opts.logger
1652
1711
  });
1712
+ if (res.code === 0 && hdcClientFailure(`${res.stdout}
1713
+ ${res.stderr}`)) return { ...res, code: 1 };
1714
+ return res;
1653
1715
  }
1654
1716
  function shell(opts) {
1655
1717
  const { command, ...rest } = opts;
@@ -1657,10 +1719,15 @@ function shell(opts) {
1657
1719
  }
1658
1720
  function ensureOk(result, command) {
1659
1721
  if (result.code !== 0) {
1660
- throw new CommandFailedError(command, result.code, result.stderr.trim());
1722
+ throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
1661
1723
  }
1662
1724
  return result;
1663
1725
  }
1726
+ function withTargetHint(detail) {
1727
+ if (!/need connect-key|Not match target/i.test(detail)) return detail;
1728
+ return `${detail}
1729
+ 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\`.`;
1730
+ }
1664
1731
  var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
1665
1732
  function parseRemoteExit(stdout) {
1666
1733
  let code = null;
@@ -1677,7 +1744,7 @@ async function shellChecked(opts) {
1677
1744
  }
1678
1745
  function ensureRemoteOk(result, command) {
1679
1746
  if (result.code !== 0) {
1680
- throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
1747
+ throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
1681
1748
  }
1682
1749
  return result;
1683
1750
  }
@@ -1763,11 +1830,11 @@ async function startEmulator(opts) {
1763
1830
  fs16.writeFileSync(PID_FILE, String(child.pid));
1764
1831
  child.unref();
1765
1832
  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));
1833
+ const earlyExit = new Promise((resolve7) => {
1834
+ child.once("exit", (code) => resolve7(code));
1835
+ child.once("error", () => resolve7(-1));
1769
1836
  });
1770
- const liveness = new Promise((resolve6) => setTimeout(() => resolve6("alive"), 2e3));
1837
+ const liveness = new Promise((resolve7) => setTimeout(() => resolve7("alive"), 2e3));
1771
1838
  const outcome = await Promise.race([earlyExit, liveness]);
1772
1839
  if (outcome !== "alive") {
1773
1840
  const tail = readTail(logPath, 4e3);
@@ -1847,6 +1914,43 @@ async function stopEmulator(logger = noopLogger) {
1847
1914
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
1848
1915
  });
1849
1916
  }
1917
+ async function isQemuRunning(logger) {
1918
+ if (os8.platform() !== "win32") {
1919
+ try {
1920
+ const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
1921
+ if (Number.isFinite(pid) && pid > 0) {
1922
+ process.kill(pid, 0);
1923
+ return true;
1924
+ }
1925
+ } catch {
1926
+ }
1927
+ }
1928
+ const [cmd, ...args] = os8.platform() === "win32" ? ["tasklist", "/FI", "IMAGENAME eq qemu-system-x86_64.exe", "/NH"] : ["pgrep", "-f", "qemu-system-x86_64"];
1929
+ try {
1930
+ const res = await runProcess({ command: cmd, args, timeoutMs: 1e4, logger });
1931
+ return os8.platform() === "win32" ? /qemu-system-x86_64/i.test(res.stdout) : res.code === 0;
1932
+ } catch {
1933
+ return false;
1934
+ }
1935
+ }
1936
+ async function getEmulatorStatus(config, opts = {}) {
1937
+ const logger = opts.logger ?? noopLogger;
1938
+ const address = opts.address ?? "127.0.0.1:55555";
1939
+ const dir = getEmulatorDir(config);
1940
+ let connected = false;
1941
+ try {
1942
+ const targets = await hdcExec({ config, args: ["list", "targets", "-v"], timeoutMs: 1e4, logger });
1943
+ connected = targets.stdout.split("\n").some((l) => l.trim().startsWith(address) && /\bConnected\b/.test(l));
1944
+ } catch {
1945
+ }
1946
+ return {
1947
+ installed: isEmulatorInstalled(config),
1948
+ dir,
1949
+ running: await isQemuRunning(logger),
1950
+ address,
1951
+ connected
1952
+ };
1953
+ }
1850
1954
 
1851
1955
  // src/hdc/devices.ts
1852
1956
  var STATUS_TOKENS = /* @__PURE__ */ new Set([
@@ -1885,9 +1989,8 @@ async function listDevices(config, opts = {}) {
1885
1989
  return parseDeviceList(res.stdout);
1886
1990
  }
1887
1991
  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;
1992
+ const resolved = resolveDeviceSerial(serial);
1993
+ if (resolved) return resolved;
1891
1994
  const usable = (await listDevices(config, opts)).filter(
1892
1995
  (d) => d.status === "Connected" || d.status === "Unknown"
1893
1996
  );
@@ -1901,6 +2004,7 @@ async function selectDevice(config, serial, opts = {}) {
1901
2004
  }
1902
2005
 
1903
2006
  // src/hdc/param.ts
2007
+ var PARAM_MISSING_RE = /^Get parameter ".*" fail!/;
1904
2008
  async function paramGet(config, key, opts = {}) {
1905
2009
  const res = await shell({
1906
2010
  config,
@@ -1910,7 +2014,30 @@ async function paramGet(config, key, opts = {}) {
1910
2014
  logger: opts.logger
1911
2015
  });
1912
2016
  ensureOk(res, `hdc shell param get ${key}`);
1913
- return res.stdout.trim();
2017
+ const value = res.stdout.trim();
2018
+ if (PARAM_MISSING_RE.test(value)) throw new OniroError(`System parameter ${key} is not set: ${value}`);
2019
+ return value;
2020
+ }
2021
+ var PARAM_SEPARATOR = "__ONIRO_PARAM__";
2022
+ function parseParamValues(keys, stdout) {
2023
+ const parts = stdout.split(PARAM_SEPARATOR);
2024
+ const values = {};
2025
+ keys.forEach((key, i) => {
2026
+ const value = parts[i]?.trim();
2027
+ values[key] = value && !PARAM_MISSING_RE.test(value) ? value : void 0;
2028
+ });
2029
+ return values;
2030
+ }
2031
+ async function paramGetMany(config, keys, opts = {}) {
2032
+ const res = await shell({
2033
+ config,
2034
+ command: keys.map((k) => `param get ${k}; echo ${PARAM_SEPARATOR}`).join("; "),
2035
+ deviceSerial: opts.deviceSerial,
2036
+ timeoutMs: opts.timeoutMs ?? 1e4,
2037
+ logger: opts.logger
2038
+ });
2039
+ ensureOk(res, "hdc shell param get \u2026");
2040
+ return parseParamValues(keys, res.stdout);
1914
2041
  }
1915
2042
  async function paramSet(config, key, value, opts = {}) {
1916
2043
  const res = await shell({
@@ -1924,29 +2051,66 @@ async function paramSet(config, key, value, opts = {}) {
1924
2051
  }
1925
2052
 
1926
2053
  // src/hdc/files.ts
2054
+ import * as path17 from "path";
2055
+ function normaliseLocalPath(local) {
2056
+ const abs = path17.resolve(local);
2057
+ const long = toLongPath(abs);
2058
+ return long !== abs ? long : path17.join(toLongPath(path17.dirname(abs)), path17.basename(abs));
2059
+ }
2060
+ function assertDevicePath(remote) {
2061
+ if (/^[A-Za-z]:[\\/]/.test(remote)) {
2062
+ throw new OniroError(
2063
+ `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.`
2064
+ );
2065
+ }
2066
+ }
2067
+ function appSandboxDir(bundle) {
2068
+ return `/data/app/el2/100/base/${bundle}`;
2069
+ }
2070
+ function shQuote(s) {
2071
+ return `'${s.replace(/'/g, `'\\''`)}'`;
2072
+ }
1927
2073
  async function sendFile(opts) {
2074
+ assertDevicePath(opts.remote);
2075
+ const local = normaliseLocalPath(opts.local);
2076
+ const sandbox = opts.bundle ? appSandboxDir(opts.bundle) : void 0;
2077
+ const remote = sandbox && !opts.remote.startsWith("/") ? path17.posix.join(sandbox, opts.remote) : opts.remote;
2078
+ if (sandbox && !remote.startsWith(`${sandbox}/`)) {
2079
+ throw new OniroError(`${remote} is outside the sandbox of ${opts.bundle} (${sandbox}).`);
2080
+ }
2081
+ const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
2082
+ if (sandbox) {
2083
+ const mkdir = `mkdir -p ${shQuote(path17.posix.dirname(remote))}`;
2084
+ ensureRemoteOk(await shellChecked({ ...device, command: mkdir }), `hdc shell ${mkdir}`);
2085
+ }
1928
2086
  const res = await hdcExec({
1929
- config: opts.config,
1930
- args: ["file", "send", opts.local, opts.remote],
1931
- deviceSerial: opts.deviceSerial,
2087
+ ...device,
2088
+ args: ["file", "send", local, remote],
1932
2089
  timeoutMs: opts.timeoutMs ?? 6e5,
1933
2090
  abortSignal: opts.abortSignal,
1934
- onOutput: opts.onOutput,
1935
- logger: opts.logger
2091
+ onOutput: opts.onOutput
1936
2092
  });
1937
- ensureOk(res, `hdc file send ${opts.local} ${opts.remote}`);
2093
+ ensureOk(res, `hdc file send ${local} ${remote}`);
2094
+ if (sandbox) {
2095
+ const top = path17.posix.join(sandbox, path17.posix.relative(sandbox, remote).split("/")[0]);
2096
+ const chown = `chown -R "$(stat -c %u:%g ${shQuote(sandbox)})" ${shQuote(top)}`;
2097
+ ensureRemoteOk(await shellChecked({ ...device, command: chown }), `hdc shell ${chown}`);
2098
+ }
2099
+ return remote;
1938
2100
  }
1939
2101
  async function recvFile(opts) {
2102
+ assertDevicePath(opts.remote);
2103
+ const local = normaliseLocalPath(opts.local);
1940
2104
  const res = await hdcExec({
1941
2105
  config: opts.config,
1942
- args: ["file", "recv", opts.remote, opts.local],
2106
+ args: ["file", "recv", opts.remote, local],
1943
2107
  deviceSerial: opts.deviceSerial,
1944
2108
  timeoutMs: opts.timeoutMs ?? 6e5,
1945
2109
  abortSignal: opts.abortSignal,
1946
2110
  onOutput: opts.onOutput,
1947
2111
  logger: opts.logger
1948
2112
  });
1949
- ensureOk(res, `hdc file recv ${opts.remote} ${opts.local}`);
2113
+ ensureOk(res, `hdc file recv ${opts.remote} ${local}`);
1950
2114
  }
1951
2115
 
1952
2116
  // src/hdc/display.ts
@@ -1966,7 +2130,7 @@ async function getDisplaySize(config, opts = {}) {
1966
2130
 
1967
2131
  // src/hdc/wait.ts
1968
2132
  function delay(ms, signal) {
1969
- return new Promise((resolve6, reject) => {
2133
+ return new Promise((resolve7, reject) => {
1970
2134
  if (signal?.aborted) {
1971
2135
  reject(new CancelledError("Wait cancelled."));
1972
2136
  return;
@@ -1981,7 +2145,7 @@ function delay(ms, signal) {
1981
2145
  };
1982
2146
  const timer = setTimeout(() => {
1983
2147
  cleanup();
1984
- resolve6();
2148
+ resolve7();
1985
2149
  }, ms);
1986
2150
  signal?.addEventListener("abort", onAbort, { once: true });
1987
2151
  });
@@ -2010,20 +2174,73 @@ async function waitForCondition(opts) {
2010
2174
  }
2011
2175
 
2012
2176
  // src/hdc/app.ts
2013
- import * as fs18 from "fs";
2177
+ import * as fs19 from "fs";
2178
+ import * as path20 from "path";
2179
+
2180
+ // src/build/discoverHaps.ts
2181
+ import * as fs17 from "fs";
2014
2182
  import * as path18 from "path";
2183
+ function moduleOfHap(hapFileName) {
2184
+ const dash = hapFileName.indexOf("-");
2185
+ return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
2186
+ }
2187
+ async function discoverHaps(opts) {
2188
+ const { projectDir } = opts;
2189
+ const maxDepth = opts.maxDepth ?? 8;
2190
+ const result = {};
2191
+ const walk = async (dir, depth) => {
2192
+ if (depth > maxDepth) return;
2193
+ const entries = await fs17.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2194
+ for (const e of entries) {
2195
+ const full = path18.join(dir, e.name);
2196
+ if (e.isDirectory()) {
2197
+ if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
2198
+ await walk(full, depth + 1);
2199
+ } else if (e.isFile() && e.name.endsWith(".hap")) {
2200
+ const moduleName = moduleOfHap(e.name);
2201
+ const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
2202
+ if (e.name.includes("unsigned")) bucket.unsigned.push(full);
2203
+ else if (e.name.includes("signed")) bucket.signed.push(full);
2204
+ }
2205
+ }
2206
+ };
2207
+ await walk(projectDir, 0);
2208
+ const mtime = (p) => fs17.statSync(p).mtimeMs;
2209
+ for (const bucket of Object.values(result)) {
2210
+ bucket.signed.sort((a, b) => mtime(b) - mtime(a));
2211
+ bucket.unsigned.sort((a, b) => mtime(b) - mtime(a));
2212
+ }
2213
+ return result;
2214
+ }
2215
+ function pickHap(haps, module) {
2216
+ const pick = (name) => {
2217
+ const signed = haps[name]?.signed[0];
2218
+ if (signed) return { module: name, path: signed, signed: true };
2219
+ const unsigned = haps[name]?.unsigned[0];
2220
+ return unsigned ? { module: name, path: unsigned, signed: false } : null;
2221
+ };
2222
+ if (module) return pick(module);
2223
+ const names = Object.keys(haps);
2224
+ const withSigned = names.filter((n) => haps[n].signed.length > 0);
2225
+ const candidates = withSigned.length > 0 ? withSigned : names.filter((n) => haps[n].unsigned.length > 0);
2226
+ if (candidates.length === 0) return null;
2227
+ if (candidates.length > 1) {
2228
+ throw new OniroError(`Multiple modules have built HAPs (${candidates.join(", ")}). Pass a module to choose.`);
2229
+ }
2230
+ return pick(candidates[0]);
2231
+ }
2015
2232
 
2016
2233
  // src/hdc/project.ts
2017
- import * as fs17 from "fs";
2018
- import * as path17 from "path";
2234
+ import * as fs18 from "fs";
2235
+ import * as path19 from "path";
2019
2236
  import JSON53 from "json5";
2020
2237
  function readJson5(filePath) {
2021
- const content = fs17.readFileSync(filePath, "utf-8");
2238
+ const content = fs18.readFileSync(filePath, "utf-8");
2022
2239
  return JSON53.parse(content);
2023
2240
  }
2024
2241
  function getBundleName(projectDir) {
2025
- const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
2026
- if (!fs17.existsSync(appJsonPath)) {
2242
+ const appJsonPath = path19.join(projectDir, "AppScope", "app.json5");
2243
+ if (!fs18.existsSync(appJsonPath)) {
2027
2244
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
2028
2245
  }
2029
2246
  const appJson = readJson5(appJsonPath);
@@ -2033,8 +2250,8 @@ function getBundleName(projectDir) {
2033
2250
  return appJson.app.bundleName;
2034
2251
  }
2035
2252
  function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2036
- const moduleJsonPath = path17.join(projectDir, moduleName, "src", "main", "module.json5");
2037
- if (!fs17.existsSync(moduleJsonPath)) {
2253
+ const moduleJsonPath = path19.join(projectDir, moduleName, "src", "main", "module.json5");
2254
+ if (!fs18.existsSync(moduleJsonPath)) {
2038
2255
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
2039
2256
  }
2040
2257
  const moduleJson = readJson5(moduleJsonPath);
@@ -2054,13 +2271,67 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2054
2271
  }
2055
2272
 
2056
2273
  // src/hdc/app.ts
2274
+ function detectInstallFailure(output) {
2275
+ return /\[Fail\]|msg:\s*error|error:\s*failed to install|code:\s*95\d{5}/i.test(output) ? output.trim() : null;
2276
+ }
2277
+ function isUnsignedHap(hapPath) {
2278
+ return path20.basename(hapPath).includes("unsigned");
2279
+ }
2280
+ function installFailureReason(output) {
2281
+ const code = /code:\s*(\d+)/i.exec(output);
2282
+ if (!code) return output.trim().split(/\r?\n/)[0] ?? "";
2283
+ const message = /error:\s*([^\r\n]+)/i.exec(output.slice(code.index + code[0].length))?.[1]?.trim().replace(/\.$/, "");
2284
+ return message ? `code ${code[1]}: ${message}` : `code ${code[1]}`;
2285
+ }
2286
+ function unsignedHapRejection(output, hap) {
2287
+ const name = path20.basename(hap.path);
2288
+ const reason = installFailureReason(output);
2289
+ 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}).`;
2290
+ if (!/9568320|signature|pkcs7/i.test(reason)) return what;
2291
+ 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.`;
2292
+ }
2293
+ function ensureInstallOk(result, command, hap) {
2294
+ const output = `${result.stdout}
2295
+ ${result.stderr}`;
2296
+ if (hap && isUnsignedHap(hap.path) && /code:\s*95\d{5}/i.test(output)) {
2297
+ throw new OniroError(unsignedHapRejection(output, hap), new CommandFailedError(command, result.code || 1, output.trim()));
2298
+ }
2299
+ ensureOk(result, command);
2300
+ const failure = detectInstallFailure(output);
2301
+ if (failure) throw new CommandFailedError(command, 1, failure);
2302
+ return result;
2303
+ }
2304
+ function existingHapOrUnsigned(hapPath) {
2305
+ if (fs19.existsSync(hapPath)) return { path: hapPath };
2306
+ const sibling = hapPath.replace(/-signed\.hap$/i, "-unsigned.hap");
2307
+ return sibling !== hapPath && fs19.existsSync(sibling) ? { path: sibling, fallback: true } : void 0;
2308
+ }
2309
+ async function resolveInstallHap(opts, logger) {
2310
+ const warnIfFallback = (hap) => {
2311
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path20.basename(hap.path)}.`);
2312
+ return hap;
2313
+ };
2314
+ const configured = opts.hapPath ?? opts.config.get("hapPath", "");
2315
+ const relative3 = configured || defaultPaths.hapPath;
2316
+ const hapPath = path20.isAbsolute(relative3) ? relative3 : path20.join(opts.projectDir, relative3);
2317
+ if (configured) {
2318
+ const hap = existingHapOrUnsigned(hapPath);
2319
+ if (!hap) throw new OniroError(`HAP file not found at: ${hapPath}. Build the app first.`);
2320
+ return warnIfFallback(hap);
2321
+ }
2322
+ if (fs19.existsSync(hapPath)) return { path: hapPath };
2323
+ const haps = await discoverHaps({ projectDir: opts.projectDir });
2324
+ const anySigned = Object.values(haps).some((m) => m.signed.length > 0);
2325
+ const entryUnsigned = anySigned ? void 0 : existingHapOrUnsigned(hapPath);
2326
+ if (entryUnsigned) return warnIfFallback(entryUnsigned);
2327
+ const picked = pickHap(haps);
2328
+ if (!picked) throw new OniroError(`No HAP found under ${opts.projectDir}. Build the app first (\`oniro-app build\`).`);
2329
+ return warnIfFallback({ path: picked.path, fallback: !picked.signed });
2330
+ }
2057
2331
  async function installApp(opts) {
2058
2332
  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
- }
2333
+ const hap = await resolveInstallHap(opts, logger);
2334
+ const hapPath = hap.path;
2064
2335
  const result = await hdcExec({
2065
2336
  config: opts.config,
2066
2337
  args: ["install", hapPath],
@@ -2069,7 +2340,7 @@ async function installApp(opts) {
2069
2340
  });
2070
2341
  if (result.stdout.trim()) logger.info(result.stdout.trim());
2071
2342
  if (result.stderr.trim()) logger.warn(result.stderr.trim());
2072
- ensureOk(result, `hdc install ${hapPath}`);
2343
+ ensureInstallOk(result, `hdc install ${hapPath}`, hap);
2073
2344
  let bundleName = "";
2074
2345
  try {
2075
2346
  bundleName = getBundleName(opts.projectDir);
@@ -2077,6 +2348,18 @@ async function installApp(opts) {
2077
2348
  }
2078
2349
  return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
2079
2350
  }
2351
+ async function isBundleInstalled(opts) {
2352
+ const safe = opts.bundle.replace(/'/g, `'\\''`);
2353
+ const res = await shell({
2354
+ config: opts.config,
2355
+ command: `bm dump -n '${safe}'`,
2356
+ deviceSerial: opts.deviceSerial,
2357
+ timeoutMs: opts.timeoutMs ?? 15e3,
2358
+ logger: opts.logger
2359
+ });
2360
+ ensureOk(res, `hdc shell bm dump -n ${opts.bundle}`);
2361
+ return res.stdout.includes("{") && !/failed to get information/i.test(res.stdout);
2362
+ }
2080
2363
  function detectAaStartFailure(output) {
2081
2364
  return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
2082
2365
  }
@@ -2147,10 +2430,10 @@ function listRunningProcesses(config, options = {}) {
2147
2430
  const logger = options.logger ?? noopLogger;
2148
2431
  const timeoutMs = options.timeoutMs ?? 1e3;
2149
2432
  const hdc = getHdcPath(config);
2150
- return new Promise((resolve6, reject) => {
2433
+ return new Promise((resolve7, reject) => {
2151
2434
  let proc;
2152
2435
  try {
2153
- proc = spawnCompat(hdc, ["track-jpid"]);
2436
+ proc = spawnCompat(hdc, [...hdcTargetArgs(options.deviceSerial), "track-jpid"]);
2154
2437
  } catch (err) {
2155
2438
  reject(new OniroError(`Failed to spawn ${hdc}: ${err instanceof Error ? err.message : String(err)}`, err));
2156
2439
  return;
@@ -2167,7 +2450,7 @@ function listRunningProcesses(config, options = {}) {
2167
2450
  if (options.targetProcessName && name === options.targetProcessName && !resolved) {
2168
2451
  resolved = true;
2169
2452
  killProcessTree(proc);
2170
- resolve6(pid);
2453
+ resolve7(pid);
2171
2454
  return;
2172
2455
  }
2173
2456
  }
@@ -2188,7 +2471,7 @@ function listRunningProcesses(config, options = {}) {
2188
2471
  if (options.targetProcessName) {
2189
2472
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2190
2473
  } else {
2191
- resolve6(processes);
2474
+ resolve7(processes);
2192
2475
  }
2193
2476
  }, timeoutMs);
2194
2477
  proc.on("close", () => {
@@ -2198,7 +2481,7 @@ function listRunningProcesses(config, options = {}) {
2198
2481
  if (options.targetProcessName) {
2199
2482
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2200
2483
  } else {
2201
- resolve6(processes);
2484
+ resolve7(processes);
2202
2485
  }
2203
2486
  });
2204
2487
  });
@@ -2220,7 +2503,11 @@ async function findRunningProcess(opts) {
2220
2503
  const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
2221
2504
  if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
2222
2505
  try {
2223
- const list = await listRunningProcesses(opts.config, { timeoutMs: 1200, logger: opts.logger });
2506
+ const list = await listRunningProcesses(opts.config, {
2507
+ timeoutMs: 1200,
2508
+ deviceSerial: opts.deviceSerial,
2509
+ logger: opts.logger
2510
+ });
2224
2511
  if (Array.isArray(list)) {
2225
2512
  const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
2226
2513
  const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
@@ -2363,12 +2650,12 @@ async function reboot(opts) {
2363
2650
  }
2364
2651
 
2365
2652
  // src/hdc/screen.ts
2366
- import * as fs19 from "fs";
2653
+ import * as fs20 from "fs";
2367
2654
  import * as os9 from "os";
2368
- import * as path19 from "path";
2655
+ import * as path21 from "path";
2369
2656
  import { randomBytes as randomBytes2 } from "crypto";
2370
2657
  function tmpName(ext) {
2371
- return path19.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
2658
+ return path21.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
2372
2659
  }
2373
2660
  async function takeScreenshot(opts) {
2374
2661
  const remote = "/data/local/tmp/oniro_screenshot.jpeg";
@@ -2378,11 +2665,11 @@ async function takeScreenshot(opts) {
2378
2665
  const local = tmpName("jpeg");
2379
2666
  try {
2380
2667
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2381
- const pixels = await fs19.promises.readFile(local);
2668
+ const pixels = await fs20.promises.readFile(local);
2382
2669
  const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
2383
2670
  return { pixels, width: size.width, height: size.height };
2384
2671
  } finally {
2385
- fs19.promises.unlink(local).catch(() => {
2672
+ fs20.promises.unlink(local).catch(() => {
2386
2673
  });
2387
2674
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2388
2675
  });
@@ -2401,9 +2688,9 @@ async function captureBurst(opts) {
2401
2688
  const local = tmpName(`burst_${i}.jpeg`);
2402
2689
  try {
2403
2690
  await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
2404
- frames.push(await fs19.promises.readFile(local));
2691
+ frames.push(await fs20.promises.readFile(local));
2405
2692
  } finally {
2406
- fs19.promises.unlink(local).catch(() => {
2693
+ fs20.promises.unlink(local).catch(() => {
2407
2694
  });
2408
2695
  }
2409
2696
  }
@@ -2415,6 +2702,55 @@ async function captureBurst(opts) {
2415
2702
  }
2416
2703
 
2417
2704
  // src/hdc/input.ts
2705
+ var UITEST_KEY_NAMES = ["Back", "Home", "Power"];
2706
+ var KEY_IDS = {
2707
+ back: 2,
2708
+ home: 1,
2709
+ power: 18,
2710
+ volumeup: 16,
2711
+ volumedown: 17,
2712
+ up: 2012,
2713
+ down: 2013,
2714
+ left: 2014,
2715
+ right: 2015,
2716
+ alt: 2045,
2717
+ shift: 2047,
2718
+ tab: 2049,
2719
+ space: 2050,
2720
+ enter: 2054,
2721
+ delete: 2055,
2722
+ backspace: 2055,
2723
+ pageup: 2068,
2724
+ pagedown: 2069,
2725
+ escape: 2070,
2726
+ esc: 2070,
2727
+ forwarddelete: 2071,
2728
+ ctrl: 2072,
2729
+ meta: 2076,
2730
+ movehome: 2081,
2731
+ moveend: 2082
2732
+ };
2733
+ function keyId(name) {
2734
+ const k = name.trim().toLowerCase();
2735
+ if (/^\d+$/.test(k)) return Number(k);
2736
+ if (/^[a-z]$/.test(k)) return 2017 + (k.charCodeAt(0) - 97);
2737
+ const id = KEY_IDS[k];
2738
+ if (id === void 0) {
2739
+ throw new OniroError(
2740
+ `Unknown key "${name}". Use a numeric KeyCode id, a letter, or one of: ${Object.keys(KEY_IDS).join(", ")}. Combine up to three with "+", e.g. Ctrl+A.`
2741
+ );
2742
+ }
2743
+ return id;
2744
+ }
2745
+ function resolveKeyEvent(key) {
2746
+ const spec = String(key).trim();
2747
+ if (spec === "") throw new OniroError('send input "keyEvent" requires a key.');
2748
+ const native = UITEST_KEY_NAMES.find((n) => n.toLowerCase() === spec.toLowerCase());
2749
+ if (native) return native;
2750
+ const parts = spec.includes("+") ? spec.split("+") : spec.split(/\s+/);
2751
+ if (parts.length > 3) throw new OniroError(`uitest injects at most three keys at once; got "${spec}".`);
2752
+ return parts.map(keyId).join(" ");
2753
+ }
2418
2754
  function reqNum(value, name, type) {
2419
2755
  if (value === void 0 || Number.isNaN(value)) {
2420
2756
  throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
@@ -2435,7 +2771,7 @@ function buildInputCommand(opts) {
2435
2771
  }
2436
2772
  case "keyEvent":
2437
2773
  if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
2438
- return `uitest uiInput keyEvent ${opts.key}`;
2774
+ return `uitest uiInput keyEvent ${resolveKeyEvent(opts.key)}`;
2439
2775
  case "inputText": {
2440
2776
  const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
2441
2777
  if (opts.pxX !== void 0 && opts.pxY !== void 0) {
@@ -2529,10 +2865,52 @@ async function sendRawTouch(opts) {
2529
2865
  }
2530
2866
 
2531
2867
  // src/hdc/dump.ts
2532
- import * as fs20 from "fs";
2868
+ import * as fs21 from "fs";
2533
2869
  import * as os10 from "os";
2534
- import * as path20 from "path";
2870
+ import * as path22 from "path";
2535
2871
  import { randomBytes as randomBytes3 } from "crypto";
2872
+
2873
+ // src/hdc/ime.ts
2874
+ var INPUT_METHOD_SA_ID = 3703;
2875
+ var IME_BUNDLE_FALLBACK = /inputmethod|keyboard|(^|\.)ime(\.|$)/i;
2876
+ function parseImeBundles(output) {
2877
+ const current = [];
2878
+ const others = [];
2879
+ const entry = /\{[^{}]*"ime"\s*:\s*"([^"/]+)\/[^"]*"[^{}]*\}/g;
2880
+ for (const m of output.matchAll(entry)) {
2881
+ const bundle = m[1];
2882
+ const list = /"isCurrentIme"\s*:\s*"true"/.test(m[0]) ? current : others;
2883
+ if (!current.includes(bundle) && !others.includes(bundle)) list.push(bundle);
2884
+ }
2885
+ return [...current, ...others];
2886
+ }
2887
+ async function getImeBundles(config, opts = {}) {
2888
+ try {
2889
+ const res = await shell({
2890
+ config,
2891
+ command: `hidumper -s ${INPUT_METHOD_SA_ID} -a '-a'`,
2892
+ deviceSerial: opts.deviceSerial,
2893
+ timeoutMs: 1e4,
2894
+ logger: opts.logger
2895
+ });
2896
+ if (res.code === 0) return parseImeBundles(res.stdout);
2897
+ } catch (err) {
2898
+ opts.logger?.debug(`could not list input methods: ${err instanceof Error ? err.message : String(err)}`);
2899
+ }
2900
+ return [];
2901
+ }
2902
+ function findKeyboardWindow(tree, imeBundles) {
2903
+ const isIme = (bundle) => imeBundles.length > 0 ? imeBundles.includes(bundle) : IME_BUNDLE_FALLBACK.test(bundle);
2904
+ const stack = [tree];
2905
+ while (stack.length > 0) {
2906
+ const n = stack.shift();
2907
+ if (n.bundle && isIme(n.bundle)) return n;
2908
+ stack.push(...n.children ?? []);
2909
+ }
2910
+ return null;
2911
+ }
2912
+
2913
+ // src/hdc/dump.ts
2536
2914
  function parseBounds(s) {
2537
2915
  const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
2538
2916
  if (!m) return null;
@@ -2567,7 +2945,46 @@ var FLAG_MAP = [
2567
2945
  ["scrollable", "scroll"],
2568
2946
  ["longClickable", "longClick"]
2569
2947
  ];
2570
- function pruneLayout(node, display) {
2948
+ function colorAlpha(color) {
2949
+ const m = /^#([0-9a-f]{2})?[0-9a-f]{6}$/i.exec(color ?? "");
2950
+ if (!m) return 0;
2951
+ return m[1] === void 0 ? 255 : parseInt(m[1], 16);
2952
+ }
2953
+ function paintsFill(a) {
2954
+ if (a.visible === "false") return false;
2955
+ if (a.opacity !== void 0 && a.opacity !== "" && !(Number(a.opacity) > 0)) return false;
2956
+ return colorAlpha(a.backgroundColor) > 0;
2957
+ }
2958
+ function findCoveredNodes(root) {
2959
+ const order = [];
2960
+ const fills = [];
2961
+ const visit = (n) => {
2962
+ const a = n.attributes ?? {};
2963
+ const entry = { node: n, bounds: parseBounds(a.bounds), end: 0 };
2964
+ if (entry.bounds && paintsFill(a)) fills.push(order.length);
2965
+ order.push(entry);
2966
+ const kids = (n.children ?? []).map((c, i) => ({ c, i, z: Number(c.attributes?.zIndex) || 0 })).sort((p, q) => p.z - q.z || p.i - q.i);
2967
+ for (const k of kids) visit(k.c);
2968
+ entry.end = order.length;
2969
+ };
2970
+ visit(root);
2971
+ const covered = /* @__PURE__ */ new Set();
2972
+ for (const p of order) {
2973
+ if (!p.bounds) continue;
2974
+ const cx = (p.bounds[0] + p.bounds[2]) / 2;
2975
+ const cy = (p.bounds[1] + p.bounds[3]) / 2;
2976
+ for (const j of fills) {
2977
+ if (j < p.end) continue;
2978
+ const q = order[j].bounds;
2979
+ if (cx >= q[0] && cx < q[2] && cy >= q[1] && cy < q[3]) {
2980
+ covered.add(p.node);
2981
+ break;
2982
+ }
2983
+ }
2984
+ }
2985
+ return covered;
2986
+ }
2987
+ function pruneLayout(node, display, covered) {
2571
2988
  const a = node.attributes ?? {};
2572
2989
  const px = parseBounds(a.bounds);
2573
2990
  const out = {};
@@ -2585,28 +3002,125 @@ function pruneLayout(node, display) {
2585
3002
  if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
2586
3003
  if (a.text) out.text = a.text;
2587
3004
  if (a.description) out.desc = a.description;
3005
+ if (a.hint) out.hint = a.hint;
2588
3006
  if (a.id) out.id = a.id;
2589
3007
  if (a.key && a.key !== a.id) out.key = a.key;
2590
3008
  if (a.bundleName) out.bundle = a.bundleName;
2591
3009
  for (const [attr, short] of FLAG_MAP) {
2592
3010
  if (a[attr] === "true") out[short] = true;
2593
3011
  }
3012
+ if (a.enabled === "false") out.disabled = true;
3013
+ if (covered?.has(node)) out.covered = true;
2594
3014
  const children = [];
2595
3015
  for (const c of node.children ?? []) {
2596
- const pc = pruneLayout(c, display);
3016
+ const pc = pruneLayout(c, display, covered);
2597
3017
  if (pc) {
2598
3018
  if (Array.isArray(pc)) children.push(...pc);
2599
3019
  else children.push(pc);
2600
3020
  }
2601
3021
  }
2602
3022
  const hasSignal = Boolean(
2603
- out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
3023
+ out.text || out.desc || out.hint || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
2604
3024
  );
2605
3025
  if (!hasSignal && children.length === 0) return null;
2606
3026
  if (!hasSignal) return children.length === 1 ? children[0] : children;
2607
3027
  if (children.length) out.children = children;
2608
3028
  return out;
2609
3029
  }
3030
+ function flattenLayout(tree) {
3031
+ const out = [];
3032
+ const visit = (n, window) => {
3033
+ const { children, ...rest } = n;
3034
+ const win = n.bundle ?? window;
3035
+ if (Object.keys(rest).length > 0) out.push({ ...rest, ...win ? { window: win } : {} });
3036
+ for (const c of children ?? []) visit(c, win);
3037
+ };
3038
+ visit(tree, void 0);
3039
+ return out;
3040
+ }
3041
+ function assertLayoutSelector(selector) {
3042
+ if (selector.text === void 0 && selector.id === void 0 && selector.type === void 0) {
3043
+ throw new OniroError("A layout selector needs a text, id, or type.");
3044
+ }
3045
+ if (selector.text !== void 0 && selector.text.trim() === "") {
3046
+ throw new OniroError("A layout selector text must not be blank.");
3047
+ }
3048
+ }
3049
+ function withoutWindow(tree, bundle) {
3050
+ const children = (tree.children ?? []).filter((c) => c.bundle !== bundle).map((c) => withoutWindow(c, bundle));
3051
+ return tree.children ? { ...tree, children } : tree;
3052
+ }
3053
+ function findLayoutNodes(tree, selector) {
3054
+ assertLayoutSelector(selector);
3055
+ const type = selector.type?.toLowerCase();
3056
+ const nodes = flattenLayout(tree).filter(
3057
+ (n) => (type === void 0 || n.type?.toLowerCase() === type) && (selector.id === void 0 || n.id === selector.id || n.key === selector.id)
3058
+ );
3059
+ if (selector.text === void 0) return nodes;
3060
+ const want = selector.text.trim().toLowerCase();
3061
+ const labels = (n) => [n.text, n.desc, n.hint].filter((s) => !!s).map((s) => s.trim().toLowerCase());
3062
+ const exact = nodes.filter((n) => labels(n).some((l) => l === want));
3063
+ if (exact.length > 0) return exact;
3064
+ return nodes.filter((n) => labels(n).some((l) => l.includes(want)));
3065
+ }
3066
+ function describeSelector(s) {
3067
+ const parts = [];
3068
+ if (s.text !== void 0) parts.push(`text "${s.text}"`);
3069
+ if (s.id !== void 0) parts.push(`id "${s.id}"`);
3070
+ if (s.type !== void 0) parts.push(`type ${s.type}`);
3071
+ return parts.join(", ");
3072
+ }
3073
+ function describeLayoutNode(n) {
3074
+ const label = n.text ?? n.desc ?? (n.hint !== void 0 ? `hint: ${n.hint}` : void 0);
3075
+ const at = n.c ? ` at ${n.c[0].toFixed(3)},${n.c[1].toFixed(3)}` : "";
3076
+ return `${n.type ?? "node"}${label !== void 0 ? ` "${label}"` : ""}${n.id ? ` #${n.id}` : ""}${at}`;
3077
+ }
3078
+ var area = (b) => b ? (b[2] - b[0]) * (b[3] - b[1]) : 0;
3079
+ function enclosesTarget(outer, inner) {
3080
+ const b = outer.b;
3081
+ const c = inner.c;
3082
+ if (!b || !c) return false;
3083
+ return c[0] >= b[0] && c[0] <= b[2] && c[1] >= b[1] && c[1] <= b[3] && area(b) > area(inner.b);
3084
+ }
3085
+ var TargetNotFoundError = class extends OniroError {
3086
+ constructor(message, reason) {
3087
+ super(message);
3088
+ this.reason = reason;
3089
+ this.name = "TargetNotFoundError";
3090
+ }
3091
+ reason;
3092
+ };
3093
+ function pickTarget(tree, selector, index) {
3094
+ const matches = findLayoutNodes(tree, selector).filter((n) => n.c !== void 0);
3095
+ const what = describeSelector(selector);
3096
+ if (matches.length === 0) {
3097
+ throw new TargetNotFoundError(`No on-screen element matches ${what}. Run \`oniro-app dump layout --flat\` to see what is there.`, "none");
3098
+ }
3099
+ const visible = matches.filter((n) => !n.covered);
3100
+ if (visible.length === 0) {
3101
+ throw new TargetNotFoundError(
3102
+ `Found ${matches.length} element(s) matching ${what}, but each is covered by an overlay (dialog, keyboard, or another layer): ${matches.map(describeLayoutNode).join("; ")}.`,
3103
+ "covered"
3104
+ );
3105
+ }
3106
+ const clickable = visible.filter((n) => n.click);
3107
+ const preferred = clickable.length > 0 ? clickable : visible;
3108
+ const candidates = preferred.filter((n) => !preferred.some((o) => o !== n && o.click && enclosesTarget(o, n)));
3109
+ if (index !== void 0) {
3110
+ const picked = candidates[index];
3111
+ if (!picked) {
3112
+ throw new TargetNotFoundError(`Index ${index} is out of range: ${candidates.length} element(s) match ${what}.`, "ambiguous");
3113
+ }
3114
+ return picked;
3115
+ }
3116
+ if (candidates.length > 1) {
3117
+ throw new TargetNotFoundError(
3118
+ `${candidates.length} elements match ${what}; pass an index to choose: ` + candidates.map((n, i) => `[${i}] ${describeLayoutNode(n)}`).join("; "),
3119
+ "ambiguous"
3120
+ );
3121
+ }
3122
+ return candidates[0];
3123
+ }
2610
3124
  async function dumpLayout(opts) {
2611
3125
  const timeoutMs = opts.timeoutMs ?? 3e4;
2612
3126
  const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
@@ -2616,33 +3130,193 @@ async function dumpLayout(opts) {
2616
3130
  if (!m) throw new OniroError(`Could not parse dumpLayout output:
2617
3131
  ${dump.stdout}`);
2618
3132
  const remote = m[1];
2619
- const local = path20.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
3133
+ const local = path22.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
2620
3134
  try {
2621
3135
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2622
- const parsed = JSON.parse(await fs20.promises.readFile(local, "utf8"));
3136
+ const parsed = JSON.parse(await fs21.promises.readFile(local, "utf8"));
2623
3137
  const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
2624
3138
  if (opts.prune === false) {
2625
3139
  return { display, tree: parsed };
2626
3140
  }
2627
- const prunedRaw = pruneLayout(parsed, display);
3141
+ const prunedRaw = pruneLayout(parsed, display, findCoveredNodes(parsed));
2628
3142
  const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
2629
- return { display, tree };
3143
+ if (!opts.detectKeyboard || opts.bundleName) return { display, tree };
3144
+ const imeBundles = await getImeBundles(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
3145
+ const kb = findKeyboardWindow(tree, imeBundles);
3146
+ const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
3147
+ return { display, tree, keyboard };
2630
3148
  } finally {
2631
- fs20.promises.unlink(local).catch(() => {
3149
+ fs21.promises.unlink(local).catch(() => {
2632
3150
  });
2633
3151
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2634
3152
  });
2635
3153
  }
2636
3154
  }
2637
3155
 
3156
+ // src/hdc/uiActions.ts
3157
+ var FOCUS_SETTLE_MS = 400;
3158
+ var KEYBOARD_SETTLE_MS = 400;
3159
+ var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
3160
+ function targetTree(layout) {
3161
+ const kb = layout.keyboard;
3162
+ return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
3163
+ }
3164
+ function fractionToPixel(f, size) {
3165
+ return Math.min(size - 1, Math.round(f * size));
3166
+ }
3167
+ function pressBack(opts) {
3168
+ return sendInput({ config: opts.config, type: "keyEvent", key: "Back", deviceSerial: opts.deviceSerial, logger: opts.logger });
3169
+ }
3170
+ async function hideKeyboard(opts) {
3171
+ const { keyboard } = await dumpLayout({ ...opts, detectKeyboard: true });
3172
+ if (!keyboard?.shown) return false;
3173
+ await pressBack(opts);
3174
+ await sleep(KEYBOARD_SETTLE_MS);
3175
+ return true;
3176
+ }
3177
+ async function locateTarget(opts) {
3178
+ assertLayoutSelector(opts.selector);
3179
+ const dumpOpts = { config: opts.config, detectKeyboard: true, deviceSerial: opts.deviceSerial, logger: opts.logger };
3180
+ let layout = await dumpLayout(dumpOpts);
3181
+ if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
3182
+ let keyboardHidden = false;
3183
+ let node;
3184
+ let failure;
3185
+ try {
3186
+ node = pickTarget(targetTree(layout), opts.selector, opts.index);
3187
+ } catch (err) {
3188
+ if (!(err instanceof TargetNotFoundError) || err.reason === "ambiguous") throw err;
3189
+ failure = err;
3190
+ }
3191
+ if ((!node || !node.click) && layout.keyboard?.shown && opts.hideKeyboard !== false) {
3192
+ opts.logger?.debug("target not reachable while the keyboard is up; dismissing it and looking again");
3193
+ await pressBack(opts);
3194
+ await sleep(KEYBOARD_SETTLE_MS);
3195
+ keyboardHidden = true;
3196
+ layout = await dumpLayout(dumpOpts);
3197
+ if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
3198
+ node = pickTarget(targetTree(layout), opts.selector, opts.index);
3199
+ }
3200
+ if (!node) throw failure;
3201
+ if (!node.c) throw new OniroError("The element has no on-screen position.");
3202
+ const x2 = fractionToPixel(node.c[0], layout.display.width);
3203
+ const y = fractionToPixel(node.c[1], layout.display.height);
3204
+ return { node, x: x2, y, display: layout.display, keyboardHidden };
3205
+ }
3206
+ function fraction(value, name) {
3207
+ if (value === void 0) return void 0;
3208
+ if (!(value >= 0 && value <= 1)) throw new OniroError(`${name} must be a fraction between 0 and 1; got ${value}.`);
3209
+ return value;
3210
+ }
3211
+ function pair(a, b, names) {
3212
+ if (a === void 0 && b === void 0) return void 0;
3213
+ if (a === void 0 || b === void 0) throw new OniroError(`Give both ${names}.`);
3214
+ return [a, b];
3215
+ }
3216
+ async function performInput(opts) {
3217
+ const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
3218
+ const startFx = pair(fraction(opts.fx, "--fx"), fraction(opts.fy, "--fy"), "--fx and --fy");
3219
+ const startPx = pair(opts.pxX, opts.pxY, "--x and --y");
3220
+ const endFx = pair(fraction(opts.fx2, "--fx2"), fraction(opts.fy2, "--fy2"), "--fx2 and --fy2");
3221
+ const endPx = pair(opts.pxX2, opts.pxY2, "--x2 and --y2");
3222
+ if ([opts.target, startFx, startPx].filter((s) => s !== void 0).length > 1) {
3223
+ throw new OniroError("Give the start point one way: a target, fractions (--fx/--fy), or pixels (--x/--y).");
3224
+ }
3225
+ if (endFx && endPx) throw new OniroError("Give the end point one way: fractions (--fx2/--fy2) or pixels (--x2/--y2).");
3226
+ let display;
3227
+ const toPx = async ([fx, fy]) => {
3228
+ display ??= await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
3229
+ if (!display) throw new OniroError("Could not read the display size to convert fractions to pixels.");
3230
+ return [fractionToPixel(fx, display.width), fractionToPixel(fy, display.height)];
3231
+ };
3232
+ const result = { keyboardHidden: false };
3233
+ let start = startPx;
3234
+ if (opts.target) {
3235
+ const located = await locateTarget({ ...device, selector: opts.target, index: opts.targetIndex });
3236
+ display = located.display;
3237
+ start = [located.x, located.y];
3238
+ result.target = located.node;
3239
+ result.keyboardHidden = located.keyboardHidden;
3240
+ } else if (startFx) {
3241
+ start = await toPx(startFx);
3242
+ }
3243
+ const end = endFx ? await toPx(endFx) : endPx;
3244
+ if (start) [result.x, result.y] = start;
3245
+ const tapToFocus = async () => {
3246
+ if (!start) return;
3247
+ await sendInput({ ...device, type: "click", pxX: start[0], pxY: start[1] });
3248
+ await sleep(FOCUS_SETTLE_MS);
3249
+ };
3250
+ const clearFocused = async () => {
3251
+ await sendInput({ ...device, type: "keyEvent", key: "Ctrl+A" });
3252
+ await sendInput({ ...device, type: "keyEvent", key: "Delete" });
3253
+ };
3254
+ switch (opts.type) {
3255
+ case "hideKeyboard":
3256
+ result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
3257
+ return result;
3258
+ case "clearText":
3259
+ await tapToFocus();
3260
+ await clearFocused();
3261
+ break;
3262
+ case "inputText":
3263
+ if (opts.clear) {
3264
+ await tapToFocus();
3265
+ await clearFocused();
3266
+ await sendInput({ ...device, type: "inputText", text: opts.text });
3267
+ } else {
3268
+ await sendInput({ ...device, type: "inputText", pxX: start?.[0], pxY: start?.[1], text: opts.text });
3269
+ }
3270
+ break;
3271
+ default:
3272
+ await sendInput({
3273
+ ...device,
3274
+ type: opts.type,
3275
+ pxX: start?.[0],
3276
+ pxY: start?.[1],
3277
+ pxX2: end?.[0],
3278
+ pxY2: end?.[1],
3279
+ speed: opts.speed,
3280
+ key: opts.key,
3281
+ text: opts.text
3282
+ });
3283
+ }
3284
+ if (opts.hideKeyboard) result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
3285
+ return result;
3286
+ }
3287
+ async function waitForTarget(opts) {
3288
+ const { selector } = opts;
3289
+ assertLayoutSelector(selector);
3290
+ let found = null;
3291
+ await waitForCondition({
3292
+ probe: async () => {
3293
+ const layout = await dumpLayout({
3294
+ config: opts.config,
3295
+ detectKeyboard: true,
3296
+ deviceSerial: opts.deviceSerial,
3297
+ logger: opts.logger
3298
+ });
3299
+ found = findLayoutNodes(targetTree(layout), selector).find((n) => !n.covered) ?? null;
3300
+ return opts.gone ? found === null : found !== null;
3301
+ },
3302
+ timeoutMs: opts.timeoutMs,
3303
+ pollMs: opts.pollMs ?? 500,
3304
+ abortSignal: opts.abortSignal,
3305
+ logger: opts.logger,
3306
+ timeoutMessage: opts.gone ? `An element matching the selector was still on screen after ${opts.timeoutMs}ms.` : `No element matching the selector appeared within ${opts.timeoutMs}ms.`
3307
+ });
3308
+ return opts.gone ? null : found;
3309
+ }
3310
+
2638
3311
  // src/hdc/hilog.ts
3312
+ import * as fs22 from "fs";
2639
3313
  function setHilogLevel(opts) {
2640
3314
  const logger = opts.logger ?? noopLogger;
2641
3315
  const hdc = getHdcPath(opts.config);
2642
- return new Promise((resolve6, reject) => {
3316
+ return new Promise((resolve7, reject) => {
2643
3317
  let child;
2644
3318
  try {
2645
- child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
3319
+ child = spawnCompat(hdc, [...hdcTargetArgs(opts.deviceSerial), "shell", "hilog", "-b", opts.level]);
2646
3320
  } catch (err) {
2647
3321
  const msg = err instanceof Error ? err.message : String(err);
2648
3322
  logger.error(`[hilog] failed to spawn hdc: ${msg}`);
@@ -2659,7 +3333,7 @@ function setHilogLevel(opts) {
2659
3333
  });
2660
3334
  child.once("close", (code) => {
2661
3335
  if (code === 0) {
2662
- resolve6();
3336
+ resolve7();
2663
3337
  } else {
2664
3338
  const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
2665
3339
  logger.error(`[hilog] ${msg}`);
@@ -2670,8 +3344,7 @@ function setHilogLevel(opts) {
2670
3344
  }
2671
3345
  function streamHilog(opts) {
2672
3346
  const hdc = getHdcPath(opts.config);
2673
- const args = [];
2674
- if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
3347
+ const args = [...hdcTargetArgs(opts.deviceSerial)];
2675
3348
  args.push("shell", "hilog");
2676
3349
  if (opts.processId && opts.processId.trim() !== "") {
2677
3350
  args.push("-P", opts.processId.trim());
@@ -2714,7 +3387,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
2714
3387
  return proc?.pid;
2715
3388
  }
2716
3389
  function waitForLog(opts) {
2717
- return new Promise((resolve6, reject) => {
3390
+ return new Promise((resolve7, reject) => {
2718
3391
  const deadline = Date.now() + opts.timeoutMs;
2719
3392
  let settled = false;
2720
3393
  let child = null;
@@ -2733,7 +3406,7 @@ function waitForLog(opts) {
2733
3406
  if (settled) return;
2734
3407
  settled = true;
2735
3408
  cleanup();
2736
- resolve6(entry);
3409
+ resolve7(entry);
2737
3410
  };
2738
3411
  const fail = (err) => {
2739
3412
  if (settled) return;
@@ -2780,7 +3453,7 @@ function waitForLog(opts) {
2780
3453
  });
2781
3454
  }
2782
3455
  function watchLog(opts) {
2783
- return new Promise((resolve6, reject) => {
3456
+ return new Promise((resolve7, reject) => {
2784
3457
  const matches = [];
2785
3458
  let settled = false;
2786
3459
  let child = null;
@@ -2797,7 +3470,7 @@ function watchLog(opts) {
2797
3470
  if (settled) return;
2798
3471
  settled = true;
2799
3472
  cleanup();
2800
- resolve6(opts.dedup === false ? matches : dedupEntries(matches));
3473
+ resolve7(opts.dedup === false ? matches : dedupEntries(matches));
2801
3474
  };
2802
3475
  const onAbort = () => {
2803
3476
  if (settled) return;
@@ -2829,6 +3502,94 @@ function watchLog(opts) {
2829
3502
  });
2830
3503
  });
2831
3504
  }
3505
+ async function captureLog(opts) {
3506
+ const logger = opts.logger ?? noopLogger;
3507
+ if (opts.clear) {
3508
+ const res = await shell({ config: opts.config, command: "hilog -r", deviceSerial: opts.deviceSerial, timeoutMs: 1e4, logger });
3509
+ ensureOk(res, "hdc shell hilog -r");
3510
+ }
3511
+ const fd = fs22.openSync(opts.file, "a");
3512
+ let lines = 0;
3513
+ let reconnects = 0;
3514
+ try {
3515
+ await new Promise((resolve7, reject) => {
3516
+ const deadline = opts.durationMs !== void 0 ? Date.now() + opts.durationMs : Infinity;
3517
+ let settled = false;
3518
+ let child = null;
3519
+ let reconnectTimer;
3520
+ let buf = "";
3521
+ let open = true;
3522
+ const write = (line) => {
3523
+ if (!open || opts.pattern && !opts.pattern.test(line)) return;
3524
+ fs22.writeSync(fd, `${line}
3525
+ `);
3526
+ lines++;
3527
+ };
3528
+ const cleanup = () => {
3529
+ if (timer) clearTimeout(timer);
3530
+ if (reconnectTimer) clearTimeout(reconnectTimer);
3531
+ opts.abortSignal?.removeEventListener("abort", finish);
3532
+ if (buf.trim()) write(buf.trimEnd());
3533
+ buf = "";
3534
+ open = false;
3535
+ if (child) {
3536
+ child.stdout.removeAllListeners("data");
3537
+ killProcessTree(child);
3538
+ child = null;
3539
+ }
3540
+ };
3541
+ function finish() {
3542
+ if (settled) return;
3543
+ settled = true;
3544
+ cleanup();
3545
+ resolve7();
3546
+ }
3547
+ const fail = (err) => {
3548
+ if (settled) return;
3549
+ settled = true;
3550
+ cleanup();
3551
+ reject(err instanceof Error ? err : new Error(String(err)));
3552
+ };
3553
+ const timer = opts.durationMs !== void 0 ? setTimeout(finish, opts.durationMs) : void 0;
3554
+ if (opts.abortSignal?.aborted) {
3555
+ finish();
3556
+ return;
3557
+ }
3558
+ opts.abortSignal?.addEventListener("abort", finish, { once: true });
3559
+ const spawnStream = () => {
3560
+ if (settled) return;
3561
+ const stream = streamHilog({ config: opts.config, domain: opts.domain, deviceSerial: opts.deviceSerial });
3562
+ child = stream;
3563
+ stream.stdout.on("data", (d) => {
3564
+ buf += d.toString();
3565
+ const parts = buf.split(/\r?\n/);
3566
+ buf = parts.pop() ?? "";
3567
+ for (const line of parts) write(line);
3568
+ });
3569
+ const onEnd = () => {
3570
+ if (settled || child !== stream) return;
3571
+ child = null;
3572
+ if (Date.now() >= deadline) return;
3573
+ reconnects++;
3574
+ logger.warn(`[hilog] stream ended; reconnecting (${reconnects}).`);
3575
+ reconnectTimer = setTimeout(() => {
3576
+ try {
3577
+ spawnStream();
3578
+ } catch (err) {
3579
+ fail(err);
3580
+ }
3581
+ }, 2e3);
3582
+ };
3583
+ stream.once("close", onEnd);
3584
+ stream.once("error", onEnd);
3585
+ };
3586
+ spawnStream();
3587
+ });
3588
+ } finally {
3589
+ fs22.closeSync(fd);
3590
+ }
3591
+ return { file: opts.file, lines, reconnects };
3592
+ }
2832
3593
  async function dumpLog(opts) {
2833
3594
  let pipe = "hilog -x";
2834
3595
  if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
@@ -2853,17 +3614,17 @@ async function dumpLog(opts) {
2853
3614
  }
2854
3615
 
2855
3616
  // src/build/runHvigorw.ts
2856
- import * as fs22 from "fs";
2857
- import * as path22 from "path";
3617
+ import * as fs24 from "fs";
3618
+ import * as path24 from "path";
2858
3619
 
2859
3620
  // src/harmonyos/target.ts
2860
- import * as fs21 from "fs";
2861
- import * as path21 from "path";
3621
+ import * as fs23 from "fs";
3622
+ import * as path23 from "path";
2862
3623
  import JSON54 from "json5";
2863
3624
  function detectRuntimeOs(projectDir, productName) {
2864
3625
  let products = [];
2865
3626
  try {
2866
- const parsed = JSON54.parse(fs21.readFileSync(path21.join(projectDir, "build-profile.json5"), "utf-8"));
3627
+ const parsed = JSON54.parse(fs23.readFileSync(path23.join(projectDir, "build-profile.json5"), "utf-8"));
2867
3628
  if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
2868
3629
  } catch {
2869
3630
  return "OpenHarmony";
@@ -2904,7 +3665,7 @@ function runHvigorw(opts) {
2904
3665
  const { config } = opts;
2905
3666
  const projectDir = toLongPath(opts.projectDir);
2906
3667
  const logger = opts.logger ?? noopLogger;
2907
- if (!fs22.existsSync(path22.join(projectDir, "build-profile.json5"))) {
3668
+ if (!fs24.existsSync(path24.join(projectDir, "build-profile.json5"))) {
2908
3669
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
2909
3670
  }
2910
3671
  let hvigorw;
@@ -2916,24 +3677,24 @@ function runHvigorw(opts) {
2916
3677
  hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
2917
3678
  logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
2918
3679
  } else {
2919
- if (!fs22.existsSync(getCmdToolsPath(config))) {
3680
+ if (!fs24.existsSync(getCmdToolsPath(config))) {
2920
3681
  throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
2921
3682
  }
2922
3683
  sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
2923
3684
  hvigorw = getHvigorwPath(config, projectDir);
2924
3685
  }
2925
- if (!fs22.existsSync(hvigorw)) {
3686
+ if (!fs24.existsSync(hvigorw)) {
2926
3687
  throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
2927
3688
  }
2928
3689
  const args = buildHvigorwArgs(opts);
2929
3690
  if (process.platform !== "win32") {
2930
3691
  try {
2931
- fs22.chmodSync(hvigorw, 493);
3692
+ fs24.chmodSync(hvigorw, 493);
2932
3693
  } catch {
2933
3694
  }
2934
3695
  }
2935
3696
  const env = { ...process.env, ...sdkEnv };
2936
- return new Promise((resolve6, reject) => {
3697
+ return new Promise((resolve7, reject) => {
2937
3698
  if (opts.abortSignal?.aborted) {
2938
3699
  reject(new CancelledError("hvigorw build cancelled."));
2939
3700
  return;
@@ -2977,46 +3738,15 @@ function runHvigorw(opts) {
2977
3738
  settled = true;
2978
3739
  cleanup();
2979
3740
  const exitCode = code ?? 1;
2980
- if (exitCode === 0) resolve6({ exitCode });
3741
+ if (exitCode === 0) resolve7({ exitCode });
2981
3742
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
2982
3743
  });
2983
3744
  });
2984
3745
  }
2985
3746
 
2986
- // src/build/discoverHaps.ts
2987
- import * as fs23 from "fs";
2988
- import * as path23 from "path";
2989
- function moduleOfHap(hapFileName) {
2990
- const dash = hapFileName.indexOf("-");
2991
- return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
2992
- }
2993
- async function discoverHaps(opts) {
2994
- const { projectDir } = opts;
2995
- const maxDepth = opts.maxDepth ?? 8;
2996
- const result = {};
2997
- const walk = async (dir, depth) => {
2998
- if (depth > maxDepth) return;
2999
- const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
3000
- for (const e of entries) {
3001
- const full = path23.join(dir, e.name);
3002
- if (e.isDirectory()) {
3003
- if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
3004
- await walk(full, depth + 1);
3005
- } else if (e.isFile() && e.name.endsWith(".hap")) {
3006
- const moduleName = moduleOfHap(e.name);
3007
- const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
3008
- if (e.name.includes("unsigned")) bucket.unsigned.push(full);
3009
- else if (e.name.includes("signed")) bucket.signed.push(full);
3010
- }
3011
- }
3012
- };
3013
- await walk(projectDir, 0);
3014
- return result;
3015
- }
3016
-
3017
3747
  // src/build/ohpm.ts
3018
- import * as fs24 from "fs";
3019
- import * as path24 from "path";
3748
+ import * as fs25 from "fs";
3749
+ import * as path25 from "path";
3020
3750
  function runOhpm(opts) {
3021
3751
  return runProcess({
3022
3752
  command: opts.ohpmPath ?? getOhpmPath(opts.config),
@@ -3029,7 +3759,7 @@ function runOhpm(opts) {
3029
3759
  });
3030
3760
  }
3031
3761
  async function ensureOhModules(opts) {
3032
- if (fs24.existsSync(path24.join(opts.projectDir, "oh_modules"))) {
3762
+ if (fs25.existsSync(path25.join(opts.projectDir, "oh_modules"))) {
3033
3763
  return { installed: false };
3034
3764
  }
3035
3765
  const res = await runOhpm({
@@ -3046,23 +3776,91 @@ async function ensureOhModules(opts) {
3046
3776
  }
3047
3777
 
3048
3778
  // src/build/codelinter.ts
3049
- var FINDING_RE = /^(.*?):(\d+):(?:\d+:)?\s*(error|warning|warn|info)\b[:\s-]*(.*)$/i;
3779
+ var FINDING_RE = /^(.*?):(\d+):(?:(\d+):)?\s*(error|warning|warn|suggestion|info)\b[:\s-]*(.*)$/i;
3050
3780
  var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
3051
- function parseCodelinterFindings(output) {
3781
+ var FILE_HEADER_RE = /^(.+\.\w+)\((\d+)\)$/;
3782
+ var ROW_RE = /^(\d+):(\d+)\s+(error|warning|warn|suggestion|info)\s+(.*)$/i;
3783
+ var RULE_RE = /^@?[\w.-]+(?:\/[\w.-]+)+$/;
3784
+ var SUMMARY_RE = /Defects:\s*(\d+);\s*Errors:\s*(\d+);\s*Warns:\s*(\d+);\s*Suggestions:\s*(\d+)/i;
3785
+ var NOTICE_RE = /does not match that of the project|error occurred during linting|incomplete report/i;
3786
+ var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
3787
+ function cleanCodelinterOutput(output) {
3788
+ return output.replace(ANSI_RE, "").split("\n").map((line) => (line.split("\r").filter((s) => s.trim() !== "").pop() ?? "").trimEnd()).join("\n");
3789
+ }
3790
+ function normaliseSeverity(s) {
3791
+ const lower = s.toLowerCase();
3792
+ return lower === "warn" ? "warning" : lower;
3793
+ }
3794
+ function parseCodelinterReport(output) {
3052
3795
  const findings = [];
3053
- for (const raw of output.split("\n")) {
3054
- const m = FINDING_RE.exec(raw.trim());
3055
- if (!m) continue;
3056
- let message = m[4].trim();
3057
- let code = "";
3058
- const codeMatch = TRAILING_CODE_RE.exec(message);
3059
- if (codeMatch) {
3060
- code = codeMatch[1];
3061
- message = message.slice(0, codeMatch.index).trim();
3796
+ const notices = [];
3797
+ let summary = null;
3798
+ let file = "";
3799
+ for (const rawLine of cleanCodelinterOutput(output).split("\n")) {
3800
+ const line = rawLine.trim();
3801
+ if (!line) continue;
3802
+ const row = ROW_RE.exec(line);
3803
+ if (row) {
3804
+ if (!file) continue;
3805
+ const columns = row[4].trim().split(/\s{2,}/);
3806
+ const last = columns[columns.length - 1];
3807
+ const code = columns.length > 1 && RULE_RE.test(last) ? last : "";
3808
+ const message = (code ? columns.slice(0, -1) : columns).join(" ").trim();
3809
+ findings.push({
3810
+ file,
3811
+ line: Number(row[1]),
3812
+ column: Number(row[2]),
3813
+ severity: normaliseSeverity(row[3]),
3814
+ code,
3815
+ message
3816
+ });
3817
+ continue;
3818
+ }
3819
+ const header = FILE_HEADER_RE.exec(line);
3820
+ if (header) {
3821
+ file = header[1];
3822
+ continue;
3062
3823
  }
3063
- findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
3824
+ const legacy = FINDING_RE.exec(line);
3825
+ if (legacy) {
3826
+ let message = legacy[5].trim();
3827
+ let code = "";
3828
+ const codeMatch = TRAILING_CODE_RE.exec(message);
3829
+ if (codeMatch) {
3830
+ code = codeMatch[1];
3831
+ message = message.slice(0, codeMatch.index).trim();
3832
+ }
3833
+ const finding = {
3834
+ file: legacy[1].trim(),
3835
+ line: Number(legacy[2]),
3836
+ severity: normaliseSeverity(legacy[4]),
3837
+ code,
3838
+ message
3839
+ };
3840
+ if (legacy[3]) finding.column = Number(legacy[3]);
3841
+ findings.push(finding);
3842
+ continue;
3843
+ }
3844
+ const totals = SUMMARY_RE.exec(line);
3845
+ if (totals) {
3846
+ summary = {
3847
+ defects: Number(totals[1]),
3848
+ errors: Number(totals[2]),
3849
+ warnings: Number(totals[3]),
3850
+ suggestions: Number(totals[4])
3851
+ };
3852
+ continue;
3853
+ }
3854
+ if (/no defects found/i.test(line)) {
3855
+ summary = { defects: 0, errors: 0, warnings: 0, suggestions: 0 };
3856
+ continue;
3857
+ }
3858
+ if (NOTICE_RE.test(line)) notices.push(line);
3064
3859
  }
3065
- return findings;
3860
+ return { findings, summary, notices, incomplete: notices.length > 0 };
3861
+ }
3862
+ function parseCodelinterFindings(output) {
3863
+ return parseCodelinterReport(output).findings;
3066
3864
  }
3067
3865
  async function runCodelinter(opts) {
3068
3866
  const res = await runProcess({
@@ -3074,16 +3872,28 @@ async function runCodelinter(opts) {
3074
3872
  logger: opts.logger
3075
3873
  });
3076
3874
  const raw = `${res.stdout}${res.stderr}`;
3077
- return { code: res.code, findings: parseCodelinterFindings(raw), raw };
3875
+ return { code: res.code, ...parseCodelinterReport(raw), raw: cleanCodelinterOutput(raw) };
3078
3876
  }
3079
3877
 
3080
3878
  // src/build/buildHap.ts
3879
+ var DIAGNOSIS_OUTPUT_LIMIT = 2e6;
3880
+ function diagnoseBuildFailure(output) {
3881
+ if (/is not exported from Kit|has no exported member .*@systemapi/i.test(output)) {
3882
+ 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`).";
3883
+ }
3884
+ if (/00308018|SDK path does not exist|SDK license agreement is not accepted/i.test(output)) {
3885
+ return "hvigor cannot find the SDK the project compiles against: check `oniro-app sdk list` against compileSdkVersion.";
3886
+ }
3887
+ return void 0;
3888
+ }
3081
3889
  async function buildHap(opts) {
3082
3890
  const logger = scopedLogger(opts.logger ?? noopLogger, "build");
3083
3891
  const warnings = [];
3084
3892
  const start = Date.now();
3085
3893
  const projectDir = toLongPath(opts.projectDir);
3086
3894
  const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
3895
+ const missingSdk = isHarmonyOs ? null : findMissingProjectSdk(opts.config, projectDir);
3896
+ if (missingSdk) throw missingSdk;
3087
3897
  if (opts.autoInstallDeps !== false) {
3088
3898
  const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
3089
3899
  const r = await ensureOhModules({
@@ -3096,23 +3906,35 @@ async function buildHap(opts) {
3096
3906
  });
3097
3907
  if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
3098
3908
  }
3099
- const { exitCode } = await runHvigorw({
3100
- config: opts.config,
3101
- projectDir,
3102
- product: opts.product,
3103
- module: opts.module,
3104
- buildMode: opts.mode,
3105
- task: opts.task,
3106
- parallel: opts.parallel,
3107
- abortSignal: opts.abortSignal,
3108
- onOutput: opts.onOutput,
3109
- logger: opts.logger
3110
- });
3909
+ let output = "";
3910
+ let exitCode;
3911
+ try {
3912
+ ({ exitCode } = await runHvigorw({
3913
+ config: opts.config,
3914
+ projectDir,
3915
+ product: opts.product,
3916
+ module: opts.module,
3917
+ buildMode: opts.mode,
3918
+ task: opts.task,
3919
+ parallel: opts.parallel,
3920
+ abortSignal: opts.abortSignal,
3921
+ onOutput: (chunk, stream) => {
3922
+ output = (output + chunk).slice(-DIAGNOSIS_OUTPUT_LIMIT);
3923
+ opts.onOutput?.(chunk, stream);
3924
+ },
3925
+ logger: opts.logger
3926
+ }));
3927
+ } catch (err) {
3928
+ const hint = diagnoseBuildFailure(output);
3929
+ if (hint && err instanceof OniroError) throw new OniroError(`${err.message}
3930
+ ${hint}`, err);
3931
+ throw err;
3932
+ }
3111
3933
  const discoveredHaps = await discoverHaps({ projectDir });
3112
3934
  const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
3113
3935
  if (!anySigned) {
3114
3936
  warnings.push(
3115
- 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)."
3937
+ 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."
3116
3938
  );
3117
3939
  }
3118
3940
  return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
@@ -3129,21 +3951,21 @@ function isValidBundleName(bundleName) {
3129
3951
  }
3130
3952
 
3131
3953
  // src/project/templates.ts
3132
- import * as fs25 from "fs";
3133
- import * as path25 from "path";
3954
+ import * as fs26 from "fs";
3955
+ import * as path26 from "path";
3134
3956
  function toHumanTemplateName(folderName) {
3135
3957
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
3136
3958
  }
3137
3959
  function listTemplates(templateRoot) {
3138
- if (!fs25.existsSync(templateRoot)) return [];
3139
- const entries = fs25.readdirSync(templateRoot, { withFileTypes: true });
3960
+ if (!fs26.existsSync(templateRoot)) return [];
3961
+ const entries = fs26.readdirSync(templateRoot, { withFileTypes: true });
3140
3962
  return entries.filter((e) => e.isDirectory()).map((e) => {
3141
- const dir = path25.join(templateRoot, e.name);
3142
- const metaPath = path25.join(dir, "template.json");
3963
+ const dir = path26.join(templateRoot, e.name);
3964
+ const metaPath = path26.join(dir, "template.json");
3143
3965
  let meta = {};
3144
- if (fs25.existsSync(metaPath)) {
3966
+ if (fs26.existsSync(metaPath)) {
3145
3967
  try {
3146
- meta = JSON.parse(fs25.readFileSync(metaPath, "utf8"));
3968
+ meta = JSON.parse(fs26.readFileSync(metaPath, "utf8"));
3147
3969
  } catch {
3148
3970
  }
3149
3971
  }
@@ -3158,57 +3980,57 @@ function listTemplates(templateRoot) {
3158
3980
  function validateTemplateLayout(templateDir, defaultModuleName) {
3159
3981
  const required = [
3160
3982
  "build-profile.json5",
3161
- path25.join("AppScope", "app.json5"),
3162
- path25.join(defaultModuleName, "src", "main", "module.json5"),
3163
- path25.join(defaultModuleName, "oh-package.json5"),
3983
+ path26.join("AppScope", "app.json5"),
3984
+ path26.join(defaultModuleName, "src", "main", "module.json5"),
3985
+ path26.join(defaultModuleName, "oh-package.json5"),
3164
3986
  "hvigorfile.ts"
3165
3987
  ];
3166
- return required.filter((rel) => !fs25.existsSync(path25.join(templateDir, rel)));
3988
+ return required.filter((rel) => !fs26.existsSync(path26.join(templateDir, rel)));
3167
3989
  }
3168
3990
 
3169
3991
  // src/project/createScaffold.ts
3170
- import * as fs27 from "fs";
3171
- import * as path26 from "path";
3992
+ import * as fs28 from "fs";
3993
+ import * as path27 from "path";
3172
3994
 
3173
3995
  // src/project/jsonHelpers.ts
3174
- import * as fs26 from "fs";
3996
+ import * as fs27 from "fs";
3175
3997
  import JSON55 from "json5";
3176
3998
  function readJson5File(filePath) {
3177
- return JSON55.parse(fs26.readFileSync(filePath, "utf8"));
3999
+ return JSON55.parse(fs27.readFileSync(filePath, "utf8"));
3178
4000
  }
3179
4001
  function writeJson5File(filePath, value) {
3180
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4002
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3181
4003
  }
3182
4004
  function readJsonFile(filePath) {
3183
- return JSON.parse(fs26.readFileSync(filePath, "utf8"));
4005
+ return JSON.parse(fs27.readFileSync(filePath, "utf8"));
3184
4006
  }
3185
4007
  function writeJsonFile(filePath, value) {
3186
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4008
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3187
4009
  }
3188
4010
 
3189
4011
  // src/project/createScaffold.ts
3190
4012
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
3191
4013
  async function pathExists(p) {
3192
4014
  try {
3193
- await fs27.promises.access(p);
4015
+ await fs28.promises.access(p);
3194
4016
  return true;
3195
4017
  } catch {
3196
4018
  return false;
3197
4019
  }
3198
4020
  }
3199
4021
  async function copyDirRecursive(srcDir, destDir) {
3200
- await fs27.promises.mkdir(destDir, { recursive: true });
3201
- const entries = await fs27.promises.readdir(srcDir, { withFileTypes: true });
4022
+ await fs28.promises.mkdir(destDir, { recursive: true });
4023
+ const entries = await fs28.promises.readdir(srcDir, { withFileTypes: true });
3202
4024
  for (const entry of entries) {
3203
- const src = path26.join(srcDir, entry.name);
3204
- const dest = path26.join(destDir, entry.name);
4025
+ const src = path27.join(srcDir, entry.name);
4026
+ const dest = path27.join(destDir, entry.name);
3205
4027
  if (entry.isDirectory()) {
3206
4028
  await copyDirRecursive(src, dest);
3207
4029
  } else if (entry.isSymbolicLink()) {
3208
4030
  continue;
3209
4031
  } else if (entry.isFile()) {
3210
4032
  if (entry.name === "template.json") continue;
3211
- await fs27.promises.copyFile(src, dest);
4033
+ await fs28.promises.copyFile(src, dest);
3212
4034
  }
3213
4035
  }
3214
4036
  }
@@ -3216,9 +4038,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
3216
4038
  const stack = [projectDir];
3217
4039
  while (stack.length > 0) {
3218
4040
  const currentDir = stack.pop();
3219
- const entries = await fs27.promises.readdir(currentDir, { withFileTypes: true });
4041
+ const entries = await fs28.promises.readdir(currentDir, { withFileTypes: true });
3220
4042
  for (const entry of entries) {
3221
- const fullPath = path26.join(currentDir, entry.name);
4043
+ const fullPath = path27.join(currentDir, entry.name);
3222
4044
  if (entry.isDirectory()) {
3223
4045
  if (IGNORED_DIRS.has(entry.name)) continue;
3224
4046
  stack.push(fullPath);
@@ -3240,23 +4062,23 @@ function toJavaPropertiesPath(p) {
3240
4062
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
3241
4063
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
3242
4064
  `;
3243
- fs27.writeFileSync(path26.join(projectDir, "local.properties"), content, "utf8");
4065
+ fs28.writeFileSync(path27.join(projectDir, "local.properties"), content, "utf8");
3244
4066
  }
3245
4067
  function renameIfExists(fromPath, toPath) {
3246
- if (fs27.existsSync(fromPath) && fromPath !== toPath) {
3247
- fs27.renameSync(fromPath, toPath);
4068
+ if (fs28.existsSync(fromPath) && fromPath !== toPath) {
4069
+ fs28.renameSync(fromPath, toPath);
3248
4070
  }
3249
4071
  }
3250
4072
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3251
- const appJsonPath = path26.join(projectDir, "AppScope", "app.json5");
3252
- if (fs27.existsSync(appJsonPath)) {
4073
+ const appJsonPath = path27.join(projectDir, "AppScope", "app.json5");
4074
+ if (fs28.existsSync(appJsonPath)) {
3253
4075
  const appJson = readJson5File(appJsonPath);
3254
4076
  appJson.app = appJson.app ?? {};
3255
4077
  appJson.app.bundleName = args.bundleName;
3256
4078
  writeJson5File(appJsonPath, appJson);
3257
4079
  }
3258
- const stringsPath = path26.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3259
- if (fs27.existsSync(stringsPath)) {
4080
+ const stringsPath = path27.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
4081
+ if (fs28.existsSync(stringsPath)) {
3260
4082
  const strings = readJsonFile(stringsPath);
3261
4083
  if (Array.isArray(strings.string)) {
3262
4084
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -3264,8 +4086,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3264
4086
  }
3265
4087
  writeJsonFile(stringsPath, strings);
3266
4088
  }
3267
- const buildProfilePath = path26.join(projectDir, "build-profile.json5");
3268
- if (fs27.existsSync(buildProfilePath)) {
4089
+ const buildProfilePath = path27.join(projectDir, "build-profile.json5");
4090
+ if (fs28.existsSync(buildProfilePath)) {
3269
4091
  const buildProfile = readJson5File(buildProfilePath);
3270
4092
  buildProfile.app = buildProfile.app ?? {};
3271
4093
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -3278,23 +4100,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3278
4100
  }
3279
4101
  writeJson5File(buildProfilePath, buildProfile);
3280
4102
  }
3281
- const moduleJsonPath = path26.join(projectDir, args.moduleName, "src", "main", "module.json5");
3282
- if (fs27.existsSync(moduleJsonPath)) {
4103
+ const moduleJsonPath = path27.join(projectDir, args.moduleName, "src", "main", "module.json5");
4104
+ if (fs28.existsSync(moduleJsonPath)) {
3283
4105
  const moduleJson = readJson5File(moduleJsonPath);
3284
4106
  moduleJson.module = moduleJson.module ?? {};
3285
4107
  moduleJson.module.name = args.moduleName;
3286
4108
  writeJson5File(moduleJsonPath, moduleJson);
3287
4109
  }
3288
- const moduleOhPackagePath = path26.join(projectDir, args.moduleName, "oh-package.json5");
3289
- if (fs27.existsSync(moduleOhPackagePath)) {
4110
+ const moduleOhPackagePath = path27.join(projectDir, args.moduleName, "oh-package.json5");
4111
+ if (fs28.existsSync(moduleOhPackagePath)) {
3290
4112
  const pkg = readJson5File(moduleOhPackagePath);
3291
4113
  pkg.name = args.moduleName;
3292
4114
  writeJson5File(moduleOhPackagePath, pkg);
3293
4115
  }
3294
- const vscodeDir = path26.join(projectDir, ".vscode");
3295
- fs27.mkdirSync(vscodeDir, { recursive: true });
4116
+ const vscodeDir = path27.join(projectDir, ".vscode");
4117
+ fs28.mkdirSync(vscodeDir, { recursive: true });
3296
4118
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
3297
- writeJsonFile(path26.join(vscodeDir, "settings.json"), {
4119
+ writeJsonFile(path27.join(vscodeDir, "settings.json"), {
3298
4120
  "oniro.hapPath": hapPath,
3299
4121
  "files.associations": { "*.json5": "jsonc" }
3300
4122
  });
@@ -3311,7 +4133,7 @@ async function createScaffold(opts) {
3311
4133
  if (!opts.location || !await pathExists(opts.location)) {
3312
4134
  throw new OniroError(`Location does not exist: ${opts.location}`);
3313
4135
  }
3314
- const templateDir = path26.join(opts.templateRoot, opts.templateId);
4136
+ const templateDir = path27.join(opts.templateRoot, opts.templateId);
3315
4137
  if (!await pathExists(templateDir)) {
3316
4138
  throw new OniroError(`Template not found: ${templateDir}`);
3317
4139
  }
@@ -3346,17 +4168,17 @@ async function createScaffold(opts) {
3346
4168
  }
3347
4169
  }
3348
4170
  }
3349
- const projectDir = path26.join(opts.location, opts.projectName);
4171
+ const projectDir = path27.join(opts.location, opts.projectName);
3350
4172
  if (await pathExists(projectDir)) {
3351
4173
  if (!opts.overwrite) {
3352
4174
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
3353
4175
  }
3354
- await fs27.promises.rm(projectDir, { recursive: true, force: true });
4176
+ await fs28.promises.rm(projectDir, { recursive: true, force: true });
3355
4177
  }
3356
4178
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
3357
4179
  await copyDirRecursive(templateDir, projectDir);
3358
4180
  if (moduleName !== defaultModuleName) {
3359
- renameIfExists(path26.join(projectDir, defaultModuleName), path26.join(projectDir, moduleName));
4181
+ renameIfExists(path27.join(projectDir, defaultModuleName), path27.join(projectDir, moduleName));
3360
4182
  }
3361
4183
  updateTemplateConfigs(
3362
4184
  projectDir,
@@ -3373,11 +4195,11 @@ async function createScaffold(opts) {
3373
4195
  }
3374
4196
 
3375
4197
  // src/project/listModules.ts
3376
- import * as fs28 from "fs";
3377
- import * as path27 from "path";
4198
+ import * as fs29 from "fs";
4199
+ import * as path28 from "path";
3378
4200
  function listModules(opts) {
3379
- const buildProfile = path27.join(opts.projectDir, "build-profile.json5");
3380
- if (!fs28.existsSync(buildProfile)) {
4201
+ const buildProfile = path28.join(opts.projectDir, "build-profile.json5");
4202
+ if (!fs29.existsSync(buildProfile)) {
3381
4203
  throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
3382
4204
  }
3383
4205
  const parsed = readJson5File(buildProfile);
@@ -3389,12 +4211,12 @@ function listModules(opts) {
3389
4211
  }
3390
4212
 
3391
4213
  // src/project/abilities.ts
3392
- import * as fs29 from "fs";
3393
- import * as path28 from "path";
4214
+ import * as fs30 from "fs";
4215
+ import * as path29 from "path";
3394
4216
  function listAbilities(opts) {
3395
4217
  const moduleName = opts.moduleName ?? "entry";
3396
- const moduleJsonPath = path28.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3397
- if (!fs29.existsSync(moduleJsonPath)) {
4218
+ const moduleJsonPath = path29.join(opts.projectDir, moduleName, "src", "main", "module.json5");
4219
+ if (!fs30.existsSync(moduleJsonPath)) {
3398
4220
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
3399
4221
  }
3400
4222
  const parsed = readJson5File(moduleJsonPath);
@@ -3402,12 +4224,12 @@ function listAbilities(opts) {
3402
4224
  }
3403
4225
 
3404
4226
  // src/project/prepareSigning.ts
3405
- import * as fs30 from "fs";
3406
- import * as path29 from "path";
4227
+ import * as fs31 from "fs";
4228
+ import * as path30 from "path";
3407
4229
  function hasSigningArtifacts(dir) {
3408
- if (!fs30.existsSync(path29.join(dir, "signatures"))) return false;
3409
- const buildProfile = path29.join(dir, "build-profile.json5");
3410
- if (!fs30.existsSync(buildProfile)) return false;
4230
+ if (!fs31.existsSync(path30.join(dir, "signatures"))) return false;
4231
+ const buildProfile = path30.join(dir, "build-profile.json5");
4232
+ if (!fs31.existsSync(buildProfile)) return false;
3411
4233
  try {
3412
4234
  const parsed = readJson5File(buildProfile);
3413
4235
  return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
@@ -3425,6 +4247,7 @@ function prepareSigning(opts) {
3425
4247
  apl: opts.apl,
3426
4248
  appFeature: opts.appFeature,
3427
4249
  acls: opts.acls,
4250
+ appPrivilegeCapabilities: opts.appPrivilegeCapabilities,
3428
4251
  passwords: opts.passwords,
3429
4252
  logger: opts.logger
3430
4253
  });
@@ -3453,19 +4276,31 @@ var PROPS = [
3453
4276
  ["model", "const.product.model"],
3454
4277
  ["manufacturer", "const.product.manufacturer"],
3455
4278
  ["brand", "const.product.brand"],
4279
+ ["osDistName", "const.product.os.dist.name"],
4280
+ ["osDistVersion", "const.product.os.dist.version"],
3456
4281
  ["osFullName", "const.ohos.fullname"],
3457
4282
  ["osReleaseType", "const.ohos.releasetype"],
3458
4283
  ["sdkApiVersion", "const.ohos.apiversion"],
3459
- ["buildVersion", "const.product.software.version"]
4284
+ ["buildVersion", "const.product.software.version"],
4285
+ ["abi", "const.product.cpu.abilist"],
4286
+ ["deviceType", "const.product.devicetype"]
3460
4287
  ];
3461
4288
  async function getDeviceInfo(opts) {
3462
4289
  const info = { serial: opts.deviceSerial ?? "(default)", display: null };
3463
- for (const [field, prop] of PROPS) {
3464
- try {
3465
- const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
4290
+ try {
4291
+ const values = await paramGetMany(
4292
+ opts.config,
4293
+ PROPS.map(([, prop]) => prop),
4294
+ { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger }
4295
+ );
4296
+ for (const [field, prop] of PROPS) {
4297
+ const value = values[prop];
3466
4298
  if (value) info[field] = value;
3467
- } catch {
3468
4299
  }
4300
+ } catch {
4301
+ }
4302
+ if (info.osDistName || info.osFullName) {
4303
+ info.osFamily = /harmonyos/i.test(info.osDistName ?? "") ? "HarmonyOS" : "OpenHarmony";
3469
4304
  }
3470
4305
  const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
3471
4306
  if (screen) info.display = { width: screen.width, height: screen.height };
@@ -3497,8 +4332,8 @@ async function diffHapAssets(opts) {
3497
4332
  }
3498
4333
 
3499
4334
  // src/install/apply.ts
3500
- import * as fs31 from "fs";
3501
- import * as path30 from "path";
4335
+ import * as fs32 from "fs";
4336
+ import * as path31 from "path";
3502
4337
  function decideInstallMethod(s) {
3503
4338
  if (!s.signInfoInconsistent) return "replace";
3504
4339
  if (s.isSystemBundle && !s.allowUninstall) return "refuse";
@@ -3516,39 +4351,31 @@ async function pidOf(config, bundle, deviceSerial) {
3516
4351
  const proc = await findRunningProcess({ config, bundle, deviceSerial });
3517
4352
  return proc ? Number(proc.pid) : null;
3518
4353
  }
3519
- async function resolveHapPath(opts) {
3520
- let hapPath = opts.hapPath;
3521
- if (hapPath) {
3522
- if (!path30.isAbsolute(hapPath) && opts.projectDir) hapPath = path30.join(opts.projectDir, hapPath);
4354
+ async function resolveHap(opts, logger) {
4355
+ let hap;
4356
+ if (opts.hapPath) {
4357
+ const hapPath = !path31.isAbsolute(opts.hapPath) && opts.projectDir ? path31.join(opts.projectDir, opts.hapPath) : opts.hapPath;
4358
+ hap = existingHapOrUnsigned(hapPath);
4359
+ if (!hap) throw new OniroError(`HAP not found at ${hapPath}.`);
3523
4360
  } else {
3524
4361
  if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
3525
- const haps = await discoverHaps({ projectDir: opts.projectDir });
3526
- if (opts.module) {
3527
- const signed = haps[opts.module]?.signed ?? [];
3528
- if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
3529
- hapPath = signed[0];
3530
- } else {
3531
- const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
3532
- if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
3533
- if (withSigned.length > 1) {
3534
- throw new OniroError(
3535
- `Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
3536
- );
3537
- }
3538
- hapPath = withSigned[0][1].signed[0];
3539
- }
4362
+ const picked = pickHap(await discoverHaps({ projectDir: opts.projectDir }), opts.module);
4363
+ if (!picked) throw new OniroError(`No HAP found${opts.module ? ` for module ${opts.module}` : ""}. Build first.`);
4364
+ hap = { path: picked.path, fallback: !picked.signed };
3540
4365
  }
3541
- if (!fs31.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3542
- return hapPath;
4366
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path31.basename(hap.path)}.`);
4367
+ return hap;
3543
4368
  }
3544
4369
  async function applyChanges(opts) {
3545
4370
  const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
3546
4371
  const { config, bundle, deviceSerial } = opts;
3547
4372
  const isSystemBundle = opts.isSystemBundle ?? false;
3548
4373
  const allowUninstall = opts.allowUninstall ?? false;
3549
- const hapPath = await resolveHapPath(opts);
4374
+ const hap = await resolveHap(opts, logger);
4375
+ const hapPath = hap.path;
4376
+ logger.info(`installing ${hapPath}`);
3550
4377
  let assetsChanged = false;
3551
- if (opts.installedHapPath && fs31.existsSync(opts.installedHapPath)) {
4378
+ if (opts.installedHapPath && fs32.existsSync(opts.installedHapPath)) {
3552
4379
  try {
3553
4380
  const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
3554
4381
  assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
@@ -3569,12 +4396,21 @@ async function applyChanges(opts) {
3569
4396
  if (decision === "uninstall-install") {
3570
4397
  logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
3571
4398
  await uninstallApp({ config, bundle, deviceSerial });
3572
- ensureOk(await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }), `hdc install ${hapPath}`);
4399
+ ensureInstallOk(
4400
+ await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }),
4401
+ `hdc install ${hapPath}`,
4402
+ hap
4403
+ );
3573
4404
  installMethod = "uninstall-install";
3574
4405
  } else {
3575
- ensureOk(replaceRes, `hdc install -r ${hapPath}`);
4406
+ ensureInstallOk(replaceRes, `hdc install -r ${hapPath}`, hap);
3576
4407
  installMethod = "replace";
3577
4408
  }
4409
+ if (!await isBundleInstalled({ config, bundle, deviceSerial })) {
4410
+ throw new OniroError(
4411
+ `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).`
4412
+ );
4413
+ }
3578
4414
  let postInstallPid = await pidOf(config, bundle, deviceSerial);
3579
4415
  const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
3580
4416
  let method = installMethod;
@@ -3649,9 +4485,9 @@ var AUTH_PATHS = {
3649
4485
  };
3650
4486
 
3651
4487
  // src/harmonyos/auth/tokenStore.ts
3652
- import * as fs32 from "fs";
4488
+ import * as fs33 from "fs";
3653
4489
  import * as os11 from "os";
3654
- import * as path31 from "path";
4490
+ import * as path32 from "path";
3655
4491
  import * as crypto3 from "crypto";
3656
4492
  var ALGORITHM = "aes-256-gcm";
3657
4493
  var KEY_LENGTH = 32;
@@ -3687,7 +4523,7 @@ function permissionHint(dir) {
3687
4523
  }
3688
4524
  function ensureDir(dir) {
3689
4525
  try {
3690
- fs32.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4526
+ fs33.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
3691
4527
  } catch (err) {
3692
4528
  if (err.code === "EACCES") {
3693
4529
  throw new OniroError(permissionHint(dir), err);
@@ -3698,17 +4534,17 @@ function ensureDir(dir) {
3698
4534
  function createTokenStore(opts) {
3699
4535
  const logger = opts.logger ?? noopLogger;
3700
4536
  const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
3701
- const tokenPath = path31.join(authDir, TOKEN_FILE);
3702
- const keyPath = path31.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4537
+ const tokenPath = path32.join(authDir, TOKEN_FILE);
4538
+ const keyPath = path32.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
3703
4539
  function loadOrCreateKey() {
3704
- if (fs32.existsSync(keyPath)) {
3705
- const existing = fs32.readFileSync(keyPath);
4540
+ if (fs33.existsSync(keyPath)) {
4541
+ const existing = fs33.readFileSync(keyPath);
3706
4542
  if (existing.length === KEY_LENGTH) return existing;
3707
4543
  logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
3708
4544
  }
3709
- ensureDir(path31.dirname(keyPath));
4545
+ ensureDir(path32.dirname(keyPath));
3710
4546
  const key = crypto3.randomBytes(KEY_LENGTH);
3711
- fs32.writeFileSync(keyPath, key, { mode: FILE_MODE });
4547
+ fs33.writeFileSync(keyPath, key, { mode: FILE_MODE });
3712
4548
  return key;
3713
4549
  }
3714
4550
  return {
@@ -3718,12 +4554,12 @@ function createTokenStore(opts) {
3718
4554
  const payload = Buffer.from(JSON.stringify(session), "utf8");
3719
4555
  const blob = encrypt2(loadOrCreateKey(), payload);
3720
4556
  ensureDir(authDir);
3721
- fs32.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4557
+ fs33.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
3722
4558
  },
3723
4559
  load() {
3724
- if (!fs32.existsSync(tokenPath)) return null;
4560
+ if (!fs33.existsSync(tokenPath)) return null;
3725
4561
  try {
3726
- const blob = JSON.parse(fs32.readFileSync(tokenPath, "utf8"));
4562
+ const blob = JSON.parse(fs33.readFileSync(tokenPath, "utf8"));
3727
4563
  if (!isEncryptedBlob(blob)) return null;
3728
4564
  const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
3729
4565
  return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
@@ -3735,7 +4571,7 @@ function createTokenStore(opts) {
3735
4571
  }
3736
4572
  logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
3737
4573
  try {
3738
- fs32.rmSync(tokenPath, { force: true });
4574
+ fs33.rmSync(tokenPath, { force: true });
3739
4575
  } catch {
3740
4576
  }
3741
4577
  return null;
@@ -3743,7 +4579,7 @@ function createTokenStore(opts) {
3743
4579
  },
3744
4580
  clear() {
3745
4581
  try {
3746
- fs32.rmSync(tokenPath, { force: true });
4582
+ fs33.rmSync(tokenPath, { force: true });
3747
4583
  } catch (err) {
3748
4584
  throw new OniroError(`Failed to remove ${tokenPath}.`, err);
3749
4585
  }
@@ -3857,8 +4693,8 @@ function isQuit(quit) {
3857
4693
  async function startCallbackServer(opts) {
3858
4694
  let resolveCallback;
3859
4695
  let rejectCallback;
3860
- const callback = new Promise((resolve6, reject) => {
3861
- resolveCallback = resolve6;
4696
+ const callback = new Promise((resolve7, reject) => {
4697
+ resolveCallback = resolve7;
3862
4698
  rejectCallback = reject;
3863
4699
  });
3864
4700
  callback.catch(() => {
@@ -3917,9 +4753,9 @@ async function startCallbackServer(opts) {
3917
4753
  }
3918
4754
  });
3919
4755
  server.keepAliveTimeout = 1;
3920
- await new Promise((resolve6, reject) => {
4756
+ await new Promise((resolve7, reject) => {
3921
4757
  server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
3922
- server.listen(0, "127.0.0.1", resolve6);
4758
+ server.listen(0, "127.0.0.1", resolve7);
3923
4759
  });
3924
4760
  const port = server.address().port;
3925
4761
  return {
@@ -3935,12 +4771,12 @@ async function startCallbackServer(opts) {
3935
4771
  return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
3936
4772
  },
3937
4773
  stop() {
3938
- return new Promise((resolve6) => {
4774
+ return new Promise((resolve7) => {
3939
4775
  server.closeAllConnections?.();
3940
- const timer = setTimeout(resolve6, 100);
4776
+ const timer = setTimeout(resolve7, 100);
3941
4777
  server.close(() => {
3942
4778
  clearTimeout(timer);
3943
- resolve6();
4779
+ resolve7();
3944
4780
  });
3945
4781
  });
3946
4782
  }
@@ -3972,11 +4808,11 @@ function openBrowser(url) {
3972
4808
  windowsHide: true
3973
4809
  });
3974
4810
  child.unref();
3975
- return new Promise((resolve6, reject) => {
4811
+ return new Promise((resolve7, reject) => {
3976
4812
  child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
3977
4813
  child.on(
3978
4814
  "exit",
3979
- (code) => code === 0 ? resolve6() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
4815
+ (code) => code === 0 ? resolve7() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
3980
4816
  );
3981
4817
  });
3982
4818
  }
@@ -4224,7 +5060,7 @@ function mapAgcError(operation, statusCode, reasonPhrase, body) {
4224
5060
  }
4225
5061
 
4226
5062
  // src/harmonyos/signing/profile.ts
4227
- import * as fs33 from "fs";
5063
+ import * as fs34 from "fs";
4228
5064
  import { X509Certificate } from "crypto";
4229
5065
  import forge from "node-forge";
4230
5066
  function extractProfileJson(buffer) {
@@ -4256,7 +5092,7 @@ function extractProfileJson(buffer) {
4256
5092
  function readProvisionProfile(profilePath) {
4257
5093
  let raw;
4258
5094
  try {
4259
- const json = extractProfileJson(fs33.readFileSync(profilePath));
5095
+ const json = extractProfileJson(fs34.readFileSync(profilePath));
4260
5096
  if (!json) return null;
4261
5097
  raw = JSON.parse(json);
4262
5098
  } catch {
@@ -4285,7 +5121,7 @@ function parseCertificate(pem) {
4285
5121
  }
4286
5122
  function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4287
5123
  try {
4288
- const der = forge.util.createBuffer(fs33.readFileSync(p12Path).toString("binary"));
5124
+ const der = forge.util.createBuffer(fs34.readFileSync(p12Path).toString("binary"));
4289
5125
  const p12 = forge.pkcs12.pkcs12FromAsn1(forge.asn1.fromDer(der), keyPwd);
4290
5126
  const certBag = forge.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
4291
5127
  const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
@@ -4301,7 +5137,7 @@ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4301
5137
  }
4302
5138
  }
4303
5139
  function verifySigningMaterial(opts) {
4304
- const pems = fs33.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
5140
+ const pems = fs34.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
4305
5141
  const chain = (pems ?? []).map(parseCertificate);
4306
5142
  const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
4307
5143
  if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
@@ -4320,14 +5156,14 @@ function verifySigningMaterial(opts) {
4320
5156
 
4321
5157
  // src/harmonyos/signing/autoSign.ts
4322
5158
  import * as crypto9 from "crypto";
4323
- import * as fs38 from "fs";
4324
- import * as path37 from "path";
5159
+ import * as fs39 from "fs";
5160
+ import * as path38 from "path";
4325
5161
  import JSON57 from "json5";
4326
5162
 
4327
5163
  // src/harmonyos/signing/agc.ts
4328
5164
  import * as crypto6 from "crypto";
4329
- import * as fs34 from "fs";
4330
- import * as path32 from "path";
5165
+ import * as fs35 from "fs";
5166
+ import * as path33 from "path";
4331
5167
  var ENDPOINTS = {
4332
5168
  CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
4333
5169
  CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
@@ -4413,8 +5249,8 @@ function createAgcClient(http3, auth) {
4413
5249
  if (target.sha256 && actual !== target.sha256.toLowerCase()) {
4414
5250
  throw new ChecksumMismatchError(target.sha256, actual);
4415
5251
  }
4416
- fs34.mkdirSync(path32.dirname(dest), { recursive: true, mode: 448 });
4417
- fs34.writeFileSync(dest, buffer, { mode: 384 });
5252
+ fs35.mkdirSync(path33.dirname(dest), { recursive: true, mode: 448 });
5253
+ fs35.writeFileSync(dest, buffer, { mode: 384 });
4418
5254
  }
4419
5255
  };
4420
5256
  }
@@ -4465,22 +5301,22 @@ async function registerDevices(agc, connected, logger) {
4465
5301
 
4466
5302
  // src/harmonyos/signing/keystore.ts
4467
5303
  import * as crypto7 from "crypto";
4468
- import * as fs36 from "fs";
4469
- import * as path34 from "path";
5304
+ import * as fs37 from "fs";
5305
+ import * as path35 from "path";
4470
5306
 
4471
5307
  // src/harmonyos/java.ts
4472
- import * as fs35 from "fs";
4473
- import * as path33 from "path";
5308
+ import * as fs36 from "fs";
5309
+ import * as path34 from "path";
4474
5310
  var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
4475
5311
  function findJava(sdk, env = process.env) {
4476
5312
  const candidates = [
4477
- env.JAVA_HOME && path33.join(env.JAVA_HOME, "bin", JAVA_BIN),
4478
- sdk && path33.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
5313
+ env.JAVA_HOME && path34.join(env.JAVA_HOME, "bin", JAVA_BIN),
5314
+ sdk && path34.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
4479
5315
  // macOS: the JBR is itself a bundle inside Studio's Contents.
4480
- sdk && path33.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
4481
- ...(env.PATH ?? "").split(path33.delimiter).map((dir) => dir && path33.join(dir, JAVA_BIN))
5316
+ sdk && path34.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
5317
+ ...(env.PATH ?? "").split(path34.delimiter).map((dir) => dir && path34.join(dir, JAVA_BIN))
4482
5318
  ];
4483
- const found = candidates.find((c) => !!c && fs35.existsSync(c));
5319
+ const found = candidates.find((c) => !!c && fs36.existsSync(c));
4484
5320
  if (found) return found;
4485
5321
  throw new OniroError(
4486
5322
  "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."
@@ -4494,13 +5330,13 @@ function redactArgs(args) {
4494
5330
  return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
4495
5331
  }
4496
5332
  async function generateKeystoreAndCsr(opts) {
4497
- const jar = path34.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
4498
- if (!fs36.existsSync(jar)) {
5333
+ const jar = path35.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
5334
+ if (!fs37.existsSync(jar)) {
4499
5335
  throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
4500
5336
  }
4501
5337
  const java = findJava(opts.sdk);
4502
5338
  const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
4503
- fs36.mkdirSync(path34.dirname(opts.p12Path), { recursive: true, mode: 448 });
5339
+ fs37.mkdirSync(path35.dirname(opts.p12Path), { recursive: true, mode: 448 });
4504
5340
  const run = async (what, args) => {
4505
5341
  const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
4506
5342
  opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
@@ -4514,28 +5350,28 @@ ${detail}` : ""}`);
4514
5350
  opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
4515
5351
  await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
4516
5352
  await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
4517
- return { keyPwd, csr: fs36.readFileSync(opts.csrPath, "utf8") };
5353
+ return { keyPwd, csr: fs37.readFileSync(opts.csrPath, "utf8") };
4518
5354
  }
4519
5355
 
4520
5356
  // src/harmonyos/signing/paths.ts
4521
5357
  import * as crypto8 from "crypto";
4522
- import * as path35 from "path";
5358
+ import * as path36 from "path";
4523
5359
  function sanitizeNamePart(raw) {
4524
5360
  const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
4525
5361
  return safe.slice(0, 64) || "default";
4526
5362
  }
4527
5363
  function resolveSigningMaterialPaths(config, productName, projectRoot) {
4528
5364
  const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
4529
- const base = path35.join(
5365
+ const base = path36.join(
4530
5366
  config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
4531
- `${sanitizeNamePart(productName)}_${path35.basename(projectRoot)}_${hash}=`
5367
+ `${sanitizeNamePart(productName)}_${path36.basename(projectRoot)}_${hash}=`
4532
5368
  );
4533
5369
  return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
4534
5370
  }
4535
5371
 
4536
5372
  // src/harmonyos/signing/project.ts
4537
- import * as fs37 from "fs";
4538
- import * as path36 from "path";
5373
+ import * as fs38 from "fs";
5374
+ import * as path37 from "path";
4539
5375
  import JSON56 from "json5";
4540
5376
 
4541
5377
  // src/harmonyos/signing/aclPermissionList.ts
@@ -4603,9 +5439,9 @@ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
4603
5439
 
4604
5440
  // src/harmonyos/signing/project.ts
4605
5441
  function readRequestedPermissions(moduleConfigPath) {
4606
- if (!fs37.existsSync(moduleConfigPath)) return [];
5442
+ if (!fs38.existsSync(moduleConfigPath)) return [];
4607
5443
  try {
4608
- const parsed = JSON56.parse(fs37.readFileSync(moduleConfigPath, "utf-8"));
5444
+ const parsed = JSON56.parse(fs38.readFileSync(moduleConfigPath, "utf-8"));
4609
5445
  return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
4610
5446
  } catch {
4611
5447
  return [];
@@ -4614,10 +5450,10 @@ function readRequestedPermissions(moduleConfigPath) {
4614
5450
  function collectAclPermissions(projectDir, logger = noopLogger) {
4615
5451
  const requested = /* @__PURE__ */ new Set();
4616
5452
  for (const module of listModules({ projectDir })) {
4617
- const moduleRoot = path36.resolve(projectDir, module.srcPath);
5453
+ const moduleRoot = path37.resolve(projectDir, module.srcPath);
4618
5454
  for (const sourceSet of ["main", "ohosTest"]) {
4619
5455
  for (const name of readRequestedPermissions(
4620
- path36.join(moduleRoot, "src", sourceSet, "module.json5")
5456
+ path37.join(moduleRoot, "src", sourceSet, "module.json5")
4621
5457
  )) {
4622
5458
  requested.add(name);
4623
5459
  }
@@ -4633,7 +5469,7 @@ function collectAclPermissions(projectDir, logger = noopLogger) {
4633
5469
  // src/harmonyos/signing/autoSign.ts
4634
5470
  function shouldRegenerate(input) {
4635
5471
  if (input.force) return "--force was passed";
4636
- const missing = Object.values(input.paths).find((file) => !fs38.existsSync(file));
5472
+ const missing = Object.values(input.paths).find((file) => !fs39.existsSync(file));
4637
5473
  if (missing) return `${missing} is missing`;
4638
5474
  const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
4639
5475
  if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
@@ -4672,16 +5508,16 @@ function writeSigningConfig(opts) {
4672
5508
  for (const product of app.products ?? []) {
4673
5509
  if (product.name === opts.productName) product.signingConfig = opts.productName;
4674
5510
  }
4675
- fs38.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5511
+ fs39.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
4676
5512
  }
4677
5513
  async function harmonyOsAutoSign(opts) {
4678
5514
  const logger = opts.logger ?? noopLogger;
4679
5515
  const productName = opts.productName ?? "default";
4680
- const projectDir = path37.resolve(opts.projectDir);
4681
- const buildProfilePath = path37.join(projectDir, "build-profile.json5");
5516
+ const projectDir = path38.resolve(opts.projectDir);
5517
+ const buildProfilePath = path38.join(projectDir, "build-profile.json5");
4682
5518
  let buildProfile;
4683
5519
  try {
4684
- buildProfile = JSON57.parse(fs38.readFileSync(buildProfilePath, "utf-8"));
5520
+ buildProfile = JSON57.parse(fs39.readFileSync(buildProfilePath, "utf-8"));
4685
5521
  } catch (err) {
4686
5522
  throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
4687
5523
  }
@@ -4721,7 +5557,7 @@ async function harmonyOsAutoSign(opts) {
4721
5557
  logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
4722
5558
  const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
4723
5559
  const deviceIds = await registerDevices(agc, connected, logger);
4724
- for (const file of Object.values(paths)) fs38.rmSync(file, { force: true });
5560
+ for (const file of Object.values(paths)) fs39.rmSync(file, { force: true });
4725
5561
  const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
4726
5562
  const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
4727
5563
  if (stale) {
@@ -4752,10 +5588,10 @@ async function harmonyOsAutoSign(opts) {
4752
5588
  try {
4753
5589
  verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
4754
5590
  } catch (err) {
4755
- fs38.rmSync(paths.profilePath, { force: true });
5591
+ fs39.rmSync(paths.profilePath, { force: true });
4756
5592
  throw err;
4757
5593
  }
4758
- const materialDir = path37.join(path37.dirname(paths.p12Path), "material");
5594
+ const materialDir = path38.join(path38.dirname(paths.p12Path), "material");
4759
5595
  try {
4760
5596
  getKey(materialDir);
4761
5597
  } catch {
@@ -4767,7 +5603,7 @@ async function harmonyOsAutoSign(opts) {
4767
5603
  }
4768
5604
 
4769
5605
  // src/index.ts
4770
- var VERSION = "0.12.0";
5606
+ var VERSION = "0.14.0";
4771
5607
  export {
4772
5608
  ALL_SDKS,
4773
5609
  APL_VALUES,
@@ -4786,9 +5622,13 @@ export {
4786
5622
  SIGNING_ERRORS,
4787
5623
  SdkNotInstalledError,
4788
5624
  TMP_DIR_HINT,
5625
+ TargetNotFoundError,
4789
5626
  UnsupportedPlatformError,
4790
5627
  VERSION,
5628
+ appSandboxDir,
4791
5629
  applyChanges,
5630
+ assertDevicePath,
5631
+ assertLayoutSelector,
4792
5632
  attemptHdcConnection,
4793
5633
  buildCmdWrapper,
4794
5634
  buildGestureCommands,
@@ -4798,6 +5638,8 @@ export {
4798
5638
  buildInputCommand,
4799
5639
  buildRawTouchCommand,
4800
5640
  captureBurst,
5641
+ captureLog,
5642
+ cleanCodelinterOutput,
4801
5643
  consoleLogger,
4802
5644
  createHarmonyOsSession,
4803
5645
  createInstallTempDir,
@@ -4811,10 +5653,14 @@ export {
4811
5653
  dedupEntries,
4812
5654
  defaultContext,
4813
5655
  defaultPaths,
5656
+ describeLayoutNode,
4814
5657
  detectAaStartFailure,
5658
+ detectCmdToolsFlavour,
5659
+ detectInstallFailure,
4815
5660
  detectProjectSdkVersion,
4816
5661
  detectRuntimeOs,
4817
5662
  detectSigningConfigNames,
5663
+ diagnoseBuildFailure,
4818
5664
  diffEntryNames,
4819
5665
  diffHapAssets,
4820
5666
  discoverHaps,
@@ -4827,17 +5673,24 @@ export {
4827
5673
  dumpWindow,
4828
5674
  encryptPwd,
4829
5675
  ensureFreeSpace,
5676
+ ensureInstallOk,
4830
5677
  ensureOhModules,
4831
5678
  ensureOk,
4832
5679
  ensureRemoteOk,
4833
5680
  escapeCmdArgument,
4834
5681
  escapeCmdCommand,
5682
+ existingHapOrUnsigned,
4835
5683
  extractTarball,
4836
5684
  extractZipWithProgress,
4837
5685
  findAppProcessId,
4838
5686
  findCmdToolsSourceDir,
5687
+ findCoveredNodes,
4839
5688
  findHarmonyOsSdk,
5689
+ findKeyboardWindow,
5690
+ findLayoutNodes,
5691
+ findMissingProjectSdk,
4840
5692
  findRunningProcess,
5693
+ flattenLayout,
4841
5694
  forceStop,
4842
5695
  formatBytes,
4843
5696
  generateSigningConfigs,
@@ -4849,9 +5702,11 @@ export {
4849
5702
  getDeviceInfo,
4850
5703
  getDisplaySize,
4851
5704
  getEmulatorDir,
5705
+ getEmulatorStatus,
4852
5706
  getFreeSpaceBytes,
4853
5707
  getHdcPath,
4854
5708
  getHvigorwPath,
5709
+ getImeBundles,
4855
5710
  getInstalledSdks,
4856
5711
  getKey,
4857
5712
  getMainAbility,
@@ -4863,14 +5718,21 @@ export {
4863
5718
  getSupportedSdksForUi,
4864
5719
  harmonyOsAutoSign,
4865
5720
  harmonyOsSdkLabelForApi,
5721
+ hdcClientFailure,
4866
5722
  hdcExec,
5723
+ hdcTargetArgs,
5724
+ hdcTargetArgsFor,
5725
+ hideKeyboard,
4867
5726
  installApp,
4868
5727
  installCmdTools,
4869
5728
  installEmulator,
5729
+ installFailureReason,
5730
+ isBundleInstalled,
4870
5731
  isCmdToolsInstalled,
4871
5732
  isEmulatorInstalled,
4872
5733
  isOutOfSpaceError,
4873
5734
  isRamBackedPath,
5735
+ isUnsignedHap,
4874
5736
  isValidBundleName,
4875
5737
  isValidProjectName,
4876
5738
  killProcessTree,
@@ -4880,19 +5742,28 @@ export {
4880
5742
  listModules,
4881
5743
  listRunningProcesses,
4882
5744
  listTemplates,
5745
+ locateTarget,
4883
5746
  needsCmdWrapper,
4884
5747
  nonInteractivePrompter,
4885
5748
  noopLogger,
4886
5749
  noopProgress,
5750
+ normaliseLocalPath,
4887
5751
  paramGet,
5752
+ paramGetMany,
4888
5753
  paramSet,
4889
5754
  parseBounds,
4890
5755
  parseCodelinterFindings,
5756
+ parseCodelinterReport,
4891
5757
  parseDeviceList,
4892
5758
  parseHilogLine,
5759
+ parseImeBundles,
4893
5760
  parseMainAbilityFromBmDump,
5761
+ parseParamValues,
4894
5762
  parseRemoteExit,
5763
+ performInput,
5764
+ pickHap,
4895
5765
  pickSigningKind,
5766
+ pickTarget,
4896
5767
  prepareSigning,
4897
5768
  pruneLayout,
4898
5769
  readJson5File,
@@ -4905,6 +5776,8 @@ export {
4905
5776
  removeSdk,
4906
5777
  removeTempWorkDir,
4907
5778
  requireHarmonyOsSdk,
5779
+ resolveDeviceSerial,
5780
+ resolveKeyEvent,
4908
5781
  resolveTmpRoot,
4909
5782
  runCodelinter,
4910
5783
  runHvigorw,
@@ -4928,14 +5801,17 @@ export {
4928
5801
  toLongPath,
4929
5802
  toSpaceError,
4930
5803
  uninstallApp,
5804
+ unsignedHapRejection,
4931
5805
  validateTemplateLayout,
4932
5806
  verifySha256,
4933
5807
  waitForBoot,
4934
5808
  waitForBundle,
4935
5809
  waitForCondition,
4936
5810
  waitForLog,
5811
+ waitForTarget,
4937
5812
  watchLog,
4938
5813
  waypointsToRawEvents,
5814
+ withoutWindow,
4939
5815
  writeJson5File,
4940
5816
  writeJsonFile
4941
5817
  };