@oniroproject/core 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -50,7 +50,9 @@ __export(index_exports, {
50
50
  TargetNotFoundError: () => TargetNotFoundError,
51
51
  UnsupportedPlatformError: () => UnsupportedPlatformError,
52
52
  VERSION: () => VERSION,
53
+ appSandboxDir: () => appSandboxDir,
53
54
  applyChanges: () => applyChanges,
55
+ assertDevicePath: () => assertDevicePath,
54
56
  assertLayoutSelector: () => assertLayoutSelector,
55
57
  attemptHdcConnection: () => attemptHdcConnection,
56
58
  buildCmdWrapper: () => buildCmdWrapper,
@@ -61,6 +63,8 @@ __export(index_exports, {
61
63
  buildInputCommand: () => buildInputCommand,
62
64
  buildRawTouchCommand: () => buildRawTouchCommand,
63
65
  captureBurst: () => captureBurst,
66
+ captureLog: () => captureLog,
67
+ cleanCodelinterOutput: () => cleanCodelinterOutput,
64
68
  consoleLogger: () => consoleLogger,
65
69
  createHarmonyOsSession: () => createHarmonyOsSession,
66
70
  createInstallTempDir: () => createInstallTempDir,
@@ -76,9 +80,12 @@ __export(index_exports, {
76
80
  defaultPaths: () => defaultPaths,
77
81
  describeLayoutNode: () => describeLayoutNode,
78
82
  detectAaStartFailure: () => detectAaStartFailure,
83
+ detectCmdToolsFlavour: () => detectCmdToolsFlavour,
84
+ detectInstallFailure: () => detectInstallFailure,
79
85
  detectProjectSdkVersion: () => detectProjectSdkVersion,
80
86
  detectRuntimeOs: () => detectRuntimeOs,
81
87
  detectSigningConfigNames: () => detectSigningConfigNames,
88
+ diagnoseBuildFailure: () => diagnoseBuildFailure,
82
89
  diffEntryNames: () => diffEntryNames,
83
90
  diffHapAssets: () => diffHapAssets,
84
91
  discoverHaps: () => discoverHaps,
@@ -91,11 +98,13 @@ __export(index_exports, {
91
98
  dumpWindow: () => dumpWindow,
92
99
  encryptPwd: () => encryptPwd,
93
100
  ensureFreeSpace: () => ensureFreeSpace,
101
+ ensureInstallOk: () => ensureInstallOk,
94
102
  ensureOhModules: () => ensureOhModules,
95
103
  ensureOk: () => ensureOk,
96
104
  ensureRemoteOk: () => ensureRemoteOk,
97
105
  escapeCmdArgument: () => escapeCmdArgument,
98
106
  escapeCmdCommand: () => escapeCmdCommand,
107
+ existingHapOrUnsigned: () => existingHapOrUnsigned,
99
108
  extractTarball: () => extractTarball,
100
109
  extractZipWithProgress: () => extractZipWithProgress,
101
110
  findAppProcessId: () => findAppProcessId,
@@ -104,6 +113,7 @@ __export(index_exports, {
104
113
  findHarmonyOsSdk: () => findHarmonyOsSdk,
105
114
  findKeyboardWindow: () => findKeyboardWindow,
106
115
  findLayoutNodes: () => findLayoutNodes,
116
+ findMissingProjectSdk: () => findMissingProjectSdk,
107
117
  findRunningProcess: () => findRunningProcess,
108
118
  flattenLayout: () => flattenLayout,
109
119
  forceStop: () => forceStop,
@@ -117,6 +127,7 @@ __export(index_exports, {
117
127
  getDeviceInfo: () => getDeviceInfo,
118
128
  getDisplaySize: () => getDisplaySize,
119
129
  getEmulatorDir: () => getEmulatorDir,
130
+ getEmulatorStatus: () => getEmulatorStatus,
120
131
  getFreeSpaceBytes: () => getFreeSpaceBytes,
121
132
  getHdcPath: () => getHdcPath,
122
133
  getHvigorwPath: () => getHvigorwPath,
@@ -132,15 +143,21 @@ __export(index_exports, {
132
143
  getSupportedSdksForUi: () => getSupportedSdksForUi,
133
144
  harmonyOsAutoSign: () => harmonyOsAutoSign,
134
145
  harmonyOsSdkLabelForApi: () => harmonyOsSdkLabelForApi,
146
+ hdcClientFailure: () => hdcClientFailure,
135
147
  hdcExec: () => hdcExec,
148
+ hdcTargetArgs: () => hdcTargetArgs,
149
+ hdcTargetArgsFor: () => hdcTargetArgsFor,
136
150
  hideKeyboard: () => hideKeyboard,
137
151
  installApp: () => installApp,
138
152
  installCmdTools: () => installCmdTools,
139
153
  installEmulator: () => installEmulator,
154
+ installFailureReason: () => installFailureReason,
155
+ isBundleInstalled: () => isBundleInstalled,
140
156
  isCmdToolsInstalled: () => isCmdToolsInstalled,
141
157
  isEmulatorInstalled: () => isEmulatorInstalled,
142
158
  isOutOfSpaceError: () => isOutOfSpaceError,
143
159
  isRamBackedPath: () => isRamBackedPath,
160
+ isUnsignedHap: () => isUnsignedHap,
144
161
  isValidBundleName: () => isValidBundleName,
145
162
  isValidProjectName: () => isValidProjectName,
146
163
  killProcessTree: () => killProcessTree,
@@ -155,16 +172,21 @@ __export(index_exports, {
155
172
  nonInteractivePrompter: () => nonInteractivePrompter,
156
173
  noopLogger: () => noopLogger,
157
174
  noopProgress: () => noopProgress,
175
+ normaliseLocalPath: () => normaliseLocalPath,
158
176
  paramGet: () => paramGet,
177
+ paramGetMany: () => paramGetMany,
159
178
  paramSet: () => paramSet,
160
179
  parseBounds: () => parseBounds,
161
180
  parseCodelinterFindings: () => parseCodelinterFindings,
181
+ parseCodelinterReport: () => parseCodelinterReport,
162
182
  parseDeviceList: () => parseDeviceList,
163
183
  parseHilogLine: () => parseHilogLine,
164
184
  parseImeBundles: () => parseImeBundles,
165
185
  parseMainAbilityFromBmDump: () => parseMainAbilityFromBmDump,
186
+ parseParamValues: () => parseParamValues,
166
187
  parseRemoteExit: () => parseRemoteExit,
167
188
  performInput: () => performInput,
189
+ pickHap: () => pickHap,
168
190
  pickSigningKind: () => pickSigningKind,
169
191
  pickTarget: () => pickTarget,
170
192
  prepareSigning: () => prepareSigning,
@@ -179,6 +201,7 @@ __export(index_exports, {
179
201
  removeSdk: () => removeSdk,
180
202
  removeTempWorkDir: () => removeTempWorkDir,
181
203
  requireHarmonyOsSdk: () => requireHarmonyOsSdk,
204
+ resolveDeviceSerial: () => resolveDeviceSerial,
182
205
  resolveKeyEvent: () => resolveKeyEvent,
183
206
  resolveTmpRoot: () => resolveTmpRoot,
184
207
  runCodelinter: () => runCodelinter,
@@ -203,6 +226,7 @@ __export(index_exports, {
203
226
  toLongPath: () => toLongPath,
204
227
  toSpaceError: () => toSpaceError,
205
228
  uninstallApp: () => uninstallApp,
229
+ unsignedHapRejection: () => unsignedHapRejection,
206
230
  validateTemplateLayout: () => validateTemplateLayout,
207
231
  verifySha256: () => verifySha256,
208
232
  waitForBoot: () => waitForBoot,
@@ -309,14 +333,19 @@ var OniroError = class extends Error {
309
333
  cause;
310
334
  };
311
335
  var SdkNotInstalledError = class extends OniroError {
312
- constructor(api, expectedPath) {
313
- 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
+ );
314
341
  this.api = api;
315
342
  this.expectedPath = expectedPath;
343
+ this.version = version;
316
344
  this.name = "SdkNotInstalledError";
317
345
  }
318
346
  api;
319
347
  expectedPath;
348
+ version;
320
349
  };
321
350
  var CmdToolsNotInstalledError = class extends OniroError {
322
351
  constructor(expectedPath) {
@@ -592,6 +621,24 @@ function isProjectLocalHvigorwWorking(projectDir) {
592
621
  var fs3 = __toESM(require("fs"), 1);
593
622
  var path4 = __toESM(require("path"), 1);
594
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
+ }
595
642
  function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
596
643
  const buildProfilePath = path4.join(projectRoot, "build-profile.json5");
597
644
  if (!fs3.existsSync(buildProfilePath)) {
@@ -777,14 +824,14 @@ var pipelineAsync = (0, import_node_util.promisify)(import_node_stream.pipeline)
777
824
  async function downloadFile(opts) {
778
825
  const { url, dest, progress, abortSignal } = opts;
779
826
  const proto = url.startsWith("https") ? https : http;
780
- return new Promise((resolve6, reject) => {
827
+ return new Promise((resolve7, reject) => {
781
828
  const s = Math.max(0, Math.min(100, opts.start ?? 0));
782
829
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
783
830
  let settled = false;
784
831
  const done = (err) => {
785
832
  if (settled) return;
786
833
  settled = true;
787
- err ? reject(err) : resolve6();
834
+ err ? reject(err) : resolve7();
788
835
  };
789
836
  if (abortSignal?.aborted) {
790
837
  done(new CancelledError("Download cancelled before start."));
@@ -1271,21 +1318,27 @@ async function installCmdTools(opts) {
1271
1318
  removeTempWorkDir(tmpDir, tmpRoot);
1272
1319
  }
1273
1320
  }
1321
+ function detectCmdToolsFlavour(cmdToolsPath) {
1322
+ return fs11.existsSync(path12.join(cmdToolsPath, "sdk", "default", "hms")) ? "HarmonyOS" : "OpenHarmony";
1323
+ }
1274
1324
  function getCmdToolsStatus(config) {
1275
1325
  const cmdPath = getCmdToolsPath(config);
1326
+ const hdcPath = getHdcPath(config);
1327
+ const base = { path: cmdPath, hdc: { path: hdcPath, exists: fs11.existsSync(hdcPath) } };
1276
1328
  const binDir = path12.join(cmdPath, "bin");
1277
1329
  const candidates = os7.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
1278
1330
  const bin = candidates.map((c) => path12.join(binDir, c)).find((p) => fs11.existsSync(p));
1279
1331
  if (!bin) {
1280
- return { installed: false, status: "Not installed" };
1332
+ return { installed: false, status: "Not installed", ...base };
1281
1333
  }
1334
+ const installed = { installed: true, ...base, flavour: detectCmdToolsFlavour(cmdPath) };
1282
1335
  try {
1283
1336
  const versionFile = path12.join(cmdPath, "version.txt");
1284
1337
  if (fs11.existsSync(versionFile)) {
1285
1338
  const content = fs11.readFileSync(versionFile, "utf8");
1286
1339
  for (const line of content.split(/\r?\n/)) {
1287
1340
  const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
1288
- if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
1341
+ if (m?.[1]) return { ...installed, status: `Installed (${m[1].trim()})` };
1289
1342
  }
1290
1343
  }
1291
1344
  } catch {
@@ -1299,9 +1352,9 @@ function getCmdToolsStatus(config) {
1299
1352
  });
1300
1353
  if (res.error || res.status !== 0) throw res.error ?? new Error(`exit ${String(res.status)}`);
1301
1354
  const version = res.stdout.trim();
1302
- return { installed: true, status: `Installed (${version})` };
1355
+ return { ...installed, status: `Installed (${version})` };
1303
1356
  } catch {
1304
- return { installed: true, status: "Installed (version unknown)" };
1357
+ return { ...installed, status: "Installed (version unknown)" };
1305
1358
  }
1306
1359
  }
1307
1360
  function isCmdToolsInstalled(config) {
@@ -1591,6 +1644,9 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
1591
1644
  profileTemplate.acls = profileTemplate.acls ?? {};
1592
1645
  profileTemplate.acls["allowed-acls"] = options.acls;
1593
1646
  }
1647
+ if (options.appPrivilegeCapabilities !== void 0) {
1648
+ profileTemplate["app-privilege-capabilities"] = options.appPrivilegeCapabilities;
1649
+ }
1594
1650
  fs13.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1595
1651
  }
1596
1652
  function generateP7bFile(projectDir, paths, logger, passwords) {
@@ -1680,6 +1736,12 @@ function updateBuildProfile(projectDir, kind, logger, passwords) {
1680
1736
  storeFile: "./signatures/OpenHarmony.p12"
1681
1737
  }
1682
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
+ }
1683
1745
  fs13.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1684
1746
  }
1685
1747
  function prepareMaterialDirectory(projectDir, logger) {
@@ -1711,7 +1773,7 @@ function generateSigningConfigs(options) {
1711
1773
  }
1712
1774
  const sdkPath = path14.join(sdkHome, String(sdkVersion));
1713
1775
  if (!fs13.existsSync(sdkPath)) {
1714
- 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);
1715
1777
  }
1716
1778
  const paths = {
1717
1779
  signTool: path14.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
@@ -1723,7 +1785,11 @@ function generateSigningConfigs(options) {
1723
1785
  const passwords = options.passwords ?? DEFAULT_PASSWORDS;
1724
1786
  logger.info("[sign] Starting signing configuration generation...");
1725
1787
  copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
1726
- 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
+ });
1727
1793
  generateP7bFile(projectDir, paths, logger, passwords);
1728
1794
  prepareMaterialDirectory(projectDir, logger);
1729
1795
  updateBuildProfile(projectDir, kind, logger, passwords);
@@ -1783,6 +1849,7 @@ var import_node_child_process4 = require("child_process");
1783
1849
 
1784
1850
  // src/hdc/exec.ts
1785
1851
  var fs15 = __toESM(require("fs"), 1);
1852
+ var import_node_string_decoder = require("string_decoder");
1786
1853
  function errorMessage(err) {
1787
1854
  return err instanceof Error ? err.message : String(err);
1788
1855
  }
@@ -1790,7 +1857,7 @@ function runProcess(opts) {
1790
1857
  const { command, args, cwd, env, onOutput } = opts;
1791
1858
  const timeoutMs = opts.timeoutMs ?? 6e4;
1792
1859
  const { abortSignal } = opts;
1793
- return new Promise((resolve6, reject) => {
1860
+ return new Promise((resolve7, reject) => {
1794
1861
  if (abortSignal?.aborted) {
1795
1862
  reject(new CancelledError(`Command aborted before start: ${command}`));
1796
1863
  return;
@@ -1810,6 +1877,8 @@ function runProcess(opts) {
1810
1877
  let stderr = "";
1811
1878
  let settled = false;
1812
1879
  let timedOut = false;
1880
+ const stdoutDecoder = new import_node_string_decoder.StringDecoder("utf8");
1881
+ const stderrDecoder = new import_node_string_decoder.StringDecoder("utf8");
1813
1882
  const cleanup = () => {
1814
1883
  clearTimeout(timer);
1815
1884
  abortSignal?.removeEventListener("abort", onAbort);
@@ -1827,14 +1896,14 @@ function runProcess(opts) {
1827
1896
  }, timeoutMs);
1828
1897
  abortSignal?.addEventListener("abort", onAbort, { once: true });
1829
1898
  child.stdout?.on("data", (d) => {
1830
- const s = d.toString();
1899
+ const s = stdoutDecoder.write(d);
1831
1900
  stdout += s;
1832
- onOutput?.(s, "stdout");
1901
+ if (s) onOutput?.(s, "stdout");
1833
1902
  });
1834
1903
  child.stderr?.on("data", (d) => {
1835
- const s = d.toString();
1904
+ const s = stderrDecoder.write(d);
1836
1905
  stderr += s;
1837
- onOutput?.(s, "stderr");
1906
+ if (s) onOutput?.(s, "stderr");
1838
1907
  });
1839
1908
  child.on("error", (err) => {
1840
1909
  if (settled) return;
@@ -1846,6 +1915,12 @@ function runProcess(opts) {
1846
1915
  if (settled) return;
1847
1916
  settled = true;
1848
1917
  cleanup();
1918
+ const stdoutTail = stdoutDecoder.end();
1919
+ const stderrTail = stderrDecoder.end();
1920
+ stdout += stdoutTail;
1921
+ stderr += stderrTail;
1922
+ if (stdoutTail) onOutput?.(stdoutTail, "stdout");
1923
+ if (stderrTail) onOutput?.(stderrTail, "stderr");
1849
1924
  if (timedOut) {
1850
1925
  reject(
1851
1926
  new OniroError(
@@ -1855,21 +1930,41 @@ ${stderr.trim()}` : "")
1855
1930
  );
1856
1931
  return;
1857
1932
  }
1858
- resolve6({ code: code ?? -1, stdout, stderr });
1933
+ resolve7({ code: code ?? -1, stdout, stderr });
1859
1934
  });
1860
1935
  });
1861
1936
  }
1862
- function hdcExec(opts) {
1937
+ function resolveDeviceSerial(explicit, env = process.env) {
1938
+ if (explicit && explicit !== "auto") return explicit;
1939
+ const fromEnv = env.ONIRO_DEVICE_SERIAL || env.DEVICE_SERIAL;
1940
+ return fromEnv && fromEnv !== "auto" ? fromEnv : void 0;
1941
+ }
1942
+ function hdcTargetArgs(explicit) {
1943
+ const serial = resolveDeviceSerial(explicit);
1944
+ return serial ? ["-t", serial] : [];
1945
+ }
1946
+ var HOST_COMMANDS = /* @__PURE__ */ new Set(["list", "tconn", "start", "kill", "version", "checkserver"]);
1947
+ function hdcTargetArgsFor(args, explicit) {
1948
+ if (HOST_COMMANDS.has(args[0] ?? "")) return explicit ? ["-t", explicit] : [];
1949
+ return hdcTargetArgs(explicit);
1950
+ }
1951
+ function hdcClientFailure(output) {
1952
+ const first = output.split(/\r?\n/).find((l) => l.trim() !== "")?.trim() ?? "";
1953
+ return first.startsWith("[Fail]") ? first : null;
1954
+ }
1955
+ async function hdcExec(opts) {
1863
1956
  const hdc = getHdcPath(opts.config);
1864
- const argv = opts.deviceSerial ? ["-t", opts.deviceSerial, ...opts.args] : [...opts.args];
1865
- return runProcess({
1957
+ const res = await runProcess({
1866
1958
  command: hdc,
1867
- args: argv,
1959
+ args: [...hdcTargetArgsFor(opts.args, opts.deviceSerial), ...opts.args],
1868
1960
  timeoutMs: opts.timeoutMs,
1869
1961
  abortSignal: opts.abortSignal,
1870
1962
  onOutput: opts.onOutput,
1871
1963
  logger: opts.logger
1872
1964
  });
1965
+ if (res.code === 0 && hdcClientFailure(`${res.stdout}
1966
+ ${res.stderr}`)) return { ...res, code: 1 };
1967
+ return res;
1873
1968
  }
1874
1969
  function shell(opts) {
1875
1970
  const { command, ...rest } = opts;
@@ -1877,10 +1972,15 @@ function shell(opts) {
1877
1972
  }
1878
1973
  function ensureOk(result, command) {
1879
1974
  if (result.code !== 0) {
1880
- throw new CommandFailedError(command, result.code, result.stderr.trim());
1975
+ throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
1881
1976
  }
1882
1977
  return result;
1883
1978
  }
1979
+ function withTargetHint(detail) {
1980
+ if (!/need connect-key|Not match target/i.test(detail)) return detail;
1981
+ return `${detail}
1982
+ 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\`.`;
1983
+ }
1884
1984
  var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
1885
1985
  function parseRemoteExit(stdout) {
1886
1986
  let code = null;
@@ -1897,7 +1997,7 @@ async function shellChecked(opts) {
1897
1997
  }
1898
1998
  function ensureRemoteOk(result, command) {
1899
1999
  if (result.code !== 0) {
1900
- throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
2000
+ throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
1901
2001
  }
1902
2002
  return result;
1903
2003
  }
@@ -1983,11 +2083,11 @@ async function startEmulator(opts) {
1983
2083
  fs16.writeFileSync(PID_FILE, String(child.pid));
1984
2084
  child.unref();
1985
2085
  logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1986
- const earlyExit = new Promise((resolve6) => {
1987
- child.once("exit", (code) => resolve6(code));
1988
- child.once("error", () => resolve6(-1));
2086
+ const earlyExit = new Promise((resolve7) => {
2087
+ child.once("exit", (code) => resolve7(code));
2088
+ child.once("error", () => resolve7(-1));
1989
2089
  });
1990
- const liveness = new Promise((resolve6) => setTimeout(() => resolve6("alive"), 2e3));
2090
+ const liveness = new Promise((resolve7) => setTimeout(() => resolve7("alive"), 2e3));
1991
2091
  const outcome = await Promise.race([earlyExit, liveness]);
1992
2092
  if (outcome !== "alive") {
1993
2093
  const tail = readTail(logPath, 4e3);
@@ -2067,10 +2167,48 @@ async function stopEmulator(logger = noopLogger) {
2067
2167
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
2068
2168
  });
2069
2169
  }
2170
+ async function isQemuRunning(logger) {
2171
+ if (os8.platform() !== "win32") {
2172
+ try {
2173
+ const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
2174
+ if (Number.isFinite(pid) && pid > 0) {
2175
+ process.kill(pid, 0);
2176
+ return true;
2177
+ }
2178
+ } catch {
2179
+ }
2180
+ }
2181
+ const [cmd, ...args] = os8.platform() === "win32" ? ["tasklist", "/FI", "IMAGENAME eq qemu-system-x86_64.exe", "/NH"] : ["pgrep", "-f", "qemu-system-x86_64"];
2182
+ try {
2183
+ const res = await runProcess({ command: cmd, args, timeoutMs: 1e4, logger });
2184
+ return os8.platform() === "win32" ? /qemu-system-x86_64/i.test(res.stdout) : res.code === 0;
2185
+ } catch {
2186
+ return false;
2187
+ }
2188
+ }
2189
+ async function getEmulatorStatus(config, opts = {}) {
2190
+ const logger = opts.logger ?? noopLogger;
2191
+ const address = opts.address ?? "127.0.0.1:55555";
2192
+ const dir = getEmulatorDir(config);
2193
+ let connected = false;
2194
+ try {
2195
+ const targets = await hdcExec({ config, args: ["list", "targets", "-v"], timeoutMs: 1e4, logger });
2196
+ connected = targets.stdout.split("\n").some((l) => l.trim().startsWith(address) && /\bConnected\b/.test(l));
2197
+ } catch {
2198
+ }
2199
+ return {
2200
+ installed: isEmulatorInstalled(config),
2201
+ dir,
2202
+ running: await isQemuRunning(logger),
2203
+ address,
2204
+ connected
2205
+ };
2206
+ }
2070
2207
 
2071
2208
  // src/hdc/devices.ts
2072
2209
  var STATUS_TOKENS = /* @__PURE__ */ new Set([
2073
2210
  "Connected",
2211
+ "Ready",
2074
2212
  "Offline",
2075
2213
  "Unauthorized",
2076
2214
  "Authorizing",
@@ -2105,9 +2243,8 @@ async function listDevices(config, opts = {}) {
2105
2243
  return parseDeviceList(res.stdout);
2106
2244
  }
2107
2245
  async function selectDevice(config, serial, opts = {}) {
2108
- if (serial && serial !== "auto") return serial;
2109
- const envSerial = process.env.ONIRO_DEVICE_SERIAL || process.env.DEVICE_SERIAL;
2110
- if (envSerial && envSerial !== "auto") return envSerial;
2246
+ const resolved = resolveDeviceSerial(serial);
2247
+ if (resolved) return resolved;
2111
2248
  const usable = (await listDevices(config, opts)).filter(
2112
2249
  (d) => d.status === "Connected" || d.status === "Unknown"
2113
2250
  );
@@ -2121,6 +2258,7 @@ async function selectDevice(config, serial, opts = {}) {
2121
2258
  }
2122
2259
 
2123
2260
  // src/hdc/param.ts
2261
+ var PARAM_MISSING_RE = /^Get parameter ".*" fail!/;
2124
2262
  async function paramGet(config, key, opts = {}) {
2125
2263
  const res = await shell({
2126
2264
  config,
@@ -2130,7 +2268,30 @@ async function paramGet(config, key, opts = {}) {
2130
2268
  logger: opts.logger
2131
2269
  });
2132
2270
  ensureOk(res, `hdc shell param get ${key}`);
2133
- return res.stdout.trim();
2271
+ const value = res.stdout.trim();
2272
+ if (PARAM_MISSING_RE.test(value)) throw new OniroError(`System parameter ${key} is not set: ${value}`);
2273
+ return value;
2274
+ }
2275
+ var PARAM_SEPARATOR = "__ONIRO_PARAM__";
2276
+ function parseParamValues(keys, stdout) {
2277
+ const parts = stdout.split(PARAM_SEPARATOR);
2278
+ const values = {};
2279
+ keys.forEach((key, i) => {
2280
+ const value = parts[i]?.trim();
2281
+ values[key] = value && !PARAM_MISSING_RE.test(value) ? value : void 0;
2282
+ });
2283
+ return values;
2284
+ }
2285
+ async function paramGetMany(config, keys, opts = {}) {
2286
+ const res = await shell({
2287
+ config,
2288
+ command: keys.map((k) => `param get ${k}; echo ${PARAM_SEPARATOR}`).join("; "),
2289
+ deviceSerial: opts.deviceSerial,
2290
+ timeoutMs: opts.timeoutMs ?? 1e4,
2291
+ logger: opts.logger
2292
+ });
2293
+ ensureOk(res, "hdc shell param get \u2026");
2294
+ return parseParamValues(keys, res.stdout);
2134
2295
  }
2135
2296
  async function paramSet(config, key, value, opts = {}) {
2136
2297
  const res = await shell({
@@ -2144,29 +2305,66 @@ async function paramSet(config, key, value, opts = {}) {
2144
2305
  }
2145
2306
 
2146
2307
  // src/hdc/files.ts
2308
+ var path17 = __toESM(require("path"), 1);
2309
+ function normaliseLocalPath(local) {
2310
+ const abs = path17.resolve(local);
2311
+ const long = toLongPath(abs);
2312
+ return long !== abs ? long : path17.join(toLongPath(path17.dirname(abs)), path17.basename(abs));
2313
+ }
2314
+ function assertDevicePath(remote) {
2315
+ if (/^[A-Za-z]:[\\/]/.test(remote)) {
2316
+ throw new OniroError(
2317
+ `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.`
2318
+ );
2319
+ }
2320
+ }
2321
+ function appSandboxDir(bundle) {
2322
+ return `/data/app/el2/100/base/${bundle}`;
2323
+ }
2324
+ function shQuote(s) {
2325
+ return `'${s.replace(/'/g, `'\\''`)}'`;
2326
+ }
2147
2327
  async function sendFile(opts) {
2328
+ assertDevicePath(opts.remote);
2329
+ const local = normaliseLocalPath(opts.local);
2330
+ const sandbox = opts.bundle ? appSandboxDir(opts.bundle) : void 0;
2331
+ const remote = sandbox && !opts.remote.startsWith("/") ? path17.posix.join(sandbox, opts.remote) : opts.remote;
2332
+ if (sandbox && !remote.startsWith(`${sandbox}/`)) {
2333
+ throw new OniroError(`${remote} is outside the sandbox of ${opts.bundle} (${sandbox}).`);
2334
+ }
2335
+ const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
2336
+ if (sandbox) {
2337
+ const mkdir = `mkdir -p ${shQuote(path17.posix.dirname(remote))}`;
2338
+ ensureRemoteOk(await shellChecked({ ...device, command: mkdir }), `hdc shell ${mkdir}`);
2339
+ }
2148
2340
  const res = await hdcExec({
2149
- config: opts.config,
2150
- args: ["file", "send", opts.local, opts.remote],
2151
- deviceSerial: opts.deviceSerial,
2341
+ ...device,
2342
+ args: ["file", "send", local, remote],
2152
2343
  timeoutMs: opts.timeoutMs ?? 6e5,
2153
2344
  abortSignal: opts.abortSignal,
2154
- onOutput: opts.onOutput,
2155
- logger: opts.logger
2345
+ onOutput: opts.onOutput
2156
2346
  });
2157
- ensureOk(res, `hdc file send ${opts.local} ${opts.remote}`);
2347
+ ensureOk(res, `hdc file send ${local} ${remote}`);
2348
+ if (sandbox) {
2349
+ const top = path17.posix.join(sandbox, path17.posix.relative(sandbox, remote).split("/")[0]);
2350
+ const chown = `chown -R "$(stat -c %u:%g ${shQuote(sandbox)})" ${shQuote(top)}`;
2351
+ ensureRemoteOk(await shellChecked({ ...device, command: chown }), `hdc shell ${chown}`);
2352
+ }
2353
+ return remote;
2158
2354
  }
2159
2355
  async function recvFile(opts) {
2356
+ assertDevicePath(opts.remote);
2357
+ const local = normaliseLocalPath(opts.local);
2160
2358
  const res = await hdcExec({
2161
2359
  config: opts.config,
2162
- args: ["file", "recv", opts.remote, opts.local],
2360
+ args: ["file", "recv", opts.remote, local],
2163
2361
  deviceSerial: opts.deviceSerial,
2164
2362
  timeoutMs: opts.timeoutMs ?? 6e5,
2165
2363
  abortSignal: opts.abortSignal,
2166
2364
  onOutput: opts.onOutput,
2167
2365
  logger: opts.logger
2168
2366
  });
2169
- ensureOk(res, `hdc file recv ${opts.remote} ${opts.local}`);
2367
+ ensureOk(res, `hdc file recv ${opts.remote} ${local}`);
2170
2368
  }
2171
2369
 
2172
2370
  // src/hdc/display.ts
@@ -2186,7 +2384,7 @@ async function getDisplaySize(config, opts = {}) {
2186
2384
 
2187
2385
  // src/hdc/wait.ts
2188
2386
  function delay(ms, signal) {
2189
- return new Promise((resolve6, reject) => {
2387
+ return new Promise((resolve7, reject) => {
2190
2388
  if (signal?.aborted) {
2191
2389
  reject(new CancelledError("Wait cancelled."));
2192
2390
  return;
@@ -2201,7 +2399,7 @@ function delay(ms, signal) {
2201
2399
  };
2202
2400
  const timer = setTimeout(() => {
2203
2401
  cleanup();
2204
- resolve6();
2402
+ resolve7();
2205
2403
  }, ms);
2206
2404
  signal?.addEventListener("abort", onAbort, { once: true });
2207
2405
  });
@@ -2230,20 +2428,73 @@ async function waitForCondition(opts) {
2230
2428
  }
2231
2429
 
2232
2430
  // src/hdc/app.ts
2233
- var fs18 = __toESM(require("fs"), 1);
2431
+ var fs19 = __toESM(require("fs"), 1);
2432
+ var path20 = __toESM(require("path"), 1);
2433
+
2434
+ // src/build/discoverHaps.ts
2435
+ var fs17 = __toESM(require("fs"), 1);
2234
2436
  var path18 = __toESM(require("path"), 1);
2437
+ function moduleOfHap(hapFileName) {
2438
+ const dash = hapFileName.indexOf("-");
2439
+ return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
2440
+ }
2441
+ async function discoverHaps(opts) {
2442
+ const { projectDir } = opts;
2443
+ const maxDepth = opts.maxDepth ?? 8;
2444
+ const result = {};
2445
+ const walk = async (dir, depth) => {
2446
+ if (depth > maxDepth) return;
2447
+ const entries = await fs17.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2448
+ for (const e of entries) {
2449
+ const full = path18.join(dir, e.name);
2450
+ if (e.isDirectory()) {
2451
+ if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
2452
+ await walk(full, depth + 1);
2453
+ } else if (e.isFile() && e.name.endsWith(".hap")) {
2454
+ const moduleName = moduleOfHap(e.name);
2455
+ const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
2456
+ if (e.name.includes("unsigned")) bucket.unsigned.push(full);
2457
+ else if (e.name.includes("signed")) bucket.signed.push(full);
2458
+ }
2459
+ }
2460
+ };
2461
+ await walk(projectDir, 0);
2462
+ const mtime = (p) => fs17.statSync(p).mtimeMs;
2463
+ for (const bucket of Object.values(result)) {
2464
+ bucket.signed.sort((a, b) => mtime(b) - mtime(a));
2465
+ bucket.unsigned.sort((a, b) => mtime(b) - mtime(a));
2466
+ }
2467
+ return result;
2468
+ }
2469
+ function pickHap(haps, module2) {
2470
+ const pick = (name) => {
2471
+ const signed = haps[name]?.signed[0];
2472
+ if (signed) return { module: name, path: signed, signed: true };
2473
+ const unsigned = haps[name]?.unsigned[0];
2474
+ return unsigned ? { module: name, path: unsigned, signed: false } : null;
2475
+ };
2476
+ if (module2) return pick(module2);
2477
+ const names = Object.keys(haps);
2478
+ const withSigned = names.filter((n) => haps[n].signed.length > 0);
2479
+ const candidates = withSigned.length > 0 ? withSigned : names.filter((n) => haps[n].unsigned.length > 0);
2480
+ if (candidates.length === 0) return null;
2481
+ if (candidates.length > 1) {
2482
+ throw new OniroError(`Multiple modules have built HAPs (${candidates.join(", ")}). Pass a module to choose.`);
2483
+ }
2484
+ return pick(candidates[0]);
2485
+ }
2235
2486
 
2236
2487
  // src/hdc/project.ts
2237
- var fs17 = __toESM(require("fs"), 1);
2238
- var path17 = __toESM(require("path"), 1);
2488
+ var fs18 = __toESM(require("fs"), 1);
2489
+ var path19 = __toESM(require("path"), 1);
2239
2490
  var import_json53 = __toESM(require("json5"), 1);
2240
2491
  function readJson5(filePath) {
2241
- const content = fs17.readFileSync(filePath, "utf-8");
2492
+ const content = fs18.readFileSync(filePath, "utf-8");
2242
2493
  return import_json53.default.parse(content);
2243
2494
  }
2244
2495
  function getBundleName(projectDir) {
2245
- const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
2246
- if (!fs17.existsSync(appJsonPath)) {
2496
+ const appJsonPath = path19.join(projectDir, "AppScope", "app.json5");
2497
+ if (!fs18.existsSync(appJsonPath)) {
2247
2498
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
2248
2499
  }
2249
2500
  const appJson = readJson5(appJsonPath);
@@ -2253,8 +2504,8 @@ function getBundleName(projectDir) {
2253
2504
  return appJson.app.bundleName;
2254
2505
  }
2255
2506
  function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2256
- const moduleJsonPath = path17.join(projectDir, moduleName, "src", "main", "module.json5");
2257
- if (!fs17.existsSync(moduleJsonPath)) {
2507
+ const moduleJsonPath = path19.join(projectDir, moduleName, "src", "main", "module.json5");
2508
+ if (!fs18.existsSync(moduleJsonPath)) {
2258
2509
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
2259
2510
  }
2260
2511
  const moduleJson = readJson5(moduleJsonPath);
@@ -2274,13 +2525,67 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
2274
2525
  }
2275
2526
 
2276
2527
  // src/hdc/app.ts
2528
+ function detectInstallFailure(output) {
2529
+ return /\[Fail\]|msg:\s*error|error:\s*failed to install|code:\s*95\d{5}/i.test(output) ? output.trim() : null;
2530
+ }
2531
+ function isUnsignedHap(hapPath) {
2532
+ return path20.basename(hapPath).includes("unsigned");
2533
+ }
2534
+ function installFailureReason(output) {
2535
+ const code = /code:\s*(\d+)/i.exec(output);
2536
+ if (!code) return output.trim().split(/\r?\n/)[0] ?? "";
2537
+ const message = /error:\s*([^\r\n]+)/i.exec(output.slice(code.index + code[0].length))?.[1]?.trim().replace(/\.$/, "");
2538
+ return message ? `code ${code[1]}: ${message}` : `code ${code[1]}`;
2539
+ }
2540
+ function unsignedHapRejection(output, hap) {
2541
+ const name = path20.basename(hap.path);
2542
+ const reason = installFailureReason(output);
2543
+ 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}).`;
2544
+ if (!/9568320|signature|pkcs7/i.test(reason)) return what;
2545
+ 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.`;
2546
+ }
2547
+ function ensureInstallOk(result, command, hap) {
2548
+ const output = `${result.stdout}
2549
+ ${result.stderr}`;
2550
+ if (hap && isUnsignedHap(hap.path) && /code:\s*95\d{5}/i.test(output)) {
2551
+ throw new OniroError(unsignedHapRejection(output, hap), new CommandFailedError(command, result.code || 1, output.trim()));
2552
+ }
2553
+ ensureOk(result, command);
2554
+ const failure = detectInstallFailure(output);
2555
+ if (failure) throw new CommandFailedError(command, 1, failure);
2556
+ return result;
2557
+ }
2558
+ function existingHapOrUnsigned(hapPath) {
2559
+ if (fs19.existsSync(hapPath)) return { path: hapPath };
2560
+ const sibling = hapPath.replace(/-signed\.hap$/i, "-unsigned.hap");
2561
+ return sibling !== hapPath && fs19.existsSync(sibling) ? { path: sibling, fallback: true } : void 0;
2562
+ }
2563
+ async function resolveInstallHap(opts, logger) {
2564
+ const warnIfFallback = (hap) => {
2565
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path20.basename(hap.path)}.`);
2566
+ return hap;
2567
+ };
2568
+ const configured = opts.hapPath ?? opts.config.get("hapPath", "");
2569
+ const relative3 = configured || defaultPaths.hapPath;
2570
+ const hapPath = path20.isAbsolute(relative3) ? relative3 : path20.join(opts.projectDir, relative3);
2571
+ if (configured) {
2572
+ const hap = existingHapOrUnsigned(hapPath);
2573
+ if (!hap) throw new OniroError(`HAP file not found at: ${hapPath}. Build the app first.`);
2574
+ return warnIfFallback(hap);
2575
+ }
2576
+ if (fs19.existsSync(hapPath)) return { path: hapPath };
2577
+ const haps = await discoverHaps({ projectDir: opts.projectDir });
2578
+ const anySigned = Object.values(haps).some((m) => m.signed.length > 0);
2579
+ const entryUnsigned = anySigned ? void 0 : existingHapOrUnsigned(hapPath);
2580
+ if (entryUnsigned) return warnIfFallback(entryUnsigned);
2581
+ const picked = pickHap(haps);
2582
+ if (!picked) throw new OniroError(`No HAP found under ${opts.projectDir}. Build the app first (\`oniro-app build\`).`);
2583
+ return warnIfFallback({ path: picked.path, fallback: !picked.signed });
2584
+ }
2277
2585
  async function installApp(opts) {
2278
2586
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2279
- const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
2280
- const hapPath = path18.isAbsolute(relativeHapPath) ? relativeHapPath : path18.join(opts.projectDir, relativeHapPath);
2281
- if (!fs18.existsSync(hapPath)) {
2282
- throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
2283
- }
2587
+ const hap = await resolveInstallHap(opts, logger);
2588
+ const hapPath = hap.path;
2284
2589
  const result = await hdcExec({
2285
2590
  config: opts.config,
2286
2591
  args: ["install", hapPath],
@@ -2289,7 +2594,7 @@ async function installApp(opts) {
2289
2594
  });
2290
2595
  if (result.stdout.trim()) logger.info(result.stdout.trim());
2291
2596
  if (result.stderr.trim()) logger.warn(result.stderr.trim());
2292
- ensureOk(result, `hdc install ${hapPath}`);
2597
+ ensureInstallOk(result, `hdc install ${hapPath}`, hap);
2293
2598
  let bundleName = "";
2294
2599
  try {
2295
2600
  bundleName = getBundleName(opts.projectDir);
@@ -2297,6 +2602,18 @@ async function installApp(opts) {
2297
2602
  }
2298
2603
  return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
2299
2604
  }
2605
+ async function isBundleInstalled(opts) {
2606
+ const safe = opts.bundle.replace(/'/g, `'\\''`);
2607
+ const res = await shell({
2608
+ config: opts.config,
2609
+ command: `bm dump -n '${safe}'`,
2610
+ deviceSerial: opts.deviceSerial,
2611
+ timeoutMs: opts.timeoutMs ?? 15e3,
2612
+ logger: opts.logger
2613
+ });
2614
+ ensureOk(res, `hdc shell bm dump -n ${opts.bundle}`);
2615
+ return res.stdout.includes("{") && !/failed to get information/i.test(res.stdout);
2616
+ }
2300
2617
  function detectAaStartFailure(output) {
2301
2618
  return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
2302
2619
  }
@@ -2367,10 +2684,10 @@ function listRunningProcesses(config, options = {}) {
2367
2684
  const logger = options.logger ?? noopLogger;
2368
2685
  const timeoutMs = options.timeoutMs ?? 1e3;
2369
2686
  const hdc = getHdcPath(config);
2370
- return new Promise((resolve6, reject) => {
2687
+ return new Promise((resolve7, reject) => {
2371
2688
  let proc;
2372
2689
  try {
2373
- proc = spawnCompat(hdc, ["track-jpid"]);
2690
+ proc = spawnCompat(hdc, [...hdcTargetArgs(options.deviceSerial), "track-jpid"]);
2374
2691
  } catch (err) {
2375
2692
  reject(new OniroError(`Failed to spawn ${hdc}: ${err instanceof Error ? err.message : String(err)}`, err));
2376
2693
  return;
@@ -2387,7 +2704,7 @@ function listRunningProcesses(config, options = {}) {
2387
2704
  if (options.targetProcessName && name === options.targetProcessName && !resolved) {
2388
2705
  resolved = true;
2389
2706
  killProcessTree(proc);
2390
- resolve6(pid);
2707
+ resolve7(pid);
2391
2708
  return;
2392
2709
  }
2393
2710
  }
@@ -2408,7 +2725,7 @@ function listRunningProcesses(config, options = {}) {
2408
2725
  if (options.targetProcessName) {
2409
2726
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2410
2727
  } else {
2411
- resolve6(processes);
2728
+ resolve7(processes);
2412
2729
  }
2413
2730
  }, timeoutMs);
2414
2731
  proc.on("close", () => {
@@ -2418,7 +2735,7 @@ function listRunningProcesses(config, options = {}) {
2418
2735
  if (options.targetProcessName) {
2419
2736
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2420
2737
  } else {
2421
- resolve6(processes);
2738
+ resolve7(processes);
2422
2739
  }
2423
2740
  });
2424
2741
  });
@@ -2440,7 +2757,11 @@ async function findRunningProcess(opts) {
2440
2757
  const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
2441
2758
  if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
2442
2759
  try {
2443
- const list = await listRunningProcesses(opts.config, { timeoutMs: 1200, logger: opts.logger });
2760
+ const list = await listRunningProcesses(opts.config, {
2761
+ timeoutMs: 1200,
2762
+ deviceSerial: opts.deviceSerial,
2763
+ logger: opts.logger
2764
+ });
2444
2765
  if (Array.isArray(list)) {
2445
2766
  const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
2446
2767
  const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
@@ -2583,12 +2904,12 @@ async function reboot(opts) {
2583
2904
  }
2584
2905
 
2585
2906
  // src/hdc/screen.ts
2586
- var fs19 = __toESM(require("fs"), 1);
2907
+ var fs20 = __toESM(require("fs"), 1);
2587
2908
  var os9 = __toESM(require("os"), 1);
2588
- var path19 = __toESM(require("path"), 1);
2909
+ var path21 = __toESM(require("path"), 1);
2589
2910
  var import_node_crypto = require("crypto");
2590
2911
  function tmpName(ext) {
2591
- return path19.join(os9.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
2912
+ return path21.join(os9.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
2592
2913
  }
2593
2914
  async function takeScreenshot(opts) {
2594
2915
  const remote = "/data/local/tmp/oniro_screenshot.jpeg";
@@ -2598,11 +2919,11 @@ async function takeScreenshot(opts) {
2598
2919
  const local = tmpName("jpeg");
2599
2920
  try {
2600
2921
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2601
- const pixels = await fs19.promises.readFile(local);
2922
+ const pixels = await fs20.promises.readFile(local);
2602
2923
  const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
2603
2924
  return { pixels, width: size.width, height: size.height };
2604
2925
  } finally {
2605
- fs19.promises.unlink(local).catch(() => {
2926
+ fs20.promises.unlink(local).catch(() => {
2606
2927
  });
2607
2928
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2608
2929
  });
@@ -2621,9 +2942,9 @@ async function captureBurst(opts) {
2621
2942
  const local = tmpName(`burst_${i}.jpeg`);
2622
2943
  try {
2623
2944
  await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
2624
- frames.push(await fs19.promises.readFile(local));
2945
+ frames.push(await fs20.promises.readFile(local));
2625
2946
  } finally {
2626
- fs19.promises.unlink(local).catch(() => {
2947
+ fs20.promises.unlink(local).catch(() => {
2627
2948
  });
2628
2949
  }
2629
2950
  }
@@ -2798,9 +3119,9 @@ async function sendRawTouch(opts) {
2798
3119
  }
2799
3120
 
2800
3121
  // src/hdc/dump.ts
2801
- var fs20 = __toESM(require("fs"), 1);
3122
+ var fs21 = __toESM(require("fs"), 1);
2802
3123
  var os10 = __toESM(require("os"), 1);
2803
- var path20 = __toESM(require("path"), 1);
3124
+ var path22 = __toESM(require("path"), 1);
2804
3125
  var import_node_crypto2 = require("crypto");
2805
3126
 
2806
3127
  // src/hdc/ime.ts
@@ -3063,10 +3384,10 @@ async function dumpLayout(opts) {
3063
3384
  if (!m) throw new OniroError(`Could not parse dumpLayout output:
3064
3385
  ${dump.stdout}`);
3065
3386
  const remote = m[1];
3066
- const local = path20.join(os10.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
3387
+ const local = path22.join(os10.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
3067
3388
  try {
3068
3389
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
3069
- const parsed = JSON.parse(await fs20.promises.readFile(local, "utf8"));
3390
+ const parsed = JSON.parse(await fs21.promises.readFile(local, "utf8"));
3070
3391
  const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
3071
3392
  if (opts.prune === false) {
3072
3393
  return { display, tree: parsed };
@@ -3079,7 +3400,7 @@ ${dump.stdout}`);
3079
3400
  const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
3080
3401
  return { display, tree, keyboard };
3081
3402
  } finally {
3082
- fs20.promises.unlink(local).catch(() => {
3403
+ fs21.promises.unlink(local).catch(() => {
3083
3404
  });
3084
3405
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
3085
3406
  });
@@ -3089,7 +3410,7 @@ ${dump.stdout}`);
3089
3410
  // src/hdc/uiActions.ts
3090
3411
  var FOCUS_SETTLE_MS = 400;
3091
3412
  var KEYBOARD_SETTLE_MS = 400;
3092
- var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
3413
+ var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
3093
3414
  function targetTree(layout) {
3094
3415
  const kb = layout.keyboard;
3095
3416
  return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
@@ -3242,13 +3563,14 @@ async function waitForTarget(opts) {
3242
3563
  }
3243
3564
 
3244
3565
  // src/hdc/hilog.ts
3566
+ var fs22 = __toESM(require("fs"), 1);
3245
3567
  function setHilogLevel(opts) {
3246
3568
  const logger = opts.logger ?? noopLogger;
3247
3569
  const hdc = getHdcPath(opts.config);
3248
- return new Promise((resolve6, reject) => {
3570
+ return new Promise((resolve7, reject) => {
3249
3571
  let child;
3250
3572
  try {
3251
- child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
3573
+ child = spawnCompat(hdc, [...hdcTargetArgs(opts.deviceSerial), "shell", "hilog", "-b", opts.level]);
3252
3574
  } catch (err) {
3253
3575
  const msg = err instanceof Error ? err.message : String(err);
3254
3576
  logger.error(`[hilog] failed to spawn hdc: ${msg}`);
@@ -3265,7 +3587,7 @@ function setHilogLevel(opts) {
3265
3587
  });
3266
3588
  child.once("close", (code) => {
3267
3589
  if (code === 0) {
3268
- resolve6();
3590
+ resolve7();
3269
3591
  } else {
3270
3592
  const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
3271
3593
  logger.error(`[hilog] ${msg}`);
@@ -3276,8 +3598,7 @@ function setHilogLevel(opts) {
3276
3598
  }
3277
3599
  function streamHilog(opts) {
3278
3600
  const hdc = getHdcPath(opts.config);
3279
- const args = [];
3280
- if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
3601
+ const args = [...hdcTargetArgs(opts.deviceSerial)];
3281
3602
  args.push("shell", "hilog");
3282
3603
  if (opts.processId && opts.processId.trim() !== "") {
3283
3604
  args.push("-P", opts.processId.trim());
@@ -3320,7 +3641,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
3320
3641
  return proc?.pid;
3321
3642
  }
3322
3643
  function waitForLog(opts) {
3323
- return new Promise((resolve6, reject) => {
3644
+ return new Promise((resolve7, reject) => {
3324
3645
  const deadline = Date.now() + opts.timeoutMs;
3325
3646
  let settled = false;
3326
3647
  let child = null;
@@ -3339,7 +3660,7 @@ function waitForLog(opts) {
3339
3660
  if (settled) return;
3340
3661
  settled = true;
3341
3662
  cleanup();
3342
- resolve6(entry);
3663
+ resolve7(entry);
3343
3664
  };
3344
3665
  const fail = (err) => {
3345
3666
  if (settled) return;
@@ -3386,7 +3707,7 @@ function waitForLog(opts) {
3386
3707
  });
3387
3708
  }
3388
3709
  function watchLog(opts) {
3389
- return new Promise((resolve6, reject) => {
3710
+ return new Promise((resolve7, reject) => {
3390
3711
  const matches = [];
3391
3712
  let settled = false;
3392
3713
  let child = null;
@@ -3403,7 +3724,7 @@ function watchLog(opts) {
3403
3724
  if (settled) return;
3404
3725
  settled = true;
3405
3726
  cleanup();
3406
- resolve6(opts.dedup === false ? matches : dedupEntries(matches));
3727
+ resolve7(opts.dedup === false ? matches : dedupEntries(matches));
3407
3728
  };
3408
3729
  const onAbort = () => {
3409
3730
  if (settled) return;
@@ -3435,6 +3756,94 @@ function watchLog(opts) {
3435
3756
  });
3436
3757
  });
3437
3758
  }
3759
+ async function captureLog(opts) {
3760
+ const logger = opts.logger ?? noopLogger;
3761
+ if (opts.clear) {
3762
+ const res = await shell({ config: opts.config, command: "hilog -r", deviceSerial: opts.deviceSerial, timeoutMs: 1e4, logger });
3763
+ ensureOk(res, "hdc shell hilog -r");
3764
+ }
3765
+ const fd = fs22.openSync(opts.file, "a");
3766
+ let lines = 0;
3767
+ let reconnects = 0;
3768
+ try {
3769
+ await new Promise((resolve7, reject) => {
3770
+ const deadline = opts.durationMs !== void 0 ? Date.now() + opts.durationMs : Infinity;
3771
+ let settled = false;
3772
+ let child = null;
3773
+ let reconnectTimer;
3774
+ let buf = "";
3775
+ let open = true;
3776
+ const write = (line) => {
3777
+ if (!open || opts.pattern && !opts.pattern.test(line)) return;
3778
+ fs22.writeSync(fd, `${line}
3779
+ `);
3780
+ lines++;
3781
+ };
3782
+ const cleanup = () => {
3783
+ if (timer) clearTimeout(timer);
3784
+ if (reconnectTimer) clearTimeout(reconnectTimer);
3785
+ opts.abortSignal?.removeEventListener("abort", finish);
3786
+ if (buf.trim()) write(buf.trimEnd());
3787
+ buf = "";
3788
+ open = false;
3789
+ if (child) {
3790
+ child.stdout.removeAllListeners("data");
3791
+ killProcessTree(child);
3792
+ child = null;
3793
+ }
3794
+ };
3795
+ function finish() {
3796
+ if (settled) return;
3797
+ settled = true;
3798
+ cleanup();
3799
+ resolve7();
3800
+ }
3801
+ const fail = (err) => {
3802
+ if (settled) return;
3803
+ settled = true;
3804
+ cleanup();
3805
+ reject(err instanceof Error ? err : new Error(String(err)));
3806
+ };
3807
+ const timer = opts.durationMs !== void 0 ? setTimeout(finish, opts.durationMs) : void 0;
3808
+ if (opts.abortSignal?.aborted) {
3809
+ finish();
3810
+ return;
3811
+ }
3812
+ opts.abortSignal?.addEventListener("abort", finish, { once: true });
3813
+ const spawnStream = () => {
3814
+ if (settled) return;
3815
+ const stream = streamHilog({ config: opts.config, domain: opts.domain, deviceSerial: opts.deviceSerial });
3816
+ child = stream;
3817
+ stream.stdout.on("data", (d) => {
3818
+ buf += d.toString();
3819
+ const parts = buf.split(/\r?\n/);
3820
+ buf = parts.pop() ?? "";
3821
+ for (const line of parts) write(line);
3822
+ });
3823
+ const onEnd = () => {
3824
+ if (settled || child !== stream) return;
3825
+ child = null;
3826
+ if (Date.now() >= deadline) return;
3827
+ reconnects++;
3828
+ logger.warn(`[hilog] stream ended; reconnecting (${reconnects}).`);
3829
+ reconnectTimer = setTimeout(() => {
3830
+ try {
3831
+ spawnStream();
3832
+ } catch (err) {
3833
+ fail(err);
3834
+ }
3835
+ }, 2e3);
3836
+ };
3837
+ stream.once("close", onEnd);
3838
+ stream.once("error", onEnd);
3839
+ };
3840
+ spawnStream();
3841
+ });
3842
+ } finally {
3843
+ fs22.closeSync(fd);
3844
+ }
3845
+ return { file: opts.file, lines, reconnects };
3846
+ }
3438
3847
  async function dumpLog(opts) {
3439
3848
  let pipe = "hilog -x";
3440
3849
  if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
@@ -3459,17 +3868,17 @@ async function dumpLog(opts) {
3459
3868
  }
3460
3869
 
3461
3870
  // src/build/runHvigorw.ts
3462
- var fs22 = __toESM(require("fs"), 1);
3463
- var path22 = __toESM(require("path"), 1);
3871
+ var fs24 = __toESM(require("fs"), 1);
3872
+ var path24 = __toESM(require("path"), 1);
3464
3873
 
3465
3874
  // src/harmonyos/target.ts
3466
- var fs21 = __toESM(require("fs"), 1);
3467
- var path21 = __toESM(require("path"), 1);
3875
+ var fs23 = __toESM(require("fs"), 1);
3876
+ var path23 = __toESM(require("path"), 1);
3468
3877
  var import_json54 = __toESM(require("json5"), 1);
3469
3878
  function detectRuntimeOs(projectDir, productName) {
3470
3879
  let products = [];
3471
3880
  try {
3472
- const parsed = import_json54.default.parse(fs21.readFileSync(path21.join(projectDir, "build-profile.json5"), "utf-8"));
3881
+ const parsed = import_json54.default.parse(fs23.readFileSync(path23.join(projectDir, "build-profile.json5"), "utf-8"));
3473
3882
  if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
3474
3883
  } catch {
3475
3884
  return "OpenHarmony";
@@ -3510,7 +3919,7 @@ function runHvigorw(opts) {
3510
3919
  const { config } = opts;
3511
3920
  const projectDir = toLongPath(opts.projectDir);
3512
3921
  const logger = opts.logger ?? noopLogger;
3513
- if (!fs22.existsSync(path22.join(projectDir, "build-profile.json5"))) {
3922
+ if (!fs24.existsSync(path24.join(projectDir, "build-profile.json5"))) {
3514
3923
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
3515
3924
  }
3516
3925
  let hvigorw;
@@ -3522,24 +3931,24 @@ function runHvigorw(opts) {
3522
3931
  hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
3523
3932
  logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
3524
3933
  } else {
3525
- if (!fs22.existsSync(getCmdToolsPath(config))) {
3934
+ if (!fs24.existsSync(getCmdToolsPath(config))) {
3526
3935
  throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
3527
3936
  }
3528
3937
  sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
3529
3938
  hvigorw = getHvigorwPath(config, projectDir);
3530
3939
  }
3531
- if (!fs22.existsSync(hvigorw)) {
3940
+ if (!fs24.existsSync(hvigorw)) {
3532
3941
  throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
3533
3942
  }
3534
3943
  const args = buildHvigorwArgs(opts);
3535
3944
  if (process.platform !== "win32") {
3536
3945
  try {
3537
- fs22.chmodSync(hvigorw, 493);
3946
+ fs24.chmodSync(hvigorw, 493);
3538
3947
  } catch {
3539
3948
  }
3540
3949
  }
3541
3950
  const env = { ...process.env, ...sdkEnv };
3542
- return new Promise((resolve6, reject) => {
3951
+ return new Promise((resolve7, reject) => {
3543
3952
  if (opts.abortSignal?.aborted) {
3544
3953
  reject(new CancelledError("hvigorw build cancelled."));
3545
3954
  return;
@@ -3583,46 +3992,15 @@ function runHvigorw(opts) {
3583
3992
  settled = true;
3584
3993
  cleanup();
3585
3994
  const exitCode = code ?? 1;
3586
- if (exitCode === 0) resolve6({ exitCode });
3995
+ if (exitCode === 0) resolve7({ exitCode });
3587
3996
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
3588
3997
  });
3589
3998
  });
3590
3999
  }
3591
4000
 
3592
- // src/build/discoverHaps.ts
3593
- var fs23 = __toESM(require("fs"), 1);
3594
- var path23 = __toESM(require("path"), 1);
3595
- function moduleOfHap(hapFileName) {
3596
- const dash = hapFileName.indexOf("-");
3597
- return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
3598
- }
3599
- async function discoverHaps(opts) {
3600
- const { projectDir } = opts;
3601
- const maxDepth = opts.maxDepth ?? 8;
3602
- const result = {};
3603
- const walk = async (dir, depth) => {
3604
- if (depth > maxDepth) return;
3605
- const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
3606
- for (const e of entries) {
3607
- const full = path23.join(dir, e.name);
3608
- if (e.isDirectory()) {
3609
- if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
3610
- await walk(full, depth + 1);
3611
- } else if (e.isFile() && e.name.endsWith(".hap")) {
3612
- const moduleName = moduleOfHap(e.name);
3613
- const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
3614
- if (e.name.includes("unsigned")) bucket.unsigned.push(full);
3615
- else if (e.name.includes("signed")) bucket.signed.push(full);
3616
- }
3617
- }
3618
- };
3619
- await walk(projectDir, 0);
3620
- return result;
3621
- }
3622
-
3623
4001
  // src/build/ohpm.ts
3624
- var fs24 = __toESM(require("fs"), 1);
3625
- var path24 = __toESM(require("path"), 1);
4002
+ var fs25 = __toESM(require("fs"), 1);
4003
+ var path25 = __toESM(require("path"), 1);
3626
4004
  function runOhpm(opts) {
3627
4005
  return runProcess({
3628
4006
  command: opts.ohpmPath ?? getOhpmPath(opts.config),
@@ -3635,7 +4013,7 @@ function runOhpm(opts) {
3635
4013
  });
3636
4014
  }
3637
4015
  async function ensureOhModules(opts) {
3638
- if (fs24.existsSync(path24.join(opts.projectDir, "oh_modules"))) {
4016
+ if (fs25.existsSync(path25.join(opts.projectDir, "oh_modules"))) {
3639
4017
  return { installed: false };
3640
4018
  }
3641
4019
  const res = await runOhpm({
@@ -3652,23 +4030,91 @@ async function ensureOhModules(opts) {
3652
4030
  }
3653
4031
 
3654
4032
  // src/build/codelinter.ts
3655
- var FINDING_RE = /^(.*?):(\d+):(?:\d+:)?\s*(error|warning|warn|info)\b[:\s-]*(.*)$/i;
4033
+ var FINDING_RE = /^(.*?):(\d+):(?:(\d+):)?\s*(error|warning|warn|suggestion|info)\b[:\s-]*(.*)$/i;
3656
4034
  var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
3657
- function parseCodelinterFindings(output) {
4035
+ var FILE_HEADER_RE = /^(.+\.\w+)\((\d+)\)$/;
4036
+ var ROW_RE = /^(\d+):(\d+)\s+(error|warning|warn|suggestion|info)\s+(.*)$/i;
4037
+ var RULE_RE = /^@?[\w.-]+(?:\/[\w.-]+)+$/;
4038
+ var SUMMARY_RE = /Defects:\s*(\d+);\s*Errors:\s*(\d+);\s*Warns:\s*(\d+);\s*Suggestions:\s*(\d+)/i;
4039
+ var NOTICE_RE = /does not match that of the project|error occurred during linting|incomplete report/i;
4040
+ var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
4041
+ function cleanCodelinterOutput(output) {
4042
+ return output.replace(ANSI_RE, "").split("\n").map((line) => (line.split("\r").filter((s) => s.trim() !== "").pop() ?? "").trimEnd()).join("\n");
4043
+ }
4044
+ function normaliseSeverity(s) {
4045
+ const lower = s.toLowerCase();
4046
+ return lower === "warn" ? "warning" : lower;
4047
+ }
4048
+ function parseCodelinterReport(output) {
3658
4049
  const findings = [];
3659
- for (const raw of output.split("\n")) {
3660
- const m = FINDING_RE.exec(raw.trim());
3661
- if (!m) continue;
3662
- let message = m[4].trim();
3663
- let code = "";
3664
- const codeMatch = TRAILING_CODE_RE.exec(message);
3665
- if (codeMatch) {
3666
- code = codeMatch[1];
3667
- message = message.slice(0, codeMatch.index).trim();
4050
+ const notices = [];
4051
+ let summary = null;
4052
+ let file = "";
4053
+ for (const rawLine of cleanCodelinterOutput(output).split("\n")) {
4054
+ const line = rawLine.trim();
4055
+ if (!line) continue;
4056
+ const row = ROW_RE.exec(line);
4057
+ if (row) {
4058
+ if (!file) continue;
4059
+ const columns = row[4].trim().split(/\s{2,}/);
4060
+ const last = columns[columns.length - 1];
4061
+ const code = columns.length > 1 && RULE_RE.test(last) ? last : "";
4062
+ const message = (code ? columns.slice(0, -1) : columns).join(" ").trim();
4063
+ findings.push({
4064
+ file,
4065
+ line: Number(row[1]),
4066
+ column: Number(row[2]),
4067
+ severity: normaliseSeverity(row[3]),
4068
+ code,
4069
+ message
4070
+ });
4071
+ continue;
4072
+ }
4073
+ const header = FILE_HEADER_RE.exec(line);
4074
+ if (header) {
4075
+ file = header[1];
4076
+ continue;
4077
+ }
4078
+ const legacy = FINDING_RE.exec(line);
4079
+ if (legacy) {
4080
+ let message = legacy[5].trim();
4081
+ let code = "";
4082
+ const codeMatch = TRAILING_CODE_RE.exec(message);
4083
+ if (codeMatch) {
4084
+ code = codeMatch[1];
4085
+ message = message.slice(0, codeMatch.index).trim();
4086
+ }
4087
+ const finding = {
4088
+ file: legacy[1].trim(),
4089
+ line: Number(legacy[2]),
4090
+ severity: normaliseSeverity(legacy[4]),
4091
+ code,
4092
+ message
4093
+ };
4094
+ if (legacy[3]) finding.column = Number(legacy[3]);
4095
+ findings.push(finding);
4096
+ continue;
3668
4097
  }
3669
- findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
4098
+ const totals = SUMMARY_RE.exec(line);
4099
+ if (totals) {
4100
+ summary = {
4101
+ defects: Number(totals[1]),
4102
+ errors: Number(totals[2]),
4103
+ warnings: Number(totals[3]),
4104
+ suggestions: Number(totals[4])
4105
+ };
4106
+ continue;
4107
+ }
4108
+ if (/no defects found/i.test(line)) {
4109
+ summary = { defects: 0, errors: 0, warnings: 0, suggestions: 0 };
4110
+ continue;
4111
+ }
4112
+ if (NOTICE_RE.test(line)) notices.push(line);
3670
4113
  }
3671
- return findings;
4114
+ return { findings, summary, notices, incomplete: notices.length > 0 };
4115
+ }
4116
+ function parseCodelinterFindings(output) {
4117
+ return parseCodelinterReport(output).findings;
3672
4118
  }
3673
4119
  async function runCodelinter(opts) {
3674
4120
  const res = await runProcess({
@@ -3680,16 +4126,28 @@ async function runCodelinter(opts) {
3680
4126
  logger: opts.logger
3681
4127
  });
3682
4128
  const raw = `${res.stdout}${res.stderr}`;
3683
- return { code: res.code, findings: parseCodelinterFindings(raw), raw };
4129
+ return { code: res.code, ...parseCodelinterReport(raw), raw: cleanCodelinterOutput(raw) };
3684
4130
  }
3685
4131
 
3686
4132
  // src/build/buildHap.ts
4133
+ var DIAGNOSIS_OUTPUT_LIMIT = 2e6;
4134
+ function diagnoseBuildFailure(output) {
4135
+ if (/is not exported from Kit|has no exported member .*@systemapi/i.test(output)) {
4136
+ 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`).";
4137
+ }
4138
+ if (/00308018|SDK path does not exist|SDK license agreement is not accepted/i.test(output)) {
4139
+ return "hvigor cannot find the SDK the project compiles against: check `oniro-app sdk list` against compileSdkVersion.";
4140
+ }
4141
+ return void 0;
4142
+ }
3687
4143
  async function buildHap(opts) {
3688
4144
  const logger = scopedLogger(opts.logger ?? noopLogger, "build");
3689
4145
  const warnings = [];
3690
4146
  const start = Date.now();
3691
4147
  const projectDir = toLongPath(opts.projectDir);
3692
4148
  const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
4149
+ const missingSdk = isHarmonyOs ? null : findMissingProjectSdk(opts.config, projectDir);
4150
+ if (missingSdk) throw missingSdk;
3693
4151
  if (opts.autoInstallDeps !== false) {
3694
4152
  const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
3695
4153
  const r = await ensureOhModules({
@@ -3702,23 +4160,35 @@ async function buildHap(opts) {
3702
4160
  });
3703
4161
  if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
3704
4162
  }
3705
- const { exitCode } = await runHvigorw({
3706
- config: opts.config,
3707
- projectDir,
3708
- product: opts.product,
3709
- module: opts.module,
3710
- buildMode: opts.mode,
3711
- task: opts.task,
3712
- parallel: opts.parallel,
3713
- abortSignal: opts.abortSignal,
3714
- onOutput: opts.onOutput,
3715
- logger: opts.logger
3716
- });
4163
+ let output = "";
4164
+ let exitCode;
4165
+ try {
4166
+ ({ exitCode } = await runHvigorw({
4167
+ config: opts.config,
4168
+ projectDir,
4169
+ product: opts.product,
4170
+ module: opts.module,
4171
+ buildMode: opts.mode,
4172
+ task: opts.task,
4173
+ parallel: opts.parallel,
4174
+ abortSignal: opts.abortSignal,
4175
+ onOutput: (chunk, stream) => {
4176
+ output = (output + chunk).slice(-DIAGNOSIS_OUTPUT_LIMIT);
4177
+ opts.onOutput?.(chunk, stream);
4178
+ },
4179
+ logger: opts.logger
4180
+ }));
4181
+ } catch (err) {
4182
+ const hint = diagnoseBuildFailure(output);
4183
+ if (hint && err instanceof OniroError) throw new OniroError(`${err.message}
4184
+ ${hint}`, err);
4185
+ throw err;
4186
+ }
3717
4187
  const discoveredHaps = await discoverHaps({ projectDir });
3718
4188
  const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
3719
4189
  if (!anySigned) {
3720
4190
  warnings.push(
3721
- 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)."
4191
+ 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."
3722
4192
  );
3723
4193
  }
3724
4194
  return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
@@ -3735,21 +4205,21 @@ function isValidBundleName(bundleName) {
3735
4205
  }
3736
4206
 
3737
4207
  // src/project/templates.ts
3738
- var fs25 = __toESM(require("fs"), 1);
3739
- var path25 = __toESM(require("path"), 1);
4208
+ var fs26 = __toESM(require("fs"), 1);
4209
+ var path26 = __toESM(require("path"), 1);
3740
4210
  function toHumanTemplateName(folderName) {
3741
4211
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
3742
4212
  }
3743
4213
  function listTemplates(templateRoot) {
3744
- if (!fs25.existsSync(templateRoot)) return [];
3745
- const entries = fs25.readdirSync(templateRoot, { withFileTypes: true });
4214
+ if (!fs26.existsSync(templateRoot)) return [];
4215
+ const entries = fs26.readdirSync(templateRoot, { withFileTypes: true });
3746
4216
  return entries.filter((e) => e.isDirectory()).map((e) => {
3747
- const dir = path25.join(templateRoot, e.name);
3748
- const metaPath = path25.join(dir, "template.json");
4217
+ const dir = path26.join(templateRoot, e.name);
4218
+ const metaPath = path26.join(dir, "template.json");
3749
4219
  let meta = {};
3750
- if (fs25.existsSync(metaPath)) {
4220
+ if (fs26.existsSync(metaPath)) {
3751
4221
  try {
3752
- meta = JSON.parse(fs25.readFileSync(metaPath, "utf8"));
4222
+ meta = JSON.parse(fs26.readFileSync(metaPath, "utf8"));
3753
4223
  } catch {
3754
4224
  }
3755
4225
  }
@@ -3764,57 +4234,57 @@ function listTemplates(templateRoot) {
3764
4234
  function validateTemplateLayout(templateDir, defaultModuleName) {
3765
4235
  const required = [
3766
4236
  "build-profile.json5",
3767
- path25.join("AppScope", "app.json5"),
3768
- path25.join(defaultModuleName, "src", "main", "module.json5"),
3769
- path25.join(defaultModuleName, "oh-package.json5"),
4237
+ path26.join("AppScope", "app.json5"),
4238
+ path26.join(defaultModuleName, "src", "main", "module.json5"),
4239
+ path26.join(defaultModuleName, "oh-package.json5"),
3770
4240
  "hvigorfile.ts"
3771
4241
  ];
3772
- return required.filter((rel) => !fs25.existsSync(path25.join(templateDir, rel)));
4242
+ return required.filter((rel) => !fs26.existsSync(path26.join(templateDir, rel)));
3773
4243
  }
3774
4244
 
3775
4245
  // src/project/createScaffold.ts
3776
- var fs27 = __toESM(require("fs"), 1);
3777
- var path26 = __toESM(require("path"), 1);
4246
+ var fs28 = __toESM(require("fs"), 1);
4247
+ var path27 = __toESM(require("path"), 1);
3778
4248
 
3779
4249
  // src/project/jsonHelpers.ts
3780
- var fs26 = __toESM(require("fs"), 1);
4250
+ var fs27 = __toESM(require("fs"), 1);
3781
4251
  var import_json55 = __toESM(require("json5"), 1);
3782
4252
  function readJson5File(filePath) {
3783
- return import_json55.default.parse(fs26.readFileSync(filePath, "utf8"));
4253
+ return import_json55.default.parse(fs27.readFileSync(filePath, "utf8"));
3784
4254
  }
3785
4255
  function writeJson5File(filePath, value) {
3786
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4256
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3787
4257
  }
3788
4258
  function readJsonFile(filePath) {
3789
- return JSON.parse(fs26.readFileSync(filePath, "utf8"));
4259
+ return JSON.parse(fs27.readFileSync(filePath, "utf8"));
3790
4260
  }
3791
4261
  function writeJsonFile(filePath, value) {
3792
- fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4262
+ fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3793
4263
  }
3794
4264
 
3795
4265
  // src/project/createScaffold.ts
3796
4266
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
3797
4267
  async function pathExists(p) {
3798
4268
  try {
3799
- await fs27.promises.access(p);
4269
+ await fs28.promises.access(p);
3800
4270
  return true;
3801
4271
  } catch {
3802
4272
  return false;
3803
4273
  }
3804
4274
  }
3805
4275
  async function copyDirRecursive(srcDir, destDir) {
3806
- await fs27.promises.mkdir(destDir, { recursive: true });
3807
- const entries = await fs27.promises.readdir(srcDir, { withFileTypes: true });
4276
+ await fs28.promises.mkdir(destDir, { recursive: true });
4277
+ const entries = await fs28.promises.readdir(srcDir, { withFileTypes: true });
3808
4278
  for (const entry of entries) {
3809
- const src = path26.join(srcDir, entry.name);
3810
- const dest = path26.join(destDir, entry.name);
4279
+ const src = path27.join(srcDir, entry.name);
4280
+ const dest = path27.join(destDir, entry.name);
3811
4281
  if (entry.isDirectory()) {
3812
4282
  await copyDirRecursive(src, dest);
3813
4283
  } else if (entry.isSymbolicLink()) {
3814
4284
  continue;
3815
4285
  } else if (entry.isFile()) {
3816
4286
  if (entry.name === "template.json") continue;
3817
- await fs27.promises.copyFile(src, dest);
4287
+ await fs28.promises.copyFile(src, dest);
3818
4288
  }
3819
4289
  }
3820
4290
  }
@@ -3822,9 +4292,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
3822
4292
  const stack = [projectDir];
3823
4293
  while (stack.length > 0) {
3824
4294
  const currentDir = stack.pop();
3825
- const entries = await fs27.promises.readdir(currentDir, { withFileTypes: true });
4295
+ const entries = await fs28.promises.readdir(currentDir, { withFileTypes: true });
3826
4296
  for (const entry of entries) {
3827
- const fullPath = path26.join(currentDir, entry.name);
4297
+ const fullPath = path27.join(currentDir, entry.name);
3828
4298
  if (entry.isDirectory()) {
3829
4299
  if (IGNORED_DIRS.has(entry.name)) continue;
3830
4300
  stack.push(fullPath);
@@ -3846,23 +4316,23 @@ function toJavaPropertiesPath(p) {
3846
4316
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
3847
4317
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
3848
4318
  `;
3849
- fs27.writeFileSync(path26.join(projectDir, "local.properties"), content, "utf8");
4319
+ fs28.writeFileSync(path27.join(projectDir, "local.properties"), content, "utf8");
3850
4320
  }
3851
4321
  function renameIfExists(fromPath, toPath) {
3852
- if (fs27.existsSync(fromPath) && fromPath !== toPath) {
3853
- fs27.renameSync(fromPath, toPath);
4322
+ if (fs28.existsSync(fromPath) && fromPath !== toPath) {
4323
+ fs28.renameSync(fromPath, toPath);
3854
4324
  }
3855
4325
  }
3856
4326
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3857
- const appJsonPath = path26.join(projectDir, "AppScope", "app.json5");
3858
- if (fs27.existsSync(appJsonPath)) {
4327
+ const appJsonPath = path27.join(projectDir, "AppScope", "app.json5");
4328
+ if (fs28.existsSync(appJsonPath)) {
3859
4329
  const appJson = readJson5File(appJsonPath);
3860
4330
  appJson.app = appJson.app ?? {};
3861
4331
  appJson.app.bundleName = args.bundleName;
3862
4332
  writeJson5File(appJsonPath, appJson);
3863
4333
  }
3864
- const stringsPath = path26.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3865
- if (fs27.existsSync(stringsPath)) {
4334
+ const stringsPath = path27.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
4335
+ if (fs28.existsSync(stringsPath)) {
3866
4336
  const strings = readJsonFile(stringsPath);
3867
4337
  if (Array.isArray(strings.string)) {
3868
4338
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -3870,8 +4340,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3870
4340
  }
3871
4341
  writeJsonFile(stringsPath, strings);
3872
4342
  }
3873
- const buildProfilePath = path26.join(projectDir, "build-profile.json5");
3874
- if (fs27.existsSync(buildProfilePath)) {
4343
+ const buildProfilePath = path27.join(projectDir, "build-profile.json5");
4344
+ if (fs28.existsSync(buildProfilePath)) {
3875
4345
  const buildProfile = readJson5File(buildProfilePath);
3876
4346
  buildProfile.app = buildProfile.app ?? {};
3877
4347
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -3884,23 +4354,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3884
4354
  }
3885
4355
  writeJson5File(buildProfilePath, buildProfile);
3886
4356
  }
3887
- const moduleJsonPath = path26.join(projectDir, args.moduleName, "src", "main", "module.json5");
3888
- if (fs27.existsSync(moduleJsonPath)) {
4357
+ const moduleJsonPath = path27.join(projectDir, args.moduleName, "src", "main", "module.json5");
4358
+ if (fs28.existsSync(moduleJsonPath)) {
3889
4359
  const moduleJson = readJson5File(moduleJsonPath);
3890
4360
  moduleJson.module = moduleJson.module ?? {};
3891
4361
  moduleJson.module.name = args.moduleName;
3892
4362
  writeJson5File(moduleJsonPath, moduleJson);
3893
4363
  }
3894
- const moduleOhPackagePath = path26.join(projectDir, args.moduleName, "oh-package.json5");
3895
- if (fs27.existsSync(moduleOhPackagePath)) {
4364
+ const moduleOhPackagePath = path27.join(projectDir, args.moduleName, "oh-package.json5");
4365
+ if (fs28.existsSync(moduleOhPackagePath)) {
3896
4366
  const pkg = readJson5File(moduleOhPackagePath);
3897
4367
  pkg.name = args.moduleName;
3898
4368
  writeJson5File(moduleOhPackagePath, pkg);
3899
4369
  }
3900
- const vscodeDir = path26.join(projectDir, ".vscode");
3901
- fs27.mkdirSync(vscodeDir, { recursive: true });
4370
+ const vscodeDir = path27.join(projectDir, ".vscode");
4371
+ fs28.mkdirSync(vscodeDir, { recursive: true });
3902
4372
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
3903
- writeJsonFile(path26.join(vscodeDir, "settings.json"), {
4373
+ writeJsonFile(path27.join(vscodeDir, "settings.json"), {
3904
4374
  "oniro.hapPath": hapPath,
3905
4375
  "files.associations": { "*.json5": "jsonc" }
3906
4376
  });
@@ -3917,7 +4387,7 @@ async function createScaffold(opts) {
3917
4387
  if (!opts.location || !await pathExists(opts.location)) {
3918
4388
  throw new OniroError(`Location does not exist: ${opts.location}`);
3919
4389
  }
3920
- const templateDir = path26.join(opts.templateRoot, opts.templateId);
4390
+ const templateDir = path27.join(opts.templateRoot, opts.templateId);
3921
4391
  if (!await pathExists(templateDir)) {
3922
4392
  throw new OniroError(`Template not found: ${templateDir}`);
3923
4393
  }
@@ -3952,17 +4422,17 @@ async function createScaffold(opts) {
3952
4422
  }
3953
4423
  }
3954
4424
  }
3955
- const projectDir = path26.join(opts.location, opts.projectName);
4425
+ const projectDir = path27.join(opts.location, opts.projectName);
3956
4426
  if (await pathExists(projectDir)) {
3957
4427
  if (!opts.overwrite) {
3958
4428
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
3959
4429
  }
3960
- await fs27.promises.rm(projectDir, { recursive: true, force: true });
4430
+ await fs28.promises.rm(projectDir, { recursive: true, force: true });
3961
4431
  }
3962
4432
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
3963
4433
  await copyDirRecursive(templateDir, projectDir);
3964
4434
  if (moduleName !== defaultModuleName) {
3965
- renameIfExists(path26.join(projectDir, defaultModuleName), path26.join(projectDir, moduleName));
4435
+ renameIfExists(path27.join(projectDir, defaultModuleName), path27.join(projectDir, moduleName));
3966
4436
  }
3967
4437
  updateTemplateConfigs(
3968
4438
  projectDir,
@@ -3979,11 +4449,11 @@ async function createScaffold(opts) {
3979
4449
  }
3980
4450
 
3981
4451
  // src/project/listModules.ts
3982
- var fs28 = __toESM(require("fs"), 1);
3983
- var path27 = __toESM(require("path"), 1);
4452
+ var fs29 = __toESM(require("fs"), 1);
4453
+ var path28 = __toESM(require("path"), 1);
3984
4454
  function listModules(opts) {
3985
- const buildProfile = path27.join(opts.projectDir, "build-profile.json5");
3986
- if (!fs28.existsSync(buildProfile)) {
4455
+ const buildProfile = path28.join(opts.projectDir, "build-profile.json5");
4456
+ if (!fs29.existsSync(buildProfile)) {
3987
4457
  throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
3988
4458
  }
3989
4459
  const parsed = readJson5File(buildProfile);
@@ -3995,12 +4465,12 @@ function listModules(opts) {
3995
4465
  }
3996
4466
 
3997
4467
  // src/project/abilities.ts
3998
- var fs29 = __toESM(require("fs"), 1);
3999
- var path28 = __toESM(require("path"), 1);
4468
+ var fs30 = __toESM(require("fs"), 1);
4469
+ var path29 = __toESM(require("path"), 1);
4000
4470
  function listAbilities(opts) {
4001
4471
  const moduleName = opts.moduleName ?? "entry";
4002
- const moduleJsonPath = path28.join(opts.projectDir, moduleName, "src", "main", "module.json5");
4003
- if (!fs29.existsSync(moduleJsonPath)) {
4472
+ const moduleJsonPath = path29.join(opts.projectDir, moduleName, "src", "main", "module.json5");
4473
+ if (!fs30.existsSync(moduleJsonPath)) {
4004
4474
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
4005
4475
  }
4006
4476
  const parsed = readJson5File(moduleJsonPath);
@@ -4008,12 +4478,12 @@ function listAbilities(opts) {
4008
4478
  }
4009
4479
 
4010
4480
  // src/project/prepareSigning.ts
4011
- var fs30 = __toESM(require("fs"), 1);
4012
- var path29 = __toESM(require("path"), 1);
4481
+ var fs31 = __toESM(require("fs"), 1);
4482
+ var path30 = __toESM(require("path"), 1);
4013
4483
  function hasSigningArtifacts(dir) {
4014
- if (!fs30.existsSync(path29.join(dir, "signatures"))) return false;
4015
- const buildProfile = path29.join(dir, "build-profile.json5");
4016
- if (!fs30.existsSync(buildProfile)) return false;
4484
+ if (!fs31.existsSync(path30.join(dir, "signatures"))) return false;
4485
+ const buildProfile = path30.join(dir, "build-profile.json5");
4486
+ if (!fs31.existsSync(buildProfile)) return false;
4017
4487
  try {
4018
4488
  const parsed = readJson5File(buildProfile);
4019
4489
  return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
@@ -4031,6 +4501,7 @@ function prepareSigning(opts) {
4031
4501
  apl: opts.apl,
4032
4502
  appFeature: opts.appFeature,
4033
4503
  acls: opts.acls,
4504
+ appPrivilegeCapabilities: opts.appPrivilegeCapabilities,
4034
4505
  passwords: opts.passwords,
4035
4506
  logger: opts.logger
4036
4507
  });
@@ -4059,19 +4530,31 @@ var PROPS = [
4059
4530
  ["model", "const.product.model"],
4060
4531
  ["manufacturer", "const.product.manufacturer"],
4061
4532
  ["brand", "const.product.brand"],
4533
+ ["osDistName", "const.product.os.dist.name"],
4534
+ ["osDistVersion", "const.product.os.dist.version"],
4062
4535
  ["osFullName", "const.ohos.fullname"],
4063
4536
  ["osReleaseType", "const.ohos.releasetype"],
4064
4537
  ["sdkApiVersion", "const.ohos.apiversion"],
4065
- ["buildVersion", "const.product.software.version"]
4538
+ ["buildVersion", "const.product.software.version"],
4539
+ ["abi", "const.product.cpu.abilist"],
4540
+ ["deviceType", "const.product.devicetype"]
4066
4541
  ];
4067
4542
  async function getDeviceInfo(opts) {
4068
4543
  const info = { serial: opts.deviceSerial ?? "(default)", display: null };
4069
- for (const [field, prop] of PROPS) {
4070
- try {
4071
- const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
4544
+ try {
4545
+ const values = await paramGetMany(
4546
+ opts.config,
4547
+ PROPS.map(([, prop]) => prop),
4548
+ { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger }
4549
+ );
4550
+ for (const [field, prop] of PROPS) {
4551
+ const value = values[prop];
4072
4552
  if (value) info[field] = value;
4073
- } catch {
4074
4553
  }
4554
+ } catch {
4555
+ }
4556
+ if (info.osDistName || info.osFullName) {
4557
+ info.osFamily = /harmonyos/i.test(info.osDistName ?? "") ? "HarmonyOS" : "OpenHarmony";
4075
4558
  }
4076
4559
  const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
4077
4560
  if (screen) info.display = { width: screen.width, height: screen.height };
@@ -4103,8 +4586,8 @@ async function diffHapAssets(opts) {
4103
4586
  }
4104
4587
 
4105
4588
  // src/install/apply.ts
4106
- var fs31 = __toESM(require("fs"), 1);
4107
- var path30 = __toESM(require("path"), 1);
4589
+ var fs32 = __toESM(require("fs"), 1);
4590
+ var path31 = __toESM(require("path"), 1);
4108
4591
  function decideInstallMethod(s) {
4109
4592
  if (!s.signInfoInconsistent) return "replace";
4110
4593
  if (s.isSystemBundle && !s.allowUninstall) return "refuse";
@@ -4122,39 +4605,31 @@ async function pidOf(config, bundle, deviceSerial) {
4122
4605
  const proc = await findRunningProcess({ config, bundle, deviceSerial });
4123
4606
  return proc ? Number(proc.pid) : null;
4124
4607
  }
4125
- async function resolveHapPath(opts) {
4126
- let hapPath = opts.hapPath;
4127
- if (hapPath) {
4128
- if (!path30.isAbsolute(hapPath) && opts.projectDir) hapPath = path30.join(opts.projectDir, hapPath);
4608
+ async function resolveHap(opts, logger) {
4609
+ let hap;
4610
+ if (opts.hapPath) {
4611
+ const hapPath = !path31.isAbsolute(opts.hapPath) && opts.projectDir ? path31.join(opts.projectDir, opts.hapPath) : opts.hapPath;
4612
+ hap = existingHapOrUnsigned(hapPath);
4613
+ if (!hap) throw new OniroError(`HAP not found at ${hapPath}.`);
4129
4614
  } else {
4130
4615
  if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
4131
- const haps = await discoverHaps({ projectDir: opts.projectDir });
4132
- if (opts.module) {
4133
- const signed = haps[opts.module]?.signed ?? [];
4134
- if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
4135
- hapPath = signed[0];
4136
- } else {
4137
- const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
4138
- if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
4139
- if (withSigned.length > 1) {
4140
- throw new OniroError(
4141
- `Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
4142
- );
4143
- }
4144
- hapPath = withSigned[0][1].signed[0];
4145
- }
4616
+ const picked = pickHap(await discoverHaps({ projectDir: opts.projectDir }), opts.module);
4617
+ if (!picked) throw new OniroError(`No HAP found${opts.module ? ` for module ${opts.module}` : ""}. Build first.`);
4618
+ hap = { path: picked.path, fallback: !picked.signed };
4146
4619
  }
4147
- if (!fs31.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
4148
- return hapPath;
4620
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path31.basename(hap.path)}.`);
4621
+ return hap;
4149
4622
  }
4150
4623
  async function applyChanges(opts) {
4151
4624
  const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
4152
4625
  const { config, bundle, deviceSerial } = opts;
4153
4626
  const isSystemBundle = opts.isSystemBundle ?? false;
4154
4627
  const allowUninstall = opts.allowUninstall ?? false;
4155
- const hapPath = await resolveHapPath(opts);
4628
+ const hap = await resolveHap(opts, logger);
4629
+ const hapPath = hap.path;
4630
+ logger.info(`installing ${hapPath}`);
4156
4631
  let assetsChanged = false;
4157
- if (opts.installedHapPath && fs31.existsSync(opts.installedHapPath)) {
4632
+ if (opts.installedHapPath && fs32.existsSync(opts.installedHapPath)) {
4158
4633
  try {
4159
4634
  const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
4160
4635
  assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
@@ -4175,12 +4650,21 @@ async function applyChanges(opts) {
4175
4650
  if (decision === "uninstall-install") {
4176
4651
  logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
4177
4652
  await uninstallApp({ config, bundle, deviceSerial });
4178
- ensureOk(await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }), `hdc install ${hapPath}`);
4653
+ ensureInstallOk(
4654
+ await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }),
4655
+ `hdc install ${hapPath}`,
4656
+ hap
4657
+ );
4179
4658
  installMethod = "uninstall-install";
4180
4659
  } else {
4181
- ensureOk(replaceRes, `hdc install -r ${hapPath}`);
4660
+ ensureInstallOk(replaceRes, `hdc install -r ${hapPath}`, hap);
4182
4661
  installMethod = "replace";
4183
4662
  }
4663
+ if (!await isBundleInstalled({ config, bundle, deviceSerial })) {
4664
+ throw new OniroError(
4665
+ `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).`
4666
+ );
4667
+ }
4184
4668
  let postInstallPid = await pidOf(config, bundle, deviceSerial);
4185
4669
  const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
4186
4670
  let method = installMethod;
@@ -4255,9 +4739,9 @@ var AUTH_PATHS = {
4255
4739
  };
4256
4740
 
4257
4741
  // src/harmonyos/auth/tokenStore.ts
4258
- var fs32 = __toESM(require("fs"), 1);
4742
+ var fs33 = __toESM(require("fs"), 1);
4259
4743
  var os11 = __toESM(require("os"), 1);
4260
- var path31 = __toESM(require("path"), 1);
4744
+ var path32 = __toESM(require("path"), 1);
4261
4745
  var crypto3 = __toESM(require("crypto"), 1);
4262
4746
  var ALGORITHM = "aes-256-gcm";
4263
4747
  var KEY_LENGTH = 32;
@@ -4293,7 +4777,7 @@ function permissionHint(dir) {
4293
4777
  }
4294
4778
  function ensureDir(dir) {
4295
4779
  try {
4296
- fs32.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4780
+ fs33.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4297
4781
  } catch (err) {
4298
4782
  if (err.code === "EACCES") {
4299
4783
  throw new OniroError(permissionHint(dir), err);
@@ -4304,17 +4788,17 @@ function ensureDir(dir) {
4304
4788
  function createTokenStore(opts) {
4305
4789
  const logger = opts.logger ?? noopLogger;
4306
4790
  const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
4307
- const tokenPath = path31.join(authDir, TOKEN_FILE);
4308
- const keyPath = path31.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4791
+ const tokenPath = path32.join(authDir, TOKEN_FILE);
4792
+ const keyPath = path32.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4309
4793
  function loadOrCreateKey() {
4310
- if (fs32.existsSync(keyPath)) {
4311
- const existing = fs32.readFileSync(keyPath);
4794
+ if (fs33.existsSync(keyPath)) {
4795
+ const existing = fs33.readFileSync(keyPath);
4312
4796
  if (existing.length === KEY_LENGTH) return existing;
4313
4797
  logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
4314
4798
  }
4315
- ensureDir(path31.dirname(keyPath));
4799
+ ensureDir(path32.dirname(keyPath));
4316
4800
  const key = crypto3.randomBytes(KEY_LENGTH);
4317
- fs32.writeFileSync(keyPath, key, { mode: FILE_MODE });
4801
+ fs33.writeFileSync(keyPath, key, { mode: FILE_MODE });
4318
4802
  return key;
4319
4803
  }
4320
4804
  return {
@@ -4324,12 +4808,12 @@ function createTokenStore(opts) {
4324
4808
  const payload = Buffer.from(JSON.stringify(session), "utf8");
4325
4809
  const blob = encrypt2(loadOrCreateKey(), payload);
4326
4810
  ensureDir(authDir);
4327
- fs32.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4811
+ fs33.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4328
4812
  },
4329
4813
  load() {
4330
- if (!fs32.existsSync(tokenPath)) return null;
4814
+ if (!fs33.existsSync(tokenPath)) return null;
4331
4815
  try {
4332
- const blob = JSON.parse(fs32.readFileSync(tokenPath, "utf8"));
4816
+ const blob = JSON.parse(fs33.readFileSync(tokenPath, "utf8"));
4333
4817
  if (!isEncryptedBlob(blob)) return null;
4334
4818
  const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
4335
4819
  return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
@@ -4341,7 +4825,7 @@ function createTokenStore(opts) {
4341
4825
  }
4342
4826
  logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
4343
4827
  try {
4344
- fs32.rmSync(tokenPath, { force: true });
4828
+ fs33.rmSync(tokenPath, { force: true });
4345
4829
  } catch {
4346
4830
  }
4347
4831
  return null;
@@ -4349,7 +4833,7 @@ function createTokenStore(opts) {
4349
4833
  },
4350
4834
  clear() {
4351
4835
  try {
4352
- fs32.rmSync(tokenPath, { force: true });
4836
+ fs33.rmSync(tokenPath, { force: true });
4353
4837
  } catch (err) {
4354
4838
  throw new OniroError(`Failed to remove ${tokenPath}.`, err);
4355
4839
  }
@@ -4463,8 +4947,8 @@ function isQuit(quit) {
4463
4947
  async function startCallbackServer(opts) {
4464
4948
  let resolveCallback;
4465
4949
  let rejectCallback;
4466
- const callback = new Promise((resolve6, reject) => {
4467
- resolveCallback = resolve6;
4950
+ const callback = new Promise((resolve7, reject) => {
4951
+ resolveCallback = resolve7;
4468
4952
  rejectCallback = reject;
4469
4953
  });
4470
4954
  callback.catch(() => {
@@ -4523,9 +5007,9 @@ async function startCallbackServer(opts) {
4523
5007
  }
4524
5008
  });
4525
5009
  server.keepAliveTimeout = 1;
4526
- await new Promise((resolve6, reject) => {
5010
+ await new Promise((resolve7, reject) => {
4527
5011
  server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
4528
- server.listen(0, "127.0.0.1", resolve6);
5012
+ server.listen(0, "127.0.0.1", resolve7);
4529
5013
  });
4530
5014
  const port = server.address().port;
4531
5015
  return {
@@ -4541,12 +5025,12 @@ async function startCallbackServer(opts) {
4541
5025
  return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
4542
5026
  },
4543
5027
  stop() {
4544
- return new Promise((resolve6) => {
5028
+ return new Promise((resolve7) => {
4545
5029
  server.closeAllConnections?.();
4546
- const timer = setTimeout(resolve6, 100);
5030
+ const timer = setTimeout(resolve7, 100);
4547
5031
  server.close(() => {
4548
5032
  clearTimeout(timer);
4549
- resolve6();
5033
+ resolve7();
4550
5034
  });
4551
5035
  });
4552
5036
  }
@@ -4578,11 +5062,11 @@ function openBrowser(url) {
4578
5062
  windowsHide: true
4579
5063
  });
4580
5064
  child.unref();
4581
- return new Promise((resolve6, reject) => {
5065
+ return new Promise((resolve7, reject) => {
4582
5066
  child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
4583
5067
  child.on(
4584
5068
  "exit",
4585
- (code) => code === 0 ? resolve6() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
5069
+ (code) => code === 0 ? resolve7() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
4586
5070
  );
4587
5071
  });
4588
5072
  }
@@ -4830,7 +5314,7 @@ function mapAgcError(operation, statusCode, reasonPhrase, body) {
4830
5314
  }
4831
5315
 
4832
5316
  // src/harmonyos/signing/profile.ts
4833
- var fs33 = __toESM(require("fs"), 1);
5317
+ var fs34 = __toESM(require("fs"), 1);
4834
5318
  var import_node_crypto3 = require("crypto");
4835
5319
  var import_node_forge = __toESM(require("node-forge"), 1);
4836
5320
  function extractProfileJson(buffer) {
@@ -4862,7 +5346,7 @@ function extractProfileJson(buffer) {
4862
5346
  function readProvisionProfile(profilePath) {
4863
5347
  let raw;
4864
5348
  try {
4865
- const json = extractProfileJson(fs33.readFileSync(profilePath));
5349
+ const json = extractProfileJson(fs34.readFileSync(profilePath));
4866
5350
  if (!json) return null;
4867
5351
  raw = JSON.parse(json);
4868
5352
  } catch {
@@ -4891,7 +5375,7 @@ function parseCertificate(pem) {
4891
5375
  }
4892
5376
  function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4893
5377
  try {
4894
- const der = import_node_forge.default.util.createBuffer(fs33.readFileSync(p12Path).toString("binary"));
5378
+ const der = import_node_forge.default.util.createBuffer(fs34.readFileSync(p12Path).toString("binary"));
4895
5379
  const p12 = import_node_forge.default.pkcs12.pkcs12FromAsn1(import_node_forge.default.asn1.fromDer(der), keyPwd);
4896
5380
  const certBag = import_node_forge.default.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
4897
5381
  const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
@@ -4907,7 +5391,7 @@ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4907
5391
  }
4908
5392
  }
4909
5393
  function verifySigningMaterial(opts) {
4910
- const pems = fs33.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
5394
+ const pems = fs34.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
4911
5395
  const chain = (pems ?? []).map(parseCertificate);
4912
5396
  const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
4913
5397
  if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
@@ -4926,14 +5410,14 @@ function verifySigningMaterial(opts) {
4926
5410
 
4927
5411
  // src/harmonyos/signing/autoSign.ts
4928
5412
  var crypto9 = __toESM(require("crypto"), 1);
4929
- var fs38 = __toESM(require("fs"), 1);
4930
- var path37 = __toESM(require("path"), 1);
5413
+ var fs39 = __toESM(require("fs"), 1);
5414
+ var path38 = __toESM(require("path"), 1);
4931
5415
  var import_json57 = __toESM(require("json5"), 1);
4932
5416
 
4933
5417
  // src/harmonyos/signing/agc.ts
4934
5418
  var crypto6 = __toESM(require("crypto"), 1);
4935
- var fs34 = __toESM(require("fs"), 1);
4936
- var path32 = __toESM(require("path"), 1);
5419
+ var fs35 = __toESM(require("fs"), 1);
5420
+ var path33 = __toESM(require("path"), 1);
4937
5421
  var ENDPOINTS = {
4938
5422
  CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
4939
5423
  CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
@@ -5019,8 +5503,8 @@ function createAgcClient(http3, auth) {
5019
5503
  if (target.sha256 && actual !== target.sha256.toLowerCase()) {
5020
5504
  throw new ChecksumMismatchError(target.sha256, actual);
5021
5505
  }
5022
- fs34.mkdirSync(path32.dirname(dest), { recursive: true, mode: 448 });
5023
- fs34.writeFileSync(dest, buffer, { mode: 384 });
5506
+ fs35.mkdirSync(path33.dirname(dest), { recursive: true, mode: 448 });
5507
+ fs35.writeFileSync(dest, buffer, { mode: 384 });
5024
5508
  }
5025
5509
  };
5026
5510
  }
@@ -5071,22 +5555,22 @@ async function registerDevices(agc, connected, logger) {
5071
5555
 
5072
5556
  // src/harmonyos/signing/keystore.ts
5073
5557
  var crypto7 = __toESM(require("crypto"), 1);
5074
- var fs36 = __toESM(require("fs"), 1);
5075
- var path34 = __toESM(require("path"), 1);
5558
+ var fs37 = __toESM(require("fs"), 1);
5559
+ var path35 = __toESM(require("path"), 1);
5076
5560
 
5077
5561
  // src/harmonyos/java.ts
5078
- var fs35 = __toESM(require("fs"), 1);
5079
- var path33 = __toESM(require("path"), 1);
5562
+ var fs36 = __toESM(require("fs"), 1);
5563
+ var path34 = __toESM(require("path"), 1);
5080
5564
  var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
5081
5565
  function findJava(sdk, env = process.env) {
5082
5566
  const candidates = [
5083
- env.JAVA_HOME && path33.join(env.JAVA_HOME, "bin", JAVA_BIN),
5084
- sdk && path33.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
5567
+ env.JAVA_HOME && path34.join(env.JAVA_HOME, "bin", JAVA_BIN),
5568
+ sdk && path34.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
5085
5569
  // macOS: the JBR is itself a bundle inside Studio's Contents.
5086
- sdk && path33.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
5087
- ...(env.PATH ?? "").split(path33.delimiter).map((dir) => dir && path33.join(dir, JAVA_BIN))
5570
+ sdk && path34.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
5571
+ ...(env.PATH ?? "").split(path34.delimiter).map((dir) => dir && path34.join(dir, JAVA_BIN))
5088
5572
  ];
5089
- const found = candidates.find((c) => !!c && fs35.existsSync(c));
5573
+ const found = candidates.find((c) => !!c && fs36.existsSync(c));
5090
5574
  if (found) return found;
5091
5575
  throw new OniroError(
5092
5576
  "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."
@@ -5100,13 +5584,13 @@ function redactArgs(args) {
5100
5584
  return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
5101
5585
  }
5102
5586
  async function generateKeystoreAndCsr(opts) {
5103
- const jar = path34.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
5104
- if (!fs36.existsSync(jar)) {
5587
+ const jar = path35.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
5588
+ if (!fs37.existsSync(jar)) {
5105
5589
  throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
5106
5590
  }
5107
5591
  const java = findJava(opts.sdk);
5108
5592
  const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
5109
- fs36.mkdirSync(path34.dirname(opts.p12Path), { recursive: true, mode: 448 });
5593
+ fs37.mkdirSync(path35.dirname(opts.p12Path), { recursive: true, mode: 448 });
5110
5594
  const run = async (what, args) => {
5111
5595
  const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
5112
5596
  opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
@@ -5120,28 +5604,28 @@ ${detail}` : ""}`);
5120
5604
  opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
5121
5605
  await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
5122
5606
  await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
5123
- return { keyPwd, csr: fs36.readFileSync(opts.csrPath, "utf8") };
5607
+ return { keyPwd, csr: fs37.readFileSync(opts.csrPath, "utf8") };
5124
5608
  }
5125
5609
 
5126
5610
  // src/harmonyos/signing/paths.ts
5127
5611
  var crypto8 = __toESM(require("crypto"), 1);
5128
- var path35 = __toESM(require("path"), 1);
5612
+ var path36 = __toESM(require("path"), 1);
5129
5613
  function sanitizeNamePart(raw) {
5130
5614
  const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
5131
5615
  return safe.slice(0, 64) || "default";
5132
5616
  }
5133
5617
  function resolveSigningMaterialPaths(config, productName, projectRoot) {
5134
5618
  const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
5135
- const base = path35.join(
5619
+ const base = path36.join(
5136
5620
  config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
5137
- `${sanitizeNamePart(productName)}_${path35.basename(projectRoot)}_${hash}=`
5621
+ `${sanitizeNamePart(productName)}_${path36.basename(projectRoot)}_${hash}=`
5138
5622
  );
5139
5623
  return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
5140
5624
  }
5141
5625
 
5142
5626
  // src/harmonyos/signing/project.ts
5143
- var fs37 = __toESM(require("fs"), 1);
5144
- var path36 = __toESM(require("path"), 1);
5627
+ var fs38 = __toESM(require("fs"), 1);
5628
+ var path37 = __toESM(require("path"), 1);
5145
5629
  var import_json56 = __toESM(require("json5"), 1);
5146
5630
 
5147
5631
  // src/harmonyos/signing/aclPermissionList.ts
@@ -5209,9 +5693,9 @@ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
5209
5693
 
5210
5694
  // src/harmonyos/signing/project.ts
5211
5695
  function readRequestedPermissions(moduleConfigPath) {
5212
- if (!fs37.existsSync(moduleConfigPath)) return [];
5696
+ if (!fs38.existsSync(moduleConfigPath)) return [];
5213
5697
  try {
5214
- const parsed = import_json56.default.parse(fs37.readFileSync(moduleConfigPath, "utf-8"));
5698
+ const parsed = import_json56.default.parse(fs38.readFileSync(moduleConfigPath, "utf-8"));
5215
5699
  return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
5216
5700
  } catch {
5217
5701
  return [];
@@ -5220,10 +5704,10 @@ function readRequestedPermissions(moduleConfigPath) {
5220
5704
  function collectAclPermissions(projectDir, logger = noopLogger) {
5221
5705
  const requested = /* @__PURE__ */ new Set();
5222
5706
  for (const module2 of listModules({ projectDir })) {
5223
- const moduleRoot = path36.resolve(projectDir, module2.srcPath);
5707
+ const moduleRoot = path37.resolve(projectDir, module2.srcPath);
5224
5708
  for (const sourceSet of ["main", "ohosTest"]) {
5225
5709
  for (const name of readRequestedPermissions(
5226
- path36.join(moduleRoot, "src", sourceSet, "module.json5")
5710
+ path37.join(moduleRoot, "src", sourceSet, "module.json5")
5227
5711
  )) {
5228
5712
  requested.add(name);
5229
5713
  }
@@ -5239,7 +5723,7 @@ function collectAclPermissions(projectDir, logger = noopLogger) {
5239
5723
  // src/harmonyos/signing/autoSign.ts
5240
5724
  function shouldRegenerate(input) {
5241
5725
  if (input.force) return "--force was passed";
5242
- const missing = Object.values(input.paths).find((file) => !fs38.existsSync(file));
5726
+ const missing = Object.values(input.paths).find((file) => !fs39.existsSync(file));
5243
5727
  if (missing) return `${missing} is missing`;
5244
5728
  const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
5245
5729
  if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
@@ -5278,16 +5762,16 @@ function writeSigningConfig(opts) {
5278
5762
  for (const product of app.products ?? []) {
5279
5763
  if (product.name === opts.productName) product.signingConfig = opts.productName;
5280
5764
  }
5281
- fs38.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5765
+ fs39.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5282
5766
  }
5283
5767
  async function harmonyOsAutoSign(opts) {
5284
5768
  const logger = opts.logger ?? noopLogger;
5285
5769
  const productName = opts.productName ?? "default";
5286
- const projectDir = path37.resolve(opts.projectDir);
5287
- const buildProfilePath = path37.join(projectDir, "build-profile.json5");
5770
+ const projectDir = path38.resolve(opts.projectDir);
5771
+ const buildProfilePath = path38.join(projectDir, "build-profile.json5");
5288
5772
  let buildProfile;
5289
5773
  try {
5290
- buildProfile = import_json57.default.parse(fs38.readFileSync(buildProfilePath, "utf-8"));
5774
+ buildProfile = import_json57.default.parse(fs39.readFileSync(buildProfilePath, "utf-8"));
5291
5775
  } catch (err) {
5292
5776
  throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
5293
5777
  }
@@ -5327,7 +5811,7 @@ async function harmonyOsAutoSign(opts) {
5327
5811
  logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
5328
5812
  const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
5329
5813
  const deviceIds = await registerDevices(agc, connected, logger);
5330
- for (const file of Object.values(paths)) fs38.rmSync(file, { force: true });
5814
+ for (const file of Object.values(paths)) fs39.rmSync(file, { force: true });
5331
5815
  const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
5332
5816
  const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
5333
5817
  if (stale) {
@@ -5358,10 +5842,10 @@ async function harmonyOsAutoSign(opts) {
5358
5842
  try {
5359
5843
  verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
5360
5844
  } catch (err) {
5361
- fs38.rmSync(paths.profilePath, { force: true });
5845
+ fs39.rmSync(paths.profilePath, { force: true });
5362
5846
  throw err;
5363
5847
  }
5364
- const materialDir = path37.join(path37.dirname(paths.p12Path), "material");
5848
+ const materialDir = path38.join(path38.dirname(paths.p12Path), "material");
5365
5849
  try {
5366
5850
  getKey(materialDir);
5367
5851
  } catch {
@@ -5373,7 +5857,7 @@ async function harmonyOsAutoSign(opts) {
5373
5857
  }
5374
5858
 
5375
5859
  // src/index.ts
5376
- var VERSION = "0.13.0";
5860
+ var VERSION = "0.14.1";
5377
5861
  // Annotate the CommonJS export names for ESM import in node:
5378
5862
  0 && (module.exports = {
5379
5863
  ALL_SDKS,
@@ -5396,7 +5880,9 @@ var VERSION = "0.13.0";
5396
5880
  TargetNotFoundError,
5397
5881
  UnsupportedPlatformError,
5398
5882
  VERSION,
5883
+ appSandboxDir,
5399
5884
  applyChanges,
5885
+ assertDevicePath,
5400
5886
  assertLayoutSelector,
5401
5887
  attemptHdcConnection,
5402
5888
  buildCmdWrapper,
@@ -5407,6 +5893,8 @@ var VERSION = "0.13.0";
5407
5893
  buildInputCommand,
5408
5894
  buildRawTouchCommand,
5409
5895
  captureBurst,
5896
+ captureLog,
5897
+ cleanCodelinterOutput,
5410
5898
  consoleLogger,
5411
5899
  createHarmonyOsSession,
5412
5900
  createInstallTempDir,
@@ -5422,9 +5910,12 @@ var VERSION = "0.13.0";
5422
5910
  defaultPaths,
5423
5911
  describeLayoutNode,
5424
5912
  detectAaStartFailure,
5913
+ detectCmdToolsFlavour,
5914
+ detectInstallFailure,
5425
5915
  detectProjectSdkVersion,
5426
5916
  detectRuntimeOs,
5427
5917
  detectSigningConfigNames,
5918
+ diagnoseBuildFailure,
5428
5919
  diffEntryNames,
5429
5920
  diffHapAssets,
5430
5921
  discoverHaps,
@@ -5437,11 +5928,13 @@ var VERSION = "0.13.0";
5437
5928
  dumpWindow,
5438
5929
  encryptPwd,
5439
5930
  ensureFreeSpace,
5931
+ ensureInstallOk,
5440
5932
  ensureOhModules,
5441
5933
  ensureOk,
5442
5934
  ensureRemoteOk,
5443
5935
  escapeCmdArgument,
5444
5936
  escapeCmdCommand,
5937
+ existingHapOrUnsigned,
5445
5938
  extractTarball,
5446
5939
  extractZipWithProgress,
5447
5940
  findAppProcessId,
@@ -5450,6 +5943,7 @@ var VERSION = "0.13.0";
5450
5943
  findHarmonyOsSdk,
5451
5944
  findKeyboardWindow,
5452
5945
  findLayoutNodes,
5946
+ findMissingProjectSdk,
5453
5947
  findRunningProcess,
5454
5948
  flattenLayout,
5455
5949
  forceStop,
@@ -5463,6 +5957,7 @@ var VERSION = "0.13.0";
5463
5957
  getDeviceInfo,
5464
5958
  getDisplaySize,
5465
5959
  getEmulatorDir,
5960
+ getEmulatorStatus,
5466
5961
  getFreeSpaceBytes,
5467
5962
  getHdcPath,
5468
5963
  getHvigorwPath,
@@ -5478,15 +5973,21 @@ var VERSION = "0.13.0";
5478
5973
  getSupportedSdksForUi,
5479
5974
  harmonyOsAutoSign,
5480
5975
  harmonyOsSdkLabelForApi,
5976
+ hdcClientFailure,
5481
5977
  hdcExec,
5978
+ hdcTargetArgs,
5979
+ hdcTargetArgsFor,
5482
5980
  hideKeyboard,
5483
5981
  installApp,
5484
5982
  installCmdTools,
5485
5983
  installEmulator,
5984
+ installFailureReason,
5985
+ isBundleInstalled,
5486
5986
  isCmdToolsInstalled,
5487
5987
  isEmulatorInstalled,
5488
5988
  isOutOfSpaceError,
5489
5989
  isRamBackedPath,
5990
+ isUnsignedHap,
5490
5991
  isValidBundleName,
5491
5992
  isValidProjectName,
5492
5993
  killProcessTree,
@@ -5501,16 +6002,21 @@ var VERSION = "0.13.0";
5501
6002
  nonInteractivePrompter,
5502
6003
  noopLogger,
5503
6004
  noopProgress,
6005
+ normaliseLocalPath,
5504
6006
  paramGet,
6007
+ paramGetMany,
5505
6008
  paramSet,
5506
6009
  parseBounds,
5507
6010
  parseCodelinterFindings,
6011
+ parseCodelinterReport,
5508
6012
  parseDeviceList,
5509
6013
  parseHilogLine,
5510
6014
  parseImeBundles,
5511
6015
  parseMainAbilityFromBmDump,
6016
+ parseParamValues,
5512
6017
  parseRemoteExit,
5513
6018
  performInput,
6019
+ pickHap,
5514
6020
  pickSigningKind,
5515
6021
  pickTarget,
5516
6022
  prepareSigning,
@@ -5525,6 +6031,7 @@ var VERSION = "0.13.0";
5525
6031
  removeSdk,
5526
6032
  removeTempWorkDir,
5527
6033
  requireHarmonyOsSdk,
6034
+ resolveDeviceSerial,
5528
6035
  resolveKeyEvent,
5529
6036
  resolveTmpRoot,
5530
6037
  runCodelinter,
@@ -5549,6 +6056,7 @@ var VERSION = "0.13.0";
5549
6056
  toLongPath,
5550
6057
  toSpaceError,
5551
6058
  uninstallApp,
6059
+ unsignedHapRejection,
5552
6060
  validateTemplateLayout,
5553
6061
  verifySha256,
5554
6062
  waitForBoot,