@oniroproject/core 0.13.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
  }
@@ -2578,9 +2865,9 @@ async function sendRawTouch(opts) {
2578
2865
  }
2579
2866
 
2580
2867
  // src/hdc/dump.ts
2581
- import * as fs20 from "fs";
2868
+ import * as fs21 from "fs";
2582
2869
  import * as os10 from "os";
2583
- import * as path20 from "path";
2870
+ import * as path22 from "path";
2584
2871
  import { randomBytes as randomBytes3 } from "crypto";
2585
2872
 
2586
2873
  // src/hdc/ime.ts
@@ -2843,10 +3130,10 @@ async function dumpLayout(opts) {
2843
3130
  if (!m) throw new OniroError(`Could not parse dumpLayout output:
2844
3131
  ${dump.stdout}`);
2845
3132
  const remote = m[1];
2846
- 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`);
2847
3134
  try {
2848
3135
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2849
- const parsed = JSON.parse(await fs20.promises.readFile(local, "utf8"));
3136
+ const parsed = JSON.parse(await fs21.promises.readFile(local, "utf8"));
2850
3137
  const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
2851
3138
  if (opts.prune === false) {
2852
3139
  return { display, tree: parsed };
@@ -2859,7 +3146,7 @@ ${dump.stdout}`);
2859
3146
  const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
2860
3147
  return { display, tree, keyboard };
