@oniroproject/core 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -47,9 +47,13 @@ __export(index_exports, {
47
47
  SIGNING_ERRORS: () => SIGNING_ERRORS,
48
48
  SdkNotInstalledError: () => SdkNotInstalledError,
49
49
  TMP_DIR_HINT: () => TMP_DIR_HINT,
50
+ TargetNotFoundError: () => TargetNotFoundError,
50
51
  UnsupportedPlatformError: () => UnsupportedPlatformError,
51
52
  VERSION: () => VERSION,
53
+ appSandboxDir: () => appSandboxDir,
52
54
  applyChanges: () => applyChanges,
55
+ assertDevicePath: () => assertDevicePath,
56
+ assertLayoutSelector: () => assertLayoutSelector,
53
57
  attemptHdcConnection: () => attemptHdcConnection,
54
58
  buildCmdWrapper: () => buildCmdWrapper,
55
59
  buildGestureCommands: () => buildGestureCommands,
@@ -59,6 +63,8 @@ __export(index_exports, {
59
63
  buildInputCommand: () => buildInputCommand,
60
64
  buildRawTouchCommand: () => buildRawTouchCommand,
61
65
  captureBurst: () => captureBurst,
66
+ captureLog: () => captureLog,
67
+ cleanCodelinterOutput: () => cleanCodelinterOutput,
62
68
  consoleLogger: () => consoleLogger,
63
69
  createHarmonyOsSession: () => createHarmonyOsSession,
64
70
  createInstallTempDir: () => createInstallTempDir,
@@ -72,10 +78,14 @@ __export(index_exports, {
72
78
  dedupEntries: () => dedupEntries,
73
79
  defaultContext: () => defaultContext,
74
80
  defaultPaths: () => defaultPaths,
81
+ describeLayoutNode: () => describeLayoutNode,
75
82
  detectAaStartFailure: () => detectAaStartFailure,
83
+ detectCmdToolsFlavour: () => detectCmdToolsFlavour,
84
+ detectInstallFailure: () => detectInstallFailure,
76
85
  detectProjectSdkVersion: () => detectProjectSdkVersion,
77
86
  detectRuntimeOs: () => detectRuntimeOs,
78
87
  detectSigningConfigNames: () => detectSigningConfigNames,
88
+ diagnoseBuildFailure: () => diagnoseBuildFailure,
79
89
  diffEntryNames: () => diffEntryNames,
80
90
  diffHapAssets: () => diffHapAssets,
81
91
  discoverHaps: () => discoverHaps,
@@ -88,17 +98,24 @@ __export(index_exports, {
88
98
  dumpWindow: () => dumpWindow,
89
99
  encryptPwd: () => encryptPwd,
90
100
  ensureFreeSpace: () => ensureFreeSpace,
101
+ ensureInstallOk: () => ensureInstallOk,
91
102
  ensureOhModules: () => ensureOhModules,
92
103
  ensureOk: () => ensureOk,
93
104
  ensureRemoteOk: () => ensureRemoteOk,
94
105
  escapeCmdArgument: () => escapeCmdArgument,
95
106
  escapeCmdCommand: () => escapeCmdCommand,
107
+ existingHapOrUnsigned: () => existingHapOrUnsigned,
96
108
  extractTarball: () => extractTarball,
97
109
  extractZipWithProgress: () => extractZipWithProgress,
98
110
  findAppProcessId: () => findAppProcessId,
99
111
  findCmdToolsSourceDir: () => findCmdToolsSourceDir,
112
+ findCoveredNodes: () => findCoveredNodes,
100
113
  findHarmonyOsSdk: () => findHarmonyOsSdk,
114
+ findKeyboardWindow: () => findKeyboardWindow,
115
+ findLayoutNodes: () => findLayoutNodes,
116
+ findMissingProjectSdk: () => findMissingProjectSdk,
101
117
  findRunningProcess: () => findRunningProcess,
118
+ flattenLayout: () => flattenLayout,
102
119
  forceStop: () => forceStop,
103
120
  formatBytes: () => formatBytes,
104
121
  generateSigningConfigs: () => generateSigningConfigs,
@@ -110,9 +127,11 @@ __export(index_exports, {
110
127
  getDeviceInfo: () => getDeviceInfo,
111
128
  getDisplaySize: () => getDisplaySize,
112
129
  getEmulatorDir: () => getEmulatorDir,
130
+ getEmulatorStatus: () => getEmulatorStatus,
113
131
  getFreeSpaceBytes: () => getFreeSpaceBytes,
114
132
  getHdcPath: () => getHdcPath,
115
133
  getHvigorwPath: () => getHvigorwPath,
134
+ getImeBundles: () => getImeBundles,
116
135
  getInstalledSdks: () => getInstalledSdks,
117
136
  getKey: () => getKey,
118
137
  getMainAbility: () => getMainAbility,
@@ -124,14 +143,21 @@ __export(index_exports, {
124
143
  getSupportedSdksForUi: () => getSupportedSdksForUi,
125
144
  harmonyOsAutoSign: () => harmonyOsAutoSign,
126
145
  harmonyOsSdkLabelForApi: () => harmonyOsSdkLabelForApi,
146
+ hdcClientFailure: () => hdcClientFailure,
127
147
  hdcExec: () => hdcExec,
148
+ hdcTargetArgs: () => hdcTargetArgs,
149
+ hdcTargetArgsFor: () => hdcTargetArgsFor,
150
+ hideKeyboard: () => hideKeyboard,
128
151
  installApp: () => installApp,
129
152
  installCmdTools: () => installCmdTools,
130
153
  installEmulator: () => installEmulator,
154
+ installFailureReason: () => installFailureReason,
155
+ isBundleInstalled: () => isBundleInstalled,
131
156
  isCmdToolsInstalled: () => isCmdToolsInstalled,
132
157
  isEmulatorInstalled: () => isEmulatorInstalled,
133
158
  isOutOfSpaceError: () => isOutOfSpaceError,
134
159
  isRamBackedPath: () => isRamBackedPath,
160
+ isUnsignedHap: () => isUnsignedHap,
135
161
  isValidBundleName: () => isValidBundleName,
136
162
  isValidProjectName: () => isValidProjectName,
137
163
  killProcessTree: () => killProcessTree,
@@ -141,19 +167,28 @@ __export(index_exports, {
141
167
  listModules: () => listModules,
142
168
  listRunningProcesses: () => listRunningProcesses,
143
169
  listTemplates: () => listTemplates,
170
+ locateTarget: () => locateTarget,
144
171
  needsCmdWrapper: () => needsCmdWrapper,
145
172
  nonInteractivePrompter: () => nonInteractivePrompter,
146
173
  noopLogger: () => noopLogger,
147
174
  noopProgress: () => noopProgress,
175
+ normaliseLocalPath: () => normaliseLocalPath,
148
176
  paramGet: () => paramGet,
177
+ paramGetMany: () => paramGetMany,
149
178
  paramSet: () => paramSet,
150
179
  parseBounds: () => parseBounds,
151
180
  parseCodelinterFindings: () => parseCodelinterFindings,
181
+ parseCodelinterReport: () => parseCodelinterReport,
152
182
  parseDeviceList: () => parseDeviceList,
153
183
  parseHilogLine: () => parseHilogLine,
184
+ parseImeBundles: () => parseImeBundles,
154
185
  parseMainAbilityFromBmDump: () => parseMainAbilityFromBmDump,
186
+ parseParamValues: () => parseParamValues,
155
187
  parseRemoteExit: () => parseRemoteExit,
188
+ performInput: () => performInput,
189
+ pickHap: () => pickHap,
156
190
  pickSigningKind: () => pickSigningKind,
191
+ pickTarget: () => pickTarget,
157
192
  prepareSigning: () => prepareSigning,
158
193
  pruneLayout: () => pruneLayout,
159
194
  readJson5File: () => readJson5File,
@@ -166,6 +201,8 @@ __export(index_exports, {
166
201
  removeSdk: () => removeSdk,
167
202
  removeTempWorkDir: () => removeTempWorkDir,
168
203
  requireHarmonyOsSdk: () => requireHarmonyOsSdk,
204
+ resolveDeviceSerial: () => resolveDeviceSerial,
205
+ resolveKeyEvent: () => resolveKeyEvent,
169
206
  resolveTmpRoot: () => resolveTmpRoot,
170
207
  runCodelinter: () => runCodelinter,
171
208
  runHvigorw: () => runHvigorw,
@@ -189,14 +226,17 @@ __export(index_exports, {
189
226
  toLongPath: () => toLongPath,
190
227
  toSpaceError: () => toSpaceError,
191
228
  uninstallApp: () => uninstallApp,
229
+ unsignedHapRejection: () => unsignedHapRejection,
192
230
  validateTemplateLayout: () => validateTemplateLayout,
193
231
  verifySha256: () => verifySha256,
194
232
  waitForBoot: () => waitForBoot,
195
233
  waitForBundle: () => waitForBundle,
196
234
  waitForCondition: () => waitForCondition,
197
235
  waitForLog: () => waitForLog,
236
+ waitForTarget: () => waitForTarget,
198
237
  watchLog: () => watchLog,
199
238
  waypointsToRawEvents: () => waypointsToRawEvents,
239
+ withoutWindow: () => withoutWindow,
200
240
  writeJson5File: () => writeJson5File,
201
241
  writeJsonFile: () => writeJsonFile
202
242
  });
@@ -293,14 +333,19 @@ var OniroError = class extends Error {
293
333
  cause;
294
334
  };
295
335
  var SdkNotInstalledError = class extends OniroError {
296
- constructor(api, expectedPath) {
297
- super(`SDK API ${api} is not installed at ${expectedPath}.`);
336
+ /** `version` is the release that ships `api` (e.g. `6.0` for API 20), when one is known. */
337
+ constructor(api, expectedPath, version) {
338
+ super(
339
+ `SDK API ${api} is not installed at ${expectedPath}.` + (version ? ` Install it with \`oniro-app sdk install ${version}\`.` : "")
340
+ );
298
341
  this.api = api;
299
342
  this.expectedPath = expectedPath;
343
+ this.version = version;
300
344
  this.name = "SdkNotInstalledError";
301
345
  }
302
346
  api;
303
347
  expectedPath;
348
+ version;
304
349
  };
305
350
  var CmdToolsNotInstalledError = class extends OniroError {
306
351
  constructor(expectedPath) {
@@ -576,6 +621,24 @@ function isProjectLocalHvigorwWorking(projectDir) {
576
621
  var fs3 = __toESM(require("fs"), 1);
577
622
  var path4 = __toESM(require("path"), 1);
578
623
  var import_json5 = __toESM(require("json5"), 1);
624
+ function localPropertiesSdkDir(projectRoot) {
625
+ try {
626
+ const content = fs3.readFileSync(path4.join(projectRoot, "local.properties"), "utf-8");
627
+ const m = /^\s*sdk\.dir\s*[=:]\s*(.+?)\s*$/m.exec(content);
628
+ return m ? m[1].replace(/\\(.)/g, "$1") : void 0;
629
+ } catch {
630
+ return void 0;
631
+ }
632
+ }
633
+ function findMissingProjectSdk(config, projectRoot) {
634
+ const api = detectProjectSdkVersion(projectRoot);
635
+ if (api === void 0) return null;
636
+ const expected = path4.join(getOhosBaseSdkHome(config), String(api));
637
+ if (fs3.existsSync(expected)) return null;
638
+ const localSdkDir = localPropertiesSdkDir(projectRoot);
639
+ if (localSdkDir && fs3.existsSync(path4.join(localSdkDir, String(api)))) return null;
640
+ return new SdkNotInstalledError(String(api), expected, ALL_SDKS.find((s) => s.api === String(api))?.version);
641
+ }
579
642
  function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
580
643
  const buildProfilePath = path4.join(projectRoot, "build-profile.json5");
581
644
  if (!fs3.existsSync(buildProfilePath)) {
@@ -761,14 +824,14 @@ var pipelineAsync = (0, import_node_util.promisify)(import_node_stream.pipeline)
761
824
  async function downloadFile(opts) {
762
825
  const { url, dest, progress, abortSignal } = opts;
763
826
  const proto = url.startsWith("https") ? https : http;
764
- return new Promise((resolve6, reject) => {
827
+ return new Promise((resolve7, reject) => {
765
828
  const s = Math.max(0, Math.min(100, opts.start ?? 0));
766
829
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
767
830
  let settled = false;
768
831
  const done = (err) => {
769
832
  if (settled) return;
770
833
  settled = true;
771
- err ? reject(err) : resolve6();
834
+ err ? reject(err) : resolve7();
772
835
  };
773
836
  if (abortSignal?.aborted) {
774
837
  done(new CancelledError("Download cancelled before start."));
@@ -1255,21 +1318,27 @@ async function installCmdTools(opts) {
1255
1318
  removeTempWorkDir(tmpDir, tmpRoot);
1256
1319
  }
1257
1320
  }
1321
+ function detectCmdToolsFlavour(cmdToolsPath) {
1322
+ return fs11.existsSync(path12.join(cmdToolsPath, "sdk", "default", "hms")) ? "HarmonyOS" : "OpenHarmony";
1323
+ }
1258
1324
  function getCmdToolsStatus(config) {
1259
1325
  const cmdPath = getCmdToolsPath(config);
1326
+ const hdcPath = getHdcPath(config);
1327
+ const base = { path: cmdPath, hdc: { path: hdcPath, exists: fs11.existsSync(hdcPath) } };
1260
1328
  const binDir = path12.join(cmdPath, "bin");
1261
1329
  const candidates = os7.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
1262
1330
  const bin = candidates.map((c) => path12.join(binDir, c)).find((p) => fs11.existsSync(p));
1263
1331
  if (!bin) {
1264
- return { installed: false, status: "Not installed" };
1332
+ return { installed: false, status: "Not installed", ...base };
1265
1333
  }
1334
+ const installed = { installed: true, ...base, flavour: detectCmdToolsFlavour(cmdPath) };
1266
1335
  try {
1267
1336
  const versionFile = path12.join(cmdPath, "version.txt");
1268
1337
  if (fs11.existsSync(versionFile)) {
1269
1338
  const content = fs11.readFileSync(versionFile, "utf8");
1270
1339
  for (const line of content.split(/\r?\n/)) {
1271
1340
  const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
1272
- if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
1341
+ if (m?.[1]) return { ...installed, status: `Installed (${m[1].trim()})` };
1273
1342
  }
1274
1343
  }
1275
1344
  } catch {
@@ -1283,9 +1352,9 @@ function getCmdToolsStatus(config) {
1283
1352
  });
1284
1353
  if (res.error || res.status !== 0) throw res.error ?? new Error(`exit ${String(res.status)}`);
1285
1354
  const version = res.stdout.trim();
1286
- return { installed: true, status: `Installed (${version})` };
1355
+ return { ...installed, status: `Installed (${version})` };
1287
1356
  } catch {
1288
- return { installed: true, status: "Installed (version unknown)" };
1357
+ return { ...installed, status: "Installed (version unknown)" };
1289
1358
  }
1290
1359
  }
1291
1360
  function isCmdToolsInstalled(config) {
@@ -1575,6 +1644,9 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
1575
1644
  profileTemplate.acls = profileTemplate.acls ?? {};
1576
1645
  profileTemplate.acls["allowed-acls"] = options.acls;
1577
1646
  }
1647
+ if (options.appPrivilegeCapabilities !== void 0) {
1648
+ profileTemplate["app-privilege-capabilities"] = options.appPrivilegeCapabilities;
1649
+ }
1578
1650
  fs13.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1579
1651
  }
1580
1652
  function generateP7bFile(projectDir, paths, logger, passwords) {
@@ -1664,6 +1736,12 @@ function updateBuildProfile(projectDir, kind, logger, passwords) {
1664
1736
  storeFile: "./signatures/OpenHarmony.p12"
1665
1737
  }
1666
1738
  }));
1739
+ for (const product of buildProfile.app.products ?? []) {
1740
+ if (typeof product.signingConfig !== "string" || product.signingConfig === "") {
1741
+ product.signingConfig = configNames[0];
1742
+ logger.info(`[sign] Pointing a product with no signingConfig at "${configNames[0]}".`);
1743
+ }
1744
+ }
1667
1745
  fs13.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1668
1746
  }
1669
1747
  function prepareMaterialDirectory(projectDir, logger) {
@@ -1695,7 +1773,7 @@ function generateSigningConfigs(options) {
1695
1773
  }
1696
1774
  const sdkPath = path14.join(sdkHome, String(sdkVersion));
1697
1775
  if (!fs13.existsSync(sdkPath)) {
1698
- throw new OniroError(`SDK path does not exist: ${sdkPath}`);
1776
+ throw new SdkNotInstalledError(String(sdkVersion), sdkPath, ALL_SDKS.find((s) => s.api === String(sdkVersion))?.version);
1699
1777
  }
1700
1778
  const paths = {
1701
1779
  signTool: path14.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
@@ -1707,7 +1785,11 @@ function generateSigningConfigs(options) {
1707
1785
  const passwords = options.passwords ?? DEFAULT_PASSWORDS;
1708
1786
  logger.info("[sign] Starting signing configuration generation...");
1709
1787
  copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
1710
- modifyProfileTemplate(projectDir, apl, appFeature, logger, { kind, acls: options.acls });
1788
+ modifyProfileTemplate(projectDir, apl, appFeature, logger, {
1789
+ kind,
1790
+ acls: options.acls,
1791
+ appPrivilegeCapabilities: options.appPrivilegeCapabilities
1792
+ });
1711
1793
  generateP7bFile(projectDir, paths, logger, passwords);
1712
1794
  prepareMaterialDirectory(projectDir, logger);
1713
1795
  updateBuildProfile(projectDir, kind, logger, passwords);
@@ -1774,7 +1856,7 @@ function runProcess(opts) {
1774
1856
  const { command, args, cwd, env, onOutput } = opts;
1775
1857
  const timeoutMs = opts.timeoutMs ?? 6e4;
1776
1858
  const { abortSignal } = opts;
1777
- return new Promise((resolve6, reject) => {
1859
+ return new Promise((resolve7, reject) => {
1778
1860
  if (abortSignal?.aborted) {
1779
1861
  reject(new CancelledError(`Command aborted before start: ${command}`));
1780
1862
  return;
@@ -1839,21 +1921,41 @@ ${stderr.trim()}` : "")
1839
1921
  );
1840
1922
  return;
1841
1923
  }
1842
- resolve6({ code: code ?? -1, stdout, stderr });
1924
+ resolve7({ code: code ?? -1, stdout, stderr });
1843
1925
  });
1844
1926
  });
1845
1927
  }
1846
- function hdcExec(opts) {
1928
+ function resolveDeviceSerial(explicit, env = process.env) {
1929
+ if (explicit && explicit !== "auto") return explicit;
1930
+ const fromEnv = env.ONIRO_DEVICE_SERIAL || env.DEVICE_SERIAL;
1931
+ return fromEnv && fromEnv !== "auto" ? fromEnv : void 0;
1932
+ }
1933
+ function hdcTargetArgs(explicit) {
1934
+ const serial = resolveDeviceSerial(explicit);
1935
+ return serial ? ["-t", serial] : [];
1936
+ }
1937
+ var HOST_COMMANDS = /* @__PURE__ */ new Set(["list", "tconn", "start", "kill", "version", "checkserver"]);
1938
+ function hdcTargetArgsFor(args, explicit) {
1939
+ if (HOST_COMMANDS.has(args[0] ?? "")) return explicit ? ["-t", explicit] : [];
1940
+ return hdcTargetArgs(explicit);
1941
+ }
1942
+ function hdcClientFailure(output) {
1943
+ const first = output.split(/\r?\n/).find((l) => l.trim() !== "")?.trim() ?? "";
1944
+ return first.startsWith("[Fail]") ? first : null;
1945
+ }
1946
+ async function hdcExec(opts) {
1847
1947
  const hdc = getHdcPath(opts.config);
1848
- const argv = opts.deviceSerial ? ["-t", opts.deviceSerial, ...opts.args] : [...opts.args];
1849
- return runProcess({
1948
+ const res = await runProcess({
1850
1949
  command: hdc,
1851
- args: argv,
1950
+ args: [...hdcTargetArgsFor(opts.args, opts.deviceSerial), ...opts.args],
1852
1951
  timeoutMs: opts.timeoutMs,
1853
1952
  abortSignal: opts.abortSignal,
1854
1953
  onOutput: opts.onOutput,
1855
1954
  logger: opts.logger
1856
1955
  });
1956
+ if (res.code === 0 && hdcClientFailure(`${res.stdout}
1957
+ ${res.stderr}`)) return { ...res, code: 1 };
1958
+ return res;
1857
1959
  }
1858
1960
  function shell(opts) {
1859
1961
  const { command, ...rest } = opts;
@@ -1861,10 +1963,15 @@ function shell(opts) {
1861
1963
  }
1862
1964
  function ensureOk(result, command) {
1863
1965
  if (result.code !== 0) {
1864
- throw new CommandFailedError(command, result.code, result.stderr.trim());
1966
+ throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
1865
1967
  }
1866
1968
  return result;
1867
1969
  }
1970
+ function withTargetHint(detail) {
1971
+ if (!/need connect-key|Not match target/i.test(detail)) return detail;
1972
+ return `${detail}
1973
+ 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\`.`;
1974
+ }
1868
1975
  var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
1869
1976
  function parseRemoteExit(stdout) {
1870
1977
  let code = null;
@@ -1881,7 +1988,7 @@ async function shellChecked(opts) {
1881
1988
  }
1882
1989
  function ensureRemoteOk(result, command) {
1883
1990
  if (result.code !== 0) {
1884
- throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
1991
+ throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
1885
1992
  }
1886
1993
  return result;
1887
1994
  }
@@ -1967,11 +2074,11 @@ async function startEmulator(opts) {
1967
2074
  fs16.writeFileSync(PID_FILE, String(child.pid));
1968
2075
  child.unref();
1969
2076
  logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1970
- const earlyExit = new Promise((resolve6) => {
1971
- child.once("exit", (code) => resolve6(code));
1972
- child.once("error", () => resolve6(-1));
2077
+ const earlyExit = new Promise((resolve7) => {
2078
+ child.once("exit", (code) => resolve7(code));
2079
+ child.once("error", () => resolve7(-1));
1973
2080
  });
1974
- const liveness = new Promise((resolve6) => setTimeout(() => resolve6("alive"), 2e3));
2081
+ const liveness = new Promise((resolve7) => setTimeout(() => resolve7("alive"), 2e3));
1975
2082
  const outcome = await Promise.race([earlyExit, liveness]);
1976
2083
  if (outcome !== "alive") {
1977
2084
  const tail = readTail(logPath, 4e3);
@@ -2051,6 +2158,43 @@ async function stopEmulator(logger = noopLogger) {
2051
2158
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
2052
2159
  });
2053
2160
  }
2161
+ async function isQemuRunning(logger) {
2162
+ if (os8.platform() !== "win32") {
2163
+ try {
2164
+ const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
2165
+ if (Number.isFinite(pid) && pid > 0) {
2166
+ process.kill(pid, 0);
2167
+ return true;
2168
+ }
2169
+ } catch {
2170
+ }
2171
+ }
2172
+ const [cmd, ...args] = os8.platform() === "win32" ? ["tasklist", "/FI", "IMAGENAME eq qemu-system-x86_64.exe", "/NH"] : ["pgrep", "-f", "qemu-system-x86_64"];
2173
+ try {
2174
+ const res = await runProcess({ command: cmd, args, timeoutMs: 1e4, logger });
2175
+ return os8.platform() === "win32" ? /qemu-system-x86_64/i.test(res.stdout) : res.code === 0;
2176
+ } catch {
2177
+ return false;
2178
+ }
2179
+ }
2180
+ async function getEmulatorStatus(config, opts = {}) {
2181
+ const logger = opts.logger ?? noopLogger;
2182
+ const address = opts.address ?? "127.0.0.1:55555";
2183
+ const dir = getEmulatorDir(config);
2184
+ let connected = false;
2185
+ try {
2186
+ const targets = await hdcExec({ config, args: ["list", "targets", "-v"], timeoutMs: 1e4, logger });
2187
+ connected = targets.stdout.split("\n").some((l) => l.trim().startsWith(address) && /\bConnected\b/.test(l));
2188
+ } catch {
2189
+ }
2190
+ return {
2191
+ installed: isEmulatorInstalled(config),
2192
+ dir,
2193
+ running: await isQemuRunning(logger),
2194
+ address,
2195
+ connected
2196
+ };
2197
+ }
2054
2198
 
2055
2199
  // src/hdc/devices.ts
2056
2200
  var STATUS_TOKENS = /* @__PURE__ */ new Set([
@@ -2089,9 +2233,8 @@ async function listDevices(config, opts = {}) {
2089
2233
  return parseDeviceList(res.stdout);
2090
2234
  }
2091
2235
  async function selectDevice(config, serial, opts = {}) {
2092
- if (serial && serial !== "auto") return serial;
2093
- const envSerial = process.env.ONIRO_DEVICE_SERIAL || process.env.DEVICE_SERIAL;
2094
- if (envSerial && envSerial !== "auto") return envSerial;
2236
+ const resolved = resolveDeviceSerial(serial);
2237
+ if (resolved) return resolved;
2095
2238
  const usable = (await listDevices(config, opts)).filter(
2096
2239
  (d) => d.status === "Connected" || d.status === "Unknown"
2097
2240
  );
@@ -2105,6 +2248,7 @@ async function selectDevice(config, serial, opts = {}) {
2105
2248
  }
2106
2249
 
2107
2250
  // src/hdc/param.ts
2251
+ var PARAM_MISSING_RE = /^Get parameter ".*" fail!/;
2108
2252
  async function paramGet(config, key, opts = {}) {
2109
2253
  const res = await shell({
2110
2254
  config,
@@ -2114,7 +2258,30 @@ async function paramGet(config, key, opts = {}) {
2114
2258
  logger: opts.logger
2115
2259
  });
2116
2260
  ensureOk(res, `hdc shell param get ${key}`);
2117
- return res.stdout.trim();
2261
+ const value = res.stdout.trim();
2262
+ if (PARAM_MISSING_RE.test(value)) throw new OniroError(`System parameter ${key} is not set: ${value}`);
2263
+ return value;
2264
+ }
2265
+ var PARAM_SEPARATOR = "__ONIRO_PARAM__";
2266
+ function parseParamValues(keys, stdout) {
2267
+ const parts = stdout.split(PARAM_SEPARATOR);
2268
+ const values = {};
2269
+ keys.forEach((key, i) => {
2270
+ const value = parts[i]?.trim();
2271
+ values[key] = value && !PARAM_MISSING_RE.test(value) ? value : void 0;
2272
+ });
2273
+ return values;
2274
+ }
2275
+ async function paramGetMany(config, keys, opts = {}) {
2276
+ const res = await shell({
2277
+ config,
2278
+ command: keys.map((k) => `param get ${k}; echo ${PARAM_SEPARATOR}`).join("; "),
2279
+ deviceSerial: opts.deviceSerial,
2280
+ timeoutMs: opts.timeoutMs ?? 1e4,
2281
+ logger: opts.logger
2282
+ });
2283
+ ensureOk(res, "hdc shell param get \u2026");
2284
+ return parseParamValues(keys, res.stdout);
2118
2285
  }
2119
2286
  async function paramSet(config, key, value, opts = {}) {
2120
2287
  const res = await shell({
@@ -2128,29 +2295,66 @@ async function paramSet(config, key, value, opts = {}) {
2128
2295
  }
2129
2296
 
2130
2297
  // src/hdc/files.ts
2298
+ var path17 = __toESM(require("path"), 1);
2299
+ function normaliseLocalPath(local) {
2300
+ const abs = path17.resolve(local);
2301
+ const long = toLongPath(abs);
2302
+ return long !== abs ? long : path17.join(toLongPath(path17.dirname(abs)), path17.basename(abs));
2303
+ }
2304
+ function assertDevicePath(remote) {
2305
+ if (/^[A-Za-z]:[\\/]/.test(remote)) {
2306
+ throw new OniroError(
2307
+ `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.`
2308
+ );
2309
+ }
2310
+ }
2311
+ function appSandboxDir(bundle) {
2312
+ return `/data/app/el2/100/base/${bundle}`;
2313
+ }
2314
+ function shQuote(s) {
2315
+ return `'${s.replace(/'/g, `'\\''`)}'`;
2316
+ }
2131
2317
  async function sendFile(opts) {
2318
+ assertDevicePath(opts.remote);
2319
+ const local = normaliseLocalPath(opts.local);
2320
+ const sandbox = opts.bundle ? appSandboxDir(opts.bundle) : void 0;
2321
+ const remote = sandbox && !opts.remote.startsWith("/") ? path17.posix.join(sandbox, opts.remote) : opts.remote;
2322
+ if (sandbox && !remote.startsWith(`${sandbox}/`)) {
2323
+ throw new OniroError(`${remote} is outside the sandbox of ${opts.bundle} (${sandbox}).`);
2324
+ }
2325
+ const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
2326
+ if (sandbox) {
2327
+ const mkdir = `mkdir -p ${shQuote(path17.posix.dirname(remote))}`;
2328
+ ensureRemoteOk(await shellChecked({ ...device, command: mkdir }), `hdc shell ${mkdir}`);
2329
+ }
2132
2330
  const res = await hdcExec({
2133
- config: opts.config,
2134
- args: ["file", "send", opts.local, opts.remote],
2135
- deviceSerial: opts.deviceSerial,
2331
+ ...device,
2332
+ args: ["file", "send", local, remote],
2136
2333
  timeoutMs: opts.timeoutMs ?? 6e5,
2137
2334
  abortSignal: opts.abortSignal,
2138
- onOutput: opts.onOutput,
2139
- logger: opts.logger
2335
+ onOutput: opts.onOutput
2140
2336
  });
2141
- ensureOk(res, `hdc file send ${opts.local} ${opts.remote}`);
2337
+ ensureOk(res, `hdc file send ${local} ${remote}`);
2338
+ if (sandbox) {
2339
+ const top = path17.posix.join(sandbox, path17.posix.relative(sandbox, remote).split("/")[0]);
2340
+ const chown = `chown -R "$(stat -c %u:%g ${shQuote(sandbox)})" ${shQuote(top)}`;
2341
+ ensureRemoteOk(await shellChecked({ ...device, command: chown }), `hdc shell ${chown}`);
2342
+ }
2343
+ return remote;
2142
2344
  }
2143
2345
  async function recvFile(opts) {
2346
+ assertDevicePath(opts.remote);
2347
+ const local = normaliseLocalPath(opts.local);
2144
2348
  const res = await hdcExec({
2145
2349
  config: opts.config,
2146
- args: ["file", "recv", opts.remote, opts.local],
2350
+ args: ["file", "recv", opts.remote, local],
2147
2351
  deviceSerial: opts.deviceSerial,
2148
2352
  timeoutMs: opts.timeoutMs ?? 6e5,
2149
2353
  abortSignal: opts.abortSignal,
2150
2354
  onOutput: opts.onOutput,
2151
2355
  logger: opts.logger
2152
2356
  });
2153
- ensureOk(res, `hdc file recv ${opts.remote} ${opts.local}`);
2357
+ ensureOk(res, `hdc file recv ${opts.remote} ${local}`);
2154
2358
  }
2155
2359
 
2156
2360
  // src/hdc/display.ts
@@ -2170,7 +2374,7 @@ async function getDisplaySize(config, opts = {}) {
2170
2374
 
2171
2375
  // src/hdc/wait.ts
2172
2376
  function delay(ms, signal) {
2173
- return new Promise((resolve6, reject) => {
2377
+ return new Promise((resolve7, reject) => {
2174
2378
  if (signal?.aborted) {
2175
2379
  reject(new CancelledError("Wait cancelled."));
2176
2380
  return;
@@ -2185,7 +2389,7 @@ function delay(ms, signal) {
2185
2389
  };
2186
2390
  const timer = setTimeout(() => {
2187
2391
  cleanup();
2188
- resolve6();
2392
+ resolve7();
2189
2393
  }, ms);
2190
2394
  signal?.addEventListener("abort", onAbort, { once: true });
2191
2395
  });
@@ -2214,20 +2418,73 @@ async function waitForCondition(opts) {
2214
2418
  }
2215
2419
 
2216
2420
  // src/hdc/app.ts
2217
- var fs18 = __toESM(require("fs"), 1);
2421
+ var fs19 = __toESM(require("fs"), 1);
2422
+ var path20 = __toESM(require("path"), 1);
2423
+
2424
+ // src/build/discoverHaps.ts
2425
+ var fs17 = __toESM(require("fs"), 1);
2218
2426
  var path18 = __toESM(require("path"), 1);
2427
+ function moduleOfHap(hapFileName) {
2428
+ const dash = hapFileName.indexOf("-");
2429
+ return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
2430
+ }
2431
+ async function discoverHaps(opts) {
2432
+ const { projectDir } = opts;
2433
+ const maxDepth = opts.maxDepth ?? 8;
2434
+ const result = {};
2435
+ const walk = async (dir, depth) => {
2436
+ if (depth > maxDepth) return;
2437
+ const entries = await fs17.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2438
+ for (const e of entries) {
2439
+ const full = path18.join(dir, e.name);
2440
+ if (e.isDirectory()) {
2441
+ if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
2442
+ await walk(full, depth + 1);
2443
+ } else if (e.isFile() && e.name.endsWith(".hap")) {
2444
+ const moduleName = moduleOfHap(e.name);
2445
+ const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
2446
+ if (e.name.includes("unsigned")) bucket.unsigned.push(full);
2447
+ else if (e.name.includes("signed")) bucket.signed.push(full);
2448
+ }
2449
+ }
2450
+ };
2451
+ await walk(projectDir, 0);
2452
+ const mtime = (p) => fs17.statSync(p).mtimeMs;
2453
+ for (const bucket of Object.values(result)) {
2454
+ bucket.signed.sort((a, b) => mtime(b) - mtime(a));
2455
+ bucket.unsigned.sort((a, b) => mtime(b) - mtime(a));
2456
+ }
2457
+ return result;
2458
+ }
2459
+ function pickHap(haps, module2) {
2460
+ const pick = (name) => {
2461
+ const signed = haps[name]?.signed[0];
2462
+ if (signed) return { module: name, path: signed, signed: true };
2463
+ const unsigned = haps[name]?.unsigned[0];
2464
+ return unsigned ? { module: name, path: unsigned, signed: false } : null;
2465
+ };
2466
+ if (module2) return pick(module2);
2467
+ const names = Object.keys(haps);
2468
+ const withSigned = names.filter((n) => haps[n].signed.length > 0);
2469
+ const candidates = withSigned.length > 0 ? withSigned : names.filter((n) => haps[n].unsigned.length > 0);
2470
+ if (candidates.length === 0) return null;
2471
+ if (candidates.length > 1) {
2472
+ throw new OniroError(`Multiple modules have built HAPs (${candidates.join(", ")}). Pass a module to choose.`);
2473
+ }
2474
+ return pick(candidates[0]);
2475
+ }
2219
2476
 
2220
2477
  // src/hdc/project.ts
2221
- var fs17 = __toESM(require("fs"), 1);
2222
- var path17 = __toESM(require("path"), 1);
2478
+ var fs18 = __toESM(require("fs"), 1);
2479
+ var path19 = __toESM(require("path"), 1);
2223
2480
  var import_json53 = __toESM(require("json5"), 1);
2224
2481
  function readJson5(filePath) {
2225
- const content = fs17.readFileSync(filePath, "utf-8");
2482
+ const content = fs18.readFileSync(filePath, "utf-8");
2226
2483
  return import_json53.default.parse(content);
2227
2484
  }
2228
2485
  function getBundleName(projectDir) {
2229
- const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
2230
- if (!fs17.existsSync(appJsonPath)) {
2486
+ const appJsonPath = path19.join(projectDir, "AppScope", "app.json5");
2487
+ if (!fs18.existsSync(appJsonPath)) {
2231
2488
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
2232
2489
  }
2233
2490
  const appJson = readJson5(appJsonPath);
@@ -2237,8 +2494,8 @@ function getBundleName(projectDir) {
2237
2494
  return appJson.app.bundleName;
2238
2495
  }
2239
2496
  function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2240
- const moduleJsonPath = path17.join(projectDir, moduleName, "src", "main", "module.json5");
2241
- if (!fs17.existsSync(moduleJsonPath)) {
2497
+ const moduleJsonPath = path19.join(projectDir, moduleName, "src", "main", "module.json5");
2498
+ if (!fs18.existsSync(moduleJsonPath)) {
2242
2499
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
2243
2500
  }
2244
2501
  const moduleJson = readJson5(moduleJsonPath);
@@ -2258,13 +2515,67 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2258
2515
  }
2259
2516
 
2260
2517
  // src/hdc/app.ts
2518
+ function detectInstallFailure(output) {
2519
+ return /\[Fail\]|msg:\s*error|error:\s*failed to install|code:\s*95\d{5}/i.test(output) ? output.trim() : null;
2520
+ }
2521
+ function isUnsignedHap(hapPath) {
2522
+ return path20.basename(hapPath).includes("unsigned");
2523
+ }
2524
+ function installFailureReason(output) {
2525
+ const code = /code:\s*(\d+)/i.exec(output);
2526
+ if (!code) return output.trim().split(/\r?\n/)[0] ?? "";
2527
+ const message = /error:\s*([^\r\n]+)/i.exec(output.slice(code.index + code[0].length))?.[1]?.trim().replace(/\.$/, "");
2528
+ return message ? `code ${code[1]}: ${message}` : `code ${code[1]}`;
2529
+ }
2530
+ function unsignedHapRejection(output, hap) {
2531
+ const name = path20.basename(hap.path);
2532
+ const reason = installFailureReason(output);
2533
+ 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}).`;
2534
+ if (!/9568320|signature|pkcs7/i.test(reason)) return what;
2535
+ 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.`;
2536
+ }
2537
+ function ensureInstallOk(result, command, hap) {
2538
+ const output = `${result.stdout}
2539
+ ${result.stderr}`;
2540
+ if (hap && isUnsignedHap(hap.path) && /code:\s*95\d{5}/i.test(output)) {
2541
+ throw new OniroError(unsignedHapRejection(output, hap), new CommandFailedError(command, result.code || 1, output.trim()));
2542
+ }
2543
+ ensureOk(result, command);
2544
+ const failure = detectInstallFailure(output);
2545
+ if (failure) throw new CommandFailedError(command, 1, failure);
2546
+ return result;
2547
+ }
2548
+ function existingHapOrUnsigned(hapPath) {
2549
+ if (fs19.existsSync(hapPath)) return { path: hapPath };
2550
+ const sibling = hapPath.replace(/-signed\.hap$/i, "-unsigned.hap");
2551
+ return sibling !== hapPath && fs19.existsSync(sibling) ? { path: sibling, fallback: true } : void 0;
2552
+ }
2553
+ async function resolveInstallHap(opts, logger) {
2554
+ const warnIfFallback = (hap) => {
2555
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path20.basename(hap.path)}.`);
2556
+ return hap;
2557
+ };
2558
+ const configured = opts.hapPath ?? opts.config.get("hapPath", "");
2559
+ const relative3 = configured || defaultPaths.hapPath;
2560
+ const hapPath = path20.isAbsolute(relative3) ? relative3 : path20.join(opts.projectDir, relative3);
2561
+ if (configured) {
2562
+ const hap = existingHapOrUnsigned(hapPath);
2563
+ if (!hap) throw new OniroError(`HAP file not found at: ${hapPath}. Build the app first.`);
2564
+ return warnIfFallback(hap);
2565
+ }
2566
+ if (fs19.existsSync(hapPath)) return { path: hapPath };
2567
+ const haps = await discoverHaps({ projectDir: opts.projectDir });
2568
+ const anySigned = Object.values(haps).some((m) => m.signed.length > 0);
2569
+ const entryUnsigned = anySigned ? void 0 : existingHapOrUnsigned(hapPath);
2570
+ if (entryUnsigned) return warnIfFallback(entryUnsigned);
2571
+ const picked = pickHap(haps);
2572
+ if (!picked) throw new OniroError(`No HAP found under ${opts.projectDir}. Build the app first (\`oniro-app build\`).`);
2573
+ return warnIfFallback({ path: picked.path, fallback: !picked.signed });
2574
+ }
2261
2575
  async function installApp(opts) {
2262
2576
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2263
- const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
2264
- const hapPath = path18.isAbsolute(relativeHapPath) ? relativeHapPath : path18.join(opts.projectDir, relativeHapPath);
2265
- if (!fs18.existsSync(hapPath)) {
2266
- throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
2267
- }
2577
+ const hap = await resolveInstallHap(opts, logger);
2578
+ const hapPath = hap.path;
2268
2579
  const result = await hdcExec({
2269
2580
  config: opts.config,
2270
2581
  args: ["install", hapPath],
@@ -2273,7 +2584,7 @@ async function installApp(opts) {
2273
2584
  });
2274
2585
  if (result.stdout.trim()) logger.info(result.stdout.trim());
2275
2586
  if (result.stderr.trim()) logger.warn(result.stderr.trim());
2276
- ensureOk(result, `hdc install ${hapPath}`);
2587
+ ensureInstallOk(result, `hdc install ${hapPath}`, hap);
2277
2588
  let bundleName = "";
2278
2589
  try {
2279
2590
  bundleName = getBundleName(opts.projectDir);
@@ -2281,6 +2592,18 @@ async function installApp(opts) {
2281
2592
  }
2282
2593
  return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
2283
2594
  }
2595
+ async function isBundleInstalled(opts) {
2596
+ const safe = opts.bundle.replace(/'/g, `'\\''`);
2597
+ const res = await shell({
2598
+ config: opts.config,
2599
+ command: `bm dump -n '${safe}'`,
2600
+ deviceSerial: opts.deviceSerial,
2601
+ timeoutMs: opts.timeoutMs ?? 15e3,
2602
+ logger: opts.logger
2603
+ });
2604
+ ensureOk(res, `hdc shell bm dump -n ${opts.bundle}`);
2605
+ return res.stdout.includes("{") && !/failed to get information/i.test(res.stdout);
2606
+ }
2284
2607
  function detectAaStartFailure(output) {
2285
2608
  return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
2286
2609
  }
@@ -2351,10 +2674,10 @@ function listRunningProcesses(config, options = {}) {
2351
2674
  const logger = options.logger ?? noopLogger;
2352
2675
  const timeoutMs = options.timeoutMs ?? 1e3;
2353
2676
  const hdc = getHdcPath(config);
2354
- return new Promise((resolve6, reject) => {
2677
+ return new Promise((resolve7, reject) => {
2355
2678
  let proc;
2356
2679
  try {
2357
- proc = spawnCompat(hdc, ["track-jpid"]);
2680
+ proc = spawnCompat(hdc, [...hdcTargetArgs(options.deviceSerial), "track-jpid"]);
2358
2681
  } catch (err) {
2359
2682
  reject(new OniroError(`Failed to spawn ${hdc}: ${err instanceof Error ? err.message : String(err)}`, err));
2360
2683
  return;
@@ -2371,7 +2694,7 @@ function listRunningProcesses(config, options = {}) {
2371
2694
  if (options.targetProcessName && name === options.targetProcessName && !resolved) {
2372
2695
  resolved = true;
2373
2696
  killProcessTree(proc);
2374
- resolve6(pid);
2697
+ resolve7(pid);
2375
2698
  return;
2376
2699
  }
2377
2700
  }
@@ -2392,7 +2715,7 @@ function listRunningProcesses(config, options = {}) {
2392
2715
  if (options.targetProcessName) {
2393
2716
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2394
2717
  } else {
2395
- resolve6(processes);
2718
+ resolve7(processes);
2396
2719
  }
2397
2720
  }, timeoutMs);
2398
2721
  proc.on("close", () => {
@@ -2402,7 +2725,7 @@ function listRunningProcesses(config, options = {}) {
2402
2725
  if (options.targetProcessName) {
2403
2726
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2404
2727
  } else {
2405
- resolve6(processes);
2728
+ resolve7(processes);
2406
2729
  }
2407
2730
  });
2408
2731
  });
@@ -2424,7 +2747,11 @@ async function findRunningProcess(opts) {
2424
2747
  const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
2425
2748
  if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
2426
2749
  try {
2427
- const list = await listRunningProcesses(opts.config, { timeoutMs: 1200, logger: opts.logger });
2750
+ const list = await listRunningProcesses(opts.config, {
2751
+ timeoutMs: 1200,
2752
+ deviceSerial: opts.deviceSerial,
2753
+ logger: opts.logger
2754
+ });
2428
2755
  if (Array.isArray(list)) {
2429
2756
  const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
2430
2757
  const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
@@ -2567,12 +2894,12 @@ async function reboot(opts) {
2567
2894
  }
2568
2895
 
2569
2896
  // src/hdc/screen.ts
2570
- var fs19 = __toESM(require("fs"), 1);
2897
+ var fs20 = __toESM(require("fs"), 1);
2571
2898
  var os9 = __toESM(require("os"), 1);
2572
- var path19 = __toESM(require("path"), 1);
2899
+ var path21 = __toESM(require("path"), 1);
2573
2900
  var import_node_crypto = require("crypto");
2574
2901
  function tmpName(ext) {
2575
- return path19.join(os9.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
2902
+ return path21.join(os9.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
2576
2903
  }
2577
2904
  async function takeScreenshot(opts) {
2578
2905
  const remote = "/data/local/tmp/oniro_screenshot.jpeg";
@@ -2582,11 +2909,11 @@ async function takeScreenshot(opts) {
2582
2909
  const local = tmpName("jpeg");
2583
2910
  try {
2584
2911
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2585
- const pixels = await fs19.promises.readFile(local);
2912
+ const pixels = await fs20.promises.readFile(local);
2586
2913
  const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
2587
2914
  return { pixels, width: size.width, height: size.height };
2588
2915
  } finally {
2589
- fs19.promises.unlink(local).catch(() => {
2916
+ fs20.promises.unlink(local).catch(() => {
2590
2917
  });
2591
2918
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2592
2919
  });
@@ -2605,9 +2932,9 @@ async function captureBurst(opts) {
2605
2932
  const local = tmpName(`burst_${i}.jpeg`);
2606
2933
  try {
2607
2934
  await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
2608
- frames.push(await fs19.promises.readFile(local));
2935
+ frames.push(await fs20.promises.readFile(local));
2609
2936
  } finally {
2610
- fs19.promises.unlink(local).catch(() => {
2937
+ fs20.promises.unlink(local).catch(() => {
2611
2938
  });
2612
2939
  }
2613
2940
  }
@@ -2619,6 +2946,55 @@ async function captureBurst(opts) {
2619
2946
  }
2620
2947
 
2621
2948
  // src/hdc/input.ts
2949
+ var UITEST_KEY_NAMES = ["Back", "Home", "Power"];
2950
+ var KEY_IDS = {
2951
+ back: 2,
2952
+ home: 1,
2953
+ power: 18,
2954
+ volumeup: 16,
2955
+ volumedown: 17,
2956
+ up: 2012,
2957
+ down: 2013,
2958
+ left: 2014,
2959
+ right: 2015,
2960
+ alt: 2045,
2961
+ shift: 2047,
2962
+ tab: 2049,
2963
+ space: 2050,
2964
+ enter: 2054,
2965
+ delete: 2055,
2966
+ backspace: 2055,
2967
+ pageup: 2068,
2968
+ pagedown: 2069,
2969
+ escape: 2070,
2970
+ esc: 2070,
2971
+ forwarddelete: 2071,
2972
+ ctrl: 2072,
2973
+ meta: 2076,
2974
+ movehome: 2081,
2975
+ moveend: 2082
2976
+ };
2977
+ function keyId(name) {
2978
+ const k = name.trim().toLowerCase();
2979
+ if (/^\d+$/.test(k)) return Number(k);
2980
+ if (/^[a-z]$/.test(k)) return 2017 + (k.charCodeAt(0) - 97);
2981
+ const id = KEY_IDS[k];
2982
+ if (id === void 0) {
2983
+ throw new OniroError(
2984
+ `Unknown key "${name}". Use a numeric KeyCode id, a letter, or one of: ${Object.keys(KEY_IDS).join(", ")}. Combine up to three with "+", e.g. Ctrl+A.`
2985
+ );
2986
+ }
2987
+ return id;
2988
+ }
2989
+ function resolveKeyEvent(key) {
2990
+ const spec = String(key).trim();
2991
+ if (spec === "") throw new OniroError('send input "keyEvent" requires a key.');
2992
+ const native = UITEST_KEY_NAMES.find((n) => n.toLowerCase() === spec.toLowerCase());
2993
+ if (native) return native;
2994
+ const parts = spec.includes("+") ? spec.split("+") : spec.split(/\s+/);
2995
+ if (parts.length > 3) throw new OniroError(`uitest injects at most three keys at once; got "${spec}".`);
2996
+ return parts.map(keyId).join(" ");
2997
+ }
2622
2998
  function reqNum(value, name, type) {
2623
2999
  if (value === void 0 || Number.isNaN(value)) {
2624
3000
  throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
@@ -2639,7 +3015,7 @@ function buildInputCommand(opts) {
2639
3015
  }
2640
3016
  case "keyEvent":
2641
3017
  if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
2642
- return `uitest uiInput keyEvent ${opts.key}`;
3018
+ return `uitest uiInput keyEvent ${resolveKeyEvent(opts.key)}`;
2643
3019
  case "inputText": {
2644
3020
  const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
2645
3021
  if (opts.pxX !== void 0 && opts.pxY !== void 0) {
@@ -2733,10 +3109,52 @@ async function sendRawTouch(opts) {
2733
3109
  }
2734
3110
 
2735
3111
  // src/hdc/dump.ts
2736
- var fs20 = __toESM(require("fs"), 1);
3112
+ var fs21 = __toESM(require("fs"), 1);
2737
3113
  var os10 = __toESM(require("os"), 1);
2738
- var path20 = __toESM(require("path"), 1);
3114
+ var path22 = __toESM(require("path"), 1);
2739
3115
  var import_node_crypto2 = require("crypto");
3116
+
3117
+ // src/hdc/ime.ts
3118
+ var INPUT_METHOD_SA_ID = 3703;
3119
+ var IME_BUNDLE_FALLBACK = /inputmethod|keyboard|(^|\.)ime(\.|$)/i;
3120
+ function parseImeBundles(output) {
3121
+ const current = [];
3122
+ const others = [];
3123
+ const entry = /\{[^{}]*"ime"\s*:\s*"([^"/]+)\/[^"]*"[^{}]*\}/g;
3124
+ for (const m of output.matchAll(entry)) {
3125
+ const bundle = m[1];
3126
+ const list = /"isCurrentIme"\s*:\s*"true"/.test(m[0]) ? current : others;
3127
+ if (!current.includes(bundle) && !others.includes(bundle)) list.push(bundle);
3128
+ }
3129
+ return [...current, ...others];
3130
+ }
3131
+ async function getImeBundles(config, opts = {}) {
3132
+ try {
3133
+ const res = await shell({
3134
+ config,
3135
+ command: `hidumper -s ${INPUT_METHOD_SA_ID} -a '-a'`,
3136
+ deviceSerial: opts.deviceSerial,
3137
+ timeoutMs: 1e4,
3138
+ logger: opts.logger
3139
+ });
3140
+ if (res.code === 0) return parseImeBundles(res.stdout);
3141
+ } catch (err) {
3142
+ opts.logger?.debug(`could not list input methods: ${err instanceof Error ? err.message : String(err)}`);
3143
+ }
3144
+ return [];
3145
+ }
3146
+ function findKeyboardWindow(tree, imeBundles) {
3147
+ const isIme = (bundle) => imeBundles.length > 0 ? imeBundles.includes(bundle) : IME_BUNDLE_FALLBACK.test(bundle);
3148
+ const stack = [tree];
3149
+ while (stack.length > 0) {
3150
+ const n = stack.shift();
3151
+ if (n.bundle && isIme(n.bundle)) return n;
3152
+ stack.push(...n.children ?? []);
3153
+ }
3154
+ return null;
3155
+ }
3156
+
3157
+ // src/hdc/dump.ts
2740
3158
  function parseBounds(s) {
2741
3159
  const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
2742
3160
  if (!m) return null;
@@ -2771,7 +3189,46 @@ var FLAG_MAP = [
2771
3189
  ["scrollable", "scroll"],
2772
3190
  ["longClickable", "longClick"]
2773
3191
  ];
2774
- function pruneLayout(node, display) {
3192
+ function colorAlpha(color) {
3193
+ const m = /^#([0-9a-f]{2})?[0-9a-f]{6}$/i.exec(color ?? "");
3194
+ if (!m) return 0;
3195
+ return m[1] === void 0 ? 255 : parseInt(m[1], 16);
3196
+ }
3197
+ function paintsFill(a) {
3198
+ if (a.visible === "false") return false;
3199
+ if (a.opacity !== void 0 && a.opacity !== "" && !(Number(a.opacity) > 0)) return false;
3200
+ return colorAlpha(a.backgroundColor) > 0;
3201
+ }
3202
+ function findCoveredNodes(root) {
3203
+ const order = [];
3204
+ const fills = [];
3205
+ const visit = (n) => {
3206
+ const a = n.attributes ?? {};
3207
+ const entry = { node: n, bounds: parseBounds(a.bounds), end: 0 };
3208
+ if (entry.bounds && paintsFill(a)) fills.push(order.length);
3209
+ order.push(entry);
3210
+ const kids = (n.children ?? []).map((c, i) => ({ c, i, z: Number(c.attributes?.zIndex) || 0 })).sort((p, q) => p.z - q.z || p.i - q.i);
3211
+ for (const k of kids) visit(k.c);
3212
+ entry.end = order.length;
3213
+ };
3214
+ visit(root);
3215
+ const covered = /* @__PURE__ */ new Set();
3216
+ for (const p of order) {
3217
+ if (!p.bounds) continue;
3218
+ const cx = (p.bounds[0] + p.bounds[2]) / 2;
3219
+ const cy = (p.bounds[1] + p.bounds[3]) / 2;
3220
+ for (const j of fills) {
3221
+ if (j < p.end) continue;
3222
+ const q = order[j].bounds;
3223
+ if (cx >= q[0] && cx < q[2] && cy >= q[1] && cy < q[3]) {
3224
+ covered.add(p.node);
3225
+ break;
3226
+ }
3227
+ }
3228
+ }
3229
+ return covered;
3230
+ }
3231
+ function pruneLayout(node, display, covered) {
2775
3232
  const a = node.attributes ?? {};
2776
3233
  const px = parseBounds(a.bounds);
2777
3234
  const out = {};
@@ -2789,28 +3246,125 @@ function pruneLayout(node, display) {
2789
3246
  if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
2790
3247
  if (a.text) out.text = a.text;
2791
3248
  if (a.description) out.desc = a.description;
3249
+ if (a.hint) out.hint = a.hint;
2792
3250
  if (a.id) out.id = a.id;
2793
3251
  if (a.key && a.key !== a.id) out.key = a.key;
2794
3252
  if (a.bundleName) out.bundle = a.bundleName;
2795
3253
  for (const [attr, short] of FLAG_MAP) {
2796
3254
  if (a[attr] === "true") out[short] = true;
2797
3255
  }
3256
+ if (a.enabled === "false") out.disabled = true;
3257
+ if (covered?.has(node)) out.covered = true;
2798
3258
  const children = [];
2799
3259
  for (const c of node.children ?? []) {
2800
- const pc = pruneLayout(c, display);
3260
+ const pc = pruneLayout(c, display, covered);
2801
3261
  if (pc) {
2802
3262
  if (Array.isArray(pc)) children.push(...pc);
2803
3263
  else children.push(pc);
2804
3264
  }
2805
3265
  }
2806
3266
  const hasSignal = Boolean(
2807
- out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
3267
+ out.text || out.desc || out.hint || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
2808
3268
  );
2809
3269
  if (!hasSignal && children.length === 0) return null;
2810
3270
  if (!hasSignal) return children.length === 1 ? children[0] : children;
2811
3271
  if (children.length) out.children = children;
2812
3272
  return out;
2813
3273
  }
3274
+ function flattenLayout(tree) {
3275
+ const out = [];
3276
+ const visit = (n, window) => {
3277
+ const { children, ...rest } = n;
3278
+ const win = n.bundle ?? window;
3279
+ if (Object.keys(rest).length > 0) out.push({ ...rest, ...win ? { window: win } : {} });
3280
+ for (const c of children ?? []) visit(c, win);
3281
+ };
3282
+ visit(tree, void 0);
3283
+ return out;
3284
+ }
3285
+ function assertLayoutSelector(selector) {
3286
+ if (selector.text === void 0 && selector.id === void 0 && selector.type === void 0) {
3287
+ throw new OniroError("A layout selector needs a text, id, or type.");
3288
+ }
3289
+ if (selector.text !== void 0 && selector.text.trim() === "") {
3290
+ throw new OniroError("A layout selector text must not be blank.");
3291
+ }
3292
+ }
3293
+ function withoutWindow(tree, bundle) {
3294
+ const children = (tree.children ?? []).filter((c) => c.bundle !== bundle).map((c) => withoutWindow(c, bundle));
3295
+ return tree.children ? { ...tree, children } : tree;
3296
+ }
3297
+ function findLayoutNodes(tree, selector) {
3298
+ assertLayoutSelector(selector);
3299
+ const type = selector.type?.toLowerCase();
3300
+ const nodes = flattenLayout(tree).filter(
3301
+ (n) => (type === void 0 || n.type?.toLowerCase() === type) && (selector.id === void 0 || n.id === selector.id || n.key === selector.id)
3302
+ );
3303
+ if (selector.text === void 0) return nodes;
3304
+ const want = selector.text.trim().toLowerCase();
3305
+ const labels = (n) => [n.text, n.desc, n.hint].filter((s) => !!s).map((s) => s.trim().toLowerCase());
3306
+ const exact = nodes.filter((n) => labels(n).some((l) => l === want));
3307
+ if (exact.length > 0) return exact;
3308
+ return nodes.filter((n) => labels(n).some((l) => l.includes(want)));
3309
+ }
3310
+ function describeSelector(s) {
3311
+ const parts = [];
3312
+ if (s.text !== void 0) parts.push(`text "${s.text}"`);
3313
+ if (s.id !== void 0) parts.push(`id "${s.id}"`);
3314
+ if (s.type !== void 0) parts.push(`type ${s.type}`);
3315
+ return parts.join(", ");
3316
+ }
3317
+ function describeLayoutNode(n) {
3318
+ const label = n.text ?? n.desc ?? (n.hint !== void 0 ? `hint: ${n.hint}` : void 0);
3319
+ const at = n.c ? ` at ${n.c[0].toFixed(3)},${n.c[1].toFixed(3)}` : "";
3320
+ return `${n.type ?? "node"}${label !== void 0 ? ` "${label}"` : ""}${n.id ? ` #${n.id}` : ""}${at}`;
3321
+ }
3322
+ var area = (b) => b ? (b[2] - b[0]) * (b[3] - b[1]) : 0;
3323
+ function enclosesTarget(outer, inner) {
3324
+ const b = outer.b;
3325
+ const c = inner.c;
3326
+ if (!b || !c) return false;
3327
+ return c[0] >= b[0] && c[0] <= b[2] && c[1] >= b[1] && c[1] <= b[3] && area(b) > area(inner.b);
3328
+ }
3329
+ var TargetNotFoundError = class extends OniroError {
3330
+ constructor(message, reason) {
3331
+ super(message);
3332
+ this.reason = reason;
3333
+ this.name = "TargetNotFoundError";
3334
+ }
3335
+ reason;
3336
+ };
3337
+ function pickTarget(tree, selector, index) {
3338
+ const matches = findLayoutNodes(tree, selector).filter((n) => n.c !== void 0);
3339
+ const what = describeSelector(selector);
3340
+ if (matches.length === 0) {
3341
+ throw new TargetNotFoundError(`No on-screen element matches ${what}. Run \`oniro-app dump layout --flat\` to see what is there.`, "none");
3342
+ }
3343
+ const visible = matches.filter((n) => !n.covered);
3344
+ if (visible.length === 0) {
3345
+ throw new TargetNotFoundError(
3346
+ `Found ${matches.length} element(s) matching ${what}, but each is covered by an overlay (dialog, keyboard, or another layer): ${matches.map(describeLayoutNode).join("; ")}.`,
3347
+ "covered"
3348
+ );
3349
+ }
3350
+ const clickable = visible.filter((n) => n.click);
3351
+ const preferred = clickable.length > 0 ? clickable : visible;
3352
+ const candidates = preferred.filter((n) => !preferred.some((o) => o !== n && o.click && enclosesTarget(o, n)));
3353
+ if (index !== void 0) {
3354
+ const picked = candidates[index];
3355
+ if (!picked) {
3356
+ throw new TargetNotFoundError(`Index ${index} is out of range: ${candidates.length} element(s) match ${what}.`, "ambiguous");
3357
+ }
3358
+ return picked;
3359
+ }
3360
+ if (candidates.length > 1) {
3361
+ throw new TargetNotFoundError(
3362
+ `${candidates.length} elements match ${what}; pass an index to choose: ` + candidates.map((n, i) => `[${i}] ${describeLayoutNode(n)}`).join("; "),
3363
+ "ambiguous"
3364
+ );
3365
+ }
3366
+ return candidates[0];
3367
+ }
2814
3368
  async function dumpLayout(opts) {
2815
3369
  const timeoutMs = opts.timeoutMs ?? 3e4;
2816
3370
  const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
@@ -2820,33 +3374,193 @@ async function dumpLayout(opts) {
2820
3374
  if (!m) throw new OniroError(`Could not parse dumpLayout output:
2821
3375
  ${dump.stdout}`);
2822
3376
  const remote = m[1];
2823
- const local = path20.join(os10.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
3377
+ const local = path22.join(os10.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
2824
3378
  try {
2825
3379
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2826
- const parsed = JSON.parse(await fs20.promises.readFile(local, "utf8"));
3380
+ const parsed = JSON.parse(await fs21.promises.readFile(local, "utf8"));
2827
3381
  const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
2828
3382
  if (opts.prune === false) {
2829
3383
  return { display, tree: parsed };
2830
3384
  }
2831
- const prunedRaw = pruneLayout(parsed, display);
3385
+ const prunedRaw = pruneLayout(parsed, display, findCoveredNodes(parsed));
2832
3386
  const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
2833
- return { display, tree };
3387
+ if (!opts.detectKeyboard || opts.bundleName) return { display, tree };
3388
+ const imeBundles = await getImeBundles(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
3389
+ const kb = findKeyboardWindow(tree, imeBundles);
3390
+ const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
3391
+ return { display, tree, keyboard };
2834
3392
  } finally {
2835
- fs20.promises.unlink(local).catch(() => {
3393
+ fs21.promises.unlink(local).catch(() => {
2836
3394
  });
2837
3395
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2838
3396
  });
2839
3397
  }
2840
3398
  }
2841
3399
 
3400
+ // src/hdc/uiActions.ts
3401
+ var FOCUS_SETTLE_MS = 400;
3402
+ var KEYBOARD_SETTLE_MS = 400;
3403
+ var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
3404
+ function targetTree(layout) {
3405
+ const kb = layout.keyboard;
3406
+ return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
3407
+ }
3408
+ function fractionToPixel(f, size) {
3409
+ return Math.min(size - 1, Math.round(f * size));
3410
+ }
3411
+ function pressBack(opts) {
3412
+ return sendInput({ config: opts.config, type: "keyEvent", key: "Back", deviceSerial: opts.deviceSerial, logger: opts.logger });
3413
+ }
3414
+ async function hideKeyboard(opts) {
3415
+ const { keyboard } = await dumpLayout({ ...opts, detectKeyboard: true });
3416
+ if (!keyboard?.shown) return false;
3417
+ await pressBack(opts);
3418
+ await sleep(KEYBOARD_SETTLE_MS);
3419
+ return true;
3420
+ }
3421
+ async function locateTarget(opts) {
3422
+ assertLayoutSelector(opts.selector);
3423
+ const dumpOpts = { config: opts.config, detectKeyboard: true, deviceSerial: opts.deviceSerial, logger: opts.logger };
3424
+ let layout = await dumpLayout(dumpOpts);
3425
+ if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
3426
+ let keyboardHidden = false;
3427
+ let node;
3428
+ let failure;
3429
+ try {
3430
+ node = pickTarget(targetTree(layout), opts.selector, opts.index);
3431
+ } catch (err) {
3432
+ if (!(err instanceof TargetNotFoundError) || err.reason === "ambiguous") throw err;
3433
+ failure = err;
3434
+ }
3435
+ if ((!node || !node.click) && layout.keyboard?.shown && opts.hideKeyboard !== false) {
3436
+ opts.logger?.debug("target not reachable while the keyboard is up; dismissing it and looking again");
3437
+ await pressBack(opts);
3438
+ await sleep(KEYBOARD_SETTLE_MS);
3439
+ keyboardHidden = true;
3440
+ layout = await dumpLayout(dumpOpts);
3441
+ if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
3442
+ node = pickTarget(targetTree(layout), opts.selector, opts.index);
3443
+ }
3444
+ if (!node) throw failure;
3445
+ if (!node.c) throw new OniroError("The element has no on-screen position.");
3446
+ const x2 = fractionToPixel(node.c[0], layout.display.width);
3447
+ const y = fractionToPixel(node.c[1], layout.display.height);
3448
+ return { node, x: x2, y, display: layout.display, keyboardHidden };
3449
+ }
3450
+ function fraction(value, name) {
3451
+ if (value === void 0) return void 0;
3452
+ if (!(value >= 0 && value <= 1)) throw new OniroError(`${name} must be a fraction between 0 and 1; got ${value}.`);
3453
+ return value;
3454
+ }
3455
+ function pair(a, b, names) {
3456
+ if (a === void 0 && b === void 0) return void 0;
3457
+ if (a === void 0 || b === void 0) throw new OniroError(`Give both ${names}.`);
3458
+ return [a, b];
3459
+ }
3460
+ async function performInput(opts) {
3461
+ const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
3462
+ const startFx = pair(fraction(opts.fx, "--fx"), fraction(opts.fy, "--fy"), "--fx and --fy");
3463
+ const startPx = pair(opts.pxX, opts.pxY, "--x and --y");
3464
+ const endFx = pair(fraction(opts.fx2, "--fx2"), fraction(opts.fy2, "--fy2"), "--fx2 and --fy2");
3465
+ const endPx = pair(opts.pxX2, opts.pxY2, "--x2 and --y2");
3466
+ if ([opts.target, startFx, startPx].filter((s) => s !== void 0).length > 1) {
3467
+ throw new OniroError("Give the start point one way: a target, fractions (--fx/--fy), or pixels (--x/--y).");
3468
+ }
3469
+ if (endFx && endPx) throw new OniroError("Give the end point one way: fractions (--fx2/--fy2) or pixels (--x2/--y2).");
3470
+ let display;
3471
+ const toPx = async ([fx, fy]) => {
3472
+ display ??= await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
3473
+ if (!display) throw new OniroError("Could not read the display size to convert fractions to pixels.");
3474
+ return [fractionToPixel(fx, display.width), fractionToPixel(fy, display.height)];
3475
+ };
3476
+ const result = { keyboardHidden: false };
3477
+ let start = startPx;
3478
+ if (opts.target) {
3479
+ const located = await locateTarget({ ...device, selector: opts.target, index: opts.targetIndex });
3480
+ display = located.display;
3481
+ start = [located.x, located.y];
3482
+ result.target = located.node;
3483
+ result.keyboardHidden = located.keyboardHidden;
3484
+ } else if (startFx) {
3485
+ start = await toPx(startFx);
3486
+ }
3487
+ const end = endFx ? await toPx(endFx) : endPx;
3488
+ if (start) [result.x, result.y] = start;
3489
+ const tapToFocus = async () => {
3490
+ if (!start) return;
3491
+ await sendInput({ ...device, type: "click", pxX: start[0], pxY: start[1] });
3492
+ await sleep(FOCUS_SETTLE_MS);
3493
+ };
3494
+ const clearFocused = async () => {
3495
+ await sendInput({ ...device, type: "keyEvent", key: "Ctrl+A" });
3496
+ await sendInput({ ...device, type: "keyEvent", key: "Delete" });
3497
+ };
3498
+ switch (opts.type) {
3499
+ case "hideKeyboard":
3500
+ result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
3501
+ return result;
3502
+ case "clearText":
3503
+ await tapToFocus();
3504
+ await clearFocused();
3505
+ break;
3506
+ case "inputText":
3507
+ if (opts.clear) {
3508
+ await tapToFocus();
3509
+ await clearFocused();
3510
+ await sendInput({ ...device, type: "inputText", text: opts.text });
3511
+ } else {
3512
+ await sendInput({ ...device, type: "inputText", pxX: start?.[0], pxY: start?.[1], text: opts.text });
3513
+ }
3514
+ break;
3515
+ default:
3516
+ await sendInput({
3517
+ ...device,
3518
+ type: opts.type,
3519
+ pxX: start?.[0],
3520
+ pxY: start?.[1],
3521
+ pxX2: end?.[0],
3522
+ pxY2: end?.[1],
3523
+ speed: opts.speed,
3524
+ key: opts.key,
3525
+ text: opts.text
3526
+ });
3527
+ }
3528
+ if (opts.hideKeyboard) result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
3529
+ return result;
3530
+ }
3531
+ async function waitForTarget(opts) {
3532
+ const { selector } = opts;
3533
+ assertLayoutSelector(selector);
3534
+ let found = null;
3535
+ await waitForCondition({
3536
+ probe: async () => {
3537
+ const layout = await dumpLayout({
3538
+ config: opts.config,
3539
+ detectKeyboard: true,
3540
+ deviceSerial: opts.deviceSerial,
3541
+ logger: opts.logger
3542
+ });
3543
+ found = findLayoutNodes(targetTree(layout), selector).find((n) => !n.covered) ?? null;
3544
+ return opts.gone ? found === null : found !== null;
3545
+ },
3546
+ timeoutMs: opts.timeoutMs,
3547
+ pollMs: opts.pollMs ?? 500,
3548
+ abortSignal: opts.abortSignal,
3549
+ logger: opts.logger,
3550
+ timeoutMessage: opts.gone ? `An element matching the selector was still on screen after ${opts.timeoutMs}ms.` : `No element matching the selector appeared within ${opts.timeoutMs}ms.`
3551
+ });
3552
+ return opts.gone ? null : found;
3553
+ }
3554
+
2842
3555
  // src/hdc/hilog.ts
3556
+ var fs22 = __toESM(require("fs"), 1);
2843
3557
  function setHilogLevel(opts) {
2844
3558
  const logger = opts.logger ?? noopLogger;
2845
3559
  const hdc = getHdcPath(opts.config);
2846
- return new Promise((resolve6, reject) => {
3560
+ return new Promise((resolve7, reject) => {
2847
3561
  let child;
2848
3562
  try {
2849
- child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
3563
+ child = spawnCompat(hdc, [...hdcTargetArgs(opts.deviceSerial), "shell", "hilog", "-b", opts.level]);
2850
3564
  } catch (err) {
2851
3565
  const msg = err instanceof Error ? err.message : String(err);
2852
3566
  logger.error(`[hilog] failed to spawn hdc: ${msg}`);
@@ -2863,7 +3577,7 @@ function setHilogLevel(opts) {
2863
3577
  });
2864
3578
  child.once("close", (code) => {
2865
3579
  if (code === 0) {
2866
- resolve6();
3580
+ resolve7();
2867
3581
  } else {
2868
3582
  const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
2869
3583
  logger.error(`[hilog] ${msg}`);
@@ -2874,8 +3588,7 @@ function setHilogLevel(opts) {
2874
3588
  }
2875
3589
  function streamHilog(opts) {
2876
3590
  const hdc = getHdcPath(opts.config);
2877
- const args = [];
2878
- if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
3591
+ const args = [...hdcTargetArgs(opts.deviceSerial)];
2879
3592
  args.push("shell", "hilog");
2880
3593
  if (opts.processId && opts.processId.trim() !== "") {
2881
3594
  args.push("-P", opts.processId.trim());
@@ -2918,7 +3631,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
2918
3631
  return proc?.pid;
2919
3632
  }
2920
3633
  function waitForLog(opts) {
2921
- return new Promise((resolve6, reject) => {
3634
+ return new Promise((resolve7, reject) => {
2922
3635
  const deadline = Date.now() + opts.timeoutMs;
2923
3636
  let settled = false;
2924
3637
  let child = null;
@@ -2937,7 +3650,7 @@ function waitForLog(opts) {
2937
3650
  if (settled) return;
2938
3651
  settled = true;
2939
3652
  cleanup();
2940
- resolve6(entry);
3653
+ resolve7(entry);
2941
3654
  };
2942
3655
  const fail = (err) => {
2943
3656
  if (settled) return;
@@ -2984,7 +3697,7 @@ function waitForLog(opts) {
2984
3697
  });
2985
3698
  }
2986
3699
  function watchLog(opts) {
2987
- return new Promise((resolve6, reject) => {
3700
+ return new Promise((resolve7, reject) => {
2988
3701
  const matches = [];
2989
3702
  let settled = false;
2990
3703
  let child = null;
@@ -3001,7 +3714,7 @@ function watchLog(opts) {
3001
3714
  if (settled) return;
3002
3715
  settled = true;
3003
3716
  cleanup();
3004
- resolve6(opts.dedup === false ? matches : dedupEntries(matches));
3717
+ resolve7(opts.dedup === false ? matches : dedupEntries(matches));
3005
3718
  };
3006
3719
  const onAbort = () => {
3007
3720
  if (settled) return;
@@ -3033,6 +3746,94 @@ function watchLog(opts) {
3033
3746
  });
3034
3747
  });
3035
3748
  }
3749
+ async function captureLog(opts) {
3750
+ const logger = opts.logger ?? noopLogger;
3751
+ if (opts.clear) {
3752
+ const res = await shell({ config: opts.config, command: "hilog -r", deviceSerial: opts.deviceSerial, timeoutMs: 1e4, logger });
3753
+ ensureOk(res, "hdc shell hilog -r");
3754
+ }
3755
+ const fd = fs22.openSync(opts.file, "a");
3756
+ let lines = 0;
3757
+ let reconnects = 0;
3758
+ try {
3759
+ await new Promise((resolve7, reject) => {
3760
+ const deadline = opts.durationMs !== void 0 ? Date.now() + opts.durationMs : Infinity;
3761
+ let settled = false;
3762
+ let child = null;
3763
+ let reconnectTimer;
3764
+ let buf = "";
3765
+ let open = true;
3766
+ const write = (line) => {
3767
+ if (!open || opts.pattern && !opts.pattern.test(line)) return;
3768
+ fs22.writeSync(fd, `${line}
3769
+ `);
3770
+ lines++;
3771
+ };
3772
+ const cleanup = () => {
3773
+ if (timer) clearTimeout(timer);
3774
+ if (reconnectTimer) clearTimeout(reconnectTimer);
3775
+ opts.abortSignal?.removeEventListener("abort", finish);
3776
+ if (buf.trim()) write(buf.trimEnd());
3777
+ buf = "";
3778
+ open = false;
3779
+ if (child) {
3780
+ child.stdout.removeAllListeners("data");
3781
+ killProcessTree(child);
3782
+ child = null;
3783
+ }
3784
+ };
3785
+ function finish() {
3786
+ if (settled) return;
3787
+ settled = true;
3788
+ cleanup();
3789
+ resolve7();
3790
+ }
3791
+ const fail = (err) => {
3792
+ if (settled) return;
3793
+ settled = true;
3794
+ cleanup();
3795
+ reject(err instanceof Error ? err : new Error(String(err)));
3796
+ };
3797
+ const timer = opts.durationMs !== void 0 ? setTimeout(finish, opts.durationMs) : void 0;
3798
+ if (opts.abortSignal?.aborted) {
3799
+ finish();
3800
+ return;
3801
+ }
3802
+ opts.abortSignal?.addEventListener("abort", finish, { once: true });
3803
+ const spawnStream = () => {
3804
+ if (settled) return;
3805
+ const stream = streamHilog({ config: opts.config, domain: opts.domain, deviceSerial: opts.deviceSerial });
3806
+ child = stream;
3807
+ stream.stdout.on("data", (d) => {
3808
+ buf += d.toString();
3809
+ const parts = buf.split(/\r?\n/);
3810
+ buf = parts.pop() ?? "";
3811
+ for (const line of parts) write(line);
3812
+ });
3813
+ const onEnd = () => {
3814
+ if (settled || child !== stream) return;
3815
+ child = null;
3816
+ if (Date.now() >= deadline) return;
3817
+ reconnects++;
3818
+ logger.warn(`[hilog] stream ended; reconnecting (${reconnects}).`);
3819
+ reconnectTimer = setTimeout(() => {
3820
+ try {
3821
+ spawnStream();
3822
+ } catch (err) {
3823
+ fail(err);
3824
+ }
3825
+ }, 2e3);
3826
+ };
3827
+ stream.once("close", onEnd);
3828
+ stream.once("error", onEnd);
3829
+ };
3830
+ spawnStream();
3831
+ });
3832
+ } finally {
3833
+ fs22.closeSync(fd);
3834
+ }
3835
+ return { file: opts.file, lines, reconnects };
3836
+ }
3036
3837
  async function dumpLog(opts) {
3037
3838
  let pipe = "hilog -x";
3038
3839
  if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
@@ -3057,17 +3858,17 @@ async function dumpLog(opts) {
3057
3858
  }
3058
3859
 
3059
3860
  // src/build/runHvigorw.ts
3060
- var fs22 = __toESM(require("fs"), 1);
3061
- var path22 = __toESM(require("path"), 1);
3861
+ var fs24 = __toESM(require("fs"), 1);
3862
+ var path24 = __toESM(require("path"), 1);
3062
3863
 
3063
3864
  // src/harmonyos/target.ts
3064
- var fs21 = __toESM(require("fs"), 1);
3065
- var path21 = __toESM(require("path"), 1);
3865
+ var fs23 = __toESM(require("fs"), 1);
3866
+ var path23 = __toESM(require("path"), 1);
3066
3867
  var import_json54 = __toESM(require("json5"), 1);
3067
3868
  function detectRuntimeOs(projectDir, productName) {
3068
3869
  let products = [];
3069
3870
  try {
3070
- const parsed = import_json54.default.parse(fs21.readFileSync(path21.join(projectDir, "build-profile.json5"), "utf-8"));
3871
+ const parsed = import_json54.default.parse(fs23.readFileSync(path23.join(projectDir, "build-profile.json5"), "utf-8"));
3071
3872
  if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
3072
3873
  } catch {
3073
3874
  return "OpenHarmony";
@@ -3108,7 +3909,7 @@ function runHvigorw(opts) {
3108
3909
  const { config } = opts;
3109
3910
  const projectDir = toLongPath(opts.projectDir);
3110
3911
  const logger = opts.logger ?? noopLogger;
3111
- if (!fs22.existsSync(path22.join(projectDir, "build-profile.json5"))) {
3912
+ if (!fs24.existsSync(path24.join(projectDir, "build-profile.json5"))) {
3112
3913
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
3113
3914
  }
3114
3915
  let hvigorw;
@@ -3120,24 +3921,24 @@ function runHvigorw(opts) {
3120
3921
  hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
3121
3922
  logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
3122
3923
  } else {
3123
- if (!fs22.existsSync(getCmdToolsPath(config))) {
3924
+ if (!fs24.existsSync(getCmdToolsPath(config))) {
3124
3925
  throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
3125
3926
  }
3126
3927
  sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
3127
3928
  hvigorw = getHvigorwPath(config, projectDir);
3128
3929
  }
3129
- if (!fs22.existsSync(hvigorw)) {
3930
+ if (!fs24.existsSync(hvigorw)) {
3130
3931
  throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
3131
3932
  }
3132
3933
  const args = buildHvigorwArgs(opts);
3133
3934
  if (process.platform !== "win32") {
3134
3935
  try {
3135
- fs22.chmodSync(hvigorw, 493);
3936
+ fs24.chmodSync(hvigorw, 493);
3136
3937
  } catch {
3137
3938
  }
3138
3939
  }
3139
3940
  const env = { ...process.env, ...sdkEnv };
3140
- return new Promise((resolve6, reject) => {
3941
+ return new Promise((resolve7, reject) => {
3141
3942
  if (opts.abortSignal?.aborted) {
3142
3943
  reject(new CancelledError("hvigorw build cancelled."));
3143
3944
  return;
@@ -3181,46 +3982,15 @@ function runHvigorw(opts) {
3181
3982
  settled = true;
3182
3983
  cleanup();
3183
3984
  const exitCode = code ?? 1;
3184
- if (exitCode === 0) resolve6({ exitCode });
3985
+ if (exitCode === 0) resolve7({ exitCode });
3185
3986
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
3186
3987
  });
3187
3988
  });
3188
3989
  }
3189
3990
 
3190
- // src/build/discoverHaps.ts
3191
- var fs23 = __toESM(require("fs"), 1);
3192
- var path23 = __toESM(require("path"), 1);
3193
- function moduleOfHap(hapFileName) {
3194
- const dash = hapFileName.indexOf("-");
3195
- return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
3196
- }
3197
- async function discoverHaps(opts) {
3198
- const { projectDir } = opts;
3199
- const maxDepth = opts.maxDepth ?? 8;
3200
- const result = {};
3201
- const walk = async (dir, depth) => {
3202
- if (depth > maxDepth) return;
3203
- const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
3204
- for (const e of entries) {
3205
- const full = path23.join(dir, e.name);
3206
- if (e.isDirectory()) {
3207
- if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
3208
- await walk(full, depth + 1);
3209
- } else if (e.isFile() && e.name.endsWith(".hap")) {
3210
- const moduleName = moduleOfHap(e.name);
3211
- const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
3212
- if (e.name.includes("unsigned")) bucket.unsigned.push(full);
3213
- else if (e.name.includes("signed")) bucket.signed.push(full);
3214
- }
3215
- }
3216
- };
3217
- await walk(projectDir, 0);
3218
- return result;
3219
- }
3220
-
3221
3991
  // src/build/ohpm.ts
3222
- var fs24 = __toESM(require("fs"), 1);
3223
- var path24 = __toESM(require("path"), 1);
3992
+ var fs25 = __toESM(require("fs"), 1);
3993
+ var path25 = __toESM(require("path"), 1);
3224
3994
  function runOhpm(opts) {
3225
3995
  return runProcess({
3226
3996
  command: opts.ohpmPath ?? getOhpmPath(opts.config),
@@ -3233,7 +4003,7 @@ function runOhpm(opts) {
3233
4003
  });
3234
4004
  }
3235
4005
  async function ensureOhModules(opts) {
3236
- if (fs24.existsSync(path24.join(opts.projectDir, "oh_modules"))) {
4006
+ if (fs25.existsSync(path25.join(opts.projectDir, "oh_modules"))) {
3237
4007
  return { installed: false };
3238
4008
  }
3239
4009
  const res = await runOhpm({
@@ -3250,23 +4020,91 @@ async function ensureOhModules(opts) {
3250
4020
  }
3251
4021
 
3252
4022
  // src/build/codelinter.ts
3253
- var FINDING_RE = /^(.*?):(\d+):(?:\d+:)?\s*(error|warning|warn|info)\b[:\s-]*(.*)$/i;
4023
+ var FINDING_RE = /^(.*?):(\d+):(?:(\d+):)?\s*(error|warning|warn|suggestion|info)\b[:\s-]*(.*)$/i;
3254
4024
  var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
3255
- function parseCodelinterFindings(output) {
4025
+ var FILE_HEADER_RE = /^(.+\.\w+)\((\d+)\)$/;
4026
+ var ROW_RE = /^(\d+):(\d+)\s+(error|warning|warn|suggestion|info)\s+(.*)$/i;
4027
+ var RULE_RE = /^@?[\w.-]+(?:\/[\w.-]+)+$/;
4028
+ var SUMMARY_RE = /Defects:\s*(\d+);\s*Errors:\s*(\d+);\s*Warns:\s*(\d+);\s*Suggestions:\s*(\d+)/i;
4029
+ var NOTICE_RE = /does not match that of the project|error occurred during linting|incomplete report/i;
4030
+ var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
4031
+ function cleanCodelinterOutput(output) {
4032
+ return output.replace(ANSI_RE, "").split("\n").map((line) => (line.split("\r").filter((s) => s.trim() !== "").pop() ?? "").trimEnd()).join("\n");
4033
+ }
4034
+ function normaliseSeverity(s) {
4035
+ const lower = s.toLowerCase();
4036
+ return lower === "warn" ? "warning" : lower;
4037
+ }
4038
+ function parseCodelinterReport(output) {
3256
4039
  const findings = [];
3257
- for (const raw of output.split("\n")) {
3258
- const m = FINDING_RE.exec(raw.trim());
3259
- if (!m) continue;
3260
- let message = m[4].trim();
3261
- let code = "";
3262
- const codeMatch = TRAILING_CODE_RE.exec(message);
3263
- if (codeMatch) {
3264
- code = codeMatch[1];
3265
- message = message.slice(0, codeMatch.index).trim();
4040
+ const notices = [];
4041
+ let summary = null;
4042
+ let file = "";
4043
+ for (const rawLine of cleanCodelinterOutput(output).split("\n")) {
4044
+ const line = rawLine.trim();
4045
+ if (!line) continue;
4046
+ const row = ROW_RE.exec(line);
4047
+ if (row) {
4048
+ if (!file) continue;
4049
+ const columns = row[4].trim().split(/\s{2,}/);
4050
+ const last = columns[columns.length - 1];
4051
+ const code = columns.length > 1 && RULE_RE.test(last) ? last : "";
4052
+ const message = (code ? columns.slice(0, -1) : columns).join(" ").trim();
4053
+ findings.push({
4054
+ file,
4055
+ line: Number(row[1]),
4056
+ column: Number(row[2]),
4057
+ severity: normaliseSeverity(row[3]),
4058
+ code,
4059
+ message
4060
+ });
4061
+ continue;
4062
+ }
4063
+ const header = FILE_HEADER_RE.exec(line);
4064
+ if (header) {
4065
+ file = header[1];
4066
+ continue;
3266
4067
  }
3267
- findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
4068
+ const legacy = FINDING_RE.exec(line);
4069
+ if (legacy) {
4070
+ let message = legacy[5].trim();
4071
+ let code = "";
4072
+ const codeMatch = TRAILING_CODE_RE.exec(message);
4073
+ if (codeMatch) {
4074
+ code = codeMatch[1];
4075
+ message = message.slice(0, codeMatch.index).trim();
4076
+ }
4077
+ const finding = {
4078
+ file: legacy[1].trim(),
4079
+ line: Number(legacy[2]),
4080
+ severity: normaliseSeverity(legacy[4]),
4081
+ code,
4082
+ message
4083
+ };
4084
+ if (legacy[3]) finding.column = Number(legacy[3]);
4085
+ findings.push(finding);
4086
+ continue;
4087
+ }
4088
+ const totals = SUMMARY_RE.exec(line);
4089
+ if (totals) {
4090
+ summary = {
4091
+ defects: Number(totals[1]),
4092
+ errors: Number(totals[2]),
4093
+ warnings: Number(totals[3]),
4094
+ suggestions: Number(totals[4])
4095
+ };
4096
+ continue;
4097
+ }
4098
+ if (/no defects found/i.test(line)) {
4099
+ summary = { defects: 0, errors: 0, warnings: 0, suggestions: 0 };
4100
+ continue;
4101
+ }
4102
+ if (NOTICE_RE.test(line)) notices.push(line);
3268
4103
  }
3269
- return findings;
4104
+ return { findings, summary, notices, incomplete: notices.length > 0 };
4105
+ }
4106
+ function parseCodelinterFindings(output) {
4107
+ return parseCodelinterReport(output).findings;
3270
4108
  }
3271
4109
  async function runCodelinter(opts) {
3272
4110
  const res = await runProcess({
@@ -3278,16 +4116,28 @@ async function runCodelinter(opts) {
3278
4116
  logger: opts.logger
3279
4117
  });
3280
4118
  const raw = `${res.stdout}${res.stderr}`;
3281
- return { code: res.code, findings: parseCodelinterFindings(raw), raw };
4119
+ return { code: res.code, ...parseCodelinterReport(raw), raw: cleanCodelinterOutput(raw) };
3282
4120
  }
3283
4121
 
3284
4122
  // src/build/buildHap.ts
4123
+ var DIAGNOSIS_OUTPUT_LIMIT = 2e6;
4124
+ function diagnoseBuildFailure(output) {
4125
+ if (/is not exported from Kit|has no exported member .*@systemapi/i.test(output)) {
4126
+ 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`).";
4127
+ }
4128
+ if (/00308018|SDK path does not exist|SDK license agreement is not accepted/i.test(output)) {
4129
+ return "hvigor cannot find the SDK the project compiles against: check `oniro-app sdk list` against compileSdkVersion.";
4130
+ }
4131
+ return void 0;
4132
+ }
3285
4133
  async function buildHap(opts) {
3286
4134
  const logger = scopedLogger(opts.logger ?? noopLogger, "build");
3287
4135
  const warnings = [];
3288
4136
  const start = Date.now();
3289
4137
  const projectDir = toLongPath(opts.projectDir);
3290
4138
  const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
4139
+ const missingSdk = isHarmonyOs ? null : findMissingProjectSdk(opts.config, projectDir);
4140
+ if (missingSdk) throw missingSdk;
3291
4141
  if (opts.autoInstallDeps !== false) {
3292
4142
  const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
3293
4143
  const r = await ensureOhModules({
@@ -3300,23 +4150,35 @@ async function buildHap(opts) {
3300
4150
  });
3301
4151
  if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
3302
4152
  }
3303
- const { exitCode } = await runHvigorw({
3304
- config: opts.config,
3305
- projectDir,
3306
- product: opts.product,
3307
- module: opts.module,
3308
- buildMode: opts.mode,
3309
- task: opts.task,
3310
- parallel: opts.parallel,
3311
- abortSignal: opts.abortSignal,
3312
- onOutput: opts.onOutput,
3313
- logger: opts.logger
3314
- });
4153
+ let output = "";
4154
+ let exitCode;
4155
+ try {
4156
+ ({ exitCode } = await runHvigorw({
4157
+ config: opts.config,
4158
+ projectDir,
4159
+ product: opts.product,
4160
+ module: opts.module,
4161
+ buildMode: opts.mode,
4162
+ task: opts.task,
4163
+ parallel: opts.parallel,
4164
+ abortSignal: opts.abortSignal,
4165
+ onOutput: (chunk, stream) => {
4166
+ output = (output + chunk).slice(-DIAGNOSIS_OUTPUT_LIMIT);
4167
+ opts.onOutput?.(chunk, stream);
4168
+ },
4169
+ logger: opts.logger
4170
+ }));
4171
+ } catch (err) {
4172
+ const hint = diagnoseBuildFailure(output);
4173
+ if (hint && err instanceof OniroError) throw new OniroError(`${err.message}
4174
+ ${hint}`, err);
4175
+ throw err;
4176
+ }
3315
4177
  const discoveredHaps = await discoverHaps({ projectDir });
3316
4178
  const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
3317
4179
  if (!anySigned) {
3318
4180
  warnings.push(
3319
- 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)."
4181
+ 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."
3320
4182
  );
3321
4183
  }
3322
4184
  return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
@@ -3333,21 +4195,21 @@ function isValidBundleName(bundleName) {
3333
4195
  }
3334
4196
 
3335
4197
  // src/project/templates.ts
3336
- var fs25 = __toESM(require("fs"), 1);
3337
- var path25 = __toESM(require("path"), 1);
4198
+ var fs26 = __toESM(require("fs"), 1);
4199
+ var path26 = __toESM(require("path"), 1);
3338
4200
  function toHumanTemplateName(folderName) {
3339
4201
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
3340
4202
  }
3341
4203
  function listTemplates(templateRoot) {
3342
- if (!fs25.existsSync(templateRoot)) return [];
3343
- const entries = fs25.readdirSync(templateRoot, { withFileTypes: true });
4204
+ if (!fs26.existsSync(templateRoot)) return [];
4205
+ const entries = fs26.readdirSync(templateRoot, { withFileTypes: true });
3344
4206
  return entries.filter((e) => e.isDirectory()).map((e) => {
3345
- const dir = path25.join(templateRoot, e.name);
3346
- const metaPath = path25.join(dir, "template.json");
4207
+ const dir = path26.join(templateRoot, e.name);
4208
+ const metaPath = path26.join(dir, "template.json");
3347
4209
  let meta = {};
3348
- if (fs25.existsSync(metaPath)) {
4210
+ if (fs26.existsSync(metaPath)) {
3349
4211
  try {
3350
- meta = JSON.parse(fs25.readFileSync(metaPath, "utf8"));
4212
+ meta = JSON.parse(fs26.readFileSync(metaPath, "utf8"));
3351
4213
  } catch {
3352
4214
  }
3353
4215
  }
@@ -3362,57 +4224,57 @@ function listTemplates(templateRoot) {
3362
4224
  function validateTemplateLayout(templateDir, defaultModuleName) {
3363
4225
  const required = [
3364
4226
  "build-profile.json5",
3365
- path25.join("AppScope", "app.json5"),
3366
- path25.join(defaultModuleName, "src", "main", "module.json5"),
3367
- path25.join(defaultModuleName, "oh-package.json5"),
4227
+ path26.join("AppScope", "app.json5"),
4228
+ path26.join(defaultModuleName, "src", "main", "module.json5"),
4229
+ path26.join(defaultModuleName, "oh-package.json5"),
3368
4230
  "hvigorfile.ts"
3369
4231
  ];
3370
- return required.filter((rel) => !fs25.existsSync(path25.join(templateDir, rel)));
4232
+ return required.filter((rel) => !fs26.existsSync(path26.join(templateDir, rel)));
3371
4233
  }
3372
4234
 
3373
4235
  // src/project/createScaffold.ts
3374
- var fs27 = __toESM(require("fs"), 1);
3375
- var path26 = __toESM(require("path"), 1);
4236
+ var fs28 = __toESM(require("fs"), 1);
4237
+ var path27 = __toESM(require("path"), 1);
3376
4238
 
3377
4239
  // src/project/jsonHelpers.ts
3378
- var fs26 = __toESM(require("fs"), 1);
4240
+ var fs27 = __toESM(require("fs"), 1);
3379
4241
  var import_json55 = __toESM(require("json5"), 1);
3380
4242
  function readJson5File(filePath) {
3381
- return import_json55.default.parse(fs26.readFileSync(filePath, "utf8"));
4243
+ return import_json55.default.parse(fs27.readFileSync(filePath, "utf8"));
3382
4244
  }
3383
4245
  function writeJson5File(filePath, value) {
3384
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4246
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3385
4247
  }
3386
4248
  function readJsonFile(filePath) {
3387
- return JSON.parse(fs26.readFileSync(filePath, "utf8"));
4249
+ return JSON.parse(fs27.readFileSync(filePath, "utf8"));
3388
4250
  }
3389
4251
  function writeJsonFile(filePath, value) {
3390
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4252
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3391
4253
  }
3392
4254
 
3393
4255
  // src/project/createScaffold.ts
3394
4256
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
3395
4257
  async function pathExists(p) {
3396
4258
  try {
3397
- await fs27.promises.access(p);
4259
+ await fs28.promises.access(p);
3398
4260
  return true;
3399
4261
  } catch {
3400
4262
  return false;
3401
4263
  }
3402
4264
  }
3403
4265
  async function copyDirRecursive(srcDir, destDir) {
3404
- await fs27.promises.mkdir(destDir, { recursive: true });
3405
- const entries = await fs27.promises.readdir(srcDir, { withFileTypes: true });
4266
+ await fs28.promises.mkdir(destDir, { recursive: true });
4267
+ const entries = await fs28.promises.readdir(srcDir, { withFileTypes: true });
3406
4268
  for (const entry of entries) {
3407
- const src = path26.join(srcDir, entry.name);
3408
- const dest = path26.join(destDir, entry.name);
4269
+ const src = path27.join(srcDir, entry.name);
4270
+ const dest = path27.join(destDir, entry.name);
3409
4271
  if (entry.isDirectory()) {
3410
4272
  await copyDirRecursive(src, dest);
3411
4273
  } else if (entry.isSymbolicLink()) {
3412
4274
  continue;
3413
4275
  } else if (entry.isFile()) {
3414
4276
  if (entry.name === "template.json") continue;
3415
- await fs27.promises.copyFile(src, dest);
4277
+ await fs28.promises.copyFile(src, dest);
3416
4278
  }
3417
4279
  }
3418
4280
  }
@@ -3420,9 +4282,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
3420
4282
  const stack = [projectDir];
3421
4283
  while (stack.length > 0) {
3422
4284
  const currentDir = stack.pop();
3423
- const entries = await fs27.promises.readdir(currentDir, { withFileTypes: true });
4285
+ const entries = await fs28.promises.readdir(currentDir, { withFileTypes: true });
3424
4286
  for (const entry of entries) {
3425
- const fullPath = path26.join(currentDir, entry.name);
4287
+ const fullPath = path27.join(currentDir, entry.name);
3426
4288
  if (entry.isDirectory()) {
3427
4289
  if (IGNORED_DIRS.has(entry.name)) continue;
3428
4290
  stack.push(fullPath);
@@ -3444,23 +4306,23 @@ function toJavaPropertiesPath(p) {
3444
4306
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
3445
4307
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
3446
4308
  `;
3447
- fs27.writeFileSync(path26.join(projectDir, "local.properties"), content, "utf8");
4309
+ fs28.writeFileSync(path27.join(projectDir, "local.properties"), content, "utf8");
3448
4310
  }
3449
4311
  function renameIfExists(fromPath, toPath) {
3450
- if (fs27.existsSync(fromPath) && fromPath !== toPath) {
3451
- fs27.renameSync(fromPath, toPath);
4312
+ if (fs28.existsSync(fromPath) && fromPath !== toPath) {
4313
+ fs28.renameSync(fromPath, toPath);
3452
4314
  }
3453
4315
  }
3454
4316
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3455
- const appJsonPath = path26.join(projectDir, "AppScope", "app.json5");
3456
- if (fs27.existsSync(appJsonPath)) {
4317
+ const appJsonPath = path27.join(projectDir, "AppScope", "app.json5");
4318
+ if (fs28.existsSync(appJsonPath)) {
3457
4319
  const appJson = readJson5File(appJsonPath);
3458
4320
  appJson.app = appJson.app ?? {};
3459
4321
  appJson.app.bundleName = args.bundleName;
3460
4322
  writeJson5File(appJsonPath, appJson);
3461
4323
  }
3462
- const stringsPath = path26.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3463
- if (fs27.existsSync(stringsPath)) {
4324
+ const stringsPath = path27.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
4325
+ if (fs28.existsSync(stringsPath)) {
3464
4326
  const strings = readJsonFile(stringsPath);
3465
4327
  if (Array.isArray(strings.string)) {
3466
4328
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -3468,8 +4330,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3468
4330
  }
3469
4331
  writeJsonFile(stringsPath, strings);
3470
4332
  }
3471
- const buildProfilePath = path26.join(projectDir, "build-profile.json5");
3472
- if (fs27.existsSync(buildProfilePath)) {
4333
+ const buildProfilePath = path27.join(projectDir, "build-profile.json5");
4334
+ if (fs28.existsSync(buildProfilePath)) {
3473
4335
  const buildProfile = readJson5File(buildProfilePath);
3474
4336
  buildProfile.app = buildProfile.app ?? {};
3475
4337
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -3482,23 +4344,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3482
4344
  }
3483
4345
  writeJson5File(buildProfilePath, buildProfile);
3484
4346
  }
3485
- const moduleJsonPath = path26.join(projectDir, args.moduleName, "src", "main", "module.json5");
3486
- if (fs27.existsSync(moduleJsonPath)) {
4347
+ const moduleJsonPath = path27.join(projectDir, args.moduleName, "src", "main", "module.json5");
4348
+ if (fs28.existsSync(moduleJsonPath)) {
3487
4349
  const moduleJson = readJson5File(moduleJsonPath);
3488
4350
  moduleJson.module = moduleJson.module ?? {};
3489
4351
  moduleJson.module.name = args.moduleName;
3490
4352
  writeJson5File(moduleJsonPath, moduleJson);
3491
4353
  }
3492
- const moduleOhPackagePath = path26.join(projectDir, args.moduleName, "oh-package.json5");
3493
- if (fs27.existsSync(moduleOhPackagePath)) {
4354
+ const moduleOhPackagePath = path27.join(projectDir, args.moduleName, "oh-package.json5");
4355
+ if (fs28.existsSync(moduleOhPackagePath)) {
3494
4356
  const pkg = readJson5File(moduleOhPackagePath);
3495
4357
  pkg.name = args.moduleName;
3496
4358
  writeJson5File(moduleOhPackagePath, pkg);
3497
4359
  }
3498
- const vscodeDir = path26.join(projectDir, ".vscode");
3499
- fs27.mkdirSync(vscodeDir, { recursive: true });
4360
+ const vscodeDir = path27.join(projectDir, ".vscode");
4361
+ fs28.mkdirSync(vscodeDir, { recursive: true });
3500
4362
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
3501
- writeJsonFile(path26.join(vscodeDir, "settings.json"), {
4363
+ writeJsonFile(path27.join(vscodeDir, "settings.json"), {
3502
4364
  "oniro.hapPath": hapPath,
3503
4365
  "files.associations": { "*.json5": "jsonc" }
3504
4366
  });
@@ -3515,7 +4377,7 @@ async function createScaffold(opts) {
3515
4377
  if (!opts.location || !await pathExists(opts.location)) {
3516
4378
  throw new OniroError(`Location does not exist: ${opts.location}`);
3517
4379
  }
3518
- const templateDir = path26.join(opts.templateRoot, opts.templateId);
4380
+ const templateDir = path27.join(opts.templateRoot, opts.templateId);
3519
4381
  if (!await pathExists(templateDir)) {
3520
4382
  throw new OniroError(`Template not found: ${templateDir}`);
3521
4383
  }
@@ -3550,17 +4412,17 @@ async function createScaffold(opts) {
3550
4412
  }
3551
4413
  }
3552
4414
  }
3553
- const projectDir = path26.join(opts.location, opts.projectName);
4415
+ const projectDir = path27.join(opts.location, opts.projectName);
3554
4416
  if (await pathExists(projectDir)) {
3555
4417
  if (!opts.overwrite) {
3556
4418
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
3557
4419
  }
3558
- await fs27.promises.rm(projectDir, { recursive: true, force: true });
4420
+ await fs28.promises.rm(projectDir, { recursive: true, force: true });
3559
4421
  }
3560
4422
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
3561
4423
  await copyDirRecursive(templateDir, projectDir);
3562
4424
  if (moduleName !== defaultModuleName) {
3563
- renameIfExists(path26.join(projectDir, defaultModuleName), path26.join(projectDir, moduleName));
4425
+ renameIfExists(path27.join(projectDir, defaultModuleName), path27.join(projectDir, moduleName));
3564
4426
  }
3565
4427
  updateTemplateConfigs(
3566
4428
  projectDir,
@@ -3577,11 +4439,11 @@ async function createScaffold(opts) {
3577
4439
  }
3578
4440
 
3579
4441
  // src/project/listModules.ts
3580
- var fs28 = __toESM(require("fs"), 1);
3581
- var path27 = __toESM(require("path"), 1);
4442
+ var fs29 = __toESM(require("fs"), 1);
4443
+ var path28 = __toESM(require("path"), 1);
3582
4444
  function listModules(opts) {
3583
- const buildProfile = path27.join(opts.projectDir, "build-profile.json5");
3584
- if (!fs28.existsSync(buildProfile)) {
4445
+ const buildProfile = path28.join(opts.projectDir, "build-profile.json5");
4446
+ if (!fs29.existsSync(buildProfile)) {
3585
4447
  throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
3586
4448
  }
3587
4449
  const parsed = readJson5File(buildProfile);
@@ -3593,12 +4455,12 @@ function listModules(opts) {
3593
4455
  }
3594
4456
 
3595
4457
  // src/project/abilities.ts
3596
- var fs29 = __toESM(require("fs"), 1);
3597
- var path28 = __toESM(require("path"), 1);
4458
+ var fs30 = __toESM(require("fs"), 1);
4459
+ var path29 = __toESM(require("path"), 1);
3598
4460
  function listAbilities(opts) {
3599
4461
  const moduleName = opts.moduleName ?? "entry";
3600
- const moduleJsonPath = path28.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3601
- if (!fs29.existsSync(moduleJsonPath)) {
4462
+ const moduleJsonPath = path29.join(opts.projectDir, moduleName, "src", "main", "module.json5");
4463
+ if (!fs30.existsSync(moduleJsonPath)) {
3602
4464
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
3603
4465
  }
3604
4466
  const parsed = readJson5File(moduleJsonPath);
@@ -3606,12 +4468,12 @@ function listAbilities(opts) {
3606
4468
  }
3607
4469
 
3608
4470
  // src/project/prepareSigning.ts
3609
- var fs30 = __toESM(require("fs"), 1);
3610
- var path29 = __toESM(require("path"), 1);
4471
+ var fs31 = __toESM(require("fs"), 1);
4472
+ var path30 = __toESM(require("path"), 1);
3611
4473
  function hasSigningArtifacts(dir) {
3612
- if (!fs30.existsSync(path29.join(dir, "signatures"))) return false;
3613
- const buildProfile = path29.join(dir, "build-profile.json5");
3614
- if (!fs30.existsSync(buildProfile)) return false;
4474
+ if (!fs31.existsSync(path30.join(dir, "signatures"))) return false;
4475
+ const buildProfile = path30.join(dir, "build-profile.json5");
4476
+ if (!fs31.existsSync(buildProfile)) return false;
3615
4477
  try {
3616
4478
  const parsed = readJson5File(buildProfile);
3617
4479
  return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
@@ -3629,6 +4491,7 @@ function prepareSigning(opts) {
3629
4491
  apl: opts.apl,
3630
4492
  appFeature: opts.appFeature,
3631
4493
  acls: opts.acls,
4494
+ appPrivilegeCapabilities: opts.appPrivilegeCapabilities,
3632
4495
  passwords: opts.passwords,
3633
4496
  logger: opts.logger
3634
4497
  });
@@ -3657,19 +4520,31 @@ var PROPS = [
3657
4520
  ["model", "const.product.model"],
3658
4521
  ["manufacturer", "const.product.manufacturer"],
3659
4522
  ["brand", "const.product.brand"],
4523
+ ["osDistName", "const.product.os.dist.name"],
4524
+ ["osDistVersion", "const.product.os.dist.version"],
3660
4525
  ["osFullName", "const.ohos.fullname"],
3661
4526
  ["osReleaseType", "const.ohos.releasetype"],
3662
4527
  ["sdkApiVersion", "const.ohos.apiversion"],
3663
- ["buildVersion", "const.product.software.version"]
4528
+ ["buildVersion", "const.product.software.version"],
4529
+ ["abi", "const.product.cpu.abilist"],
4530
+ ["deviceType", "const.product.devicetype"]
3664
4531
  ];
3665
4532
  async function getDeviceInfo(opts) {
3666
4533
  const info = { serial: opts.deviceSerial ?? "(default)", display: null };
3667
- for (const [field, prop] of PROPS) {
3668
- try {
3669
- const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
4534
+ try {
4535
+ const values = await paramGetMany(
4536
+ opts.config,
4537
+ PROPS.map(([, prop]) => prop),
4538
+ { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger }
4539
+ );
4540
+ for (const [field, prop] of PROPS) {
4541
+ const value = values[prop];
3670
4542
  if (value) info[field] = value;
3671
- } catch {
3672
4543
  }
4544
+ } catch {
4545
+ }
4546
+ if (info.osDistName || info.osFullName) {
4547
+ info.osFamily = /harmonyos/i.test(info.osDistName ?? "") ? "HarmonyOS" : "OpenHarmony";
3673
4548
  }
3674
4549
  const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
3675
4550
  if (screen) info.display = { width: screen.width, height: screen.height };
@@ -3701,8 +4576,8 @@ async function diffHapAssets(opts) {
3701
4576
  }
3702
4577
 
3703
4578
  // src/install/apply.ts
3704
- var fs31 = __toESM(require("fs"), 1);
3705
- var path30 = __toESM(require("path"), 1);
4579
+ var fs32 = __toESM(require("fs"), 1);
4580
+ var path31 = __toESM(require("path"), 1);
3706
4581
  function decideInstallMethod(s) {
3707
4582
  if (!s.signInfoInconsistent) return "replace";
3708
4583
  if (s.isSystemBundle && !s.allowUninstall) return "refuse";
@@ -3720,39 +4595,31 @@ async function pidOf(config, bundle, deviceSerial) {
3720
4595
  const proc = await findRunningProcess({ config, bundle, deviceSerial });
3721
4596
  return proc ? Number(proc.pid) : null;
3722
4597
  }
3723
- async function resolveHapPath(opts) {
3724
- let hapPath = opts.hapPath;
3725
- if (hapPath) {
3726
- if (!path30.isAbsolute(hapPath) && opts.projectDir) hapPath = path30.join(opts.projectDir, hapPath);
4598
+ async function resolveHap(opts, logger) {
4599
+ let hap;
4600
+ if (opts.hapPath) {
4601
+ const hapPath = !path31.isAbsolute(opts.hapPath) && opts.projectDir ? path31.join(opts.projectDir, opts.hapPath) : opts.hapPath;
4602
+ hap = existingHapOrUnsigned(hapPath);
4603
+ if (!hap) throw new OniroError(`HAP not found at ${hapPath}.`);
3727
4604
  } else {
3728
4605
  if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
3729
- const haps = await discoverHaps({ projectDir: opts.projectDir });
3730
- if (opts.module) {
3731
- const signed = haps[opts.module]?.signed ?? [];
3732
- if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
3733
- hapPath = signed[0];
3734
- } else {
3735
- const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
3736
- if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
3737
- if (withSigned.length > 1) {
3738
- throw new OniroError(
3739
- `Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
3740
- );
3741
- }
3742
- hapPath = withSigned[0][1].signed[0];
3743
- }
4606
+ const picked = pickHap(await discoverHaps({ projectDir: opts.projectDir }), opts.module);
4607
+ if (!picked) throw new OniroError(`No HAP found${opts.module ? ` for module ${opts.module}` : ""}. Build first.`);
4608
+ hap = { path: picked.path, fallback: !picked.signed };
3744
4609
  }
3745
- if (!fs31.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3746
- return hapPath;
4610
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path31.basename(hap.path)}.`);
4611
+ return hap;
3747
4612
  }
3748
4613
  async function applyChanges(opts) {
3749
4614
  const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
3750
4615
  const { config, bundle, deviceSerial } = opts;
3751
4616
  const isSystemBundle = opts.isSystemBundle ?? false;
3752
4617
  const allowUninstall = opts.allowUninstall ?? false;
3753
- const hapPath = await resolveHapPath(opts);
4618
+ const hap = await resolveHap(opts, logger);
4619
+ const hapPath = hap.path;
4620
+ logger.info(`installing ${hapPath}`);
3754
4621
  let assetsChanged = false;
3755
- if (opts.installedHapPath && fs31.existsSync(opts.installedHapPath)) {
4622
+ if (opts.installedHapPath && fs32.existsSync(opts.installedHapPath)) {
3756
4623
  try {
3757
4624
  const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
3758
4625
  assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
@@ -3773,12 +4640,21 @@ async function applyChanges(opts) {
3773
4640
  if (decision === "uninstall-install") {
3774
4641
  logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
3775
4642
  await uninstallApp({ config, bundle, deviceSerial });
3776
- ensureOk(await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }), `hdc install ${hapPath}`);
4643
+ ensureInstallOk(
4644
+ await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }),
4645
+ `hdc install ${hapPath}`,
4646
+ hap
4647
+ );
3777
4648
  installMethod = "uninstall-install";
3778
4649
  } else {
3779
- ensureOk(replaceRes, `hdc install -r ${hapPath}`);
4650
+ ensureInstallOk(replaceRes, `hdc install -r ${hapPath}`, hap);
3780
4651
  installMethod = "replace";
3781
4652
  }
4653
+ if (!await isBundleInstalled({ config, bundle, deviceSerial })) {
4654
+ throw new OniroError(
4655
+ `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).`
4656
+ );
4657
+ }
3782
4658
  let postInstallPid = await pidOf(config, bundle, deviceSerial);
3783
4659
  const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
3784
4660
  let method = installMethod;
@@ -3853,9 +4729,9 @@ var AUTH_PATHS = {
3853
4729
  };
3854
4730
 
3855
4731
  // src/harmonyos/auth/tokenStore.ts
3856
- var fs32 = __toESM(require("fs"), 1);
4732
+ var fs33 = __toESM(require("fs"), 1);
3857
4733
  var os11 = __toESM(require("os"), 1);
3858
- var path31 = __toESM(require("path"), 1);
4734
+ var path32 = __toESM(require("path"), 1);
3859
4735
  var crypto3 = __toESM(require("crypto"), 1);
3860
4736
  var ALGORITHM = "aes-256-gcm";
3861
4737
  var KEY_LENGTH = 32;
@@ -3891,7 +4767,7 @@ function permissionHint(dir) {
3891
4767
  }
3892
4768
  function ensureDir(dir) {
3893
4769
  try {
3894
- fs32.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4770
+ fs33.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
3895
4771
  } catch (err) {
3896
4772
  if (err.code === "EACCES") {
3897
4773
  throw new OniroError(permissionHint(dir), err);
@@ -3902,17 +4778,17 @@ function ensureDir(dir) {
3902
4778
  function createTokenStore(opts) {
3903
4779
  const logger = opts.logger ?? noopLogger;
3904
4780
  const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
3905
- const tokenPath = path31.join(authDir, TOKEN_FILE);
3906
- const keyPath = path31.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4781
+ const tokenPath = path32.join(authDir, TOKEN_FILE);
4782
+ const keyPath = path32.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
3907
4783
  function loadOrCreateKey() {
3908
- if (fs32.existsSync(keyPath)) {
3909
- const existing = fs32.readFileSync(keyPath);
4784
+ if (fs33.existsSync(keyPath)) {
4785
+ const existing = fs33.readFileSync(keyPath);
3910
4786
  if (existing.length === KEY_LENGTH) return existing;
3911
4787
  logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
3912
4788
  }
3913
- ensureDir(path31.dirname(keyPath));
4789
+ ensureDir(path32.dirname(keyPath));
3914
4790
  const key = crypto3.randomBytes(KEY_LENGTH);
3915
- fs32.writeFileSync(keyPath, key, { mode: FILE_MODE });
4791
+ fs33.writeFileSync(keyPath, key, { mode: FILE_MODE });
3916
4792
  return key;
3917
4793
  }
3918
4794
  return {
@@ -3922,12 +4798,12 @@ function createTokenStore(opts) {
3922
4798
  const payload = Buffer.from(JSON.stringify(session), "utf8");
3923
4799
  const blob = encrypt2(loadOrCreateKey(), payload);
3924
4800
  ensureDir(authDir);
3925
- fs32.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4801
+ fs33.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
3926
4802
  },
3927
4803
  load() {
3928
- if (!fs32.existsSync(tokenPath)) return null;
4804
+ if (!fs33.existsSync(tokenPath)) return null;
3929
4805
  try {
3930
- const blob = JSON.parse(fs32.readFileSync(tokenPath, "utf8"));
4806
+ const blob = JSON.parse(fs33.readFileSync(tokenPath, "utf8"));
3931
4807
  if (!isEncryptedBlob(blob)) return null;
3932
4808
  const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
3933
4809
  return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
@@ -3939,7 +4815,7 @@ function createTokenStore(opts) {
3939
4815
  }
3940
4816
  logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
3941
4817
  try {
3942
- fs32.rmSync(tokenPath, { force: true });
4818
+ fs33.rmSync(tokenPath, { force: true });
3943
4819
  } catch {
3944
4820
  }
3945
4821
  return null;
@@ -3947,7 +4823,7 @@ function createTokenStore(opts) {
3947
4823
  },
3948
4824
  clear() {
3949
4825
  try {
3950
- fs32.rmSync(tokenPath, { force: true });
4826
+ fs33.rmSync(tokenPath, { force: true });
3951
4827
  } catch (err) {
3952
4828
  throw new OniroError(`Failed to remove ${tokenPath}.`, err);
3953
4829
  }
@@ -4061,8 +4937,8 @@ function isQuit(quit) {
4061
4937
  async function startCallbackServer(opts) {
4062
4938
  let resolveCallback;
4063
4939
  let rejectCallback;
4064
- const callback = new Promise((resolve6, reject) => {
4065
- resolveCallback = resolve6;
4940
+ const callback = new Promise((resolve7, reject) => {
4941
+ resolveCallback = resolve7;
4066
4942
  rejectCallback = reject;
4067
4943
  });
4068
4944
  callback.catch(() => {
@@ -4121,9 +4997,9 @@ async function startCallbackServer(opts) {
4121
4997
  }
4122
4998
  });
4123
4999
  server.keepAliveTimeout = 1;
4124
- await new Promise((resolve6, reject) => {
5000
+ await new Promise((resolve7, reject) => {
4125
5001
  server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
4126
- server.listen(0, "127.0.0.1", resolve6);
5002
+ server.listen(0, "127.0.0.1", resolve7);
4127
5003
  });
4128
5004
  const port = server.address().port;
4129
5005
  return {
@@ -4139,12 +5015,12 @@ async function startCallbackServer(opts) {
4139
5015
  return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
4140
5016
  },
4141
5017
  stop() {
4142
- return new Promise((resolve6) => {
5018
+ return new Promise((resolve7) => {
4143
5019
  server.closeAllConnections?.();
4144
- const timer = setTimeout(resolve6, 100);
5020
+ const timer = setTimeout(resolve7, 100);
4145
5021
  server.close(() => {
4146
5022
  clearTimeout(timer);
4147
- resolve6();
5023
+ resolve7();
4148
5024
  });
4149
5025
  });
4150
5026
  }
@@ -4176,11 +5052,11 @@ function openBrowser(url) {
4176
5052
  windowsHide: true
4177
5053
  });
4178
5054
  child.unref();
4179
- return new Promise((resolve6, reject) => {
5055
+ return new Promise((resolve7, reject) => {
4180
5056
  child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
4181
5057
  child.on(
4182
5058
  "exit",
4183
- (code) => code === 0 ? resolve6() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
5059
+ (code) => code === 0 ? resolve7() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
4184
5060
  );
4185
5061
  });
4186
5062
  }
@@ -4428,7 +5304,7 @@ function mapAgcError(operation, statusCode, reasonPhrase, body) {
4428
5304
  }
4429
5305
 
4430
5306
  // src/harmonyos/signing/profile.ts
4431
- var fs33 = __toESM(require("fs"), 1);
5307
+ var fs34 = __toESM(require("fs"), 1);
4432
5308
  var import_node_crypto3 = require("crypto");
4433
5309
  var import_node_forge = __toESM(require("node-forge"), 1);
4434
5310
  function extractProfileJson(buffer) {
@@ -4460,7 +5336,7 @@ function extractProfileJson(buffer) {
4460
5336
  function readProvisionProfile(profilePath) {
4461
5337
  let raw;
4462
5338
  try {
4463
- const json = extractProfileJson(fs33.readFileSync(profilePath));
5339
+ const json = extractProfileJson(fs34.readFileSync(profilePath));
4464
5340
  if (!json) return null;
4465
5341
  raw = JSON.parse(json);
4466
5342
  } catch {
@@ -4489,7 +5365,7 @@ function parseCertificate(pem) {
4489
5365
  }
4490
5366
  function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4491
5367
  try {
4492
- const der = import_node_forge.default.util.createBuffer(fs33.readFileSync(p12Path).toString("binary"));
5368
+ const der = import_node_forge.default.util.createBuffer(fs34.readFileSync(p12Path).toString("binary"));
4493
5369
  const p12 = import_node_forge.default.pkcs12.pkcs12FromAsn1(import_node_forge.default.asn1.fromDer(der), keyPwd);
4494
5370
  const certBag = import_node_forge.default.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
4495
5371
  const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
@@ -4505,7 +5381,7 @@ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4505
5381
  }
4506
5382
  }
4507
5383
  function verifySigningMaterial(opts) {
4508
- const pems = fs33.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
5384
+ const pems = fs34.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
4509
5385
  const chain = (pems ?? []).map(parseCertificate);
4510
5386
  const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
4511
5387
  if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
@@ -4524,14 +5400,14 @@ function verifySigningMaterial(opts) {
4524
5400
 
4525
5401
  // src/harmonyos/signing/autoSign.ts
4526
5402
  var crypto9 = __toESM(require("crypto"), 1);
4527
- var fs38 = __toESM(require("fs"), 1);
4528
- var path37 = __toESM(require("path"), 1);
5403
+ var fs39 = __toESM(require("fs"), 1);
5404
+ var path38 = __toESM(require("path"), 1);
4529
5405
  var import_json57 = __toESM(require("json5"), 1);
4530
5406
 
4531
5407
  // src/harmonyos/signing/agc.ts
4532
5408
  var crypto6 = __toESM(require("crypto"), 1);
4533
- var fs34 = __toESM(require("fs"), 1);
4534
- var path32 = __toESM(require("path"), 1);
5409
+ var fs35 = __toESM(require("fs"), 1);
5410
+ var path33 = __toESM(require("path"), 1);
4535
5411
  var ENDPOINTS = {
4536
5412
  CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
4537
5413
  CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
@@ -4617,8 +5493,8 @@ function createAgcClient(http3, auth) {
4617
5493
  if (target.sha256 && actual !== target.sha256.toLowerCase()) {
4618
5494
  throw new ChecksumMismatchError(target.sha256, actual);
4619
5495
  }
4620
- fs34.mkdirSync(path32.dirname(dest), { recursive: true, mode: 448 });
4621
- fs34.writeFileSync(dest, buffer, { mode: 384 });
5496
+ fs35.mkdirSync(path33.dirname(dest), { recursive: true, mode: 448 });
5497
+ fs35.writeFileSync(dest, buffer, { mode: 384 });
4622
5498
  }
4623
5499
  };
4624
5500
  }
@@ -4669,22 +5545,22 @@ async function registerDevices(agc, connected, logger) {
4669
5545
 
4670
5546
  // src/harmonyos/signing/keystore.ts
4671
5547
  var crypto7 = __toESM(require("crypto"), 1);
4672
- var fs36 = __toESM(require("fs"), 1);
4673
- var path34 = __toESM(require("path"), 1);
5548
+ var fs37 = __toESM(require("fs"), 1);
5549
+ var path35 = __toESM(require("path"), 1);
4674
5550
 
4675
5551
  // src/harmonyos/java.ts
4676
- var fs35 = __toESM(require("fs"), 1);
4677
- var path33 = __toESM(require("path"), 1);
5552
+ var fs36 = __toESM(require("fs"), 1);
5553
+ var path34 = __toESM(require("path"), 1);
4678
5554
  var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
4679
5555
  function findJava(sdk, env = process.env) {
4680
5556
  const candidates = [
4681
- env.JAVA_HOME && path33.join(env.JAVA_HOME, "bin", JAVA_BIN),
4682
- sdk && path33.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
5557
+ env.JAVA_HOME && path34.join(env.JAVA_HOME, "bin", JAVA_BIN),
5558
+ sdk && path34.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
4683
5559
  // macOS: the JBR is itself a bundle inside Studio's Contents.
4684
- sdk && path33.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
4685
- ...(env.PATH ?? "").split(path33.delimiter).map((dir) => dir && path33.join(dir, JAVA_BIN))
5560
+ sdk && path34.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
5561
+ ...(env.PATH ?? "").split(path34.delimiter).map((dir) => dir && path34.join(dir, JAVA_BIN))
4686
5562
  ];
4687
- const found = candidates.find((c) => !!c && fs35.existsSync(c));
5563
+ const found = candidates.find((c) => !!c && fs36.existsSync(c));
4688
5564
  if (found) return found;
4689
5565
  throw new OniroError(
4690
5566
  "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."
@@ -4698,13 +5574,13 @@ function redactArgs(args) {
4698
5574
  return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
4699
5575
  }
4700
5576
  async function generateKeystoreAndCsr(opts) {
4701
- const jar = path34.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
4702
- if (!fs36.existsSync(jar)) {
5577
+ const jar = path35.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
5578
+ if (!fs37.existsSync(jar)) {
4703
5579
  throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
4704
5580
  }
4705
5581
  const java = findJava(opts.sdk);
4706
5582
  const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
4707
- fs36.mkdirSync(path34.dirname(opts.p12Path), { recursive: true, mode: 448 });
5583
+ fs37.mkdirSync(path35.dirname(opts.p12Path), { recursive: true, mode: 448 });
4708
5584
  const run = async (what, args) => {
4709
5585
  const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
4710
5586
  opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
@@ -4718,28 +5594,28 @@ ${detail}` : ""}`);
4718
5594
  opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
4719
5595
  await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
4720
5596
  await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
4721
- return { keyPwd, csr: fs36.readFileSync(opts.csrPath, "utf8") };
5597
+ return { keyPwd, csr: fs37.readFileSync(opts.csrPath, "utf8") };
4722
5598
  }
4723
5599
 
4724
5600
  // src/harmonyos/signing/paths.ts
4725
5601
  var crypto8 = __toESM(require("crypto"), 1);
4726
- var path35 = __toESM(require("path"), 1);
5602
+ var path36 = __toESM(require("path"), 1);
4727
5603
  function sanitizeNamePart(raw) {
4728
5604
  const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
4729
5605
  return safe.slice(0, 64) || "default";
4730
5606
  }
4731
5607
  function resolveSigningMaterialPaths(config, productName, projectRoot) {
4732
5608
  const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
4733
- const base = path35.join(
5609
+ const base = path36.join(
4734
5610
  config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
4735
- `${sanitizeNamePart(productName)}_${path35.basename(projectRoot)}_${hash}=`
5611
+ `${sanitizeNamePart(productName)}_${path36.basename(projectRoot)}_${hash}=`
4736
5612
  );
4737
5613
  return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
4738
5614
  }
4739
5615
 
4740
5616
  // src/harmonyos/signing/project.ts
4741
- var fs37 = __toESM(require("fs"), 1);
4742
- var path36 = __toESM(require("path"), 1);
5617
+ var fs38 = __toESM(require("fs"), 1);
5618
+ var path37 = __toESM(require("path"), 1);
4743
5619
  var import_json56 = __toESM(require("json5"), 1);
4744
5620
 
4745
5621
  // src/harmonyos/signing/aclPermissionList.ts
@@ -4807,9 +5683,9 @@ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
4807
5683
 
4808
5684
  // src/harmonyos/signing/project.ts
4809
5685
  function readRequestedPermissions(moduleConfigPath) {
4810
- if (!fs37.existsSync(moduleConfigPath)) return [];
5686
+ if (!fs38.existsSync(moduleConfigPath)) return [];
4811
5687
  try {
4812
- const parsed = import_json56.default.parse(fs37.readFileSync(moduleConfigPath, "utf-8"));
5688
+ const parsed = import_json56.default.parse(fs38.readFileSync(moduleConfigPath, "utf-8"));
4813
5689
  return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
4814
5690
  } catch {
4815
5691
  return [];
@@ -4818,10 +5694,10 @@ function readRequestedPermissions(moduleConfigPath) {
4818
5694
  function collectAclPermissions(projectDir, logger = noopLogger) {
4819
5695
  const requested = /* @__PURE__ */ new Set();
4820
5696
  for (const module2 of listModules({ projectDir })) {
4821
- const moduleRoot = path36.resolve(projectDir, module2.srcPath);
5697
+ const moduleRoot = path37.resolve(projectDir, module2.srcPath);
4822
5698
  for (const sourceSet of ["main", "ohosTest"]) {
4823
5699
  for (const name of readRequestedPermissions(
4824
- path36.join(moduleRoot, "src", sourceSet, "module.json5")
5700
+ path37.join(moduleRoot, "src", sourceSet, "module.json5")
4825
5701
  )) {
4826
5702
  requested.add(name);
4827
5703
  }
@@ -4837,7 +5713,7 @@ function collectAclPermissions(projectDir, logger = noopLogger) {
4837
5713
  // src/harmonyos/signing/autoSign.ts
4838
5714
  function shouldRegenerate(input) {
4839
5715
  if (input.force) return "--force was passed";
4840
- const missing = Object.values(input.paths).find((file) => !fs38.existsSync(file));
5716
+ const missing = Object.values(input.paths).find((file) => !fs39.existsSync(file));
4841
5717
  if (missing) return `${missing} is missing`;
4842
5718
  const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
4843
5719
  if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
@@ -4876,16 +5752,16 @@ function writeSigningConfig(opts) {
4876
5752
  for (const product of app.products ?? []) {
4877
5753
  if (product.name === opts.productName) product.signingConfig = opts.productName;
4878
5754
  }
4879
- fs38.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5755
+ fs39.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
4880
5756
  }
4881
5757
  async function harmonyOsAutoSign(opts) {
4882
5758
  const logger = opts.logger ?? noopLogger;
4883
5759
  const productName = opts.productName ?? "default";
4884
- const projectDir = path37.resolve(opts.projectDir);
4885
- const buildProfilePath = path37.join(projectDir, "build-profile.json5");
5760
+ const projectDir = path38.resolve(opts.projectDir);
5761
+ const buildProfilePath = path38.join(projectDir, "build-profile.json5");
4886
5762
  let buildProfile;
4887
5763
  try {
4888
- buildProfile = import_json57.default.parse(fs38.readFileSync(buildProfilePath, "utf-8"));
5764
+ buildProfile = import_json57.default.parse(fs39.readFileSync(buildProfilePath, "utf-8"));
4889
5765
  } catch (err) {
4890
5766
  throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
4891
5767
  }
@@ -4925,7 +5801,7 @@ async function harmonyOsAutoSign(opts) {
4925
5801
  logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
4926
5802
  const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
4927
5803
  const deviceIds = await registerDevices(agc, connected, logger);
4928
- for (const file of Object.values(paths)) fs38.rmSync(file, { force: true });
5804
+ for (const file of Object.values(paths)) fs39.rmSync(file, { force: true });
4929
5805
  const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
4930
5806
  const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
4931
5807
  if (stale) {
@@ -4956,10 +5832,10 @@ async function harmonyOsAutoSign(opts) {
4956
5832
  try {
4957
5833
  verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
4958
5834
  } catch (err) {
4959
- fs38.rmSync(paths.profilePath, { force: true });
5835
+ fs39.rmSync(paths.profilePath, { force: true });
4960
5836
  throw err;
4961
5837
  }
4962
- const materialDir = path37.join(path37.dirname(paths.p12Path), "material");
5838
+ const materialDir = path38.join(path38.dirname(paths.p12Path), "material");
4963
5839
  try {
4964
5840
  getKey(materialDir);
4965
5841
  } catch {
@@ -4971,7 +5847,7 @@ async function harmonyOsAutoSign(opts) {
4971
5847
  }
4972
5848
 
4973
5849
  // src/index.ts
4974
- var VERSION = "0.12.0";
5850
+ var VERSION = "0.14.0";
4975
5851
  // Annotate the CommonJS export names for ESM import in node:
4976
5852
  0 && (module.exports = {
4977
5853
  ALL_SDKS,
@@ -4991,9 +5867,13 @@ var VERSION = "0.12.0";
4991
5867
  SIGNING_ERRORS,
4992
5868
  SdkNotInstalledError,
4993
5869
  TMP_DIR_HINT,
5870
+ TargetNotFoundError,
4994
5871
  UnsupportedPlatformError,
4995
5872
  VERSION,
5873
+ appSandboxDir,
4996
5874
  applyChanges,
5875
+ assertDevicePath,
5876
+ assertLayoutSelector,
4997
5877
  attemptHdcConnection,
4998
5878
  buildCmdWrapper,
4999
5879
  buildGestureCommands,
@@ -5003,6 +5883,8 @@ var VERSION = "0.12.0";
5003
5883
  buildInputCommand,
5004
5884
  buildRawTouchCommand,
5005
5885
  captureBurst,
5886
+ captureLog,
5887
+ cleanCodelinterOutput,
5006
5888
  consoleLogger,
5007
5889
  createHarmonyOsSession,
5008
5890
  createInstallTempDir,
@@ -5016,10 +5898,14 @@ var VERSION = "0.12.0";
5016
5898
  dedupEntries,
5017
5899
  defaultContext,
5018
5900
  defaultPaths,
5901
+ describeLayoutNode,
5019
5902
  detectAaStartFailure,
5903
+ detectCmdToolsFlavour,
5904
+ detectInstallFailure,
5020
5905
  detectProjectSdkVersion,
5021
5906
  detectRuntimeOs,
5022
5907
  detectSigningConfigNames,
5908
+ diagnoseBuildFailure,
5023
5909
  diffEntryNames,
5024
5910
  diffHapAssets,
5025
5911
  discoverHaps,
@@ -5032,17 +5918,24 @@ var VERSION = "0.12.0";
5032
5918
  dumpWindow,
5033
5919
  encryptPwd,
5034
5920
  ensureFreeSpace,
5921
+ ensureInstallOk,
5035
5922
  ensureOhModules,
5036
5923
  ensureOk,
5037
5924
  ensureRemoteOk,
5038
5925
  escapeCmdArgument,
5039
5926
  escapeCmdCommand,
5927
+ existingHapOrUnsigned,
5040
5928
  extractTarball,
5041
5929
  extractZipWithProgress,
5042
5930
  findAppProcessId,
5043
5931
  findCmdToolsSourceDir,
5932
+ findCoveredNodes,
5044
5933
  findHarmonyOsSdk,
5934
+ findKeyboardWindow,
5935
+ findLayoutNodes,
5936
+ findMissingProjectSdk,
5045
5937
  findRunningProcess,
5938
+ flattenLayout,
5046
5939
  forceStop,
5047
5940
  formatBytes,
5048
5941
  generateSigningConfigs,
@@ -5054,9 +5947,11 @@ var VERSION = "0.12.0";
5054
5947
  getDeviceInfo,
5055
5948
  getDisplaySize,
5056
5949
  getEmulatorDir,
5950
+ getEmulatorStatus,
5057
5951
  getFreeSpaceBytes,
5058
5952
  getHdcPath,
5059
5953
  getHvigorwPath,
5954
+ getImeBundles,
5060
5955
  getInstalledSdks,
5061
5956
  getKey,
5062
5957
  getMainAbility,
@@ -5068,14 +5963,21 @@ var VERSION = "0.12.0";
5068
5963
  getSupportedSdksForUi,
5069
5964
  harmonyOsAutoSign,
5070
5965
  harmonyOsSdkLabelForApi,
5966
+ hdcClientFailure,
5071
5967
  hdcExec,
5968
+ hdcTargetArgs,
5969
+ hdcTargetArgsFor,
5970
+ hideKeyboard,
5072
5971
  installApp,
5073
5972
  installCmdTools,
5074
5973
  installEmulator,
5974
+ installFailureReason,
5975
+ isBundleInstalled,
5075
5976
  isCmdToolsInstalled,
5076
5977
  isEmulatorInstalled,
5077
5978
  isOutOfSpaceError,
5078
5979
  isRamBackedPath,
5980
+ isUnsignedHap,
5079
5981
  isValidBundleName,
5080
5982
  isValidProjectName,
5081
5983
  killProcessTree,
@@ -5085,19 +5987,28 @@ var VERSION = "0.12.0";
5085
5987
  listModules,
5086
5988
  listRunningProcesses,
5087
5989
  listTemplates,
5990
+ locateTarget,
5088
5991
  needsCmdWrapper,
5089
5992
  nonInteractivePrompter,
5090
5993
  noopLogger,
5091
5994
  noopProgress,
5995
+ normaliseLocalPath,
5092
5996
  paramGet,
5997
+ paramGetMany,
5093
5998
  paramSet,
5094
5999
  parseBounds,
5095
6000
  parseCodelinterFindings,
6001
+ parseCodelinterReport,
5096
6002
  parseDeviceList,
5097
6003
  parseHilogLine,
6004
+ parseImeBundles,
5098
6005
  parseMainAbilityFromBmDump,
6006
+ parseParamValues,
5099
6007
  parseRemoteExit,
6008
+ performInput,
6009
+ pickHap,
5100
6010
  pickSigningKind,
6011
+ pickTarget,
5101
6012
  prepareSigning,
5102
6013
  pruneLayout,
5103
6014
  readJson5File,
@@ -5110,6 +6021,8 @@ var VERSION = "0.12.0";
5110
6021
  removeSdk,
5111
6022
  removeTempWorkDir,
5112
6023
  requireHarmonyOsSdk,
6024
+ resolveDeviceSerial,
6025
+ resolveKeyEvent,
5113
6026
  resolveTmpRoot,
5114
6027
  runCodelinter,
5115
6028
  runHvigorw,
@@ -5133,14 +6046,17 @@ var VERSION = "0.12.0";
5133
6046
  toLongPath,
5134
6047
  toSpaceError,
5135
6048
  uninstallApp,
6049
+ unsignedHapRejection,
5136
6050
  validateTemplateLayout,
5137
6051
  verifySha256,
5138
6052
  waitForBoot,
5139
6053
  waitForBundle,
5140
6054
  waitForCondition,
5141
6055
  waitForLog,
6056
+ waitForTarget,
5142
6057
  watchLog,
5143
6058
  waypointsToRawEvents,
6059
+ withoutWindow,
5144
6060
  writeJson5File,
5145
6061
  writeJsonFile
5146
6062
  });