2861
3148
  } finally {
2862
- fs20.promises.unlink(local).catch(() => {
3149
+ fs21.promises.unlink(local).catch(() => {
2863
3150
  });
2864
3151
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2865
3152
  });
@@ -2869,7 +3156,7 @@ ${dump.stdout}`);
2869
3156
  // src/hdc/uiActions.ts
2870
3157
  var FOCUS_SETTLE_MS = 400;
2871
3158
  var KEYBOARD_SETTLE_MS = 400;
2872
- var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
3159
+ var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
2873
3160
  function targetTree(layout) {
2874
3161
  const kb = layout.keyboard;
2875
3162
  return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
@@ -3022,13 +3309,14 @@ async function waitForTarget(opts) {
3022
3309
  }
3023
3310
 
3024
3311
  // src/hdc/hilog.ts
3312
+ import * as fs22 from "fs";
3025
3313
  function setHilogLevel(opts) {
3026
3314
  const logger = opts.logger ?? noopLogger;
3027
3315
  const hdc = getHdcPath(opts.config);
3028
- return new Promise((resolve6, reject) => {
3316
+ return new Promise((resolve7, reject) => {
3029
3317
  let child;
3030
3318
  try {
3031
- child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
3319
+ child = spawnCompat(hdc, [...hdcTargetArgs(opts.deviceSerial), "shell", "hilog", "-b", opts.level]);
3032
3320
  } catch (err) {
3033
3321
  const msg = err instanceof Error ? err.message : String(err);
3034
3322
  logger.error(`[hilog] failed to spawn hdc: ${msg}`);
@@ -3045,7 +3333,7 @@ function setHilogLevel(opts) {
3045
3333
  });
3046
3334
  child.once("close", (code) => {
3047
3335
  if (code === 0) {
3048
- resolve6();
3336
+ resolve7();
3049
3337
  } else {
3050
3338
  const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
3051
3339
  logger.error(`[hilog] ${msg}`);
@@ -3056,8 +3344,7 @@ function setHilogLevel(opts) {
3056
3344
  }
3057
3345
  function streamHilog(opts) {
3058
3346
  const hdc = getHdcPath(opts.config);
3059
- const args = [];
3060
- if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
3347
+ const args = [...hdcTargetArgs(opts.deviceSerial)];
3061
3348
  args.push("shell", "hilog");
3062
3349
  if (opts.processId && opts.processId.trim() !== "") {
3063
3350
  args.push("-P", opts.processId.trim());
@@ -3100,7 +3387,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
3100
3387
  return proc?.pid;
3101
3388
  }
3102
3389
  function waitForLog(opts) {
3103
- return new Promise((resolve6, reject) => {
3390
+ return new Promise((resolve7, reject) => {
3104
3391
  const deadline = Date.now() + opts.timeoutMs;
3105
3392
  let settled = false;
3106
3393
  let child = null;
@@ -3119,7 +3406,7 @@ function waitForLog(opts) {
3119
3406
  if (settled) return;
3120
3407
  settled = true;
3121
3408
  cleanup();
3122
- resolve6(entry);
3409
+ resolve7(entry);
3123
3410
  };
3124
3411
  const fail = (err) => {
3125
3412
  if (settled) return;
@@ -3166,7 +3453,7 @@ function waitForLog(opts) {
3166
3453
  });
3167
3454
  }
3168
3455
  function watchLog(opts) {
3169
- return new Promise((resolve6, reject) => {
3456
+ return new Promise((resolve7, reject) => {
3170
3457
  const matches = [];
3171
3458
  let settled = false;
3172
3459
  let child = null;
@@ -3183,7 +3470,7 @@ function watchLog(opts) {
3183
3470
  if (settled) return;
3184
3471
  settled = true;
3185
3472
  cleanup();
3186
- resolve6(opts.dedup === false ? matches : dedupEntries(matches));
3473
+ resolve7(opts.dedup === false ? matches : dedupEntries(matches));
3187
3474
  };
3188
3475
  const onAbort = () => {
3189
3476
  if (settled) return;
@@ -3215,6 +3502,94 @@ function watchLog(opts) {
3215
3502
  });
3216
3503
  });
3217
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
+ }
3218
3593
  async function dumpLog(opts) {
3219
3594
  let pipe = "hilog -x";
3220
3595
  if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
@@ -3239,17 +3614,17 @@ async function dumpLog(opts) {
3239
3614
  }
3240
3615
 
3241
3616
  // src/build/runHvigorw.ts
3242
- import * as fs22 from "fs";
3243
- import * as path22 from "path";
3617
+ import * as fs24 from "fs";
3618
+ import * as path24 from "path";
3244
3619
 
3245
3620
  // src/harmonyos/target.ts
3246
- import * as fs21 from "fs";
3247
- import * as path21 from "path";
3621
+ import * as fs23 from "fs";
3622
+ import * as path23 from "path";
3248
3623
  import JSON54 from "json5";
3249
3624
  function detectRuntimeOs(projectDir, productName) {
3250
3625
  let products = [];
3251
3626
  try {
3252
- 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"));
3253
3628
  if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
3254
3629
  } catch {
3255
3630
  return "OpenHarmony";
@@ -3290,7 +3665,7 @@ function runHvigorw(opts) {
3290
3665
  const { config } = opts;
3291
3666
  const projectDir = toLongPath(opts.projectDir);
3292
3667
  const logger = opts.logger ?? noopLogger;
3293
- if (!fs22.existsSync(path22.join(projectDir, "build-profile.json5"))) {
3668
+ if (!fs24.existsSync(path24.join(projectDir, "build-profile.json5"))) {
3294
3669
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
3295
3670
  }
3296
3671
  let hvigorw;
@@ -3302,24 +3677,24 @@ function runHvigorw(opts) {
3302
3677
  hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
3303
3678
  logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
3304
3679
  } else {
3305
- if (!fs22.existsSync(getCmdToolsPath(config))) {
3680
+ if (!fs24.existsSync(getCmdToolsPath(config))) {
3306
3681
  throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
3307
3682
  }
3308
3683
  sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
3309
3684
  hvigorw = getHvigorwPath(config, projectDir);
3310
3685
  }
3311
- if (!fs22.existsSync(hvigorw)) {
3686
+ if (!fs24.existsSync(hvigorw)) {
3312
3687
  throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
3313
3688
  }
3314
3689
  const args = buildHvigorwArgs(opts);
3315
3690
  if (process.platform !== "win32") {
3316
3691
  try {
3317
- fs22.chmodSync(hvigorw, 493);
3692
+ fs24.chmodSync(hvigorw, 493);
3318
3693
  } catch {
3319
3694
  }
3320
3695
  }
3321
3696
  const env = { ...process.env, ...sdkEnv };
3322
- return new Promise((resolve6, reject) => {
3697
+ return new Promise((resolve7, reject) => {
3323
3698
  if (opts.abortSignal?.aborted) {
3324
3699
  reject(new CancelledError("hvigorw build cancelled."));
3325
3700
  return;
@@ -3363,46 +3738,15 @@ function runHvigorw(opts) {
3363
3738
  settled = true;
3364
3739
  cleanup();
3365
3740
  const exitCode = code ?? 1;
3366
- if (exitCode === 0) resolve6({ exitCode });
3741
+ if (exitCode === 0) resolve7({ exitCode });
3367
3742
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
3368
3743
  });
3369
3744
  });
3370
3745
  }
3371
3746
 
3372
- // src/build/discoverHaps.ts
3373
- import * as fs23 from "fs";
3374
- import * as path23 from "path";
3375
- function moduleOfHap(hapFileName) {
3376
- const dash = hapFileName.indexOf("-");
3377
- return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
3378
- }
3379
- async function discoverHaps(opts) {
3380
- const { projectDir } = opts;
3381
- const maxDepth = opts.maxDepth ?? 8;
3382
- const result = {};
3383
- const walk = async (dir, depth) => {
3384
- if (depth > maxDepth) return;
3385
- const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
3386
- for (const e of entries) {
3387
- const full = path23.join(dir, e.name);
3388
- if (e.isDirectory()) {
3389
- if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
3390
- await walk(full, depth + 1);
3391
- } else if (e.isFile() && e.name.endsWith(".hap")) {
3392
- const moduleName = moduleOfHap(e.name);
3393
- const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
3394
- if (e.name.includes("unsigned")) bucket.unsigned.push(full);
3395
- else if (e.name.includes("signed")) bucket.signed.push(full);
3396
- }
3397
- }
3398
- };
3399
- await walk(projectDir, 0);
3400
- return result;
3401
- }
3402
-
3403
3747
  // src/build/ohpm.ts
3404
- import * as fs24 from "fs";
3405
- import * as path24 from "path";
3748
+ import * as fs25 from "fs";
3749
+ import * as path25 from "path";
3406
3750
  function runOhpm(opts) {
3407
3751
  return runProcess({
3408
3752
  command: opts.ohpmPath ?? getOhpmPath(opts.config),
@@ -3415,7 +3759,7 @@ function runOhpm(opts) {
3415
3759
  });
3416
3760
  }
3417
3761
  async function ensureOhModules(opts) {
3418
- if (fs24.existsSync(path24.join(opts.projectDir, "oh_modules"))) {
3762
+ if (fs25.existsSync(path25.join(opts.projectDir, "oh_modules"))) {
3419
3763
  return { installed: false };
3420
3764
  }
3421
3765
  const res = await runOhpm({
@@ -3432,23 +3776,91 @@ async function ensureOhModules(opts) {
3432
3776
  }
3433
3777
 
3434
3778
  // src/build/codelinter.ts
3435
- 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;
3436
3780
  var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
3437
- 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) {
3438
3795
  const findings = [];
3439
- for (const raw of output.split("\n")) {
3440
- const m = FINDING_RE.exec(raw.trim());
3441
- if (!m) continue;
3442
- let message = m[4].trim();
3443
- let code = "";
3444
- const codeMatch = TRAILING_CODE_RE.exec(message);
3445
- if (codeMatch) {
3446
- code = codeMatch[1];
3447
- message = message.slice(0, codeMatch.index).trim();
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;
3823
+ }
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;
3448
3843
  }
3449
- findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
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);
3450
3859
  }
3451
- return findings;
3860
+ return { findings, summary, notices, incomplete: notices.length > 0 };
3861
+ }
3862
+ function parseCodelinterFindings(output) {
3863
+ return parseCodelinterReport(output).findings;
3452
3864
  }
3453
3865
  async function runCodelinter(opts) {
3454
3866
  const res = await runProcess({
@@ -3460,16 +3872,28 @@ async function runCodelinter(opts) {
3460
3872
  logger: opts.logger
3461
3873
  });
3462
3874
  const raw = `${res.stdout}${res.stderr}`;
3463
- return { code: res.code, findings: parseCodelinterFindings(raw), raw };
3875
+ return { code: res.code, ...parseCodelinterReport(raw), raw: cleanCodelinterOutput(raw) };
3464
3876
  }
3465
3877
 
3466
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
+ }
3467
3889
  async function buildHap(opts) {
3468
3890
  const logger = scopedLogger(opts.logger ?? noopLogger, "build");
3469
3891
  const warnings = [];
3470
3892
  const start = Date.now();
3471
3893
  const projectDir = toLongPath(opts.projectDir);
3472
3894
  const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
3895
+ const missingSdk = isHarmonyOs ? null : findMissingProjectSdk(opts.config, projectDir);
3896
+ if (missingSdk) throw missingSdk;
3473
3897
  if (opts.autoInstallDeps !== false) {
3474
3898
  const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
3475
3899
  const r = await ensureOhModules({
@@ -3482,23 +3906,35 @@ async function buildHap(opts) {
3482
3906
  });
3483
3907
  if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
3484
3908
  }
3485
- const { exitCode } = await runHvigorw({
3486
- config: opts.config,
3487
- projectDir,
3488
- product: opts.product,
3489
- module: opts.module,
3490
- buildMode: opts.mode,
3491
- task: opts.task,
3492
- parallel: opts.parallel,
3493
- abortSignal: opts.abortSignal,
3494
- onOutput: opts.onOutput,
3495
- logger: opts.logger
3496
- });
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
+ }
3497
3933
  const discoveredHaps = await discoverHaps({ projectDir });
3498
3934
  const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
3499
3935
  if (!anySigned) {
3500
3936
  warnings.push(
3501
- isHarmonyOs ? "No signed HAPs found \u2014 run `oniro-app sign --harmonyos` to have AppGallery Connect issue a certificate and profile." : "No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
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."
3502
3938
  );
3503
3939
  }
3504
3940
  return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
@@ -3515,21 +3951,21 @@ function isValidBundleName(bundleName) {
3515
3951
  }
3516
3952
 
3517
3953
  // src/project/templates.ts
3518
- import * as fs25 from "fs";
3519
- import * as path25 from "path";
3954
+ import * as fs26 from "fs";
3955
+ import * as path26 from "path";
3520
3956
  function toHumanTemplateName(folderName) {
3521
3957
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
3522
3958
  }
3523
3959
  function listTemplates(templateRoot) {
3524
- if (!fs25.existsSync(templateRoot)) return [];
3525
- const entries = fs25.readdirSync(templateRoot, { withFileTypes: true });
3960
+ if (!fs26.existsSync(templateRoot)) return [];
3961
+ const entries = fs26.readdirSync(templateRoot, { withFileTypes: true });
3526
3962
  return entries.filter((e) => e.isDirectory()).map((e) => {
3527
- const dir = path25.join(templateRoot, e.name);
3528
- const metaPath = path25.join(dir, "template.json");
3963
+ const dir = path26.join(templateRoot, e.name);
3964
+ const metaPath = path26.join(dir, "template.json");
3529
3965
  let meta = {};
3530
- if (fs25.existsSync(metaPath)) {
3966
+ if (fs26.existsSync(metaPath)) {
3531
3967
  try {
3532
- meta = JSON.parse(fs25.readFileSync(metaPath, "utf8"));
3968
+ meta = JSON.parse(fs26.readFileSync(metaPath, "utf8"));
3533
3969
  } catch {
3534
3970
  }
3535
3971
  }
@@ -3544,57 +3980,57 @@ function listTemplates(templateRoot) {
3544
3980
  function validateTemplateLayout(templateDir, defaultModuleName) {
3545
3981
  const required = [
3546
3982
  "build-profile.json5",
3547
- path25.join("AppScope", "app.json5"),
3548
- path25.join(defaultModuleName, "src", "main", "module.json5"),
3549
- 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"),
3550
3986
  "hvigorfile.ts"
3551
3987
  ];
3552
- return required.filter((rel) => !fs25.existsSync(path25.join(templateDir, rel)));
3988
+ return required.filter((rel) => !fs26.existsSync(path26.join(templateDir, rel)));
3553
3989
  }
3554
3990
 
3555
3991
  // src/project/createScaffold.ts
3556
- import * as fs27 from "fs";
3557
- import * as path26 from "path";
3992
+ import * as fs28 from "fs";
3993
+ import * as path27 from "path";
3558
3994
 
3559
3995
  // src/project/jsonHelpers.ts
3560
- import * as fs26 from "fs";
3996
+ import * as fs27 from "fs";
3561
3997
  import JSON55 from "json5";
3562
3998
  function readJson5File(filePath) {
3563
- return JSON55.parse(fs26.readFileSync(filePath, "utf8"));
3999
+ return JSON55.parse(fs27.readFileSync(filePath, "utf8"));
3564
4000
  }
3565
4001
  function writeJson5File(filePath, value) {
3566
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4002
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3567
4003
  }
3568
4004
  function readJsonFile(filePath) {
3569
- return JSON.parse(fs26.readFileSync(filePath, "utf8"));
4005
+ return JSON.parse(fs27.readFileSync(filePath, "utf8"));
3570
4006
  }
3571
4007
  function writeJsonFile(filePath, value) {
3572
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4008
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3573
4009
  }
3574
4010
 
3575
4011
  // src/project/createScaffold.ts
3576
4012
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
3577
4013
  async function pathExists(p) {
3578
4014
  try {
3579
- await fs27.promises.access(p);
4015
+ await fs28.promises.access(p);
3580
4016
  return true;
3581
4017
  } catch {
3582
4018
  return false;
3583
4019
  }
3584
4020
  }
3585
4021
  async function copyDirRecursive(srcDir, destDir) {
3586
- await fs27.promises.mkdir(destDir, { recursive: true });
3587
- 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 });
3588
4024
  for (const entry of entries) {
3589
- const src = path26.join(srcDir, entry.name);
3590
- const dest = path26.join(destDir, entry.name);
4025
+ const src = path27.join(srcDir, entry.name);
4026
+ const dest = path27.join(destDir, entry.name);
3591
4027
  if (entry.isDirectory()) {
3592
4028
  await copyDirRecursive(src, dest);
3593
4029
  } else if (entry.isSymbolicLink()) {
3594
4030
  continue;
3595
4031
  } else if (entry.isFile()) {
3596
4032
  if (entry.name === "template.json") continue;
3597
- await fs27.promises.copyFile(src, dest);
4033
+ await fs28.promises.copyFile(src, dest);
3598
4034
  }
3599
4035
  }
3600
4036
  }
@@ -3602,9 +4038,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
3602
4038
  const stack = [projectDir];
3603
4039
  while (stack.length > 0) {
3604
4040
  const currentDir = stack.pop();
3605
- const entries = await fs27.promises.readdir(currentDir, { withFileTypes: true });
4041
+ const entries = await fs28.promises.readdir(currentDir, { withFileTypes: true });
3606
4042
  for (const entry of entries) {
3607
- const fullPath = path26.join(currentDir, entry.name);
4043
+ const fullPath = path27.join(currentDir, entry.name);
3608
4044
  if (entry.isDirectory()) {
3609
4045
  if (IGNORED_DIRS.has(entry.name)) continue;
3610
4046
  stack.push(fullPath);
@@ -3626,23 +4062,23 @@ function toJavaPropertiesPath(p) {
3626
4062
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
3627
4063
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
3628
4064
  `;
3629
- fs27.writeFileSync(path26.join(projectDir, "local.properties"), content, "utf8");
4065
+ fs28.writeFileSync(path27.join(projectDir, "local.properties"), content, "utf8");
3630
4066
  }
3631
4067
  function renameIfExists(fromPath, toPath) {
3632
- if (fs27.existsSync(fromPath) && fromPath !== toPath) {
3633
- fs27.renameSync(fromPath, toPath);
4068
+ if (fs28.existsSync(fromPath) && fromPath !== toPath) {
4069
+ fs28.renameSync(fromPath, toPath);
3634
4070
  }
3635
4071
  }
3636
4072
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3637
- const appJsonPath = path26.join(projectDir, "AppScope", "app.json5");
3638
- if (fs27.existsSync(appJsonPath)) {
4073
+ const appJsonPath = path27.join(projectDir, "AppScope", "app.json5");
4074
+ if (fs28.existsSync(appJsonPath)) {
3639
4075
  const appJson = readJson5File(appJsonPath);
3640
4076
  appJson.app = appJson.app ?? {};
3641
4077
  appJson.app.bundleName = args.bundleName;
3642
4078
  writeJson5File(appJsonPath, appJson);
3643
4079
  }
3644
- const stringsPath = path26.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3645
- if (fs27.existsSync(stringsPath)) {
4080
+ const stringsPath = path27.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
4081
+ if (fs28.existsSync(stringsPath)) {
3646
4082
  const strings = readJsonFile(stringsPath);
3647
4083
  if (Array.isArray(strings.string)) {
3648
4084
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -3650,8 +4086,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3650
4086
  }
3651
4087
  writeJsonFile(stringsPath, strings);
3652
4088
  }
3653
- const buildProfilePath = path26.join(projectDir, "build-profile.json5");
3654
- if (fs27.existsSync(buildProfilePath)) {
4089
+ const buildProfilePath = path27.join(projectDir, "build-profile.json5");
4090
+ if (fs28.existsSync(buildProfilePath)) {
3655
4091
  const buildProfile = readJson5File(buildProfilePath);
3656
4092
  buildProfile.app = buildProfile.app ?? {};
3657
4093
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -3664,23 +4100,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3664
4100
  }
3665
4101
  writeJson5File(buildProfilePath, buildProfile);
3666
4102
  }
3667
- const moduleJsonPath = path26.join(projectDir, args.moduleName, "src", "main", "module.json5");
3668
- if (fs27.existsSync(moduleJsonPath)) {
4103
+ const moduleJsonPath = path27.join(projectDir, args.moduleName, "src", "main", "module.json5");
4104
+ if (fs28.existsSync(moduleJsonPath)) {
3669
4105
  const moduleJson = readJson5File(moduleJsonPath);
3670
4106
  moduleJson.module = moduleJson.module ?? {};
3671
4107
  moduleJson.module.name = args.moduleName;
3672
4108
  writeJson5File(moduleJsonPath, moduleJson);
3673
4109
  }
3674
- const moduleOhPackagePath = path26.join(projectDir, args.moduleName, "oh-package.json5");
3675
- if (fs27.existsSync(moduleOhPackagePath)) {
4110
+ const moduleOhPackagePath = path27.join(projectDir, args.moduleName, "oh-package.json5");
4111
+ if (fs28.existsSync(moduleOhPackagePath)) {
3676
4112
  const pkg = readJson5File(moduleOhPackagePath);
3677
4113
  pkg.name = args.moduleName;
3678
4114
  writeJson5File(moduleOhPackagePath, pkg);
3679
4115
  }
3680
- const vscodeDir = path26.join(projectDir, ".vscode");
3681
- fs27.mkdirSync(vscodeDir, { recursive: true });
4116
+ const vscodeDir = path27.join(projectDir, ".vscode");
4117
+ fs28.mkdirSync(vscodeDir, { recursive: true });
3682
4118
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
3683
- writeJsonFile(path26.join(vscodeDir, "settings.json"), {
4119
+ writeJsonFile(path27.join(vscodeDir, "settings.json"), {
3684
4120
  "oniro.hapPath": hapPath,
3685
4121
  "files.associations": { "*.json5": "jsonc" }
3686
4122
  });
@@ -3697,7 +4133,7 @@ async function createScaffold(opts) {
3697
4133
  if (!opts.location || !await pathExists(opts.location)) {
3698
4134
  throw new OniroError(`Location does not exist: ${opts.location}`);
3699
4135
  }
3700
- const templateDir = path26.join(opts.templateRoot, opts.templateId);
4136
+ const templateDir = path27.join(opts.templateRoot, opts.templateId);
3701
4137
  if (!await pathExists(templateDir)) {
3702
4138
  throw new OniroError(`Template not found: ${templateDir}`);
3703
4139
  }
@@ -3732,17 +4168,17 @@ async function createScaffold(opts) {
3732
4168
  }
3733
4169
  }
3734
4170
  }
3735
- const projectDir = path26.join(opts.location, opts.projectName);
4171
+ const projectDir = path27.join(opts.location, opts.projectName);
3736
4172
  if (await pathExists(projectDir)) {
3737
4173
  if (!opts.overwrite) {
3738
4174
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
3739
4175
  }
3740
- await fs27.promises.rm(projectDir, { recursive: true, force: true });
4176
+ await fs28.promises.rm(projectDir, { recursive: true, force: true });
3741
4177
  }
3742
4178
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
3743
4179
  await copyDirRecursive(templateDir, projectDir);
3744
4180
  if (moduleName !== defaultModuleName) {
3745
- renameIfExists(path26.join(projectDir, defaultModuleName), path26.join(projectDir, moduleName));
4181
+ renameIfExists(path27.join(projectDir, defaultModuleName), path27.join(projectDir, moduleName));
3746
4182
  }
3747
4183
  updateTemplateConfigs(
3748
4184
  projectDir,
@@ -3759,11 +4195,11 @@ async function createScaffold(opts) {
3759
4195
  }
3760
4196
 
3761
4197
  // src/project/listModules.ts
3762
- import * as fs28 from "fs";
3763
- import * as path27 from "path";
4198
+ import * as fs29 from "fs";
4199
+ import * as path28 from "path";
3764
4200
  function listModules(opts) {
3765
- const buildProfile = path27.join(opts.projectDir, "build-profile.json5");
3766
- if (!fs28.existsSync(buildProfile)) {
4201
+ const buildProfile = path28.join(opts.projectDir, "build-profile.json5");
4202
+ if (!fs29.existsSync(buildProfile)) {
3767
4203
  throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
3768
4204
  }
3769
4205
  const parsed = readJson5File(buildProfile);
@@ -3775,12 +4211,12 @@ function listModules(opts) {
3775
4211
  }
3776
4212
 
3777
4213
  // src/project/abilities.ts
3778
- import * as fs29 from "fs";
3779
- import * as path28 from "path";
4214
+ import * as fs30 from "fs";
4215
+ import * as path29 from "path";
3780
4216
  function listAbilities(opts) {
3781
4217
  const moduleName = opts.moduleName ?? "entry";
3782
- const moduleJsonPath = path28.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3783
- if (!fs29.existsSync(moduleJsonPath)) {
4218
+ const moduleJsonPath = path29.join(opts.projectDir, moduleName, "src", "main", "module.json5");
4219
+ if (!fs30.existsSync(moduleJsonPath)) {
3784
4220
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
3785
4221
  }
3786
4222
  const parsed = readJson5File(moduleJsonPath);
@@ -3788,12 +4224,12 @@ function listAbilities(opts) {
3788
4224
  }
3789
4225
 
3790
4226
  // src/project/prepareSigning.ts
3791
- import * as fs30 from "fs";
3792
- import * as path29 from "path";
4227
+ import * as fs31 from "fs";
4228
+ import * as path30 from "path";
3793
4229
  function hasSigningArtifacts(dir) {
3794
- if (!fs30.existsSync(path29.join(dir, "signatures"))) return false;
3795
- const buildProfile = path29.join(dir, "build-profile.json5");
3796
- if (!fs30.existsSync(buildProfile)) return false;
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;
3797
4233
  try {
3798
4234
  const parsed = readJson5File(buildProfile);
3799
4235
  return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
@@ -3811,6 +4247,7 @@ function prepareSigning(opts) {
3811
4247
  apl: opts.apl,
3812
4248
  appFeature: opts.appFeature,
3813
4249
  acls: opts.acls,
4250
+ appPrivilegeCapabilities: opts.appPrivilegeCapabilities,
3814
4251
  passwords: opts.passwords,
3815
4252
  logger: opts.logger
3816
4253
  });
@@ -3839,19 +4276,31 @@ var PROPS = [
3839
4276
  ["model", "const.product.model"],
3840
4277
  ["manufacturer", "const.product.manufacturer"],
3841
4278
  ["brand", "const.product.brand"],
4279
+ ["osDistName", "const.product.os.dist.name"],
4280
+ ["osDistVersion", "const.product.os.dist.version"],
3842
4281
  ["osFullName", "const.ohos.fullname"],
3843
4282
  ["osReleaseType", "const.ohos.releasetype"],
3844
4283
  ["sdkApiVersion", "const.ohos.apiversion"],
3845
- ["buildVersion", "const.product.software.version"]
4284
+ ["buildVersion", "const.product.software.version"],
4285
+ ["abi", "const.product.cpu.abilist"],
4286
+ ["deviceType", "const.product.devicetype"]
3846
4287
  ];
3847
4288
  async function getDeviceInfo(opts) {
3848
4289
  const info = { serial: opts.deviceSerial ?? "(default)", display: null };
3849
- for (const [field, prop] of PROPS) {
3850
- try {
3851
- const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
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];
3852
4298
  if (value) info[field] = value;
3853
- } catch {
3854
4299
  }
4300
+ } catch {
4301
+ }
4302
+ if (info.osDistName || info.osFullName) {
4303
+ info.osFamily = /harmonyos/i.test(info.osDistName ?? "") ? "HarmonyOS" : "OpenHarmony";
3855
4304
  }
3856
4305
  const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
3857
4306
  if (screen) info.display = { width: screen.width, height: screen.height };
@@ -3883,8 +4332,8 @@ async function diffHapAssets(opts) {
3883
4332
  }
3884
4333
 
3885
4334
  // src/install/apply.ts
3886
- import * as fs31 from "fs";
3887
- import * as path30 from "path";
4335
+ import * as fs32 from "fs";
4336
+ import * as path31 from "path";
3888
4337
  function decideInstallMethod(s) {
3889
4338
  if (!s.signInfoInconsistent) return "replace";
3890
4339
  if (s.isSystemBundle && !s.allowUninstall) return "refuse";
@@ -3902,39 +4351,31 @@ async function pidOf(config, bundle, deviceSerial) {
3902
4351
  const proc = await findRunningProcess({ config, bundle, deviceSerial });
3903
4352
  return proc ? Number(proc.pid) : null;
3904
4353
  }
3905
- async function resolveHapPath(opts) {
3906
- let hapPath = opts.hapPath;
3907
- if (hapPath) {
3908
- if (!path30.isAbsolute(hapPath) && opts.projectDir) hapPath = path30.join(opts.projectDir, hapPath);
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}.`);
3909
4360
  } else {
3910
4361
  if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
3911
- const haps = await discoverHaps({ projectDir: opts.projectDir });
3912
- if (opts.module) {
3913
- const signed = haps[opts.module]?.signed ?? [];
3914
- if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
3915
- hapPath = signed[0];
3916
- } else {
3917
- const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
3918
- if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
3919
- if (withSigned.length > 1) {
3920
- throw new OniroError(
3921
- `Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
3922
- );
3923
- }
3924
- hapPath = withSigned[0][1].signed[0];
3925
- }
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 };
3926
4365
  }
3927
- if (!fs31.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3928
- return hapPath;
4366
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path31.basename(hap.path)}.`);
4367
+ return hap;
3929
4368
  }
3930
4369
  async function applyChanges(opts) {
3931
4370
  const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
3932
4371
  const { config, bundle, deviceSerial } = opts;
3933
4372
  const isSystemBundle = opts.isSystemBundle ?? false;
3934
4373
  const allowUninstall = opts.allowUninstall ?? false;
3935
- const hapPath = await resolveHapPath(opts);
4374
+ const hap = await resolveHap(opts, logger);
4375
+ const hapPath = hap.path;
4376
+ logger.info(`installing ${hapPath}`);
3936
4377
  let assetsChanged = false;
3937
- if (opts.installedHapPath && fs31.existsSync(opts.installedHapPath)) {
4378
+ if (opts.installedHapPath && fs32.existsSync(opts.installedHapPath)) {
3938
4379
  try {
3939
4380
  const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
3940
4381
  assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
@@ -3955,12 +4396,21 @@ async function applyChanges(opts) {
3955
4396
  if (decision === "uninstall-install") {
3956
4397
  logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
3957
4398
  await uninstallApp({ config, bundle, deviceSerial });
3958
- 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
+ );
3959
4404
  installMethod = "uninstall-install";
3960
4405
  } else {
3961
- ensureOk(replaceRes, `hdc install -r ${hapPath}`);
4406
+ ensureInstallOk(replaceRes, `hdc install -r ${hapPath}`, hap);
3962
4407
  installMethod = "replace";
3963
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
+ }
3964
4414
  let postInstallPid = await pidOf(config, bundle, deviceSerial);
3965
4415
  const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
3966
4416
  let method = installMethod;
@@ -4035,9 +4485,9 @@ var AUTH_PATHS = {
4035
4485
  };
4036
4486
 
4037
4487
  // src/harmonyos/auth/tokenStore.ts
4038
- import * as fs32 from "fs";
4488
+ import * as fs33 from "fs";
4039
4489
  import * as os11 from "os";
4040
- import * as path31 from "path";
4490
+ import * as path32 from "path";
4041
4491
  import * as crypto3 from "crypto";
4042
4492
  var ALGORITHM = "aes-256-gcm";
4043
4493
  var KEY_LENGTH = 32;
@@ -4073,7 +4523,7 @@ function permissionHint(dir) {
4073
4523
  }
4074
4524
  function ensureDir(dir) {
4075
4525
  try {
4076
- fs32.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4526
+ fs33.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4077
4527
  } catch (err) {
4078
4528
  if (err.code === "EACCES") {
4079
4529
  throw new OniroError(permissionHint(dir), err);
@@ -4084,17 +4534,17 @@ function ensureDir(dir) {
4084
4534
  function createTokenStore(opts) {
4085
4535
  const logger = opts.logger ?? noopLogger;
4086
4536
  const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
4087
- const tokenPath = path31.join(authDir, TOKEN_FILE);
4088
- const keyPath = path31.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4537
+ const tokenPath = path32.join(authDir, TOKEN_FILE);
4538
+ const keyPath = path32.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4089
4539
  function loadOrCreateKey() {
4090
- if (fs32.existsSync(keyPath)) {
4091
- const existing = fs32.readFileSync(keyPath);
4540
+ if (fs33.existsSync(keyPath)) {
4541
+ const existing = fs33.readFileSync(keyPath);
4092
4542
  if (existing.length === KEY_LENGTH) return existing;
4093
4543
  logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
4094
4544
  }
4095
- ensureDir(path31.dirname(keyPath));
4545
+ ensureDir(path32.dirname(keyPath));
4096
4546
  const key = crypto3.randomBytes(KEY_LENGTH);
4097
- fs32.writeFileSync(keyPath, key, { mode: FILE_MODE });
4547
+ fs33.writeFileSync(keyPath, key, { mode: FILE_MODE });
4098
4548
  return key;
4099
4549
  }
4100
4550
  return {
@@ -4104,12 +4554,12 @@ function createTokenStore(opts) {
4104
4554
  const payload = Buffer.from(JSON.stringify(session), "utf8");
4105
4555
  const blob = encrypt2(loadOrCreateKey(), payload);
4106
4556
  ensureDir(authDir);
4107
- fs32.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4557
+ fs33.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4108
4558
  },
4109
4559
  load() {
4110
- if (!fs32.existsSync(tokenPath)) return null;
4560
+ if (!fs33.existsSync(tokenPath)) return null;
4111
4561
  try {
4112
- const blob = JSON.parse(fs32.readFileSync(tokenPath, "utf8"));
4562
+ const blob = JSON.parse(fs33.readFileSync(tokenPath, "utf8"));
4113
4563
  if (!isEncryptedBlob(blob)) return null;
4114
4564
  const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
4115
4565
  return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
@@ -4121,7 +4571,7 @@ function createTokenStore(opts) {
4121
4571
  }
4122
4572
  logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
4123
4573
  try {
4124
- fs32.rmSync(tokenPath, { force: true });
4574
+ fs33.rmSync(tokenPath, { force: true });
4125
4575
  } catch {
4126
4576
  }
4127
4577
  return null;
@@ -4129,7 +4579,7 @@ function createTokenStore(opts) {
4129
4579
  },
4130
4580
  clear() {
4131
4581
  try {
4132
- fs32.rmSync(tokenPath, { force: true });
4582
+ fs33.rmSync(tokenPath, { force: true });
4133
4583
  } catch (err) {
4134
4584
  throw new OniroError(`Failed to remove ${tokenPath}.`, err);
4135
4585
  }
@@ -4243,8 +4693,8 @@ function isQuit(quit) {
4243
4693
  async function startCallbackServer(opts) {
4244
4694
  let resolveCallback;
4245
4695
  let rejectCallback;
4246
- const callback = new Promise((resolve6, reject) => {
4247
- resolveCallback = resolve6;
4696
+ const callback = new Promise((resolve7, reject) => {
4697
+ resolveCallback = resolve7;
4248
4698
  rejectCallback = reject;
4249
4699
  });
4250
4700
  callback.catch(() => {
@@ -4303,9 +4753,9 @@ async function startCallbackServer(opts) {
4303
4753
  }
4304
4754
  });
4305
4755
  server.keepAliveTimeout = 1;
4306
- await new Promise((resolve6, reject) => {
4756
+ await new Promise((resolve7, reject) => {
4307
4757
  server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
4308
- server.listen(0, "127.0.0.1", resolve6);
4758
+ server.listen(0, "127.0.0.1", resolve7);
4309
4759
  });
4310
4760
  const port = server.address().port;
4311
4761
  return {
@@ -4321,12 +4771,12 @@ async function startCallbackServer(opts) {
4321
4771
  return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
4322
4772
  },
4323
4773
  stop() {
4324
- return new Promise((resolve6) => {
4774
+ return new Promise((resolve7) => {
4325
4775
  server.closeAllConnections?.();
4326
- const timer = setTimeout(resolve6, 100);
4776
+ const timer = setTimeout(resolve7, 100);
4327
4777
  server.close(() => {
4328
4778
  clearTimeout(timer);
4329
- resolve6();
4779
+ resolve7();
4330
4780
  });
4331
4781
  });
4332
4782
  }
@@ -4358,11 +4808,11 @@ function openBrowser(url) {
4358
4808
  windowsHide: true
4359
4809
  });
4360
4810
  child.unref();
4361
- return new Promise((resolve6, reject) => {
4811
+ return new Promise((resolve7, reject) => {
4362
4812
  child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
4363
4813
  child.on(
4364
4814
  "exit",
4365
- (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}.`))
4366
4816
  );
4367
4817
  });
4368
4818
  }
@@ -4610,7 +5060,7 @@ function mapAgcError(operation, statusCode, reasonPhrase, body) {
4610
5060
  }
4611
5061
 
4612
5062
  // src/harmonyos/signing/profile.ts
4613
- import * as fs33 from "fs";
5063
+ import * as fs34 from "fs";
4614
5064
  import { X509Certificate } from "crypto";
4615
5065
  import forge from "node-forge";
4616
5066
  function extractProfileJson(buffer) {
@@ -4642,7 +5092,7 @@ function extractProfileJson(buffer) {
4642
5092
  function readProvisionProfile(profilePath) {
4643
5093
  let raw;
4644
5094
  try {
4645
- const json = extractProfileJson(fs33.readFileSync(profilePath));
5095
+ const json = extractProfileJson(fs34.readFileSync(profilePath));
4646
5096
  if (!json) return null;
4647
5097
  raw = JSON.parse(json);
4648
5098
  } catch {
@@ -4671,7 +5121,7 @@ function parseCertificate(pem) {
4671
5121
  }
4672
5122
  function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4673
5123
  try {
4674
- const der = forge.util.createBuffer(fs33.readFileSync(p12Path).toString("binary"));
5124
+ const der = forge.util.createBuffer(fs34.readFileSync(p12Path).toString("binary"));
4675
5125
  const p12 = forge.pkcs12.pkcs12FromAsn1(forge.asn1.fromDer(der), keyPwd);
4676
5126
  const certBag = forge.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
4677
5127
  const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
@@ -4687,7 +5137,7 @@ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4687
5137
  }
4688
5138
  }
4689
5139
  function verifySigningMaterial(opts) {
4690
- 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);
4691
5141
  const chain = (pems ?? []).map(parseCertificate);
4692
5142
  const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
4693
5143
  if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
@@ -4706,14 +5156,14 @@ function verifySigningMaterial(opts) {
4706
5156
 
4707
5157
  // src/harmonyos/signing/autoSign.ts
4708
5158
  import * as crypto9 from "crypto";
4709
- import * as fs38 from "fs";
4710
- import * as path37 from "path";
5159
+ import * as fs39 from "fs";
5160
+ import * as path38 from "path";
4711
5161
  import JSON57 from "json5";
4712
5162
 
4713
5163
  // src/harmonyos/signing/agc.ts
4714
5164
  import * as crypto6 from "crypto";
4715
- import * as fs34 from "fs";
4716
- import * as path32 from "path";
5165
+ import * as fs35 from "fs";
5166
+ import * as path33 from "path";
4717
5167
  var ENDPOINTS = {
4718
5168
  CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
4719
5169
  CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
@@ -4799,8 +5249,8 @@ function createAgcClient(http3, auth) {
4799
5249
  if (target.sha256 && actual !== target.sha256.toLowerCase()) {
4800
5250
  throw new ChecksumMismatchError(target.sha256, actual);
4801
5251
  }
4802
- fs34.mkdirSync(path32.dirname(dest), { recursive: true, mode: 448 });
4803
- fs34.writeFileSync(dest, buffer, { mode: 384 });
5252
+ fs35.mkdirSync(path33.dirname(dest), { recursive: true, mode: 448 });
5253
+ fs35.writeFileSync(dest, buffer, { mode: 384 });
4804
5254
  }
4805
5255
  };
4806
5256
  }
@@ -4851,22 +5301,22 @@ async function registerDevices(agc, connected, logger) {
4851
5301
 
4852
5302
  // src/harmonyos/signing/keystore.ts
4853
5303
  import * as crypto7 from "crypto";
4854
- import * as fs36 from "fs";
4855
- import * as path34 from "path";
5304
+ import * as fs37 from "fs";
5305
+ import * as path35 from "path";
4856
5306
 
4857
5307
  // src/harmonyos/java.ts
4858
- import * as fs35 from "fs";
4859
- import * as path33 from "path";
5308
+ import * as fs36 from "fs";
5309
+ import * as path34 from "path";
4860
5310
  var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
4861
5311
  function findJava(sdk, env = process.env) {
4862
5312
  const candidates = [
4863
- env.JAVA_HOME && path33.join(env.JAVA_HOME, "bin", JAVA_BIN),
4864
- 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),
4865
5315
  // macOS: the JBR is itself a bundle inside Studio's Contents.
4866
- sdk && path33.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
4867
- ...(env.PATH ?? "").split(path33.delimiter).map((dir) => dir && path33.join(dir, JAVA_BIN))
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))
4868
5318
  ];
4869
- const found = candidates.find((c) => !!c && fs35.existsSync(c));
5319
+ const found = candidates.find((c) => !!c && fs36.existsSync(c));
4870
5320
  if (found) return found;
4871
5321
  throw new OniroError(
4872
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."
@@ -4880,13 +5330,13 @@ function redactArgs(args) {
4880
5330
  return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
4881
5331
  }
4882
5332
  async function generateKeystoreAndCsr(opts) {
4883
- const jar = path34.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
4884
- if (!fs36.existsSync(jar)) {
5333
+ const jar = path35.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
5334
+ if (!fs37.existsSync(jar)) {
4885
5335
  throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
4886
5336
  }
4887
5337
  const java = findJava(opts.sdk);
4888
5338
  const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
4889
- fs36.mkdirSync(path34.dirname(opts.p12Path), { recursive: true, mode: 448 });
5339
+ fs37.mkdirSync(path35.dirname(opts.p12Path), { recursive: true, mode: 448 });
4890
5340
  const run = async (what, args) => {
4891
5341
  const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
4892
5342
  opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
@@ -4900,28 +5350,28 @@ ${detail}` : ""}`);
4900
5350
  opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
4901
5351
  await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
4902
5352
  await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
4903
- return { keyPwd, csr: fs36.readFileSync(opts.csrPath, "utf8") };
5353
+ return { keyPwd, csr: fs37.readFileSync(opts.csrPath, "utf8") };
4904
5354
  }
4905
5355
 
4906
5356
  // src/harmonyos/signing/paths.ts
4907
5357
  import * as crypto8 from "crypto";
4908
- import * as path35 from "path";
5358
+ import * as path36 from "path";
4909
5359
  function sanitizeNamePart(raw) {
4910
5360
  const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
4911
5361
  return safe.slice(0, 64) || "default";
4912
5362
  }
4913
5363
  function resolveSigningMaterialPaths(config, productName, projectRoot) {
4914
5364
  const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
4915
- const base = path35.join(
5365
+ const base = path36.join(
4916
5366
  config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
4917
- `${sanitizeNamePart(productName)}_${path35.basename(projectRoot)}_${hash}=`
5367
+ `${sanitizeNamePart(productName)}_${path36.basename(projectRoot)}_${hash}=`
4918
5368
  );
4919
5369
  return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
4920
5370
  }
4921
5371
 
4922
5372
  // src/harmonyos/signing/project.ts
4923
- import * as fs37 from "fs";
4924
- import * as path36 from "path";
5373
+ import * as fs38 from "fs";
5374
+ import * as path37 from "path";
4925
5375
  import JSON56 from "json5";
4926
5376
 
4927
5377
  // src/harmonyos/signing/aclPermissionList.ts
@@ -4989,9 +5439,9 @@ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
4989
5439
 
4990
5440
  // src/harmonyos/signing/project.ts
4991
5441
  function readRequestedPermissions(moduleConfigPath) {
4992
- if (!fs37.existsSync(moduleConfigPath)) return [];
5442
+ if (!fs38.existsSync(moduleConfigPath)) return [];
4993
5443
  try {
4994
- const parsed = JSON56.parse(fs37.readFileSync(moduleConfigPath, "utf-8"));
5444
+ const parsed = JSON56.parse(fs38.readFileSync(moduleConfigPath, "utf-8"));
4995
5445
  return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
4996
5446
  } catch {
4997
5447
  return [];
@@ -5000,10 +5450,10 @@ function readRequestedPermissions(moduleConfigPath) {
5000
5450
  function collectAclPermissions(projectDir, logger = noopLogger) {
5001
5451
  const requested = /* @__PURE__ */ new Set();
5002
5452
  for (const module of listModules({ projectDir })) {
5003
- const moduleRoot = path36.resolve(projectDir, module.srcPath);
5453
+ const moduleRoot = path37.resolve(projectDir, module.srcPath);
5004
5454
  for (const sourceSet of ["main", "ohosTest"]) {
5005
5455
  for (const name of readRequestedPermissions(
5006
- path36.join(moduleRoot, "src", sourceSet, "module.json5")
5456
+ path37.join(moduleRoot, "src", sourceSet, "module.json5")
5007
5457
  )) {
5008
5458
  requested.add(name);
5009
5459
  }
@@ -5019,7 +5469,7 @@ function collectAclPermissions(projectDir, logger = noopLogger) {
5019
5469
  // src/harmonyos/signing/autoSign.ts
5020
5470
  function shouldRegenerate(input) {
5021
5471
  if (input.force) return "--force was passed";
5022
- const missing = Object.values(input.paths).find((file) => !fs38.existsSync(file));
5472
+ const missing = Object.values(input.paths).find((file) => !fs39.existsSync(file));
5023
5473
  if (missing) return `${missing} is missing`;
5024
5474
  const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
5025
5475
  if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
@@ -5058,16 +5508,16 @@ function writeSigningConfig(opts) {
5058
5508
  for (const product of app.products ?? []) {
5059
5509
  if (product.name === opts.productName) product.signingConfig = opts.productName;
5060
5510
  }
5061
- fs38.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5511
+ fs39.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5062
5512
  }
5063
5513
  async function harmonyOsAutoSign(opts) {
5064
5514
  const logger = opts.logger ?? noopLogger;
5065
5515
  const productName = opts.productName ?? "default";
5066
- const projectDir = path37.resolve(opts.projectDir);
5067
- const buildProfilePath = path37.join(projectDir, "build-profile.json5");
5516
+ const projectDir = path38.resolve(opts.projectDir);
5517
+ const buildProfilePath = path38.join(projectDir, "build-profile.json5");
5068
5518
  let buildProfile;
5069
5519
  try {
5070
- buildProfile = JSON57.parse(fs38.readFileSync(buildProfilePath, "utf-8"));
5520
+ buildProfile = JSON57.parse(fs39.readFileSync(buildProfilePath, "utf-8"));
5071
5521
  } catch (err) {
5072
5522
  throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
5073
5523
  }
@@ -5107,7 +5557,7 @@ async function harmonyOsAutoSign(opts) {
5107
5557
  logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
5108
5558
  const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
5109
5559
  const deviceIds = await registerDevices(agc, connected, logger);
5110
- 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 });
5111
5561
  const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
5112
5562
  const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
5113
5563
  if (stale) {
@@ -5138,10 +5588,10 @@ async function harmonyOsAutoSign(opts) {
5138
5588
  try {
5139
5589
  verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
5140
5590
  } catch (err) {
5141
- fs38.rmSync(paths.profilePath, { force: true });
5591
+ fs39.rmSync(paths.profilePath, { force: true });
5142
5592
  throw err;
5143
5593
  }
5144
- const materialDir = path37.join(path37.dirname(paths.p12Path), "material");
5594
+ const materialDir = path38.join(path38.dirname(paths.p12Path), "material");
5145
5595
  try {
5146
5596
  getKey(materialDir);
5147
5597
  } catch {
@@ -5153,7 +5603,7 @@ async function harmonyOsAutoSign(opts) {
5153
5603
  }
5154
5604
 
5155
5605
  // src/index.ts
5156
- var VERSION = "0.13.0";
5606
+ var VERSION = "0.14.0";
5157
5607
  export {
5158
5608
  ALL_SDKS,
5159
5609
  APL_VALUES,
@@ -5175,7 +5625,9 @@ export {
5175
5625
  TargetNotFoundError,
5176
5626
  UnsupportedPlatformError,
5177
5627
  VERSION,
5628
+ appSandboxDir,
5178
5629
  applyChanges,
5630
+ assertDevicePath,
5179
5631
  assertLayoutSelector,
5180
5632
  attemptHdcConnection,
5181
5633
  buildCmdWrapper,
@@ -5186,6 +5638,8 @@ export {
5186
5638
  buildInputCommand,
5187
5639
  buildRawTouchCommand,
5188
5640
  captureBurst,
5641
+ captureLog,
5642
+ cleanCodelinterOutput,
5189
5643
  consoleLogger,
5190
5644
  createHarmonyOsSession,
5191
5645
  createInstallTempDir,
@@ -5201,9 +5655,12 @@ export {
5201
5655
  defaultPaths,
5202
5656
  describeLayoutNode,
5203
5657
  detectAaStartFailure,
5658
+ detectCmdToolsFlavour,
5659
+ detectInstallFailure,
5204
5660
  detectProjectSdkVersion,
5205
5661
  detectRuntimeOs,
5206
5662
  detectSigningConfigNames,
5663
+ diagnoseBuildFailure,
5207
5664
  diffEntryNames,
5208
5665
  diffHapAssets,
5209
5666
  discoverHaps,
@@ -5216,11 +5673,13 @@ export {
5216
5673
  dumpWindow,
5217
5674
  encryptPwd,
5218
5675
  ensureFreeSpace,
5676
+ ensureInstallOk,
5219
5677
  ensureOhModules,
5220
5678
  ensureOk,
5221
5679
  ensureRemoteOk,
5222
5680
  escapeCmdArgument,
5223
5681
  escapeCmdCommand,
5682
+ existingHapOrUnsigned,
5224
5683
  extractTarball,
5225
5684
  extractZipWithProgress,
5226
5685
  findAppProcessId,
@@ -5229,6 +5688,7 @@ export {
5229
5688
  findHarmonyOsSdk,
5230
5689
  findKeyboardWindow,
5231
5690
  findLayoutNodes,
5691
+ findMissingProjectSdk,
5232
5692
  findRunningProcess,
5233
5693
  flattenLayout,
5234
5694
  forceStop,
@@ -5242,6 +5702,7 @@ export {
5242
5702
  getDeviceInfo,
5243
5703
  getDisplaySize,
5244
5704
  getEmulatorDir,
5705
+ getEmulatorStatus,
5245
5706
  getFreeSpaceBytes,
5246
5707
  getHdcPath,
5247
5708
  getHvigorwPath,
@@ -5257,15 +5718,21 @@ export {
5257
5718
  getSupportedSdksForUi,
5258
5719
  harmonyOsAutoSign,
5259
5720
  harmonyOsSdkLabelForApi,
5721
+ hdcClientFailure,
5260
5722
  hdcExec,
5723
+ hdcTargetArgs,
5724
+ hdcTargetArgsFor,
5261
5725
  hideKeyboard,
5262
5726
  installApp,
5263
5727
  installCmdTools,
5264
5728
  installEmulator,
5729
+ installFailureReason,
5730
+ isBundleInstalled,
5265
5731
  isCmdToolsInstalled,
5266
5732
  isEmulatorInstalled,
5267
5733
  isOutOfSpaceError,
5268
5734
  isRamBackedPath,
5735
+ isUnsignedHap,
5269
5736
  isValidBundleName,
5270
5737
  isValidProjectName,
5271
5738
  killProcessTree,
@@ -5280,16 +5747,21 @@ export {
5280
5747
  nonInteractivePrompter,
5281
5748
  noopLogger,
5282
5749
  noopProgress,
5750
+ normaliseLocalPath,
5283
5751
  paramGet,
5752
+ paramGetMany,
5284
5753
  paramSet,
5285
5754
  parseBounds,
5286
5755
  parseCodelinterFindings,
5756
+ parseCodelinterReport,
5287
5757
  parseDeviceList,
5288
5758
  parseHilogLine,
5289
5759
  parseImeBundles,
5290
5760
  parseMainAbilityFromBmDump,
5761
+ parseParamValues,
5291
5762
  parseRemoteExit,
5292
5763
  performInput,
5764
+ pickHap,
5293
5765
  pickSigningKind,
5294
5766
  pickTarget,
5295
5767
  prepareSigning,
@@ -5304,6 +5776,7 @@ export {
5304
5776
  removeSdk,
5305
5777
  removeTempWorkDir,
5306
5778
  requireHarmonyOsSdk,
5779
+ resolveDeviceSerial,
5307
5780
  resolveKeyEvent,
5308
5781
  resolveTmpRoot,
5309
5782
  runCodelinter,
@@ -5328,6 +5801,7 @@ export {
5328
5801
  toLongPath,
5329
5802
  toSpaceError,
5330
5803
  uninstallApp,
5804
+ unsignedHapRejection,
5331
5805
  validateTemplateLayout,
5332
5806
  verifySha256,
5333
5807
  waitForBoot,