@oniroproject/core 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -15,6 +15,15 @@ var consoleLogger = {
15
15
  warn: (m) => console.warn(m),
16
16
  error: (m) => console.error(m)
17
17
  };
18
+ function scopedLogger(logger, scope) {
19
+ const prefix = `[${scope}] `;
20
+ return {
21
+ debug: (m) => logger.debug(prefix + m),
22
+ info: (m) => logger.info(prefix + m),
23
+ warn: (m) => logger.warn(prefix + m),
24
+ error: (m) => logger.error(prefix + m)
25
+ };
26
+ }
18
27
 
19
28
  // src/ports/progress.ts
20
29
  var noopProgress = {
@@ -96,9 +105,9 @@ var CmdToolsNotInstalledError = class extends OniroError {
96
105
  expectedPath;
97
106
  };
98
107
  var UnsupportedPlatformError = class extends OniroError {
99
- constructor(platform6) {
100
- super(`Unsupported platform: ${platform6}.`);
101
- this.platform = platform6;
108
+ constructor(platform7) {
109
+ super(`Unsupported platform: ${platform7}.`);
110
+ this.platform = platform7;
102
111
  this.name = "UnsupportedPlatformError";
103
112
  }
104
113
  platform;
@@ -113,12 +122,33 @@ var ChecksumMismatchError = class extends OniroError {
113
122
  expected;
114
123
  actual;
115
124
  };
125
+ var InsufficientSpaceError = class extends OniroError {
126
+ constructor(message, dir, cause) {
127
+ super(message, cause);
128
+ this.dir = dir;
129
+ this.name = "InsufficientSpaceError";
130
+ }
131
+ dir;
132
+ };
116
133
  var CancelledError = class extends OniroError {
117
134
  constructor(message = "Operation cancelled.") {
118
135
  super(message);
119
136
  this.name = "CancelledError";
120
137
  }
121
138
  };
139
+ var CommandFailedError = class extends OniroError {
140
+ constructor(command, exitCode, stderr) {
141
+ super(`Command failed (exit ${exitCode}): ${command}${stderr ? `
142
+ ${stderr}` : ""}`);
143
+ this.command = command;
144
+ this.exitCode = exitCode;
145
+ this.stderr = stderr;
146
+ this.name = "CommandFailedError";
147
+ }
148
+ command;
149
+ exitCode;
150
+ stderr;
151
+ };
122
152
 
123
153
  // src/ports/context.ts
124
154
  function defaultContext(overrides = {}) {
@@ -130,45 +160,45 @@ function defaultContext(overrides = {}) {
130
160
 
131
161
  // src/sdk/constants.ts
132
162
  var ALL_SDKS = [
133
- { version: "4.0", api: "10" },
134
- { version: "4.1", api: "11" },
135
- { version: "5.0.0", api: "12" },
136
- { version: "5.0.1", api: "13" },
137
- { version: "5.0.2", api: "14" },
138
- { version: "5.0.3", api: "15" },
139
- { version: "5.1.0", api: "18" },
140
- { version: "6.0", api: "20" },
141
- { version: "6.1", api: "23" }
163
+ { version: "4.0", api: "10", tarballStrip: { linuxWindows: 1, darwin: 3 } },
164
+ { version: "4.1", api: "11", tarballStrip: { linuxWindows: 1, darwin: 3 } },
165
+ { version: "5.0.0", api: "12", tarballStrip: { linuxWindows: 0, darwin: 3 } },
166
+ { version: "5.0.1", api: "13", tarballStrip: { linuxWindows: 0, darwin: 3 } },
167
+ { version: "5.0.2", api: "14", tarballStrip: { linuxWindows: 1, darwin: 3 } },
168
+ { version: "5.0.3", api: "15", tarballStrip: { linuxWindows: 1, darwin: 3 } },
169
+ { version: "5.1.0", api: "18", tarballStrip: { linuxWindows: 1, darwin: 3 } },
170
+ { version: "5.1.1", api: "19", tarballStrip: { linuxWindows: 1, darwin: 3 } },
171
+ { version: "6.0", api: "20", tarballStrip: { linuxWindows: 0, darwin: 3 } },
172
+ { version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } }
142
173
  ];
143
174
  var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
144
175
 
145
176
  // src/sdk/platform.ts
146
177
  import * as os2 from "os";
147
178
  function getOsFolder() {
148
- const platform6 = os2.platform();
149
- if (platform6 === "linux") return "linux";
150
- if (platform6 === "darwin") return "darwin";
151
- if (platform6 === "win32") return "windows";
152
- throw new UnsupportedPlatformError(platform6);
179
+ const platform7 = os2.platform();
180
+ if (platform7 === "linux") return "linux";
181
+ if (platform7 === "darwin") return "darwin";
182
+ if (platform7 === "win32") return "windows";
183
+ throw new UnsupportedPlatformError(platform7);
153
184
  }
154
185
  function getSdkFilename(version) {
155
- const platform6 = os2.platform();
186
+ const platform7 = os2.platform();
156
187
  const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
157
- if (platform6 === "linux") {
158
- const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
159
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip };
188
+ const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
189
+ if (platform7 === "linux") {
190
+ return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
160
191
  }
161
- if (platform6 === "darwin") {
192
+ if (platform7 === "darwin") {
162
193
  if (os2.arch() === "arm64") {
163
- return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: 3 };
194
+ return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
164
195
  }
165
- return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: 3 };
196
+ return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
166
197
  }
167
- if (platform6 === "win32") {
168
- const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
169
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip };
198
+ if (platform7 === "win32") {
199
+ return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
170
200
  }
171
- throw new UnsupportedPlatformError(platform6);
201
+ throw new UnsupportedPlatformError(platform7);
172
202
  }
173
203
 
174
204
  // src/sdk/paths.ts
@@ -193,19 +223,24 @@ function pickExisting(candidates) {
193
223
  }
194
224
  return candidates[0];
195
225
  }
196
- function getCmdToolsBin(config) {
226
+ function getCmdToolsBin(config, name = "ohpm") {
197
227
  const binDir = path2.join(getCmdToolsPath(config), "bin");
198
228
  if (os3.platform() === "win32") {
199
229
  return pickExisting([
200
- path2.join(binDir, "ohpm.exe"),
201
- path2.join(binDir, "ohpm.cmd"),
202
- path2.join(binDir, "ohpm.bat"),
203
- path2.join(binDir, "ohpm")
230
+ path2.join(binDir, `${name}.exe`),
231
+ path2.join(binDir, `${name}.cmd`),
232
+ path2.join(binDir, `${name}.bat`),
233
+ path2.join(binDir, name)
204
234
  ]);
205
235
  }
206
- return path2.join(binDir, "ohpm");
236
+ return path2.join(binDir, name);
237
+ }
238
+ function getOhpmPath(config) {
239
+ return getCmdToolsBin(config, "ohpm");
207
240
  }
208
241
  function getHdcPath(config) {
242
+ const override = config.get("hdcPath", "");
243
+ if (override) return override;
209
244
  const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
210
245
  if (os3.platform() === "win32") {
211
246
  return pickExisting([
@@ -218,15 +253,20 @@ function getHdcPath(config) {
218
253
  return path2.join(base, "hdc");
219
254
  }
220
255
  function getHvigorwPath(config, projectDir) {
221
- const cmdToolsBin = path2.join(getCmdToolsPath(config), "bin");
222
- return pickExisting([
256
+ const local = pickExisting([
223
257
  path2.join(projectDir, "hvigorw"),
224
258
  path2.join(projectDir, "hvigorw.bat"),
225
- path2.join(projectDir, "hvigorw.cmd"),
226
- path2.join(cmdToolsBin, "hvigorw"),
227
- path2.join(cmdToolsBin, "hvigorw.bat"),
228
- path2.join(cmdToolsBin, "hvigorw.cmd")
259
+ path2.join(projectDir, "hvigorw.cmd")
229
260
  ]);
261
+ if (fs.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
262
+ return local;
263
+ }
264
+ return getCmdToolsBin(config, "hvigorw");
265
+ }
266
+ function isProjectLocalHvigorwWorking(projectDir) {
267
+ const wrapper = path2.join(projectDir, "hvigor", "hvigor-wrapper.js");
268
+ const nodeModules = path2.join(projectDir, "hvigor", "node_modules");
269
+ return fs.existsSync(wrapper) && fs.existsSync(nodeModules);
230
270
  }
231
271
 
232
272
  // src/sdk/detectProjectSdk.ts
@@ -251,8 +291,164 @@ function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
251
291
  }
252
292
  }
253
293
 
254
- // src/sdk/download.ts
294
+ // src/sdk/tmp.ts
255
295
  import * as fs3 from "fs";
296
+ import * as os4 from "os";
297
+ import * as path4 from "path";
298
+ var INSTALL_TMP_DIRNAME = ".oniro-tmp";
299
+ var TMPFS_MAGIC = 16914836;
300
+ var RAMFS_MAGIC = 2240043254;
301
+ var HINT_EXAMPLE = os4.platform() === "win32" ? "D:\\oniro-tmp" : "~/.cache/oniro-tmp";
302
+ var TMP_DIR_HINT = `Point the scratch directory at a filesystem with more room using --tmp-dir <path> (or the ONIRO_TMP_DIR environment variable), e.g. --tmp-dir ${HINT_EXAMPLE}.`;
303
+ function resolveTmpRoot(config, override, installRoot) {
304
+ const explicit = (override ?? "").trim();
305
+ if (explicit) return path4.resolve(explicit);
306
+ const configured = config.get("tmpDir", "").trim();
307
+ if (configured) return path4.resolve(configured);
308
+ if (installRoot) return path4.join(path4.resolve(installRoot), INSTALL_TMP_DIRNAME);
309
+ return os4.tmpdir();
310
+ }
311
+ function createTempWorkDir(root, prefix) {
312
+ try {
313
+ fs3.mkdirSync(root, { recursive: true });
314
+ return fs3.mkdtempSync(path4.join(root, prefix));
315
+ } catch (err) {
316
+ if (isOutOfSpaceError(err)) throw toSpaceError(err, root, "a temporary directory");
317
+ throw new OniroError(
318
+ `Cannot create a temporary directory in '${root}': ${err instanceof Error ? err.message : String(err)}
319
+ ${TMP_DIR_HINT}`,
320
+ err
321
+ );
322
+ }
323
+ }
324
+ function removeTempWorkDir(dir, root) {
325
+ fs3.rmSync(dir, { recursive: true, force: true });
326
+ if (!root || path4.basename(root) !== INSTALL_TMP_DIRNAME) return;
327
+ try {
328
+ fs3.rmdirSync(root);
329
+ } catch {
330
+ }
331
+ }
332
+ function createInstallTempDir(opts) {
333
+ const requested = (opts.override ?? "").trim() || opts.config.get("tmpDir", "").trim();
334
+ const root = resolveTmpRoot(opts.config, opts.override, opts.installRoot);
335
+ try {
336
+ return { dir: createTempWorkDir(root, opts.prefix), root };
337
+ } catch (err) {
338
+ const fallback = os4.tmpdir();
339
+ if (requested || isOutOfSpaceError(err) || path4.resolve(fallback) === path4.resolve(root)) throw err;
340
+ const reason = err instanceof Error ? err.message.split("\n")[0] : String(err);
341
+ (opts.logger ?? noopLogger).warn(`${reason} Falling back to '${fallback}'.`);
342
+ return { dir: createTempWorkDir(fallback, opts.prefix), root: fallback };
343
+ }
344
+ }
345
+ function nearestExisting(dir) {
346
+ let current = path4.resolve(dir);
347
+ for (; ; ) {
348
+ if (fs3.existsSync(current)) return current;
349
+ const parent = path4.dirname(current);
350
+ if (parent === current) return null;
351
+ current = parent;
352
+ }
353
+ }
354
+ function getFreeSpaceBytes(dir) {
355
+ const target = nearestExisting(dir);
356
+ if (!target) return null;
357
+ try {
358
+ const stats = fs3.statfsSync(target);
359
+ return Number(stats.bsize) * Number(stats.bavail);
360
+ } catch {
361
+ return null;
362
+ }
363
+ }
364
+ function isRamBackedPath(dir) {
365
+ const target = nearestExisting(dir);
366
+ if (!target) return false;
367
+ try {
368
+ const type = Number(fs3.statfsSync(target).type);
369
+ return type === TMPFS_MAGIC || type === RAMFS_MAGIC;
370
+ } catch {
371
+ return false;
372
+ }
373
+ }
374
+ function formatBytes(bytes) {
375
+ const units = ["B", "KiB", "MiB", "GiB", "TiB"];
376
+ let value = Math.max(0, bytes);
377
+ let unit = 0;
378
+ while (value >= 1024 && unit < units.length - 1) {
379
+ value /= 1024;
380
+ unit++;
381
+ }
382
+ return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
383
+ }
384
+ function isOutOfSpaceError(err) {
385
+ if (err instanceof InsufficientSpaceError) return true;
386
+ const seen = /* @__PURE__ */ new Set();
387
+ let current = err;
388
+ while (current && typeof current === "object" && !seen.has(current)) {
389
+ seen.add(current);
390
+ const e = current;
391
+ if (e.code === "ENOSPC" || e.code === "EDQUOT") return true;
392
+ if (typeof e.message === "string" && /ENOSPC|EDQUOT|no space left on device/i.test(e.message)) return true;
393
+ current = e.cause;
394
+ }
395
+ return false;
396
+ }
397
+ function spaceMessage(dir, what, opts) {
398
+ const need = typeof opts.requiredBytes === "number" ? `needs about ${formatBytes(opts.requiredBytes)}` : null;
399
+ const free = typeof opts.freeBytes === "number" ? `${formatBytes(opts.freeBytes)} available` : null;
400
+ const amounts = [need, free].filter(Boolean).join(", ");
401
+ const lines = [`Not enough free space in '${dir}' for ${what}${amounts ? ` (${amounts})` : ""}.`];
402
+ if (isRamBackedPath(dir)) {
403
+ lines.push(
404
+ `'${dir}' is on a RAM-backed filesystem (tmpfs), so its capacity is limited by free memory, not by free disk.`
405
+ );
406
+ }
407
+ if (opts.tmpDirHint) lines.push(TMP_DIR_HINT);
408
+ return lines.join("\n");
409
+ }
410
+ function ensureFreeSpace(dir, requiredBytes, what) {
411
+ if (!Number.isFinite(requiredBytes) || requiredBytes <= 0) return;
412
+ const freeBytes = getFreeSpaceBytes(dir);
413
+ if (freeBytes === null || freeBytes >= requiredBytes) return;
414
+ throw new InsufficientSpaceError(
415
+ spaceMessage(dir, what, { requiredBytes, freeBytes, tmpDirHint: true }),
416
+ dir
417
+ );
418
+ }
419
+ function errorPath(err) {
420
+ const seen = /* @__PURE__ */ new Set();
421
+ let current = err;
422
+ while (current && typeof current === "object" && !seen.has(current)) {
423
+ seen.add(current);
424
+ const e = current;
425
+ if (typeof e.path === "string" && e.path) return e.path;
426
+ if (typeof e.dest === "string" && e.dest) return e.dest;
427
+ current = e.cause;
428
+ }
429
+ return null;
430
+ }
431
+ function isInside(child, parent) {
432
+ const rel = path4.relative(path4.resolve(parent), path4.resolve(child));
433
+ return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
434
+ }
435
+ function toSpaceError(err, tmpRoot, what) {
436
+ if (err instanceof InsufficientSpaceError) return err;
437
+ if (!isOutOfSpaceError(err)) return err;
438
+ const failed = errorPath(err);
439
+ const failedDir = failed ? path4.dirname(path4.resolve(failed)) : path4.resolve(tmpRoot);
440
+ const inTmp = isInside(failedDir, tmpRoot);
441
+ const dir = inTmp ? path4.resolve(tmpRoot) : failedDir;
442
+ return new InsufficientSpaceError(
443
+ spaceMessage(dir, what, { freeBytes: getFreeSpaceBytes(dir), tmpDirHint: inTmp }),
444
+ dir,
445
+ err
446
+ );
447
+ }
448
+
449
+ // src/sdk/download.ts
450
+ import * as fs4 from "fs";
451
+ import * as path5 from "path";
256
452
  import * as crypto from "crypto";
257
453
  import { pipeline } from "stream";
258
454
  import { promisify } from "util";
@@ -262,36 +458,57 @@ var pipelineAsync = promisify(pipeline);
262
458
  async function downloadFile(opts) {
263
459
  const { url, dest, progress, abortSignal } = opts;
264
460
  const proto = url.startsWith("https") ? https : http;
265
- return new Promise((resolve2, reject) => {
461
+ return new Promise((resolve4, reject) => {
266
462
  const s = Math.max(0, Math.min(100, opts.start ?? 0));
267
463
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
268
464
  let settled = false;
269
465
  const done = (err) => {
270
466
  if (settled) return;
271
467
  settled = true;
272
- err ? reject(err) : resolve2();
468
+ err ? reject(err) : resolve4();
273
469
  };
274
470
  if (abortSignal?.aborted) {
275
471
  done(new CancelledError("Download cancelled before start."));
276
472
  return;
277
473
  }
278
- const file = fs3.createWriteStream(dest);
474
+ const destDir = path5.dirname(path5.resolve(dest));
475
+ const what = opts.what ?? `the download of '${url}'`;
476
+ const writeFailed = (err) => isOutOfSpaceError(err) ? toSpaceError(err, opts.tmpRoot ?? destDir, what) : new OniroError(`Error writing '${dest}': ${err instanceof Error ? err.message : String(err)}`, err);
477
+ const file = fs4.createWriteStream(dest);
478
+ let activeResponse;
479
+ const failWith = (err) => {
480
+ file.close(() => fs4.rm(dest, { force: true }, () => done(err)));
481
+ };
482
+ file.on("error", (err) => {
483
+ try {
484
+ activeResponse?.destroy?.();
485
+ } catch {
486
+ }
487
+ failWith(writeFailed(err));
488
+ });
279
489
  const req = proto.get(url, (response) => {
490
+ activeResponse = response;
280
491
  if (response.statusCode !== 200) {
281
492
  try {
282
493
  response.destroy();
283
494
  } catch {
284
495
  }
285
- try {
286
- file.close();
287
- } catch {
288
- }
289
- fs3.unlink(dest, () => {
290
- });
291
- done(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
496
+ failWith(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
292
497
  return;
293
498
  }
294
499
  const total = parseInt(response.headers["content-length"] || "0", 10);
500
+ if (total > 0) {
501
+ try {
502
+ ensureFreeSpace(opts.tmpRoot ?? destDir, total, what);
503
+ } catch (err) {
504
+ try {
505
+ response.destroy();
506
+ } catch {
507
+ }
508
+ failWith(err);
509
+ return;
510
+ }
511
+ }
295
512
  let downloaded = 0;
296
513
  let lastOverall = Math.round(s);
297
514
  response.on("data", (chunk) => {
@@ -315,38 +532,26 @@ async function downloadFile(opts) {
315
532
  const inc = endOverall - lastOverall;
316
533
  if (inc > 0) progress.report({ message: "Downloading: 100%", increment: inc });
317
534
  }
318
- file.close((err) => err ? done(err) : done());
535
+ file.close((err) => err ? done(writeFailed(err)) : done());
319
536
  });
320
537
  abortSignal?.addEventListener("abort", () => {
321
538
  response.destroy();
322
- file.close();
323
- fs3.unlink(dest, () => {
324
- });
325
- done(new CancelledError("Download cancelled."));
539
+ failWith(new CancelledError("Download cancelled."));
326
540
  });
327
541
  });
328
542
  req.on("error", (err) => {
329
- try {
330
- file.close();
331
- } catch {
332
- }
333
- fs3.unlink(dest, () => {
334
- });
335
- done(new OniroError(`Error downloading '${url}': ${err.message}`, err));
543
+ failWith(new OniroError(`Error downloading '${url}': ${err.message}`, err));
336
544
  });
337
545
  abortSignal?.addEventListener("abort", () => {
338
546
  req.destroy();
339
- file.close();
340
- fs3.unlink(dest, () => {
341
- });
342
- done(new CancelledError("Download cancelled."));
547
+ failWith(new CancelledError("Download cancelled."));
343
548
  });
344
549
  });
345
550
  }
346
551
  async function verifySha256(filePath, sha256Path) {
347
- const expected = fs3.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
552
+ const expected = fs4.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
348
553
  const hash = crypto.createHash("sha256");
349
- await pipelineAsync(fs3.createReadStream(filePath), hash);
554
+ await pipelineAsync(fs4.createReadStream(filePath), hash);
350
555
  const actual = hash.digest("hex");
351
556
  if (actual !== expected) {
352
557
  throw new ChecksumMismatchError(expected, actual);
@@ -354,9 +559,9 @@ async function verifySha256(filePath, sha256Path) {
354
559
  }
355
560
 
356
561
  // src/sdk/extract.ts
357
- import * as fs4 from "fs";
358
- import * as os4 from "os";
359
- import * as path4 from "path";
562
+ import * as fs5 from "fs";
563
+ import * as os5 from "os";
564
+ import * as path6 from "path";
360
565
  import { pipeline as pipeline2 } from "stream";
361
566
  import { promisify as promisify2 } from "util";
362
567
  import * as tar from "tar";
@@ -373,12 +578,12 @@ async function extractZipWithProgress(opts) {
373
578
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
374
579
  let processed = 0;
375
580
  let lastOverall = Math.round(s);
376
- await fs4.promises.mkdir(dest, { recursive: true });
377
- const destRoot = path4.resolve(dest);
581
+ await fs5.promises.mkdir(dest, { recursive: true });
582
+ const destRoot = path6.resolve(dest);
378
583
  const safeResolveTarget = (entryName) => {
379
- const resolved = path4.resolve(destRoot, entryName);
380
- const rel = path4.relative(destRoot, resolved);
381
- if (rel.startsWith("..") || path4.isAbsolute(rel)) {
584
+ const resolved = path6.resolve(destRoot, entryName);
585
+ const rel = path6.relative(destRoot, resolved);
586
+ if (rel.startsWith("..") || path6.isAbsolute(rel)) {
382
587
  throw new OniroError(`Blocked ZIP entry with illegal path: ${entryName}`);
383
588
  }
384
589
  return resolved;
@@ -390,25 +595,25 @@ async function extractZipWithProgress(opts) {
390
595
  const attr = file.attr ? file.attr >>> 16 : 0;
391
596
  const isSymlink = "isSymbolicLink" in file ? Boolean(file.isSymbolicLink) : (attr & 61440) === 40960;
392
597
  if (file.isDirectory) {
393
- await fs4.promises.mkdir(targetPath, { recursive: true });
598
+ await fs5.promises.mkdir(targetPath, { recursive: true });
394
599
  } else if (isSymlink) {
395
- await fs4.promises.mkdir(path4.dirname(targetPath), { recursive: true });
600
+ await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
396
601
  const linkTargetBuffer = await zip.entryData(file);
397
602
  const linkTarget = linkTargetBuffer.toString("utf8");
398
603
  try {
399
- const isWin = os4.platform() === "win32";
400
- await fs4.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
604
+ const isWin = os5.platform() === "win32";
605
+ await fs5.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
401
606
  } catch (e) {
402
607
  logger.warn(`Failed to create symlink at ${targetPath}: ${String(e)}`);
403
608
  }
404
609
  } else {
405
- await fs4.promises.mkdir(path4.dirname(targetPath), { recursive: true });
610
+ await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
406
611
  const readStream = await zip.stream(file);
407
- const writeStream = fs4.createWriteStream(targetPath);
612
+ const writeStream = fs5.createWriteStream(targetPath);
408
613
  await pipelineAsync2(readStream, writeStream);
409
614
  if (attr > 0) {
410
615
  try {
411
- await fs4.promises.chmod(targetPath, attr & 511);
616
+ await fs5.promises.chmod(targetPath, attr & 511);
412
617
  } catch {
413
618
  }
414
619
  }
@@ -440,41 +645,41 @@ async function extractTarball(tarPath, dest, strip = 0) {
440
645
  }
441
646
 
442
647
  // src/sdk/list.ts
443
- import * as fs5 from "fs";
444
- import * as path5 from "path";
648
+ import * as fs6 from "fs";
649
+ import * as path7 from "path";
445
650
  function getSupportedSdksForUi(config) {
446
- const base = path5.join(getSdkRootDir(config), getOsFolder());
651
+ const base = path7.join(getSdkRootDir(config), getOsFolder());
447
652
  return ALL_SDKS.map((sdk) => ({
448
653
  version: sdk.version,
449
654
  api: sdk.api,
450
- installed: fs5.existsSync(path5.join(base, sdk.api))
655
+ installed: fs6.existsSync(path7.join(base, sdk.api))
451
656
  })).sort((a, b) => Number(b.api) - Number(a.api));
452
657
  }
453
658
  function getInstalledSdks(config) {
454
659
  const sdkRoot = getSdkRootDir(config);
455
660
  const versions = /* @__PURE__ */ new Set();
456
- if (!fs5.existsSync(sdkRoot)) return [];
661
+ if (!fs6.existsSync(sdkRoot)) return [];
457
662
  for (const osFolder of ["linux", "darwin", "windows"]) {
458
- const osPath = path5.join(sdkRoot, osFolder);
459
- if (!fs5.existsSync(osPath) || !fs5.statSync(osPath).isDirectory()) continue;
460
- for (const api of fs5.readdirSync(osPath)) {
461
- const apiPath = path5.join(osPath, api);
462
- if (fs5.statSync(apiPath).isDirectory()) versions.add(api);
663
+ const osPath = path7.join(sdkRoot, osFolder);
664
+ if (!fs6.existsSync(osPath) || !fs6.statSync(osPath).isDirectory()) continue;
665
+ for (const api of fs6.readdirSync(osPath)) {
666
+ const apiPath = path7.join(osPath, api);
667
+ if (fs6.statSync(apiPath).isDirectory()) versions.add(api);
463
668
  }
464
669
  }
465
670
  return ALL_SDKS.filter((sdk) => versions.has(sdk.api)).map((sdk) => sdk.version);
466
671
  }
467
672
 
468
673
  // src/sdk/remove.ts
469
- import * as fs6 from "fs";
470
- import * as path6 from "path";
674
+ import * as fs7 from "fs";
675
+ import * as path8 from "path";
471
676
  function removeSdk(config, api) {
472
677
  const sdkRoot = getSdkRootDir(config);
473
678
  let removed = false;
474
679
  for (const osFolder of ["linux", "darwin", "windows"]) {
475
- const sdkPath = path6.join(sdkRoot, osFolder, api);
476
- if (fs6.existsSync(sdkPath)) {
477
- fs6.rmSync(sdkPath, { recursive: true, force: true });
680
+ const sdkPath = path8.join(sdkRoot, osFolder, api);
681
+ if (fs7.existsSync(sdkPath)) {
682
+ fs7.rmSync(sdkPath, { recursive: true, force: true });
478
683
  removed = true;
479
684
  }
480
685
  }
@@ -482,30 +687,52 @@ function removeSdk(config, api) {
482
687
  }
483
688
 
484
689
  // src/sdk/install.ts
485
- import * as fs7 from "fs";
486
- import * as os5 from "os";
487
- import * as path7 from "path";
690
+ import * as fs9 from "fs";
691
+ import * as path9 from "path";
692
+
693
+ // src/sdk/move.ts
694
+ import * as fs8 from "fs";
695
+ function movePath(src, dest) {
696
+ try {
697
+ fs8.renameSync(src, dest);
698
+ } catch (err) {
699
+ if (err.code !== "EXDEV") throw err;
700
+ fs8.cpSync(src, dest, { recursive: true });
701
+ fs8.rmSync(src, { recursive: true, force: true });
702
+ }
703
+ }
704
+
705
+ // src/sdk/install.ts
488
706
  async function downloadAndInstallSdk(opts) {
489
707
  const { config, version, api, progress, abortSignal } = opts;
490
708
  const logger = opts.logger ?? noopLogger;
491
709
  const { filename, osFolder, strip } = getSdkFilename(version);
492
- const downloadUrl = `${OHOS_URL_BASE}/${version}-Release/${filename}`;
710
+ const urlBase = config.get("sdkUrlBase", OHOS_URL_BASE);
711
+ const downloadUrl = `${urlBase}/${version}-Release/${filename}`;
493
712
  const sha256Url = `${downloadUrl}.sha256`;
494
- const tmpDir = fs7.mkdtempSync(path7.join(os5.tmpdir(), "oniro-sdk-"));
495
- const tarPath = path7.join(tmpDir, filename);
496
- const sha256Path = path7.join(tmpDir, `${filename}.sha256`);
497
- const extractDir = path7.join(tmpDir, "extract");
498
- fs7.mkdirSync(extractDir);
499
- const sdkInstallDir = path7.join(getSdkRootDir(config), osFolder, api);
500
- fs7.mkdirSync(path7.dirname(sdkInstallDir), { recursive: true });
713
+ const what = `the SDK ${version} install`;
714
+ const sdkRoot = getSdkRootDir(config);
715
+ const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
716
+ config,
717
+ override: opts.tmpDir,
718
+ installRoot: sdkRoot,
719
+ prefix: "oniro-sdk-",
720
+ logger
721
+ });
722
+ const tarPath = path9.join(tmpDir, filename);
723
+ const sha256Path = path9.join(tmpDir, `${filename}.sha256`);
724
+ const extractDir = path9.join(tmpDir, "extract");
725
+ fs9.mkdirSync(extractDir);
726
+ const sdkInstallDir = path9.join(sdkRoot, osFolder, api);
727
+ fs9.mkdirSync(path9.dirname(sdkInstallDir), { recursive: true });
501
728
  const checkCancelled = () => {
502
729
  if (abortSignal?.aborted) throw new CancelledError();
503
730
  };
504
731
  try {
505
732
  progress?.report({ message: "Downloading SDK archive...", increment: 0 });
506
- await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35 });
733
+ await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35, what, tmpRoot });
507
734
  progress?.report({ message: "Downloading checksum...", increment: 0 });
508
- await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10 });
735
+ await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10, what, tmpRoot });
509
736
  progress?.report({ message: "Verifying checksum...", increment: 0 });
510
737
  await verifySha256(tarPath, sha256Path);
511
738
  progress?.report({ message: "Verifying checksum...", increment: 5 });
@@ -514,13 +741,13 @@ async function downloadAndInstallSdk(opts) {
514
741
  await extractTarball(tarPath, extractDir, strip);
515
742
  progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
516
743
  checkCancelled();
517
- const osContentPath = path7.join(extractDir, osFolder);
518
- if (!fs7.existsSync(osContentPath)) {
744
+ const osContentPath = path9.join(extractDir, osFolder);
745
+ if (!fs9.existsSync(osContentPath)) {
519
746
  throw new OniroError(
520
747
  `Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
521
748
  );
522
749
  }
523
- const zipFiles = fs7.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
750
+ const zipFiles = fs9.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
524
751
  const componentsStart = 60;
525
752
  const componentsBudget = 35;
526
753
  const n = zipFiles.length;
@@ -535,39 +762,40 @@ async function downloadAndInstallSdk(opts) {
535
762
  for (const zipFile of zipFiles) {
536
763
  checkCancelled();
537
764
  logger.info(`Extracting component ${zipFile}`);
538
- const zipPath = path7.join(osContentPath, zipFile);
765
+ const zipPath = path9.join(osContentPath, zipFile);
539
766
  const thisBudget = base + (rem > 0 ? 1 : 0);
540
767
  if (rem > 0) rem--;
541
768
  await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
542
769
  cursor += thisBudget;
543
- fs7.unlinkSync(zipPath);
770
+ fs9.unlinkSync(zipPath);
544
771
  }
545
772
  progress?.report({ message: "Finalizing installation...", increment: 0 });
546
- if (fs7.existsSync(sdkInstallDir)) {
547
- fs7.rmSync(sdkInstallDir, { recursive: true, force: true });
773
+ if (fs9.existsSync(sdkInstallDir)) {
774
+ fs9.rmSync(sdkInstallDir, { recursive: true, force: true });
548
775
  }
549
- fs7.renameSync(osContentPath, sdkInstallDir);
776
+ movePath(osContentPath, sdkInstallDir);
550
777
  progress?.report({ message: "Finalizing installation...", increment: 3 });
551
778
  progress?.report({ message: "Cleaning up...", increment: 0 });
552
- fs7.rmSync(tmpDir, { recursive: true, force: true });
779
+ removeTempWorkDir(tmpDir, tmpRoot);
553
780
  progress?.report({ message: "Cleaning up...", increment: 2 });
554
781
  } catch (err) {
555
- logger.error(`SDK install failed: ${err instanceof Error ? err.message : String(err)}`);
556
- fs7.rmSync(tmpDir, { recursive: true, force: true });
557
- throw err;
782
+ const wrapped = toSpaceError(err, tmpRoot, what);
783
+ logger.error(`SDK install failed: ${wrapped instanceof Error ? wrapped.message : String(wrapped)}`);
784
+ removeTempWorkDir(tmpDir, tmpRoot);
785
+ throw wrapped;
558
786
  }
559
787
  }
560
788
 
561
789
  // src/cmdTools/install.ts
562
- import * as fs8 from "fs";
790
+ import * as fs10 from "fs";
563
791
  import * as os6 from "os";
564
- import * as path8 from "path";
792
+ import * as path10 from "path";
565
793
  import { execFileSync } from "child_process";
566
- function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
567
- if (platform6 === "linux") {
794
+ function getCmdToolsDownloadUrl(config, platform7 = os6.platform()) {
795
+ if (platform7 === "linux") {
568
796
  return config.get("cmdToolsUrlLinux", defaultPaths.cmdToolsUrlLinux);
569
797
  }
570
- if (platform6 === "win32") {
798
+ if (platform7 === "win32") {
571
799
  const url = config.get("cmdToolsUrlWindows", "");
572
800
  if (!url) {
573
801
  throw new OniroError(
@@ -576,7 +804,7 @@ function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
576
804
  }
577
805
  return url;
578
806
  }
579
- if (platform6 === "darwin") {
807
+ if (platform7 === "darwin") {
580
808
  const url = config.get("cmdToolsUrlMac", "");
581
809
  if (!url) {
582
810
  throw new OniroError(
@@ -585,20 +813,20 @@ function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
585
813
  }
586
814
  return url;
587
815
  }
588
- throw new UnsupportedPlatformError(platform6);
816
+ throw new UnsupportedPlatformError(platform7);
589
817
  }
590
818
  function findCmdToolsSourceDir(extractPath) {
591
819
  const known = [
592
- path8.join(extractPath, "command-line-tools"),
593
- path8.join(extractPath, "oh-command-line-tools"),
594
- path8.join(extractPath, "commandline-tools")
820
+ path10.join(extractPath, "command-line-tools"),
821
+ path10.join(extractPath, "oh-command-line-tools"),
822
+ path10.join(extractPath, "commandline-tools")
595
823
  ];
596
824
  for (const c of known) {
597
- if (fs8.existsSync(c)) return c;
825
+ if (fs10.existsSync(c)) return c;
598
826
  }
599
- const entries = fs8.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path8.join(extractPath, e.name));
827
+ const entries = fs10.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path10.join(extractPath, e.name));
600
828
  for (const dir of entries) {
601
- if (fs8.existsSync(path8.join(dir, "bin"))) return dir;
829
+ if (fs10.existsSync(path10.join(dir, "bin"))) return dir;
602
830
  }
603
831
  throw new OniroError("Could not locate command line tools folder in the extracted archive.");
604
832
  }
@@ -606,53 +834,62 @@ async function installCmdTools(opts) {
606
834
  const { config, progress, abortSignal } = opts;
607
835
  const logger = opts.logger ?? noopLogger;
608
836
  const CMD_PATH = getCmdToolsPath(config);
609
- const tmpDir = fs8.mkdtempSync(path8.join(os6.tmpdir(), "oniro-cmdtools-"));
610
- const zipPath = opts.localZipPath ?? path8.join(tmpDir, "oh-command-line-tools.zip");
611
- const extractPath = path8.join(tmpDir, "oh-command-line-tools");
837
+ const what = "the command-line tools install";
838
+ const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
839
+ config,
840
+ override: opts.tmpDir,
841
+ installRoot: path10.dirname(CMD_PATH),
842
+ prefix: "oniro-cmdtools-",
843
+ logger
844
+ });
845
+ const zipPath = opts.localZipPath ?? path10.join(tmpDir, "oh-command-line-tools.zip");
846
+ const extractPath = path10.join(tmpDir, "oh-command-line-tools");
612
847
  try {
613
848
  if (!opts.localZipPath) {
614
849
  const url = getCmdToolsDownloadUrl(config);
615
850
  progress?.report({ message: "Downloading command line tools...", increment: 0 });
616
- await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50 });
851
+ await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
617
852
  }
618
853
  progress?.report({ message: "Extracting tools...", increment: 0 });
619
854
  await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
620
- fs8.mkdirSync(CMD_PATH, { recursive: true });
855
+ fs10.mkdirSync(CMD_PATH, { recursive: true });
621
856
  const srcDir = findCmdToolsSourceDir(extractPath);
622
- for (const entry of fs8.readdirSync(srcDir)) {
623
- const src = path8.join(srcDir, entry);
624
- const dest = path8.join(CMD_PATH, entry);
625
- if (fs8.statSync(src).isDirectory()) {
626
- if (fs8.existsSync(dest)) fs8.rmSync(dest, { recursive: true, force: true });
627
- fs8.renameSync(src, dest);
857
+ for (const entry of fs10.readdirSync(srcDir)) {
858
+ const src = path10.join(srcDir, entry);
859
+ const dest = path10.join(CMD_PATH, entry);
860
+ if (fs10.statSync(src).isDirectory()) {
861
+ if (fs10.existsSync(dest)) fs10.rmSync(dest, { recursive: true, force: true });
862
+ movePath(src, dest);
628
863
  } else {
629
- fs8.copyFileSync(src, dest);
864
+ fs10.copyFileSync(src, dest);
630
865
  }
631
866
  }
632
- const binDir = path8.join(CMD_PATH, "bin");
633
- if (fs8.existsSync(binDir) && os6.platform() !== "win32") {
634
- for (const file of fs8.readdirSync(binDir)) {
635
- fs8.chmodSync(path8.join(binDir, file), 493);
867
+ const binDir = path10.join(CMD_PATH, "bin");
868
+ if (fs10.existsSync(binDir) && os6.platform() !== "win32") {
869
+ for (const file of fs10.readdirSync(binDir)) {
870
+ fs10.chmodSync(path10.join(binDir, file), 493);
636
871
  }
637
872
  }
638
873
  progress?.report({ message: "Finalizing installation...", increment: 5 });
639
874
  progress?.report({ message: "Cleaning up...", increment: 0 });
875
+ } catch (err) {
876
+ throw toSpaceError(err, tmpRoot, what);
640
877
  } finally {
641
- fs8.rmSync(tmpDir, { recursive: true, force: true });
878
+ removeTempWorkDir(tmpDir, tmpRoot);
642
879
  }
643
880
  }
644
881
  function getCmdToolsStatus(config) {
645
882
  const cmdPath = getCmdToolsPath(config);
646
- const binDir = path8.join(cmdPath, "bin");
883
+ const binDir = path10.join(cmdPath, "bin");
647
884
  const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
648
- const bin = candidates.map((c) => path8.join(binDir, c)).find((p) => fs8.existsSync(p));
885
+ const bin = candidates.map((c) => path10.join(binDir, c)).find((p) => fs10.existsSync(p));
649
886
  if (!bin) {
650
887
  return { installed: false, status: "Not installed" };
651
888
  }
652
889
  try {
653
- const versionFile = path8.join(cmdPath, "version.txt");
654
- if (fs8.existsSync(versionFile)) {
655
- const content = fs8.readFileSync(versionFile, "utf8");
890
+ const versionFile = path10.join(cmdPath, "version.txt");
891
+ if (fs10.existsSync(versionFile)) {
892
+ const content = fs10.readFileSync(versionFile, "utf8");
656
893
  for (const line of content.split(/\r?\n/)) {
657
894
  const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
658
895
  if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
@@ -672,14 +909,14 @@ function isCmdToolsInstalled(config) {
672
909
  }
673
910
  function removeCmdTools(config) {
674
911
  const cmdPath = getCmdToolsPath(config);
675
- if (fs8.existsSync(cmdPath)) {
676
- fs8.rmSync(cmdPath, { recursive: true, force: true });
912
+ if (fs10.existsSync(cmdPath)) {
913
+ fs10.rmSync(cmdPath, { recursive: true, force: true });
677
914
  }
678
915
  }
679
916
 
680
917
  // src/sign/encryptKey.ts
681
- import * as fs9 from "fs";
682
- import * as path9 from "path";
918
+ import * as fs11 from "fs";
919
+ import * as path11 from "path";
683
920
  import * as crypto2 from "crypto";
684
921
  var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
685
922
  var DAEMON_SALT_KEY_LENGTH = 16;
@@ -751,33 +988,33 @@ function getRootKey(fdComponents, salt) {
751
988
  function createAndStoreKey(dir, keyLength) {
752
989
  const key = crypto2.randomBytes(keyLength);
753
990
  const hash = crypto2.createHash("sha256").update(key).digest("hex");
754
- const filePath = path9.join(dir, hash);
755
- fs9.writeFileSync(filePath, key);
756
- fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
991
+ const filePath = path11.join(dir, hash);
992
+ fs11.writeFileSync(filePath, key);
993
+ fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
757
994
  return key;
758
995
  }
759
996
  function createAndStoreEnKey(rootKey, ceDir) {
760
997
  const workKey = crypto2.randomBytes(DAEMON_WORK_KEY_LENGTH);
761
998
  const encrypted = encrypt(rootKey, workKey);
762
999
  const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
763
- const filePath = path9.join(ceDir, hash);
764
- fs9.writeFileSync(filePath, encrypted);
765
- fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1000
+ const filePath = path11.join(ceDir, hash);
1001
+ fs11.writeFileSync(filePath, encrypted);
1002
+ fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
766
1003
  }
767
1004
  function createMaterial(materialPath) {
768
- fs9.mkdirSync(materialPath, { recursive: true });
769
- fs9.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
770
- const fdDir = path9.join(materialPath, "fd");
771
- const acDir = path9.join(materialPath, "ac");
772
- const ceDir = path9.join(materialPath, "ce");
773
- fs9.mkdirSync(fdDir, { recursive: true });
774
- fs9.mkdirSync(acDir, { recursive: true });
775
- fs9.mkdirSync(ceDir, { recursive: true });
1005
+ fs11.mkdirSync(materialPath, { recursive: true });
1006
+ fs11.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
1007
+ const fdDir = path11.join(materialPath, "fd");
1008
+ const acDir = path11.join(materialPath, "ac");
1009
+ const ceDir = path11.join(materialPath, "ce");
1010
+ fs11.mkdirSync(fdDir, { recursive: true });
1011
+ fs11.mkdirSync(acDir, { recursive: true });
1012
+ fs11.mkdirSync(ceDir, { recursive: true });
776
1013
  const fdSubDirs = ["0", "1", "2"];
777
1014
  const fdComponents = [];
778
1015
  for (const sub of fdSubDirs) {
779
- const subDir = path9.join(fdDir, sub);
780
- fs9.mkdirSync(subDir, { recursive: true });
1016
+ const subDir = path11.join(fdDir, sub);
1017
+ fs11.mkdirSync(subDir, { recursive: true });
781
1018
  const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
782
1019
  fdComponents.push(comp);
783
1020
  }
@@ -786,24 +1023,24 @@ function createMaterial(materialPath) {
786
1023
  createAndStoreEnKey(rootKey, ceDir);
787
1024
  }
788
1025
  function readSingleFile(dir) {
789
- if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) {
1026
+ if (!fs11.existsSync(dir) || !fs11.statSync(dir).isDirectory()) {
790
1027
  throw new Error(`Missing signing material directory: ${dir}`);
791
1028
  }
792
- const files = fs9.readdirSync(dir).filter((f) => f !== ".DS_Store");
1029
+ const files = fs11.readdirSync(dir).filter((f) => f !== ".DS_Store");
793
1030
  if (files.length !== 1) {
794
1031
  throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
795
1032
  }
796
- return fs9.readFileSync(path9.join(dir, files[0]));
1033
+ return fs11.readFileSync(path11.join(dir, files[0]));
797
1034
  }
798
1035
  function getKey(materialPath) {
799
- if (!fs9.existsSync(materialPath) || !fs9.statSync(materialPath).isDirectory()) {
1036
+ if (!fs11.existsSync(materialPath) || !fs11.statSync(materialPath).isDirectory()) {
800
1037
  throw new Error("Material directory does not exist.");
801
1038
  }
802
- const fdDir = path9.join(materialPath, "fd");
803
- const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path9.join(fdDir, sub)));
804
- const salt = readSingleFile(path9.join(materialPath, "ac"));
1039
+ const fdDir = path11.join(materialPath, "fd");
1040
+ const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path11.join(fdDir, sub)));
1041
+ const salt = readSingleFile(path11.join(materialPath, "ac"));
805
1042
  const rootKey = getRootKey(fdComponents, salt);
806
- const workMaterial = readSingleFile(path9.join(materialPath, "ce"));
1043
+ const workMaterial = readSingleFile(path11.join(materialPath, "ce"));
807
1044
  return decrypt(rootKey, workMaterial);
808
1045
  }
809
1046
  function encryptPwd(password, materialPath) {
@@ -818,8 +1055,8 @@ function decryptPwd(encryptedHex, materialPath) {
818
1055
  }
819
1056
 
820
1057
  // src/sign/generateSigningConfigs.ts
821
- import * as fs10 from "fs";
822
- import * as path10 from "path";
1058
+ import * as fs12 from "fs";
1059
+ import * as path12 from "path";
823
1060
  import { execFileSync as execFileSync2 } from "child_process";
824
1061
  import JSON52 from "json5";
825
1062
 
@@ -873,6 +1110,7 @@ zAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/
873
1110
  // src/sign/generateSigningConfigs.ts
874
1111
  var APL_VALUES = ["normal", "system_basic", "system_core"];
875
1112
  var APP_FEATURE_VALUES = ["hos_normal_app", "hos_system_app"];
1113
+ var DEFAULT_PASSWORDS = { store: "123456", key: "123456" };
876
1114
  function pickSigningKind(apl) {
877
1115
  return apl === "normal" ? "profile-release" : "application-release";
878
1116
  }
@@ -886,18 +1124,20 @@ var SIGNING_KIND_CONFIG = {
886
1124
  certBasename: "OpenHarmonyApplication.cer"
887
1125
  }
888
1126
  };
889
- function copyFilesToProject(projectDir, paths, kind, logger) {
1127
+ function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
890
1128
  logger.info("[sign] Copying signing material into project...");
891
- const signaturesDir = path10.join(projectDir, "signatures");
892
- fs10.mkdirSync(signaturesDir, { recursive: true });
893
- fs10.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
894
- fs10.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
895
- fs10.copyFileSync(paths.unsignedProfileTemplate, path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
1129
+ const signaturesDir = path12.join(projectDir, "signatures");
1130
+ fs12.mkdirSync(signaturesDir, { recursive: true });
1131
+ fs12.copyFileSync(paths.keystore, path12.join(signaturesDir, "OpenHarmony.p12"));
1132
+ fs12.copyFileSync(paths.profileCert, path12.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
1133
+ fs12.copyFileSync(paths.unsignedProfileTemplate, path12.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
896
1134
  if (kind === "application-release") {
897
- fs10.writeFileSync(
898
- path10.join(signaturesDir, "OpenHarmonyApplication.cer"),
899
- OPEN_HARMONY_APPLICATION_CERT
900
- );
1135
+ const dest = path12.join(signaturesDir, "OpenHarmonyApplication.cer");
1136
+ if (applicationCertPath) {
1137
+ fs12.copyFileSync(applicationCertPath, dest);
1138
+ } else {
1139
+ fs12.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
1140
+ }
901
1141
  }
902
1142
  }
903
1143
  function resolveAppFeature(apl, override) {
@@ -905,7 +1145,7 @@ function resolveAppFeature(apl, override) {
905
1145
  return apl === "normal" ? "hos_normal_app" : "hos_system_app";
906
1146
  }
907
1147
  function extractLeafCertificate(chainPath) {
908
- const certContent = fs10.readFileSync(chainPath, "utf-8");
1148
+ const certContent = fs12.readFileSync(chainPath, "utf-8");
909
1149
  const parts = certContent.split("-----END CERTIFICATE-----");
910
1150
  if (parts.length < 3) {
911
1151
  throw new OniroError(`${chainPath} does not contain enough certificates.`);
@@ -915,20 +1155,20 @@ function extractLeafCertificate(chainPath) {
915
1155
  function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}) {
916
1156
  const kind = options.kind ?? pickSigningKind(apl);
917
1157
  logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
918
- const appJsonPath = path10.join(projectDir, "AppScope/app.json5");
919
- const profileTemplatePath = path10.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
920
- if (!fs10.existsSync(appJsonPath)) {
1158
+ const appJsonPath = path12.join(projectDir, "AppScope/app.json5");
1159
+ const profileTemplatePath = path12.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
1160
+ if (!fs12.existsSync(appJsonPath)) {
921
1161
  throw new OniroError(`${appJsonPath} does not exist.`);
922
1162
  }
923
1163
  let appJson;
924
1164
  try {
925
- appJson = JSON52.parse(fs10.readFileSync(appJsonPath, "utf-8"));
1165
+ appJson = JSON52.parse(fs12.readFileSync(appJsonPath, "utf-8"));
926
1166
  } catch (e) {
927
1167
  throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
928
1168
  }
929
1169
  let profileTemplate;
930
1170
  try {
931
- profileTemplate = JSON.parse(fs10.readFileSync(profileTemplatePath, "utf-8"));
1171
+ profileTemplate = JSON.parse(fs12.readFileSync(profileTemplatePath, "utf-8"));
932
1172
  } catch (e) {
933
1173
  throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
934
1174
  }
@@ -941,7 +1181,7 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
941
1181
  profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
942
1182
  profileTemplate["bundle-info"]["apl"] = apl;
943
1183
  profileTemplate["bundle-info"]["app-feature"] = appFeature;
944
- const distCertChainPath = path10.join(
1184
+ const distCertChainPath = path12.join(
945
1185
  projectDir,
946
1186
  "signatures",
947
1187
  SIGNING_KIND_CONFIG[kind].certBasename
@@ -951,13 +1191,13 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
951
1191
  profileTemplate.acls = profileTemplate.acls ?? {};
952
1192
  profileTemplate.acls["allowed-acls"] = options.acls;
953
1193
  }
954
- fs10.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1194
+ fs12.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
955
1195
  }
956
- function generateP7bFile(projectDir, paths, logger) {
1196
+ function generateP7bFile(projectDir, paths, logger, passwords) {
957
1197
  logger.info("[sign] Generating P7b profile via hap-sign-tool...");
958
- const signaturesDir = path10.join(projectDir, "signatures");
959
- const profileTemplatePath = path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
960
- const outputProfilePath = path10.join(signaturesDir, "app1-profile.p7b");
1198
+ const signaturesDir = path12.join(projectDir, "signatures");
1199
+ const profileTemplatePath = path12.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
1200
+ const outputProfilePath = path12.join(signaturesDir, "app1-profile.p7b");
961
1201
  const args = [
962
1202
  "-jar",
963
1203
  paths.signTool,
@@ -977,9 +1217,9 @@ function generateP7bFile(projectDir, paths, logger) {
977
1217
  "-outFile",
978
1218
  outputProfilePath,
979
1219
  "-keyPwd",
980
- "123456",
1220
+ passwords.key,
981
1221
  "-keystorePwd",
982
- "123456"
1222
+ passwords.store
983
1223
  ];
984
1224
  try {
985
1225
  execFileSync2("java", args, { stdio: "inherit" });
@@ -991,11 +1231,11 @@ function generateP7bFile(projectDir, paths, logger) {
991
1231
  }
992
1232
  }
993
1233
  function detectSigningConfigNames(projectDir) {
994
- const buildProfilePath = path10.join(projectDir, "build-profile.json5");
995
- if (!fs10.existsSync(buildProfilePath)) return ["default"];
1234
+ const buildProfilePath = path12.join(projectDir, "build-profile.json5");
1235
+ if (!fs12.existsSync(buildProfilePath)) return ["default"];
996
1236
  let parsed;
997
1237
  try {
998
- parsed = JSON52.parse(fs10.readFileSync(buildProfilePath, "utf-8"));
1238
+ parsed = JSON52.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
999
1239
  } catch {
1000
1240
  return ["default"];
1001
1241
  }
@@ -1010,16 +1250,16 @@ function detectSigningConfigNames(projectDir) {
1010
1250
  }
1011
1251
  return names.length > 0 ? names : ["default"];
1012
1252
  }
1013
- function updateBuildProfile(projectDir, kind, logger) {
1253
+ function updateBuildProfile(projectDir, kind, logger, passwords) {
1014
1254
  logger.info("[sign] Writing signing configs into build-profile.json5...");
1015
- const materialDir = path10.join(projectDir, "signatures", "material");
1016
- const buildProfilePath = path10.join(projectDir, "build-profile.json5");
1017
- const encryptedStorePassword = encryptPwd("123456", materialDir);
1018
- const encryptedKeyPassword = encryptPwd("123456", materialDir);
1255
+ const materialDir = path12.join(projectDir, "signatures", "material");
1256
+ const buildProfilePath = path12.join(projectDir, "build-profile.json5");
1257
+ const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
1258
+ const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
1019
1259
  let buildProfile = { app: {} };
1020
- if (fs10.existsSync(buildProfilePath)) {
1260
+ if (fs12.existsSync(buildProfilePath)) {
1021
1261
  try {
1022
- buildProfile = JSON52.parse(fs10.readFileSync(buildProfilePath, "utf-8"));
1262
+ buildProfile = JSON52.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
1023
1263
  } catch (e) {
1024
1264
  throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
1025
1265
  }
@@ -1040,13 +1280,13 @@ function updateBuildProfile(projectDir, kind, logger) {
1040
1280
  storeFile: "./signatures/OpenHarmony.p12"
1041
1281
  }
1042
1282
  }));
1043
- fs10.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1283
+ fs12.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1044
1284
  }
1045
1285
  function prepareMaterialDirectory(projectDir, logger) {
1046
1286
  logger.info("[sign] Generating fresh signing material...");
1047
- const materialDir = path10.join(projectDir, "signatures", "material");
1048
- if (fs10.existsSync(materialDir)) {
1049
- fs10.rmSync(materialDir, { recursive: true, force: true });
1287
+ const materialDir = path12.join(projectDir, "signatures", "material");
1288
+ if (fs12.existsSync(materialDir)) {
1289
+ fs12.rmSync(materialDir, { recursive: true, force: true });
1050
1290
  }
1051
1291
  createMaterial(materialDir);
1052
1292
  }
@@ -1069,116 +1309,224 @@ function generateSigningConfigs(options) {
1069
1309
  "Could not detect project SDK version (compileSdkVersion missing in build-profile.json5)."
1070
1310
  );
1071
1311
  }
1072
- const sdkPath = path10.join(sdkHome, String(sdkVersion));
1073
- if (!fs10.existsSync(sdkPath)) {
1312
+ const sdkPath = path12.join(sdkHome, String(sdkVersion));
1313
+ if (!fs12.existsSync(sdkPath)) {
1074
1314
  throw new OniroError(`SDK path does not exist: ${sdkPath}`);
1075
1315
  }
1076
1316
  const paths = {
1077
- signTool: path10.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
1078
- keystore: path10.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
1079
- profileCert: path10.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
1080
- unsignedProfileTemplate: path10.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
1317
+ signTool: path12.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
1318
+ keystore: path12.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
1319
+ profileCert: path12.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
1320
+ unsignedProfileTemplate: path12.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
1081
1321
  };
1082
1322
  const kind = pickSigningKind(apl);
1323
+ const passwords = options.passwords ?? DEFAULT_PASSWORDS;
1083
1324
  logger.info("[sign] Starting signing configuration generation...");
1084
- copyFilesToProject(projectDir, paths, kind, logger);
1325
+ copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
1085
1326
  modifyProfileTemplate(projectDir, apl, appFeature, logger, { kind, acls: options.acls });
1086
- generateP7bFile(projectDir, paths, logger);
1327
+ generateP7bFile(projectDir, paths, logger, passwords);
1087
1328
  prepareMaterialDirectory(projectDir, logger);
1088
- updateBuildProfile(projectDir, kind, logger);
1329
+ updateBuildProfile(projectDir, kind, logger, passwords);
1089
1330
  logger.info("[sign] Signing configuration generated successfully.");
1090
1331
  }
1091
1332
 
1092
1333
  // src/emulator/install.ts
1093
- import * as fs11 from "fs";
1094
- import * as os7 from "os";
1095
- import * as path11 from "path";
1334
+ import * as fs13 from "fs";
1335
+ import * as path13 from "path";
1096
1336
  async function installEmulator(opts) {
1097
1337
  const { config, progress, abortSignal } = opts;
1098
1338
  const logger = opts.logger ?? noopLogger;
1099
1339
  const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
1100
1340
  const emulatorDir = getEmulatorDir(config);
1101
- const tmpDir = fs11.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
1102
- const tmpZip = path11.join(tmpDir, "oniro_emulator.zip");
1341
+ const what = "the emulator install";
1342
+ const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
1343
+ config,
1344
+ override: opts.tmpDir,
1345
+ installRoot: path13.dirname(emulatorDir),
1346
+ prefix: "oniro-emulator-",
1347
+ logger
1348
+ });
1349
+ const tmpZip = path13.join(tmpDir, "oniro_emulator.zip");
1103
1350
  try {
1104
- fs11.mkdirSync(emulatorDir, { recursive: true });
1351
+ fs13.mkdirSync(emulatorDir, { recursive: true });
1105
1352
  progress?.report({ message: "Downloading emulator...", increment: 0 });
1106
- await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
1353
+ await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
1107
1354
  progress?.report({ message: "Extracting emulator...", increment: 0 });
1108
1355
  await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
1109
- const runSh = path11.join(emulatorDir, "images", "run.sh");
1110
- if (fs11.existsSync(runSh)) {
1111
- fs11.chmodSync(runSh, 493);
1356
+ const runSh = path13.join(emulatorDir, "images", "run.sh");
1357
+ if (fs13.existsSync(runSh)) {
1358
+ fs13.chmodSync(runSh, 493);
1112
1359
  }
1113
1360
  progress?.report({ message: "Finalizing installation...", increment: 5 });
1361
+ } catch (err) {
1362
+ throw toSpaceError(err, tmpRoot, what);
1114
1363
  } finally {
1115
- fs11.rmSync(tmpDir, { recursive: true, force: true });
1364
+ removeTempWorkDir(tmpDir, tmpRoot);
1116
1365
  }
1117
1366
  }
1118
1367
  function isEmulatorInstalled(config) {
1119
- return fs11.existsSync(path11.join(getEmulatorDir(config), "images", "run.sh"));
1368
+ const imagesDir = path13.join(getEmulatorDir(config), "images");
1369
+ return fs13.existsSync(path13.join(imagesDir, "run.sh")) || fs13.existsSync(path13.join(imagesDir, "run.bat"));
1120
1370
  }
1121
1371
  function removeEmulator(config) {
1122
1372
  const emulatorDir = getEmulatorDir(config);
1123
- if (fs11.existsSync(emulatorDir)) {
1124
- fs11.rmSync(emulatorDir, { recursive: true, force: true });
1373
+ if (fs13.existsSync(emulatorDir)) {
1374
+ fs13.rmSync(emulatorDir, { recursive: true, force: true });
1125
1375
  }
1126
1376
  }
1127
1377
 
1128
1378
  // src/emulator/lifecycle.ts
1129
- import * as fs12 from "fs";
1130
- import * as os8 from "os";
1131
- import * as path12 from "path";
1132
- import { exec, spawn } from "child_process";
1133
- var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
1134
- function execPromise(cmd, cwd, logger) {
1135
- return new Promise((resolve2, reject) => {
1136
- exec(cmd, { cwd }, (error, stdout, stderr) => {
1137
- if (stdout?.trim()) logger.info(`[emulator] ${stdout.trim()}`);
1138
- if (stderr?.trim()) logger.warn(`[emulator] ${stderr.trim()}`);
1139
- if (error) {
1140
- logger.error(`[emulator] ${error.message}`);
1141
- reject(error);
1142
- } else {
1143
- resolve2();
1379
+ import * as fs14 from "fs";
1380
+ import * as os7 from "os";
1381
+ import * as path14 from "path";
1382
+ import { spawn as spawn2 } from "child_process";
1383
+
1384
+ // src/hdc/exec.ts
1385
+ import { spawn } from "child_process";
1386
+ function runProcess(opts) {
1387
+ const { command, args, cwd, env, onOutput } = opts;
1388
+ const timeoutMs = opts.timeoutMs ?? 6e4;
1389
+ const { abortSignal } = opts;
1390
+ return new Promise((resolve4, reject) => {
1391
+ if (abortSignal?.aborted) {
1392
+ reject(new CancelledError(`Command aborted before start: ${command}`));
1393
+ return;
1394
+ }
1395
+ const child = spawn(command, [...args], { cwd, env, shell: false });
1396
+ let stdout = "";
1397
+ let stderr = "";
1398
+ let settled = false;
1399
+ let timedOut = false;
1400
+ const cleanup = () => {
1401
+ clearTimeout(timer);
1402
+ abortSignal?.removeEventListener("abort", onAbort);
1403
+ };
1404
+ const onAbort = () => {
1405
+ if (settled) return;
1406
+ settled = true;
1407
+ cleanup();
1408
+ try {
1409
+ child.kill("SIGKILL");
1410
+ } catch {
1411
+ }
1412
+ reject(new CancelledError(`Command aborted: ${command} ${args.join(" ")}`));
1413
+ };
1414
+ const timer = setTimeout(() => {
1415
+ timedOut = true;
1416
+ try {
1417
+ child.kill("SIGKILL");
1418
+ } catch {
1419
+ }
1420
+ }, timeoutMs);
1421
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
1422
+ child.stdout.on("data", (d) => {
1423
+ const s = d.toString();
1424
+ stdout += s;
1425
+ onOutput?.(s, "stdout");
1426
+ });
1427
+ child.stderr.on("data", (d) => {
1428
+ const s = d.toString();
1429
+ stderr += s;
1430
+ onOutput?.(s, "stderr");
1431
+ });
1432
+ child.on("error", (err) => {
1433
+ if (settled) return;
1434
+ settled = true;
1435
+ cleanup();
1436
+ reject(new OniroError(`Failed to spawn ${command}: ${err.message}`, err));
1437
+ });
1438
+ child.on("close", (code) => {
1439
+ if (settled) return;
1440
+ settled = true;
1441
+ cleanup();
1442
+ if (timedOut) {
1443
+ reject(
1444
+ new OniroError(
1445
+ `Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}` + (stderr.trim() ? `
1446
+ ${stderr.trim()}` : "")
1447
+ )
1448
+ );
1449
+ return;
1144
1450
  }
1451
+ resolve4({ code: code ?? -1, stdout, stderr });
1145
1452
  });
1146
1453
  });
1147
1454
  }
1455
+ function hdcExec(opts) {
1456
+ const hdc = getHdcPath(opts.config);
1457
+ const argv = opts.deviceSerial ? ["-t", opts.deviceSerial, ...opts.args] : [...opts.args];
1458
+ return runProcess({
1459
+ command: hdc,
1460
+ args: argv,
1461
+ timeoutMs: opts.timeoutMs,
1462
+ abortSignal: opts.abortSignal,
1463
+ onOutput: opts.onOutput,
1464
+ logger: opts.logger
1465
+ });
1466
+ }
1467
+ function shell(opts) {
1468
+ const { command, ...rest } = opts;
1469
+ return hdcExec({ ...rest, args: ["shell", command] });
1470
+ }
1471
+ function ensureOk(result, command) {
1472
+ if (result.code !== 0) {
1473
+ throw new CommandFailedError(command, result.code, result.stderr.trim());
1474
+ }
1475
+ return result;
1476
+ }
1477
+ var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
1478
+ function parseRemoteExit(stdout) {
1479
+ let code = null;
1480
+ for (const m of stdout.matchAll(REMOTE_RC_RE)) code = Number(m[1]);
1481
+ if (code === null) return null;
1482
+ return { code, stdout: stdout.replace(REMOTE_RC_RE, "").replace(/[ \t\r\n]+$/, "") };
1483
+ }
1484
+ async function shellChecked(opts) {
1485
+ const { command, ...rest } = opts;
1486
+ const res = await hdcExec({ ...rest, args: ["shell", `${command}; echo "__ONIRO_RC=$?__"`] });
1487
+ const parsed = parseRemoteExit(res.stdout);
1488
+ if (!parsed) return res;
1489
+ return { code: parsed.code, stdout: parsed.stdout, stderr: res.stderr };
1490
+ }
1491
+ function ensureRemoteOk(result, command) {
1492
+ if (result.code !== 0) {
1493
+ throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
1494
+ }
1495
+ return result;
1496
+ }
1497
+
1498
+ // src/emulator/lifecycle.ts
1499
+ var PID_FILE = path14.join(os7.tmpdir(), "oniro_emulator.pid");
1148
1500
  async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
1149
- const hdc = getHdcPath(config);
1150
1501
  try {
1151
- await execPromise(`"${hdc}" start -r`, void 0, logger);
1152
- await execPromise(`"${hdc}" tconn ${address}`, void 0, logger);
1153
- return await new Promise((resolve2) => {
1154
- exec(`"${hdc}" list targets`, (_error, stdout) => {
1155
- resolve2(stdout?.includes(address) ?? false);
1156
- });
1157
- });
1502
+ await hdcExec({ config, args: ["start", "-r"], logger });
1503
+ await hdcExec({ config, args: ["tconn", address], logger });
1504
+ const targets = await hdcExec({ config, args: ["list", "targets"], logger });
1505
+ return targets.stdout.includes(address);
1158
1506
  } catch (err) {
1159
1507
  logger.warn(`hdc connection attempt failed: ${err.message}`);
1160
1508
  return false;
1161
1509
  }
1162
1510
  }
1163
1511
  function resolveLauncher(imagesPath, headless, connect) {
1164
- const runSh = path12.join(imagesPath, "run.sh");
1165
- const runBat = path12.join(imagesPath, "run.bat");
1512
+ const runSh = path14.join(imagesPath, "run.sh");
1513
+ const runBat = path14.join(imagesPath, "run.bat");
1166
1514
  const extra = [];
1167
1515
  if (headless) extra.push("--headless");
1168
1516
  if (connect) extra.push("--connect", connect);
1169
- if (os8.platform() === "win32") {
1170
- if (fs12.existsSync(runBat)) {
1517
+ if (os7.platform() === "win32") {
1518
+ if (fs14.existsSync(runBat)) {
1171
1519
  return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
1172
1520
  }
1173
- if (fs12.existsSync(runSh)) {
1521
+ if (fs14.existsSync(runSh)) {
1174
1522
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1175
1523
  }
1176
1524
  throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
1177
1525
  }
1178
- if (fs12.existsSync(runSh)) {
1526
+ if (fs14.existsSync(runSh)) {
1179
1527
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1180
1528
  }
1181
- if (fs12.existsSync(runBat)) {
1529
+ if (fs14.existsSync(runBat)) {
1182
1530
  return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
1183
1531
  }
1184
1532
  throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
@@ -1186,9 +1534,9 @@ function resolveLauncher(imagesPath, headless, connect) {
1186
1534
  async function startEmulator(opts) {
1187
1535
  const { config } = opts;
1188
1536
  const logger = opts.logger ?? noopLogger;
1189
- if (os8.platform() !== "win32" && fs12.existsSync(PID_FILE)) {
1537
+ if (os7.platform() !== "win32" && fs14.existsSync(PID_FILE)) {
1190
1538
  try {
1191
- const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
1539
+ const pid = Number(fs14.readFileSync(PID_FILE, "utf8").trim());
1192
1540
  if (Number.isFinite(pid) && pid > 0) {
1193
1541
  try {
1194
1542
  process.kill(pid, 0);
@@ -1204,35 +1552,35 @@ async function startEmulator(opts) {
1204
1552
  logger.warn(`Could not read PID file: ${err.message}`);
1205
1553
  }
1206
1554
  }
1207
- const imagesPath = path12.join(getEmulatorDir(config), "images");
1208
- if (!fs12.existsSync(imagesPath)) {
1555
+ const imagesPath = path14.join(getEmulatorDir(config), "images");
1556
+ if (!fs14.existsSync(imagesPath)) {
1209
1557
  throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
1210
1558
  }
1211
1559
  const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
1212
- const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
1213
- const logFd = fs12.openSync(logPath, "a");
1560
+ const logPath = opts.logFile ?? (os7.platform() === "win32" ? "NUL" : "/dev/null");
1561
+ const logFd = fs14.openSync(logPath, "a");
1214
1562
  let child;
1215
1563
  try {
1216
- child = spawn(launcher.command, [...launcher.args], {
1564
+ child = spawn2(launcher.command, [...launcher.args], {
1217
1565
  cwd: launcher.cwd,
1218
1566
  detached: true,
1219
1567
  stdio: ["ignore", logFd, logFd],
1220
1568
  windowsHide: true
1221
1569
  });
1222
1570
  } finally {
1223
- fs12.closeSync(logFd);
1571
+ fs14.closeSync(logFd);
1224
1572
  }
1225
1573
  if (!child.pid) {
1226
1574
  throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
1227
1575
  }
1228
- fs12.writeFileSync(PID_FILE, String(child.pid));
1576
+ fs14.writeFileSync(PID_FILE, String(child.pid));
1229
1577
  child.unref();
1230
1578
  logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1231
- const earlyExit = new Promise((resolve2) => {
1232
- child.once("exit", (code) => resolve2(code));
1233
- child.once("error", () => resolve2(-1));
1579
+ const earlyExit = new Promise((resolve4) => {
1580
+ child.once("exit", (code) => resolve4(code));
1581
+ child.once("error", () => resolve4(-1));
1234
1582
  });
1235
- const liveness = new Promise((resolve2) => setTimeout(() => resolve2("alive"), 2e3));
1583
+ const liveness = new Promise((resolve4) => setTimeout(() => resolve4("alive"), 2e3));
1236
1584
  const outcome = await Promise.race([earlyExit, liveness]);
1237
1585
  if (outcome !== "alive") {
1238
1586
  const tail = readTail(logPath, 4e3);
@@ -1272,24 +1620,24 @@ ${tail}` : ""}`);
1272
1620
  }
1273
1621
  function readTail(filePath, bytes) {
1274
1622
  try {
1275
- const stat = fs12.statSync(filePath);
1623
+ const stat = fs14.statSync(filePath);
1276
1624
  const start = Math.max(0, stat.size - bytes);
1277
- const fd = fs12.openSync(filePath, "r");
1625
+ const fd = fs14.openSync(filePath, "r");
1278
1626
  try {
1279
1627
  const buf = Buffer.alloc(stat.size - start);
1280
- fs12.readSync(fd, buf, 0, buf.length, start);
1628
+ fs14.readSync(fd, buf, 0, buf.length, start);
1281
1629
  return buf.toString("utf8");
1282
1630
  } finally {
1283
- fs12.closeSync(fd);
1631
+ fs14.closeSync(fd);
1284
1632
  }
1285
1633
  } catch {
1286
1634
  return "";
1287
1635
  }
1288
1636
  }
1289
1637
  async function stopEmulator(logger = noopLogger) {
1290
- if (fs12.existsSync(PID_FILE)) {
1638
+ if (fs14.existsSync(PID_FILE)) {
1291
1639
  try {
1292
- const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
1640
+ const pid = Number(fs14.readFileSync(PID_FILE, "utf8").trim());
1293
1641
  if (Number.isFinite(pid) && pid > 0) {
1294
1642
  try {
1295
1643
  process.kill(pid, "SIGTERM");
@@ -1302,27 +1650,194 @@ async function stopEmulator(logger = noopLogger) {
1302
1650
  logger.warn(`Could not read PID file: ${err.message}`);
1303
1651
  }
1304
1652
  }
1305
- const killCmd = os8.platform() === "win32" ? "taskkill /IM qemu-system-x86_64.exe /F" : "pkill -f qemu-system-x86_64";
1653
+ const [killCmd, ...killArgs] = os7.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
1306
1654
  try {
1307
- await execPromise(killCmd, void 0, logger);
1308
- } catch {
1655
+ await runProcess({ command: killCmd, args: killArgs, logger });
1656
+ } catch (err) {
1657
+ logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
1309
1658
  }
1310
- fs12.rm(PID_FILE, { force: true }, (err) => {
1659
+ fs14.rm(PID_FILE, { force: true }, (err) => {
1311
1660
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
1312
1661
  });
1313
1662
  }
1314
1663
 
1664
+ // src/hdc/devices.ts
1665
+ var STATUS_TOKENS = /* @__PURE__ */ new Set([
1666
+ "Connected",
1667
+ "Offline",
1668
+ "Unauthorized",
1669
+ "Authorizing",
1670
+ "Disconnected",
1671
+ "Recovery"
1672
+ ]);
1673
+ var CONNECTION_TOKENS = /* @__PURE__ */ new Set(["USB", "TCP", "UART", "BT", "UNKNOWN"]);
1674
+ function parseDeviceList(stdout) {
1675
+ const devices = [];
1676
+ for (const line of stdout.split("\n")) {
1677
+ const trimmed = line.trim();
1678
+ if (!trimmed || trimmed.startsWith("[Empty]")) continue;
1679
+ const cols = trimmed.split(/\s+/);
1680
+ const serial = cols[0];
1681
+ if (!serial) continue;
1682
+ const rest = cols.slice(1);
1683
+ const connection = rest.find((c) => CONNECTION_TOKENS.has(c));
1684
+ const status = rest.find((c) => STATUS_TOKENS.has(c)) ?? "Unknown";
1685
+ const info = { serial, status };
1686
+ if (connection) info.connection = connection;
1687
+ devices.push(info);
1688
+ }
1689
+ return devices;
1690
+ }
1691
+ async function listDevices(config, opts = {}) {
1692
+ const res = await hdcExec({
1693
+ config,
1694
+ args: ["list", "targets", "-v"],
1695
+ timeoutMs: opts.timeoutMs ?? 1e4,
1696
+ logger: opts.logger
1697
+ });
1698
+ return parseDeviceList(res.stdout);
1699
+ }
1700
+ async function selectDevice(config, serial, opts = {}) {
1701
+ if (serial && serial !== "auto") return serial;
1702
+ const envSerial = process.env.ONIRO_DEVICE_SERIAL || process.env.DEVICE_SERIAL;
1703
+ if (envSerial && envSerial !== "auto") return envSerial;
1704
+ const usable = (await listDevices(config, opts)).filter(
1705
+ (d) => d.status === "Connected" || d.status === "Unknown"
1706
+ );
1707
+ if (usable.length === 1) return usable[0].serial;
1708
+ if (usable.length === 0) {
1709
+ throw new OniroError("No connected device found. Connect a device/emulator or set ONIRO_DEVICE_SERIAL.");
1710
+ }
1711
+ throw new OniroError(
1712
+ `Multiple devices connected (${usable.map((d) => d.serial).join(", ")}). Set ONIRO_DEVICE_SERIAL to choose one.`
1713
+ );
1714
+ }
1715
+
1716
+ // src/hdc/param.ts
1717
+ async function paramGet(config, key, opts = {}) {
1718
+ const res = await shell({
1719
+ config,
1720
+ command: `param get ${key}`,
1721
+ deviceSerial: opts.deviceSerial,
1722
+ timeoutMs: opts.timeoutMs ?? 1e4,
1723
+ logger: opts.logger
1724
+ });
1725
+ ensureOk(res, `hdc shell param get ${key}`);
1726
+ return res.stdout.trim();
1727
+ }
1728
+ async function paramSet(config, key, value, opts = {}) {
1729
+ const res = await shell({
1730
+ config,
1731
+ command: `param set ${key} ${value}`,
1732
+ deviceSerial: opts.deviceSerial,
1733
+ timeoutMs: opts.timeoutMs ?? 1e4,
1734
+ logger: opts.logger
1735
+ });
1736
+ ensureOk(res, `hdc shell param set ${key} ${value}`);
1737
+ }
1738
+
1739
+ // src/hdc/files.ts
1740
+ async function sendFile(opts) {
1741
+ const res = await hdcExec({
1742
+ config: opts.config,
1743
+ args: ["file", "send", opts.local, opts.remote],
1744
+ deviceSerial: opts.deviceSerial,
1745
+ timeoutMs: opts.timeoutMs ?? 6e5,
1746
+ abortSignal: opts.abortSignal,
1747
+ onOutput: opts.onOutput,
1748
+ logger: opts.logger
1749
+ });
1750
+ ensureOk(res, `hdc file send ${opts.local} ${opts.remote}`);
1751
+ }
1752
+ async function recvFile(opts) {
1753
+ const res = await hdcExec({
1754
+ config: opts.config,
1755
+ args: ["file", "recv", opts.remote, opts.local],
1756
+ deviceSerial: opts.deviceSerial,
1757
+ timeoutMs: opts.timeoutMs ?? 6e5,
1758
+ abortSignal: opts.abortSignal,
1759
+ onOutput: opts.onOutput,
1760
+ logger: opts.logger
1761
+ });
1762
+ ensureOk(res, `hdc file recv ${opts.remote} ${opts.local}`);
1763
+ }
1764
+
1765
+ // src/hdc/display.ts
1766
+ async function getDisplaySize(config, opts = {}) {
1767
+ const res = await shell({
1768
+ config,
1769
+ command: "hidumper -s 10 -a screen",
1770
+ deviceSerial: opts.deviceSerial,
1771
+ timeoutMs: opts.timeoutMs ?? 1e4,
1772
+ logger: opts.logger
1773
+ });
1774
+ if (res.code !== 0) return null;
1775
+ const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
1776
+ if (!m) return null;
1777
+ return { width: Number(m[1]), height: Number(m[2]) };
1778
+ }
1779
+
1780
+ // src/hdc/wait.ts
1781
+ function delay(ms, signal) {
1782
+ return new Promise((resolve4, reject) => {
1783
+ if (signal?.aborted) {
1784
+ reject(new CancelledError("Wait cancelled."));
1785
+ return;
1786
+ }
1787
+ const onAbort = () => {
1788
+ cleanup();
1789
+ reject(new CancelledError("Wait cancelled."));
1790
+ };
1791
+ const cleanup = () => {
1792
+ clearTimeout(timer);
1793
+ signal?.removeEventListener("abort", onAbort);
1794
+ };
1795
+ const timer = setTimeout(() => {
1796
+ cleanup();
1797
+ resolve4();
1798
+ }, ms);
1799
+ signal?.addEventListener("abort", onAbort, { once: true });
1800
+ });
1801
+ }
1802
+ async function waitForCondition(opts) {
1803
+ const pollMs = opts.pollMs ?? 2e3;
1804
+ const heartbeatEvery = opts.heartbeatEvery ?? 6;
1805
+ const deadline = Date.now() + opts.timeoutMs;
1806
+ let attempt = 0;
1807
+ while (Date.now() < deadline) {
1808
+ if (opts.abortSignal?.aborted) throw new CancelledError("Wait cancelled.");
1809
+ attempt++;
1810
+ let ok = false;
1811
+ try {
1812
+ ok = await opts.probe();
1813
+ } catch {
1814
+ ok = false;
1815
+ }
1816
+ if (ok) return;
1817
+ if (opts.onHeartbeat && attempt % heartbeatEvery === 0) {
1818
+ opts.onHeartbeat(attempt, Math.max(0, deadline - Date.now()));
1819
+ }
1820
+ await delay(pollMs, opts.abortSignal);
1821
+ }
1822
+ throw new OniroError(opts.timeoutMessage ?? `Condition not met within ${opts.timeoutMs}ms.`);
1823
+ }
1824
+
1825
+ // src/hdc/app.ts
1826
+ import * as fs16 from "fs";
1827
+ import * as path16 from "path";
1828
+ import { spawn as spawn3 } from "child_process";
1829
+
1315
1830
  // src/hdc/project.ts
1316
- import * as fs13 from "fs";
1317
- import * as path13 from "path";
1831
+ import * as fs15 from "fs";
1832
+ import * as path15 from "path";
1318
1833
  import JSON53 from "json5";
1319
1834
  function readJson5(filePath) {
1320
- const content = fs13.readFileSync(filePath, "utf-8");
1835
+ const content = fs15.readFileSync(filePath, "utf-8");
1321
1836
  return JSON53.parse(content);
1322
1837
  }
1323
1838
  function getBundleName(projectDir) {
1324
- const appJsonPath = path13.join(projectDir, "AppScope", "app.json5");
1325
- if (!fs13.existsSync(appJsonPath)) {
1839
+ const appJsonPath = path15.join(projectDir, "AppScope", "app.json5");
1840
+ if (!fs15.existsSync(appJsonPath)) {
1326
1841
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
1327
1842
  }
1328
1843
  const appJson = readJson5(appJsonPath);
@@ -1331,59 +1846,118 @@ function getBundleName(projectDir) {
1331
1846
  }
1332
1847
  return appJson.app.bundleName;
1333
1848
  }
1334
- function getMainAbility(projectDir, moduleName = "entry") {
1335
- const moduleJsonPath = path13.join(projectDir, moduleName, "src", "main", "module.json5");
1336
- if (!fs13.existsSync(moduleJsonPath)) {
1849
+ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
1850
+ const moduleJsonPath = path15.join(projectDir, moduleName, "src", "main", "module.json5");
1851
+ if (!fs15.existsSync(moduleJsonPath)) {
1337
1852
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
1338
1853
  }
1339
1854
  const moduleJson = readJson5(moduleJsonPath);
1340
- if (!moduleJson.module?.mainElement) {
1341
- throw new OniroError("mainElement not found in module.json5");
1342
- }
1343
- return moduleJson.module.mainElement;
1855
+ const module = moduleJson.module ?? {};
1856
+ if (abilityName) {
1857
+ const match = module.abilities?.find((a) => a.name === abilityName);
1858
+ if (match?.name) return match.name;
1859
+ throw new OniroError(`Ability "${abilityName}" not found in module.json5 at ${moduleJsonPath}`);
1860
+ }
1861
+ if (module.mainElement) return module.mainElement;
1862
+ const abilities = module.abilities ?? [];
1863
+ const visible = abilities.find((a) => a.visible && a.name);
1864
+ if (visible?.name) return visible.name;
1865
+ const first = abilities.find((a) => a.name);
1866
+ if (first?.name) return first.name;
1867
+ throw new OniroError("mainElement not found in module.json5");
1344
1868
  }
1345
1869
 
1346
1870
  // src/hdc/app.ts
1347
- import * as fs14 from "fs";
1348
- import * as path14 from "path";
1349
- import { exec as exec2, spawn as spawn2 } from "child_process";
1350
- function execPromise2(cmd, logger) {
1351
- return new Promise((resolve2, reject) => {
1352
- exec2(cmd, (error, stdout, stderr) => {
1353
- if (stdout?.trim()) logger.info(`[hdc] ${stdout.trim()}`);
1354
- if (stderr?.trim()) logger.warn(`[hdc] ${stderr.trim()}`);
1355
- if (error) {
1356
- logger.error(`[hdc] ${error.message}`);
1357
- reject(error);
1358
- } else {
1359
- resolve2();
1360
- }
1361
- });
1362
- });
1363
- }
1364
1871
  async function installApp(opts) {
1365
- const logger = opts.logger ?? noopLogger;
1872
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1366
1873
  const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
1367
- const hapPath = path14.isAbsolute(relativeHapPath) ? relativeHapPath : path14.join(opts.projectDir, relativeHapPath);
1368
- if (!fs14.existsSync(hapPath)) {
1874
+ const hapPath = path16.isAbsolute(relativeHapPath) ? relativeHapPath : path16.join(opts.projectDir, relativeHapPath);
1875
+ if (!fs16.existsSync(hapPath)) {
1369
1876
  throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
1370
1877
  }
1371
- const hdc = getHdcPath(opts.config);
1372
- await execPromise2(`"${hdc}" install "${hapPath}"`, logger);
1878
+ const result = await hdcExec({ config: opts.config, args: ["install", hapPath], timeoutMs: 3e5 });
1879
+ if (result.stdout.trim()) logger.info(result.stdout.trim());
1880
+ if (result.stderr.trim()) logger.warn(result.stderr.trim());
1881
+ ensureOk(result, `hdc install ${hapPath}`);
1882
+ let bundleName = "";
1883
+ try {
1884
+ bundleName = getBundleName(opts.projectDir);
1885
+ } catch {
1886
+ }
1887
+ return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
1888
+ }
1889
+ function detectAaStartFailure(output) {
1890
+ return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
1891
+ }
1892
+ function parseMainAbilityFromBmDump(stdout) {
1893
+ const start = stdout.indexOf("{");
1894
+ if (start < 0) return null;
1895
+ let info;
1896
+ try {
1897
+ info = JSON.parse(stdout.slice(start));
1898
+ } catch {
1899
+ return null;
1900
+ }
1901
+ const mods = Array.isArray(info?.hapModuleInfos) ? info.hapModuleInfos : [];
1902
+ const abilityOf = (m) => m?.mainElementName || m?.mainAbility || void 0;
1903
+ const isEntry = (m) => m?.moduleType === 1 || m?.moduleType === "1" || m?.moduleType === "entry";
1904
+ const entry = mods.find((m) => isEntry(m) && abilityOf(m));
1905
+ const any = mods.find((m) => abilityOf(m));
1906
+ return entry && abilityOf(entry) || any && abilityOf(any) || null;
1907
+ }
1908
+ async function resolveInstalledMainAbility(opts) {
1909
+ const res = await shell({
1910
+ config: opts.config,
1911
+ command: `bm dump -n ${opts.bundle}`,
1912
+ deviceSerial: opts.deviceSerial,
1913
+ timeoutMs: 15e3,
1914
+ logger: opts.logger
1915
+ });
1916
+ const ability = parseMainAbilityFromBmDump(res.stdout);
1917
+ if (!ability) {
1918
+ throw new OniroError(
1919
+ `Could not determine the main ability for installed bundle ${opts.bundle} (is it installed?). Pass an explicit ability instead.`
1920
+ );
1921
+ }
1922
+ return ability;
1373
1923
  }
1374
1924
  async function launchApp(opts) {
1375
- const logger = opts.logger ?? noopLogger;
1376
- const bundleName = getBundleName(opts.projectDir);
1377
- const mainAbility = getMainAbility(opts.projectDir, opts.moduleName);
1378
- const hdc = getHdcPath(opts.config);
1379
- await execPromise2(`"${hdc}" shell aa start -a ${mainAbility} -b ${bundleName}`, logger);
1925
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1926
+ let bundleName;
1927
+ let mainAbility;
1928
+ if (opts.bundleName) {
1929
+ bundleName = opts.bundleName;
1930
+ mainAbility = opts.abilityName ?? await resolveInstalledMainAbility({
1931
+ config: opts.config,
1932
+ bundle: bundleName,
1933
+ deviceSerial: opts.deviceSerial,
1934
+ logger: opts.logger
1935
+ });
1936
+ } else {
1937
+ if (!opts.projectDir) throw new OniroError("launchApp requires either bundleName or projectDir.");
1938
+ bundleName = getBundleName(opts.projectDir);
1939
+ mainAbility = getMainAbility(opts.projectDir, opts.moduleName, opts.abilityName);
1940
+ }
1941
+ const result = await hdcExec({
1942
+ config: opts.config,
1943
+ args: ["shell", "aa", "start", "-a", mainAbility, "-b", bundleName],
1944
+ deviceSerial: opts.deviceSerial
1945
+ });
1946
+ if (result.stdout.trim()) logger.info(result.stdout.trim());
1947
+ if (result.stderr.trim()) logger.warn(result.stderr.trim());
1948
+ ensureOk(result, `hdc shell aa start -a ${mainAbility} -b ${bundleName}`);
1949
+ const failure = detectAaStartFailure(`${result.stdout}
1950
+ ${result.stderr}`);
1951
+ if (failure) {
1952
+ throw new OniroError(`Failed to launch ${bundleName} (${mainAbility}): ${failure}`);
1953
+ }
1380
1954
  }
1381
1955
  function listRunningProcesses(config, options = {}) {
1382
1956
  const logger = options.logger ?? noopLogger;
1383
1957
  const timeoutMs = options.timeoutMs ?? 1e3;
1384
1958
  const hdc = getHdcPath(config);
1385
- return new Promise((resolve2, reject) => {
1386
- const proc = spawn2(hdc, ["track-jpid"]);
1959
+ return new Promise((resolve4, reject) => {
1960
+ const proc = spawn3(hdc, ["track-jpid"]);
1387
1961
  const processes = [];
1388
1962
  let resolved = false;
1389
1963
  proc.stdout.on("data", (data) => {
@@ -1396,7 +1970,7 @@ function listRunningProcesses(config, options = {}) {
1396
1970
  if (options.targetProcessName && name === options.targetProcessName && !resolved) {
1397
1971
  resolved = true;
1398
1972
  proc.kill();
1399
- resolve2(pid);
1973
+ resolve4(pid);
1400
1974
  return;
1401
1975
  }
1402
1976
  }
@@ -1417,7 +1991,7 @@ function listRunningProcesses(config, options = {}) {
1417
1991
  if (options.targetProcessName) {
1418
1992
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
1419
1993
  } else {
1420
- resolve2(processes);
1994
+ resolve4(processes);
1421
1995
  }
1422
1996
  }, timeoutMs);
1423
1997
  proc.on("close", () => {
@@ -1427,7 +2001,7 @@ function listRunningProcesses(config, options = {}) {
1427
2001
  if (options.targetProcessName) {
1428
2002
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
1429
2003
  } else {
1430
- resolve2(processes);
2004
+ resolve4(processes);
1431
2005
  }
1432
2006
  });
1433
2007
  });
@@ -1437,14 +2011,419 @@ async function findAppProcessId(config, projectDir, logger) {
1437
2011
  const result = await listRunningProcesses(config, { targetProcessName: bundleName, logger });
1438
2012
  return result;
1439
2013
  }
2014
+ async function findRunningProcess(opts) {
2015
+ const safe = opts.bundle.replace(/'/g, `'\\''`);
2016
+ const res = await shell({
2017
+ config: opts.config,
2018
+ command: `pidof '${safe}'`,
2019
+ deviceSerial: opts.deviceSerial,
2020
+ timeoutMs: opts.timeoutMs ?? 1e4,
2021
+ logger: opts.logger
2022
+ });
2023
+ const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
2024
+ if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
2025
+ try {
2026
+ const list = await listRunningProcesses(opts.config, { timeoutMs: 1200, logger: opts.logger });
2027
+ if (Array.isArray(list)) {
2028
+ const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
2029
+ const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
2030
+ if (match) return match;
2031
+ }
2032
+ } catch {
2033
+ }
2034
+ return null;
2035
+ }
2036
+ async function uninstallApp(opts) {
2037
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2038
+ const res = await hdcExec({
2039
+ config: opts.config,
2040
+ args: ["uninstall", opts.bundle],
2041
+ deviceSerial: opts.deviceSerial,
2042
+ timeoutMs: opts.timeoutMs ?? 12e4
2043
+ });
2044
+ if (res.stdout.trim()) logger.info(res.stdout.trim());
2045
+ if (res.stderr.trim()) logger.warn(res.stderr.trim());
2046
+ ensureOk(res, `hdc uninstall ${opts.bundle}`);
2047
+ }
2048
+ async function forceStop(opts) {
2049
+ const res = await shell({
2050
+ config: opts.config,
2051
+ command: `aa force-stop ${opts.bundle}`,
2052
+ deviceSerial: opts.deviceSerial,
2053
+ timeoutMs: opts.timeoutMs ?? 3e4,
2054
+ logger: opts.logger
2055
+ });
2056
+ ensureOk(res, `hdc shell aa force-stop ${opts.bundle}`);
2057
+ }
2058
+
2059
+ // src/hdc/lifecycle.ts
2060
+ async function isReachable(config, deviceSerial) {
2061
+ try {
2062
+ const res = await shell({ config, command: "echo oniro_ping", deviceSerial, timeoutMs: 5e3 });
2063
+ return res.code === 0 && res.stdout.includes("oniro_ping");
2064
+ } catch {
2065
+ return false;
2066
+ }
2067
+ }
2068
+ var TCP_SERIAL_RE = /^.+:\d+$/;
2069
+ async function reconnectIfTcp(config, deviceSerial, logger) {
2070
+ if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
2071
+ try {
2072
+ await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2073
+ } catch {
2074
+ }
2075
+ }
2076
+ async function waitForBundle(opts) {
2077
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2078
+ const timeoutMs = opts.timeoutMs ?? 18e4;
2079
+ await waitForCondition({
2080
+ probe: async () => {
2081
+ await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
2082
+ return await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
2083
+ },
2084
+ timeoutMs,
2085
+ pollMs: 2e3,
2086
+ abortSignal: opts.abortSignal,
2087
+ onHeartbeat: (attempt, remaining) => logger.info(`waiting for ${opts.bundle} (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
2088
+ timeoutMessage: `Bundle ${opts.bundle} did not start within ${timeoutMs}ms.`
2089
+ });
2090
+ }
2091
+ async function waitForBoot(opts) {
2092
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2093
+ const timeoutMs = opts.timeoutMs ?? 18e4;
2094
+ await waitForCondition({
2095
+ probe: async () => {
2096
+ await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
2097
+ if (!await isReachable(opts.config, opts.deviceSerial)) return false;
2098
+ if (!opts.untilPidOf) return true;
2099
+ return await findRunningProcess({ config: opts.config, bundle: opts.untilPidOf, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
2100
+ },
2101
+ timeoutMs,
2102
+ pollMs: 2e3,
2103
+ abortSignal: opts.abortSignal,
2104
+ onHeartbeat: (attempt, remaining) => logger.info(`waiting for boot (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
2105
+ timeoutMessage: `Device did not boot within ${timeoutMs}ms.`
2106
+ });
2107
+ }
2108
+ async function reboot(opts) {
2109
+ const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2110
+ const mode = opts.mode ?? "system";
2111
+ const timeoutMs = opts.timeoutMs ?? 18e4;
2112
+ logger.info(`Rebooting device (${mode})...`);
2113
+ try {
2114
+ if (mode === "system") {
2115
+ await paramSet(opts.config, "ohos.startup.powerctrl", "reboot", {
2116
+ deviceSerial: opts.deviceSerial,
2117
+ timeoutMs: 15e3,
2118
+ logger: opts.logger
2119
+ });
2120
+ } else {
2121
+ await shell({ config: opts.config, command: `reboot ${mode}`, deviceSerial: opts.deviceSerial, timeoutMs: 15e3 });
2122
+ }
2123
+ } catch (err) {
2124
+ logger.debug(`reboot command did not return cleanly: ${err.message}`);
2125
+ }
2126
+ if (mode !== "system" || !opts.waitForBundle) return;
2127
+ try {
2128
+ await waitForCondition({
2129
+ probe: async () => !await isReachable(opts.config, opts.deviceSerial),
2130
+ timeoutMs: Math.min(timeoutMs, 3e4),
2131
+ pollMs: 1500,
2132
+ abortSignal: opts.abortSignal,
2133
+ timeoutMessage: "device did not become unreachable"
2134
+ });
2135
+ } catch {
2136
+ }
2137
+ await waitForBundle({
2138
+ config: opts.config,
2139
+ bundle: opts.waitForBundle,
2140
+ deviceSerial: opts.deviceSerial,
2141
+ timeoutMs,
2142
+ abortSignal: opts.abortSignal,
2143
+ logger: opts.logger
2144
+ });
2145
+ }
2146
+
2147
+ // src/hdc/screen.ts
2148
+ import * as fs17 from "fs";
2149
+ import * as os8 from "os";
2150
+ import * as path17 from "path";
2151
+ import { randomBytes as randomBytes2 } from "crypto";
2152
+ function tmpName(ext) {
2153
+ return path17.join(os8.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
2154
+ }
2155
+ async function takeScreenshot(opts) {
2156
+ const remote = "/data/local/tmp/oniro_screenshot.jpeg";
2157
+ const timeoutMs = opts.timeoutMs ?? 3e4;
2158
+ const cap = await shell({ config: opts.config, command: `snapshot_display -f ${remote}`, deviceSerial: opts.deviceSerial, timeoutMs });
2159
+ ensureOk(cap, `snapshot_display -f ${remote}`);
2160
+ const local = tmpName("jpeg");
2161
+ try {
2162
+ await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2163
+ const pixels = await fs17.promises.readFile(local);
2164
+ const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
2165
+ return { pixels, width: size.width, height: size.height };
2166
+ } finally {
2167
+ fs17.promises.unlink(local).catch(() => {
2168
+ });
2169
+ void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2170
+ });
2171
+ }
2172
+ }
2173
+ async function captureBurst(opts) {
2174
+ const count = Math.max(1, Math.floor(opts.count));
2175
+ const remoteDir = `/data/local/tmp/oniro_burst_${randomBytes2(6).toString("hex")}`;
2176
+ const sleepSec = opts.intervalMs > 0 ? (opts.intervalMs / 1e3).toFixed(3) : "0";
2177
+ const loop = `mkdir -p ${remoteDir}; i=0; while [ $i -lt ${count} ]; do snapshot_display -f ${remoteDir}/f$i.jpeg; [ ${count} -gt 1 ] && sleep ${sleepSec}; i=$((i+1)); done`;
2178
+ const cap = await shell({ config: opts.config, command: loop, deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 12e4 });
2179
+ ensureOk(cap, "snapshot_display burst");
2180
+ const frames = [];
2181
+ try {
2182
+ for (let i = 0; i < count; i++) {
2183
+ const local = tmpName(`burst_${i}.jpeg`);
2184
+ try {
2185
+ await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
2186
+ frames.push(await fs17.promises.readFile(local));
2187
+ } finally {
2188
+ fs17.promises.unlink(local).catch(() => {
2189
+ });
2190
+ }
2191
+ }
2192
+ } finally {
2193
+ void shell({ config: opts.config, command: `rm -rf ${remoteDir}`, deviceSerial: opts.deviceSerial }).catch(() => {
2194
+ });
2195
+ }
2196
+ return frames;
2197
+ }
2198
+
2199
+ // src/hdc/input.ts
2200
+ function reqNum(value, name, type) {
2201
+ if (value === void 0 || Number.isNaN(value)) {
2202
+ throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
2203
+ }
2204
+ return value;
2205
+ }
2206
+ function buildInputCommand(opts) {
2207
+ switch (opts.type) {
2208
+ case "click":
2209
+ case "doubleClick":
2210
+ case "longClick":
2211
+ return `uitest uiInput ${opts.type} ${reqNum(opts.pxX, "pxX", opts.type)} ${reqNum(opts.pxY, "pxY", opts.type)}`;
2212
+ case "swipe":
2213
+ case "drag":
2214
+ case "fling": {
2215
+ const base = `uitest uiInput ${opts.type} ${reqNum(opts.pxX, "pxX", opts.type)} ${reqNum(opts.pxY, "pxY", opts.type)} ${reqNum(opts.pxX2, "pxX2", opts.type)} ${reqNum(opts.pxY2, "pxY2", opts.type)}`;
2216
+ return opts.speed !== void 0 ? `${base} ${opts.speed}` : base;
2217
+ }
2218
+ case "keyEvent":
2219
+ if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
2220
+ return `uitest uiInput keyEvent ${opts.key}`;
2221
+ case "inputText": {
2222
+ const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
2223
+ if (opts.pxX !== void 0 && opts.pxY !== void 0) {
2224
+ return `uitest uiInput inputText ${opts.pxX} ${opts.pxY} '${t}'`;
2225
+ }
2226
+ return `uitest uiInput text '${t}'`;
2227
+ }
2228
+ }
2229
+ }
2230
+ async function sendInput(opts) {
2231
+ const cmd = buildInputCommand(opts);
2232
+ const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2233
+ ensureRemoteOk(res, `hdc shell ${cmd}`);
2234
+ }
2235
+ function buildGestureCommands(waypoints) {
2236
+ if (waypoints.length < 2) {
2237
+ throw new OniroError("sendGesture requires at least two waypoints.");
2238
+ }
2239
+ const cmds = [];
2240
+ for (let i = 1; i < waypoints.length; i++) {
2241
+ const a = waypoints[i - 1];
2242
+ const b = waypoints[i];
2243
+ const dt = b.t - a.t;
2244
+ const dist = Math.hypot(b.x - a.x, b.y - a.y);
2245
+ let cmd = `uitest uiInput drag ${a.x} ${a.y} ${b.x} ${b.y}`;
2246
+ if (dt > 0 && dist > 0) cmd += ` ${Math.round(dist / (dt / 1e3))}`;
2247
+ cmds.push(cmd);
2248
+ }
2249
+ return cmds;
2250
+ }
2251
+ function buildRawTouchCommand(events) {
2252
+ if (events.length === 0) throw new OniroError("sendRawTouch requires at least one event.");
2253
+ const ops = [];
2254
+ let prev = null;
2255
+ for (const ev of events) {
2256
+ if (prev && ev.t > prev.t) ops.push(`-i ${ev.t - prev.t}`);
2257
+ if (ev.type === "down") ops.push(`-d ${ev.x} ${ev.y}`);
2258
+ else if (ev.type === "up") ops.push(`-u ${ev.x} ${ev.y}`);
2259
+ else ops.push(`-m ${prev ? `${prev.x} ${prev.y} ` : ""}${ev.x} ${ev.y}`);
2260
+ prev = ev;
2261
+ }
2262
+ return `uinput -T ${ops.join(" ")}`;
2263
+ }
2264
+ function waypointsToRawEvents(waypoints, holdStartMs = 0, holdEndMs = 0) {
2265
+ if (waypoints.length < 2) throw new OniroError("sendGesture requires at least two waypoints.");
2266
+ const first = waypoints[0];
2267
+ const events = [{ type: "down", x: first.x, y: first.y, t: 0 }];
2268
+ for (let i = 1; i < waypoints.length; i++) {
2269
+ const w = waypoints[i];
2270
+ events.push({ type: "move", x: w.x, y: w.y, t: w.t + holdStartMs });
2271
+ }
2272
+ const last = waypoints[waypoints.length - 1];
2273
+ events.push({ type: "up", x: last.x, y: last.y, t: last.t + holdStartMs + holdEndMs });
2274
+ return events;
2275
+ }
2276
+ var UITEST_VELOCITY_MIN = 200;
2277
+ var UITEST_VELOCITY_MAX = 4e4;
2278
+ function buildGestureUitestCommand(waypoints, opts = {}) {
2279
+ if (waypoints.length < 2) throw new OniroError("sendGesture requires at least two waypoints.");
2280
+ const a = waypoints[0];
2281
+ const b = waypoints[waypoints.length - 1];
2282
+ const dist = Math.hypot(b.x - a.x, b.y - a.y);
2283
+ const dtMs = b.t - a.t;
2284
+ const op = (opts.holdStartMs ?? 0) > 0 ? "drag" : "swipe";
2285
+ let cmd = `uitest uiInput ${op} ${a.x} ${a.y} ${b.x} ${b.y}`;
2286
+ if (dtMs > 0 && dist > 0) {
2287
+ const v = Math.round(dist / (dtMs / 1e3));
2288
+ cmd += ` ${Math.max(UITEST_VELOCITY_MIN, Math.min(UITEST_VELOCITY_MAX, v))}`;
2289
+ }
2290
+ return cmd;
2291
+ }
2292
+ async function sendGesture(opts) {
2293
+ if ((opts.holdEndMs ?? 0) > 0) {
2294
+ opts.logger?.warn(
2295
+ "sendGesture: hold-end has no uitest equivalent and is ignored; use sendRawTouch for a trailing hold."
2296
+ );
2297
+ }
2298
+ if (opts.waypoints.length > 2) {
2299
+ opts.logger?.debug(
2300
+ `sendGesture: ${opts.waypoints.length} waypoints collapsed to first\u2192last (uitest has no polyline path).`
2301
+ );
2302
+ }
2303
+ const cmd = buildGestureUitestCommand(opts.waypoints, { holdStartMs: opts.holdStartMs });
2304
+ const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2305
+ ensureRemoteOk(res, `hdc shell ${cmd}`);
2306
+ }
2307
+ async function sendRawTouch(opts) {
2308
+ const cmd = buildRawTouchCommand(opts.events);
2309
+ const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2310
+ ensureRemoteOk(res, `hdc shell ${cmd}`);
2311
+ }
2312
+
2313
+ // src/hdc/dump.ts
2314
+ import * as fs18 from "fs";
2315
+ import * as os9 from "os";
2316
+ import * as path18 from "path";
2317
+ import { randomBytes as randomBytes3 } from "crypto";
2318
+ function parseBounds(s) {
2319
+ const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
2320
+ if (!m) return null;
2321
+ return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
2322
+ }
2323
+ var STRUCTURAL_TYPES = /* @__PURE__ */ new Set([
2324
+ "Stack",
2325
+ "Column",
2326
+ "Row",
2327
+ "Flex",
2328
+ "__Common__",
2329
+ "Grid",
2330
+ "GridItem",
2331
+ "List",
2332
+ "ListItem",
2333
+ "Scroll",
2334
+ "Swiper",
2335
+ "SwiperContent",
2336
+ "Navigator",
2337
+ "Tabs",
2338
+ "TabContent",
2339
+ "RelativeContainer",
2340
+ "WaterFlow",
2341
+ "FlowItem"
2342
+ ]);
2343
+ var FLAG_MAP = [
2344
+ ["clickable", "click"],
2345
+ ["checkable", "check"],
2346
+ ["checked", "checked"],
2347
+ ["selected", "sel"],
2348
+ ["focused", "focus"],
2349
+ ["scrollable", "scroll"],
2350
+ ["longClickable", "longClick"]
2351
+ ];
2352
+ function pruneLayout(node, display) {
2353
+ const a = node.attributes ?? {};
2354
+ const px = parseBounds(a.bounds);
2355
+ const out = {};
2356
+ if (px && display) {
2357
+ const w = display.width || 1;
2358
+ const h = display.height || 1;
2359
+ out.b = [
2360
+ +(px[0] / w).toFixed(3),
2361
+ +(px[1] / h).toFixed(3),
2362
+ +(px[2] / w).toFixed(3),
2363
+ +(px[3] / h).toFixed(3)
2364
+ ];
2365
+ out.c = [+((px[0] + px[2]) / 2 / w).toFixed(3), +((px[1] + px[3]) / 2 / h).toFixed(3)];
2366
+ }
2367
+ if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
2368
+ if (a.text) out.text = a.text;
2369
+ if (a.description) out.desc = a.description;
2370
+ if (a.id) out.id = a.id;
2371
+ if (a.key && a.key !== a.id) out.key = a.key;
2372
+ if (a.bundleName) out.bundle = a.bundleName;
2373
+ for (const [attr, short] of FLAG_MAP) {
2374
+ if (a[attr] === "true") out[short] = true;
2375
+ }
2376
+ const children = [];
2377
+ for (const c of node.children ?? []) {
2378
+ const pc = pruneLayout(c, display);
2379
+ if (pc) {
2380
+ if (Array.isArray(pc)) children.push(...pc);
2381
+ else children.push(pc);
2382
+ }
2383
+ }
2384
+ const hasSignal = Boolean(
2385
+ out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
2386
+ );
2387
+ if (!hasSignal && children.length === 0) return null;
2388
+ if (!hasSignal) return children.length === 1 ? children[0] : children;
2389
+ if (children.length) out.children = children;
2390
+ return out;
2391
+ }
2392
+ async function dumpLayout(opts) {
2393
+ const timeoutMs = opts.timeoutMs ?? 3e4;
2394
+ const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
2395
+ const dump = await shell({ config: opts.config, command: `uitest dumpLayout${bundleArg}`, deviceSerial: opts.deviceSerial, timeoutMs });
2396
+ ensureOk(dump, "uitest dumpLayout");
2397
+ const m = dump.stdout.match(/saved to:\s*(\S+)/i);
2398
+ if (!m) throw new OniroError(`Could not parse dumpLayout output:
2399
+ ${dump.stdout}`);
2400
+ const remote = m[1];
2401
+ const local = path18.join(os9.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
2402
+ try {
2403
+ await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2404
+ const parsed = JSON.parse(await fs18.promises.readFile(local, "utf8"));
2405
+ const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
2406
+ if (opts.prune === false) {
2407
+ return { display, tree: parsed };
2408
+ }
2409
+ const prunedRaw = pruneLayout(parsed, display);
2410
+ const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
2411
+ return { display, tree };
2412
+ } finally {
2413
+ fs18.promises.unlink(local).catch(() => {
2414
+ });
2415
+ void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2416
+ });
2417
+ }
2418
+ }
1440
2419
 
1441
2420
  // src/hdc/hilog.ts
1442
- import { spawn as spawn3 } from "child_process";
2421
+ import { spawn as spawn4 } from "child_process";
1443
2422
  function setHilogLevel(opts) {
1444
2423
  const logger = opts.logger ?? noopLogger;
1445
2424
  const hdc = getHdcPath(opts.config);
1446
- return new Promise((resolve2, reject) => {
1447
- const child = spawn3(hdc, ["shell", "hilog", "-b", opts.level]);
2425
+ return new Promise((resolve4, reject) => {
2426
+ const child = spawn4(hdc, ["shell", "hilog", "-b", opts.level]);
1448
2427
  let stderr = "";
1449
2428
  child.stderr.on("data", (chunk) => {
1450
2429
  stderr += chunk.toString();
@@ -1455,7 +2434,7 @@ function setHilogLevel(opts) {
1455
2434
  });
1456
2435
  child.once("close", (code) => {
1457
2436
  if (code === 0) {
1458
- resolve2();
2437
+ resolve4();
1459
2438
  } else {
1460
2439
  const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
1461
2440
  logger.error(`[hilog] ${msg}`);
@@ -1466,11 +2445,16 @@ function setHilogLevel(opts) {
1466
2445
  }
1467
2446
  function streamHilog(opts) {
1468
2447
  const hdc = getHdcPath(opts.config);
1469
- const args = ["shell", "hilog"];
2448
+ const args = [];
2449
+ if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
2450
+ args.push("shell", "hilog");
1470
2451
  if (opts.processId && opts.processId.trim() !== "") {
1471
2452
  args.push("-P", opts.processId.trim());
1472
2453
  }
1473
- return spawn3(hdc, args);
2454
+ if (opts.domain !== void 0 && `${opts.domain}` !== "") {
2455
+ args.push("-D", `${opts.domain}`);
2456
+ }
2457
+ return spawn4(hdc, args);
1474
2458
  }
1475
2459
  var HILOG_LINE_RE = /^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(\d+)\s+(\d+)\s+([DIWEF])\s+(\S+):\s+(.*)$/;
1476
2460
  function parseHilogLine(line) {
@@ -1485,30 +2469,194 @@ function parseHilogLine(line) {
1485
2469
  message: match[6]
1486
2470
  };
1487
2471
  }
2472
+ function entryText(e) {
2473
+ return `${e.tag}: ${e.message}`;
2474
+ }
2475
+ function dedupEntries(entries) {
2476
+ const out = [];
2477
+ let lastKey = "";
2478
+ for (const e of entries) {
2479
+ const key = `${e.level}|${e.tag}|${e.message}`;
2480
+ if (key === lastKey) continue;
2481
+ out.push(e);
2482
+ lastKey = key;
2483
+ }
2484
+ return out;
2485
+ }
2486
+ async function resolveProcessId(config, bundle, deviceSerial) {
2487
+ if (!bundle) return void 0;
2488
+ const proc = await findRunningProcess({ config, bundle, deviceSerial });
2489
+ return proc?.pid;
2490
+ }
2491
+ function waitForLog(opts) {
2492
+ return new Promise((resolve4, reject) => {
2493
+ const deadline = Date.now() + opts.timeoutMs;
2494
+ let settled = false;
2495
+ let child = null;
2496
+ let reconnectTimer;
2497
+ let buf = "";
2498
+ const cleanup = () => {
2499
+ clearTimeout(timer);
2500
+ if (reconnectTimer) clearTimeout(reconnectTimer);
2501
+ opts.abortSignal?.removeEventListener("abort", onAbort);
2502
+ if (child) {
2503
+ try {
2504
+ child.kill("SIGKILL");
2505
+ } catch {
2506
+ }
2507
+ child = null;
2508
+ }
2509
+ };
2510
+ const finish = (entry) => {
2511
+ if (settled) return;
2512
+ settled = true;
2513
+ cleanup();
2514
+ resolve4(entry);
2515
+ };
2516
+ const fail = (err) => {
2517
+ if (settled) return;
2518
+ settled = true;
2519
+ cleanup();
2520
+ reject(err);
2521
+ };
2522
+ const onAbort = () => fail(new CancelledError("waitForLog cancelled."));
2523
+ const timer = setTimeout(
2524
+ () => fail(new OniroError(`No log line matched ${String(opts.pattern)} within ${opts.timeoutMs}ms.`)),
2525
+ opts.timeoutMs
2526
+ );
2527
+ opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
2528
+ const spawnStream = async () => {
2529
+ if (settled) return;
2530
+ const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
2531
+ if (settled) return;
2532
+ const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
2533
+ child = stream;
2534
+ stream.stdout.on("data", (d) => {
2535
+ buf += d.toString();
2536
+ let idx;
2537
+ while ((idx = buf.indexOf("\n")) >= 0) {
2538
+ const line = buf.slice(0, idx);
2539
+ buf = buf.slice(idx + 1);
2540
+ const entry = parseHilogLine(line);
2541
+ if (entry && opts.pattern.test(entryText(entry))) {
2542
+ finish(entry);
2543
+ return;
2544
+ }
2545
+ }
2546
+ });
2547
+ const onEnd = () => {
2548
+ if (settled || child !== stream) return;
2549
+ child = null;
2550
+ if (Date.now() < deadline) {
2551
+ reconnectTimer = setTimeout(() => void spawnStream(), 1e3);
2552
+ }
2553
+ };
2554
+ stream.once("close", onEnd);
2555
+ stream.once("error", onEnd);
2556
+ };
2557
+ void spawnStream();
2558
+ });
2559
+ }
2560
+ function watchLog(opts) {
2561
+ return new Promise((resolve4, reject) => {
2562
+ const matches = [];
2563
+ let settled = false;
2564
+ let child = null;
2565
+ let buf = "";
2566
+ const cleanup = () => {
2567
+ clearTimeout(timer);
2568
+ opts.abortSignal?.removeEventListener("abort", onAbort);
2569
+ if (child) {
2570
+ try {
2571
+ child.kill("SIGKILL");
2572
+ } catch {
2573
+ }
2574
+ child = null;
2575
+ }
2576
+ };
2577
+ const done = () => {
2578
+ if (settled) return;
2579
+ settled = true;
2580
+ cleanup();
2581
+ resolve4(opts.dedup === false ? matches : dedupEntries(matches));
2582
+ };
2583
+ const onAbort = () => {
2584
+ if (settled) return;
2585
+ settled = true;
2586
+ cleanup();
2587
+ reject(new CancelledError("watchLog cancelled."));
2588
+ };
2589
+ const timer = setTimeout(done, opts.durationMs);
2590
+ opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
2591
+ void (async () => {
2592
+ const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
2593
+ if (settled) return;
2594
+ const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
2595
+ child = stream;
2596
+ stream.stdout.on("data", (d) => {
2597
+ buf += d.toString();
2598
+ let idx;
2599
+ while ((idx = buf.indexOf("\n")) >= 0) {
2600
+ const line = buf.slice(0, idx);
2601
+ buf = buf.slice(idx + 1);
2602
+ const entry = parseHilogLine(line);
2603
+ if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
2604
+ }
2605
+ });
2606
+ })();
2607
+ });
2608
+ }
2609
+ async function dumpLog(opts) {
2610
+ let pipe = "hilog -x";
2611
+ if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
2612
+ if (opts.bundle) {
2613
+ const proc = await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial });
2614
+ if (!proc) return [];
2615
+ pipe += ` | grep -E '\\b${proc.pid}\\b'`;
2616
+ }
2617
+ if (opts.grep) {
2618
+ const safeGrep = opts.grep.replace(/'/g, `'\\''`);
2619
+ pipe += ` | grep -E '${safeGrep}'`;
2620
+ }
2621
+ if (opts.lines && opts.lines > 0) pipe += ` | tail -n ${Math.floor(opts.lines)}`;
2622
+ const res = await shell({
2623
+ config: opts.config,
2624
+ command: pipe,
2625
+ deviceSerial: opts.deviceSerial,
2626
+ timeoutMs: opts.timeoutMs ?? 3e4,
2627
+ logger: opts.logger
2628
+ });
2629
+ return res.stdout.split("\n").map((line) => parseHilogLine(line)).filter((e) => e !== null);
2630
+ }
1488
2631
 
1489
2632
  // src/build/runHvigorw.ts
1490
- import * as fs15 from "fs";
1491
- import * as path15 from "path";
1492
- import { spawn as spawn4 } from "child_process";
2633
+ import * as fs19 from "fs";
2634
+ import * as path19 from "path";
2635
+ import { spawn as spawn5 } from "child_process";
2636
+ function buildHvigorwArgs(opts) {
2637
+ const args = [opts.task ?? "assembleHap", "--mode", "module"];
2638
+ args.push("-p", `product=${opts.product ?? "default"}`);
2639
+ if (opts.module) args.push("-p", `module=${opts.module}`);
2640
+ if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
2641
+ args.push("--stacktrace", "--no-daemon");
2642
+ if (opts.parallel === false) args.push("--no-parallel");
2643
+ if (opts.extraArgs?.length) args.push(...opts.extraArgs);
2644
+ return args;
2645
+ }
1493
2646
  function runHvigorw(opts) {
1494
2647
  const { config, projectDir } = opts;
1495
2648
  const logger = opts.logger ?? noopLogger;
1496
- if (!fs15.existsSync(path15.join(projectDir, "build-profile.json5"))) {
2649
+ if (!fs19.existsSync(path19.join(projectDir, "build-profile.json5"))) {
1497
2650
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
1498
2651
  }
1499
- if (!fs15.existsSync(getCmdToolsPath(config))) {
2652
+ if (!fs19.existsSync(getCmdToolsPath(config))) {
1500
2653
  throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
1501
2654
  }
1502
2655
  const hvigorw = getHvigorwPath(config, projectDir);
1503
- const args = [opts.task ?? "assembleHap", "--mode", "module"];
1504
- args.push("-p", `product=${opts.product ?? "default"}`);
1505
- if (opts.module) args.push("-p", `module=${opts.module}`);
1506
- if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
1507
- args.push("--stacktrace", "--no-parallel", "--no-daemon");
1508
- if (opts.extraArgs?.length) args.push(...opts.extraArgs);
2656
+ const args = buildHvigorwArgs(opts);
1509
2657
  if (process.platform !== "win32") {
1510
2658
  try {
1511
- fs15.chmodSync(hvigorw, 493);
2659
+ fs19.chmodSync(hvigorw, 493);
1512
2660
  } catch {
1513
2661
  }
1514
2662
  }
@@ -1516,9 +2664,26 @@ function runHvigorw(opts) {
1516
2664
  ...process.env,
1517
2665
  OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
1518
2666
  };
1519
- return new Promise((resolve2, reject) => {
2667
+ return new Promise((resolve4, reject) => {
2668
+ if (opts.abortSignal?.aborted) {
2669
+ reject(new CancelledError("hvigorw build cancelled."));
2670
+ return;
2671
+ }
1520
2672
  logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
1521
- const child = spawn4(hvigorw, args, { cwd: projectDir, env, shell: false });
2673
+ const child = spawn5(hvigorw, args, { cwd: projectDir, env, shell: false });
2674
+ let settled = false;
2675
+ const cleanup = () => opts.abortSignal?.removeEventListener("abort", onAbort);
2676
+ const onAbort = () => {
2677
+ if (settled) return;
2678
+ settled = true;
2679
+ cleanup();
2680
+ try {
2681
+ child.kill("SIGKILL");
2682
+ } catch {
2683
+ }
2684
+ reject(new CancelledError("hvigorw build cancelled."));
2685
+ };
2686
+ opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
1522
2687
  child.stdout.on("data", (chunk) => {
1523
2688
  const text = chunk.toString();
1524
2689
  opts.onOutput?.(text, "stdout");
@@ -1529,15 +2694,153 @@ function runHvigorw(opts) {
1529
2694
  opts.onOutput?.(text, "stderr");
1530
2695
  logger.warn(text.trimEnd());
1531
2696
  });
1532
- child.on("error", (err) => reject(new OniroError(`Failed to start hvigorw: ${err.message}`, err)));
2697
+ child.on("error", (err) => {
2698
+ if (settled) return;
2699
+ settled = true;
2700
+ cleanup();
2701
+ reject(new OniroError(`Failed to start hvigorw: ${err.message}`, err));
2702
+ });
1533
2703
  child.on("close", (code) => {
2704
+ if (settled) return;
2705
+ settled = true;
2706
+ cleanup();
1534
2707
  const exitCode = code ?? 1;
1535
- if (exitCode === 0) resolve2({ exitCode });
2708
+ if (exitCode === 0) resolve4({ exitCode });
1536
2709
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
1537
2710
  });
1538
2711
  });
1539
2712
  }
1540
2713
 
2714
+ // src/build/discoverHaps.ts
2715
+ import * as fs20 from "fs";
2716
+ import * as path20 from "path";
2717
+ function moduleOfHap(hapFileName) {
2718
+ const dash = hapFileName.indexOf("-");
2719
+ return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
2720
+ }
2721
+ async function discoverHaps(opts) {
2722
+ const { projectDir } = opts;
2723
+ const maxDepth = opts.maxDepth ?? 8;
2724
+ const result = {};
2725
+ const walk = async (dir, depth) => {
2726
+ if (depth > maxDepth) return;
2727
+ const entries = await fs20.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2728
+ for (const e of entries) {
2729
+ const full = path20.join(dir, e.name);
2730
+ if (e.isDirectory()) {
2731
+ if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
2732
+ await walk(full, depth + 1);
2733
+ } else if (e.isFile() && e.name.endsWith(".hap")) {
2734
+ const moduleName = moduleOfHap(e.name);
2735
+ const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
2736
+ if (e.name.includes("unsigned")) bucket.unsigned.push(full);
2737
+ else if (e.name.includes("signed")) bucket.signed.push(full);
2738
+ }
2739
+ }
2740
+ };
2741
+ await walk(projectDir, 0);
2742
+ return result;
2743
+ }
2744
+
2745
+ // src/build/ohpm.ts
2746
+ import * as fs21 from "fs";
2747
+ import * as path21 from "path";
2748
+ function runOhpm(opts) {
2749
+ return runProcess({
2750
+ command: getOhpmPath(opts.config),
2751
+ args: [...opts.args],
2752
+ cwd: opts.projectDir,
2753
+ timeoutMs: opts.timeoutMs ?? 6e5,
2754
+ abortSignal: opts.abortSignal,
2755
+ onOutput: opts.onOutput,
2756
+ logger: opts.logger
2757
+ });
2758
+ }
2759
+ async function ensureOhModules(opts) {
2760
+ if (fs21.existsSync(path21.join(opts.projectDir, "oh_modules"))) {
2761
+ return { installed: false };
2762
+ }
2763
+ const res = await runOhpm({
2764
+ config: opts.config,
2765
+ projectDir: opts.projectDir,
2766
+ args: ["install", "--all"],
2767
+ abortSignal: opts.abortSignal,
2768
+ onOutput: opts.onOutput,
2769
+ logger: opts.logger
2770
+ });
2771
+ ensureOk(res, "ohpm install --all");
2772
+ return { installed: true };
2773
+ }
2774
+
2775
+ // src/build/codelinter.ts
2776
+ var FINDING_RE = /^(.*?):(\d+):(?:\d+:)?\s*(error|warning|warn|info)\b[:\s-]*(.*)$/i;
2777
+ var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
2778
+ function parseCodelinterFindings(output) {
2779
+ const findings = [];
2780
+ for (const raw of output.split("\n")) {
2781
+ const m = FINDING_RE.exec(raw.trim());
2782
+ if (!m) continue;
2783
+ let message = m[4].trim();
2784
+ let code = "";
2785
+ const codeMatch = TRAILING_CODE_RE.exec(message);
2786
+ if (codeMatch) {
2787
+ code = codeMatch[1];
2788
+ message = message.slice(0, codeMatch.index).trim();
2789
+ }
2790
+ findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
2791
+ }
2792
+ return findings;
2793
+ }
2794
+ async function runCodelinter(opts) {
2795
+ const res = await runProcess({
2796
+ command: getCmdToolsBin(opts.config, "codelinter"),
2797
+ args: opts.files?.length ? [...opts.files] : [],
2798
+ cwd: opts.projectDir,
2799
+ timeoutMs: opts.timeoutMs ?? 6e5,
2800
+ abortSignal: opts.abortSignal,
2801
+ logger: opts.logger
2802
+ });
2803
+ const raw = `${res.stdout}${res.stderr}`;
2804
+ return { code: res.code, findings: parseCodelinterFindings(raw), raw };
2805
+ }
2806
+
2807
+ // src/build/buildHap.ts
2808
+ async function buildHap(opts) {
2809
+ const logger = scopedLogger(opts.logger ?? noopLogger, "build");
2810
+ const warnings = [];
2811
+ const start = Date.now();
2812
+ if (opts.autoInstallDeps !== false) {
2813
+ const r = await ensureOhModules({
2814
+ config: opts.config,
2815
+ projectDir: opts.projectDir,
2816
+ abortSignal: opts.abortSignal,
2817
+ onOutput: opts.onOutput,
2818
+ logger: opts.logger
2819
+ });
2820
+ if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
2821
+ }
2822
+ const { exitCode } = await runHvigorw({
2823
+ config: opts.config,
2824
+ projectDir: opts.projectDir,
2825
+ product: opts.product,
2826
+ module: opts.module,
2827
+ buildMode: opts.mode,
2828
+ task: opts.task,
2829
+ parallel: opts.parallel,
2830
+ abortSignal: opts.abortSignal,
2831
+ onOutput: opts.onOutput,
2832
+ logger: opts.logger
2833
+ });
2834
+ const discoveredHaps = await discoverHaps({ projectDir: opts.projectDir });
2835
+ const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
2836
+ if (!anySigned) {
2837
+ warnings.push(
2838
+ "No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
2839
+ );
2840
+ }
2841
+ return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
2842
+ }
2843
+
1541
2844
  // src/project/validators.ts
1542
2845
  function isValidProjectName(name) {
1543
2846
  if (!name) return false;
@@ -1549,21 +2852,21 @@ function isValidBundleName(bundleName) {
1549
2852
  }
1550
2853
 
1551
2854
  // src/project/templates.ts
1552
- import * as fs16 from "fs";
1553
- import * as path16 from "path";
2855
+ import * as fs22 from "fs";
2856
+ import * as path22 from "path";
1554
2857
  function toHumanTemplateName(folderName) {
1555
2858
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
1556
2859
  }
1557
2860
  function listTemplates(templateRoot) {
1558
- if (!fs16.existsSync(templateRoot)) return [];
1559
- const entries = fs16.readdirSync(templateRoot, { withFileTypes: true });
2861
+ if (!fs22.existsSync(templateRoot)) return [];
2862
+ const entries = fs22.readdirSync(templateRoot, { withFileTypes: true });
1560
2863
  return entries.filter((e) => e.isDirectory()).map((e) => {
1561
- const dir = path16.join(templateRoot, e.name);
1562
- const metaPath = path16.join(dir, "template.json");
2864
+ const dir = path22.join(templateRoot, e.name);
2865
+ const metaPath = path22.join(dir, "template.json");
1563
2866
  let meta = {};
1564
- if (fs16.existsSync(metaPath)) {
2867
+ if (fs22.existsSync(metaPath)) {
1565
2868
  try {
1566
- meta = JSON.parse(fs16.readFileSync(metaPath, "utf8"));
2869
+ meta = JSON.parse(fs22.readFileSync(metaPath, "utf8"));
1567
2870
  } catch {
1568
2871
  }
1569
2872
  }
@@ -1578,57 +2881,57 @@ function listTemplates(templateRoot) {
1578
2881
  function validateTemplateLayout(templateDir, defaultModuleName) {
1579
2882
  const required = [
1580
2883
  "build-profile.json5",
1581
- path16.join("AppScope", "app.json5"),
1582
- path16.join(defaultModuleName, "src", "main", "module.json5"),
1583
- path16.join(defaultModuleName, "oh-package.json5"),
2884
+ path22.join("AppScope", "app.json5"),
2885
+ path22.join(defaultModuleName, "src", "main", "module.json5"),
2886
+ path22.join(defaultModuleName, "oh-package.json5"),
1584
2887
  "hvigorfile.ts"
1585
2888
  ];
1586
- return required.filter((rel) => !fs16.existsSync(path16.join(templateDir, rel)));
2889
+ return required.filter((rel) => !fs22.existsSync(path22.join(templateDir, rel)));
1587
2890
  }
1588
2891
 
1589
2892
  // src/project/createScaffold.ts
1590
- import * as fs18 from "fs";
1591
- import * as path17 from "path";
2893
+ import * as fs24 from "fs";
2894
+ import * as path23 from "path";
1592
2895
 
1593
2896
  // src/project/jsonHelpers.ts
1594
- import * as fs17 from "fs";
2897
+ import * as fs23 from "fs";
1595
2898
  import JSON54 from "json5";
1596
2899
  function readJson5File(filePath) {
1597
- return JSON54.parse(fs17.readFileSync(filePath, "utf8"));
2900
+ return JSON54.parse(fs23.readFileSync(filePath, "utf8"));
1598
2901
  }
1599
2902
  function writeJson5File(filePath, value) {
1600
- fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
2903
+ fs23.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
1601
2904
  }
1602
2905
  function readJsonFile(filePath) {
1603
- return JSON.parse(fs17.readFileSync(filePath, "utf8"));
2906
+ return JSON.parse(fs23.readFileSync(filePath, "utf8"));
1604
2907
  }
1605
2908
  function writeJsonFile(filePath, value) {
1606
- fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
2909
+ fs23.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
1607
2910
  }
1608
2911
 
1609
2912
  // src/project/createScaffold.ts
1610
2913
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
1611
2914
  async function pathExists(p) {
1612
2915
  try {
1613
- await fs18.promises.access(p);
2916
+ await fs24.promises.access(p);
1614
2917
  return true;
1615
2918
  } catch {
1616
2919
  return false;
1617
2920
  }
1618
2921
  }
1619
2922
  async function copyDirRecursive(srcDir, destDir) {
1620
- await fs18.promises.mkdir(destDir, { recursive: true });
1621
- const entries = await fs18.promises.readdir(srcDir, { withFileTypes: true });
2923
+ await fs24.promises.mkdir(destDir, { recursive: true });
2924
+ const entries = await fs24.promises.readdir(srcDir, { withFileTypes: true });
1622
2925
  for (const entry of entries) {
1623
- const src = path17.join(srcDir, entry.name);
1624
- const dest = path17.join(destDir, entry.name);
2926
+ const src = path23.join(srcDir, entry.name);
2927
+ const dest = path23.join(destDir, entry.name);
1625
2928
  if (entry.isDirectory()) {
1626
2929
  await copyDirRecursive(src, dest);
1627
2930
  } else if (entry.isSymbolicLink()) {
1628
2931
  continue;
1629
2932
  } else if (entry.isFile()) {
1630
2933
  if (entry.name === "template.json") continue;
1631
- await fs18.promises.copyFile(src, dest);
2934
+ await fs24.promises.copyFile(src, dest);
1632
2935
  }
1633
2936
  }
1634
2937
  }
@@ -1636,9 +2939,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
1636
2939
  const stack = [projectDir];
1637
2940
  while (stack.length > 0) {
1638
2941
  const currentDir = stack.pop();
1639
- const entries = await fs18.promises.readdir(currentDir, { withFileTypes: true });
2942
+ const entries = await fs24.promises.readdir(currentDir, { withFileTypes: true });
1640
2943
  for (const entry of entries) {
1641
- const fullPath = path17.join(currentDir, entry.name);
2944
+ const fullPath = path23.join(currentDir, entry.name);
1642
2945
  if (entry.isDirectory()) {
1643
2946
  if (IGNORED_DIRS.has(entry.name)) continue;
1644
2947
  stack.push(fullPath);
@@ -1660,23 +2963,23 @@ function toJavaPropertiesPath(p) {
1660
2963
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
1661
2964
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
1662
2965
  `;
1663
- fs18.writeFileSync(path17.join(projectDir, "local.properties"), content, "utf8");
2966
+ fs24.writeFileSync(path23.join(projectDir, "local.properties"), content, "utf8");
1664
2967
  }
1665
2968
  function renameIfExists(fromPath, toPath) {
1666
- if (fs18.existsSync(fromPath) && fromPath !== toPath) {
1667
- fs18.renameSync(fromPath, toPath);
2969
+ if (fs24.existsSync(fromPath) && fromPath !== toPath) {
2970
+ fs24.renameSync(fromPath, toPath);
1668
2971
  }
1669
2972
  }
1670
2973
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1671
- const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
1672
- if (fs18.existsSync(appJsonPath)) {
2974
+ const appJsonPath = path23.join(projectDir, "AppScope", "app.json5");
2975
+ if (fs24.existsSync(appJsonPath)) {
1673
2976
  const appJson = readJson5File(appJsonPath);
1674
2977
  appJson.app = appJson.app ?? {};
1675
2978
  appJson.app.bundleName = args.bundleName;
1676
2979
  writeJson5File(appJsonPath, appJson);
1677
2980
  }
1678
- const stringsPath = path17.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
1679
- if (fs18.existsSync(stringsPath)) {
2981
+ const stringsPath = path23.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
2982
+ if (fs24.existsSync(stringsPath)) {
1680
2983
  const strings = readJsonFile(stringsPath);
1681
2984
  if (Array.isArray(strings.string)) {
1682
2985
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -1684,8 +2987,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1684
2987
  }
1685
2988
  writeJsonFile(stringsPath, strings);
1686
2989
  }
1687
- const buildProfilePath = path17.join(projectDir, "build-profile.json5");
1688
- if (fs18.existsSync(buildProfilePath)) {
2990
+ const buildProfilePath = path23.join(projectDir, "build-profile.json5");
2991
+ if (fs24.existsSync(buildProfilePath)) {
1689
2992
  const buildProfile = readJson5File(buildProfilePath);
1690
2993
  buildProfile.app = buildProfile.app ?? {};
1691
2994
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -1698,23 +3001,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1698
3001
  }
1699
3002
  writeJson5File(buildProfilePath, buildProfile);
1700
3003
  }
1701
- const moduleJsonPath = path17.join(projectDir, args.moduleName, "src", "main", "module.json5");
1702
- if (fs18.existsSync(moduleJsonPath)) {
3004
+ const moduleJsonPath = path23.join(projectDir, args.moduleName, "src", "main", "module.json5");
3005
+ if (fs24.existsSync(moduleJsonPath)) {
1703
3006
  const moduleJson = readJson5File(moduleJsonPath);
1704
3007
  moduleJson.module = moduleJson.module ?? {};
1705
3008
  moduleJson.module.name = args.moduleName;
1706
3009
  writeJson5File(moduleJsonPath, moduleJson);
1707
3010
  }
1708
- const moduleOhPackagePath = path17.join(projectDir, args.moduleName, "oh-package.json5");
1709
- if (fs18.existsSync(moduleOhPackagePath)) {
3011
+ const moduleOhPackagePath = path23.join(projectDir, args.moduleName, "oh-package.json5");
3012
+ if (fs24.existsSync(moduleOhPackagePath)) {
1710
3013
  const pkg = readJson5File(moduleOhPackagePath);
1711
3014
  pkg.name = args.moduleName;
1712
3015
  writeJson5File(moduleOhPackagePath, pkg);
1713
3016
  }
1714
- const vscodeDir = path17.join(projectDir, ".vscode");
1715
- fs18.mkdirSync(vscodeDir, { recursive: true });
3017
+ const vscodeDir = path23.join(projectDir, ".vscode");
3018
+ fs24.mkdirSync(vscodeDir, { recursive: true });
1716
3019
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
1717
- writeJsonFile(path17.join(vscodeDir, "settings.json"), {
3020
+ writeJsonFile(path23.join(vscodeDir, "settings.json"), {
1718
3021
  "oniro.hapPath": hapPath,
1719
3022
  "files.associations": { "*.json5": "jsonc" }
1720
3023
  });
@@ -1731,7 +3034,7 @@ async function createScaffold(opts) {
1731
3034
  if (!opts.location || !await pathExists(opts.location)) {
1732
3035
  throw new OniroError(`Location does not exist: ${opts.location}`);
1733
3036
  }
1734
- const templateDir = path17.join(opts.templateRoot, opts.templateId);
3037
+ const templateDir = path23.join(opts.templateRoot, opts.templateId);
1735
3038
  if (!await pathExists(templateDir)) {
1736
3039
  throw new OniroError(`Template not found: ${templateDir}`);
1737
3040
  }
@@ -1744,17 +3047,17 @@ async function createScaffold(opts) {
1744
3047
  throw new OniroError(`Template '${opts.templateId}' is missing required files:
1745
3048
  - ${missing.join("\n- ")}`);
1746
3049
  }
1747
- const projectDir = path17.join(opts.location, opts.projectName);
3050
+ const projectDir = path23.join(opts.location, opts.projectName);
1748
3051
  if (await pathExists(projectDir)) {
1749
3052
  if (!opts.overwrite) {
1750
3053
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
1751
3054
  }
1752
- await fs18.promises.rm(projectDir, { recursive: true, force: true });
3055
+ await fs24.promises.rm(projectDir, { recursive: true, force: true });
1753
3056
  }
1754
3057
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
1755
3058
  await copyDirRecursive(templateDir, projectDir);
1756
3059
  if (moduleName !== defaultModuleName) {
1757
- renameIfExists(path17.join(projectDir, defaultModuleName), path17.join(projectDir, moduleName));
3060
+ renameIfExists(path23.join(projectDir, defaultModuleName), path23.join(projectDir, moduleName));
1758
3061
  }
1759
3062
  updateTemplateConfigs(
1760
3063
  projectDir,
@@ -1770,8 +3073,225 @@ async function createScaffold(opts) {
1770
3073
  return { projectDir };
1771
3074
  }
1772
3075
 
3076
+ // src/project/listModules.ts
3077
+ import * as fs25 from "fs";
3078
+ import * as path24 from "path";
3079
+ function listModules(opts) {
3080
+ const buildProfile = path24.join(opts.projectDir, "build-profile.json5");
3081
+ if (!fs25.existsSync(buildProfile)) {
3082
+ throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
3083
+ }
3084
+ const parsed = readJson5File(buildProfile);
3085
+ return (parsed.modules ?? []).filter((m) => Boolean(m.name)).map((m) => ({
3086
+ name: m.name,
3087
+ srcPath: m.srcPath ?? `./${m.name}`,
3088
+ targets: (m.targets ?? []).map((t) => t.name).filter((n) => Boolean(n))
3089
+ }));
3090
+ }
3091
+
3092
+ // src/project/abilities.ts
3093
+ import * as fs26 from "fs";
3094
+ import * as path25 from "path";
3095
+ function listAbilities(opts) {
3096
+ const moduleName = opts.moduleName ?? "entry";
3097
+ const moduleJsonPath = path25.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3098
+ if (!fs26.existsSync(moduleJsonPath)) {
3099
+ throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
3100
+ }
3101
+ const parsed = readJson5File(moduleJsonPath);
3102
+ return (parsed.module?.abilities ?? []).filter((a) => Boolean(a.name)).map((a) => ({ name: a.name, type: a.type, visible: a.visible, srcEntry: a.srcEntry }));
3103
+ }
3104
+
3105
+ // src/project/prepareSigning.ts
3106
+ import * as fs27 from "fs";
3107
+ import * as path26 from "path";
3108
+ function hasSigningArtifacts(dir) {
3109
+ if (!fs27.existsSync(path26.join(dir, "signatures"))) return false;
3110
+ const buildProfile = path26.join(dir, "build-profile.json5");
3111
+ if (!fs27.existsSync(buildProfile)) return false;
3112
+ try {
3113
+ const parsed = readJson5File(buildProfile);
3114
+ return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
3115
+ } catch {
3116
+ return false;
3117
+ }
3118
+ }
3119
+ function prepareSigning(opts) {
3120
+ if (hasSigningArtifacts(opts.projectDir)) {
3121
+ return { source: "present" };
3122
+ }
3123
+ generateSigningConfigs({
3124
+ projectDir: opts.projectDir,
3125
+ sdkHome: getOhosBaseSdkHome(opts.config),
3126
+ apl: opts.apl,
3127
+ appFeature: opts.appFeature,
3128
+ acls: opts.acls,
3129
+ passwords: opts.passwords,
3130
+ logger: opts.logger
3131
+ });
3132
+ return { source: "fresh" };
3133
+ }
3134
+
3135
+ // src/device/hidumper.ts
3136
+ async function dumpScreen(opts) {
3137
+ const res = await shell({ config: opts.config, command: "hidumper -s 10 -a screen", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
3138
+ if (res.code !== 0) return null;
3139
+ const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
3140
+ if (!m) return null;
3141
+ return { width: Number(m[1]), height: Number(m[2]), raw: res.stdout };
3142
+ }
3143
+ async function dumpWindow(opts) {
3144
+ const res = await shell({ config: opts.config, command: "hidumper -s WindowManagerService -a -a", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
3145
+ return res.stdout;
3146
+ }
3147
+ async function dumpRenderService(opts) {
3148
+ const res = await shell({ config: opts.config, command: "hidumper -s RenderService -a allInfo", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
3149
+ return res.stdout;
3150
+ }
3151
+
3152
+ // src/device/info.ts
3153
+ var PROPS = [
3154
+ ["model", "const.product.model"],
3155
+ ["manufacturer", "const.product.manufacturer"],
3156
+ ["brand", "const.product.brand"],
3157
+ ["osFullName", "const.ohos.fullname"],
3158
+ ["osReleaseType", "const.ohos.releasetype"],
3159
+ ["sdkApiVersion", "const.ohos.apiversion"],
3160
+ ["buildVersion", "const.product.software.version"]
3161
+ ];
3162
+ async function getDeviceInfo(opts) {
3163
+ const info = { serial: opts.deviceSerial ?? "(default)", display: null };
3164
+ for (const [field, prop] of PROPS) {
3165
+ try {
3166
+ const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
3167
+ if (value) info[field] = value;
3168
+ } catch {
3169
+ }
3170
+ }
3171
+ const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
3172
+ if (screen) info.display = { width: screen.width, height: screen.height };
3173
+ return info;
3174
+ }
3175
+
3176
+ // src/install/diffHapAssets.ts
3177
+ import StreamZip2 from "node-stream-zip";
3178
+ function diffEntryNames(installed, next) {
3179
+ const installedSet = new Set(installed);
3180
+ const nextSet = new Set(next);
3181
+ return {
3182
+ addedAssetPaths: [...nextSet].filter((n) => !installedSet.has(n)).sort(),
3183
+ removedAssetPaths: [...installedSet].filter((n) => !nextSet.has(n)).sort()
3184
+ };
3185
+ }
3186
+ async function listFileEntries(hapPath) {
3187
+ const zip = new StreamZip2.async({ file: hapPath });
3188
+ try {
3189
+ const entries = await zip.entries();
3190
+ return Object.values(entries).filter((e) => !e.isDirectory).map((e) => e.name);
3191
+ } finally {
3192
+ await zip.close();
3193
+ }
3194
+ }
3195
+ async function diffHapAssets(opts) {
3196
+ const [installed, next] = await Promise.all([listFileEntries(opts.installedHap), listFileEntries(opts.newHap)]);
3197
+ return diffEntryNames(installed, next);
3198
+ }
3199
+
3200
+ // src/install/apply.ts
3201
+ import * as fs28 from "fs";
3202
+ import * as path27 from "path";
3203
+ function decideInstallMethod(s) {
3204
+ if (!s.signInfoInconsistent) return "replace";
3205
+ if (s.isSystemBundle && !s.allowUninstall) return "refuse";
3206
+ return "uninstall-install";
3207
+ }
3208
+ function decideReboot(s) {
3209
+ if (s.assetsChanged) return { reboot: true, reason: "asset-cache" };
3210
+ if (s.isSystemBundle && s.preInstallPid !== null && s.postInstallPid === s.preInstallPid) {
3211
+ return { reboot: true, reason: "pid-unchanged" };
3212
+ }
3213
+ return { reboot: false, reason: "none" };
3214
+ }
3215
+ var SIGN_INFO_INCONSISTENT = /9568332|sign.*info.*inconsistent/i;
3216
+ async function pidOf(config, bundle, deviceSerial) {
3217
+ const proc = await findRunningProcess({ config, bundle, deviceSerial });
3218
+ return proc ? Number(proc.pid) : null;
3219
+ }
3220
+ async function resolveHapPath(opts) {
3221
+ let hapPath = opts.hapPath;
3222
+ if (hapPath) {
3223
+ if (!path27.isAbsolute(hapPath) && opts.projectDir) hapPath = path27.join(opts.projectDir, hapPath);
3224
+ } else {
3225
+ if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
3226
+ const haps = await discoverHaps({ projectDir: opts.projectDir });
3227
+ if (opts.module) {
3228
+ const signed = haps[opts.module]?.signed ?? [];
3229
+ if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
3230
+ hapPath = signed[0];
3231
+ } else {
3232
+ const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
3233
+ if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
3234
+ if (withSigned.length > 1) {
3235
+ throw new OniroError(
3236
+ `Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
3237
+ );
3238
+ }
3239
+ hapPath = withSigned[0][1].signed[0];
3240
+ }
3241
+ }
3242
+ if (!fs28.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3243
+ return hapPath;
3244
+ }
3245
+ async function applyChanges(opts) {
3246
+ const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
3247
+ const { config, bundle, deviceSerial } = opts;
3248
+ const isSystemBundle = opts.isSystemBundle ?? false;
3249
+ const allowUninstall = opts.allowUninstall ?? false;
3250
+ const hapPath = await resolveHapPath(opts);
3251
+ let assetsChanged = false;
3252
+ if (opts.installedHapPath && fs28.existsSync(opts.installedHapPath)) {
3253
+ try {
3254
+ const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
3255
+ assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
3256
+ } catch (err) {
3257
+ logger.debug(`asset diff skipped: ${err.message}`);
3258
+ }
3259
+ }
3260
+ const preInstallPid = await pidOf(config, bundle, deviceSerial);
3261
+ const replaceRes = await hdcExec({ config, args: ["install", "-r", hapPath], deviceSerial, timeoutMs: 3e5 });
3262
+ const signInfoInconsistent = SIGN_INFO_INCONSISTENT.test(`${replaceRes.stdout}${replaceRes.stderr}`);
3263
+ const decision = decideInstallMethod({ signInfoInconsistent, isSystemBundle, allowUninstall });
3264
+ let installMethod;
3265
+ if (decision === "refuse") {
3266
+ throw new OniroError(
3267
+ `Refusing to reinstall system bundle ${bundle}: signing certs are inconsistent (9568332). Align the signing certs first \u2014 uninstalling a system bundle can brick the device. Pass allowUninstall to override.`
3268
+ );
3269
+ }
3270
+ if (decision === "uninstall-install") {
3271
+ logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
3272
+ await uninstallApp({ config, bundle, deviceSerial });
3273
+ ensureOk(await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }), `hdc install ${hapPath}`);
3274
+ installMethod = "uninstall-install";
3275
+ } else {
3276
+ ensureOk(replaceRes, `hdc install -r ${hapPath}`);
3277
+ installMethod = "replace";
3278
+ }
3279
+ let postInstallPid = await pidOf(config, bundle, deviceSerial);
3280
+ const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
3281
+ let method = installMethod;
3282
+ let cacheCleared = false;
3283
+ if (rebootDecision.reboot) {
3284
+ logger.info(`rebooting (${rebootDecision.reason}) to apply changes to ${bundle}.`);
3285
+ await reboot({ config, waitForBundle: bundle, deviceSerial, abortSignal: opts.abortSignal, logger: opts.logger });
3286
+ postInstallPid = await pidOf(config, bundle, deviceSerial);
3287
+ method = "reboot";
3288
+ cacheCleared = rebootDecision.reason === "asset-cache";
3289
+ }
3290
+ return { method, preInstallPid, postInstallPid, replaced: installMethod === "replace", cacheCleared };
3291
+ }
3292
+
1773
3293
  // src/index.ts
1774
- var VERSION = "0.6.2";
3294
+ var VERSION = "0.8.0";
1775
3295
  export {
1776
3296
  ALL_SDKS,
1777
3297
  APL_VALUES,
@@ -1779,72 +3299,148 @@ export {
1779
3299
  CancelledError,
1780
3300
  ChecksumMismatchError,
1781
3301
  CmdToolsNotInstalledError,
3302
+ CommandFailedError,
3303
+ INSTALL_TMP_DIRNAME,
3304
+ InsufficientSpaceError,
1782
3305
  OHOS_URL_BASE,
1783
3306
  OniroError,
1784
3307
  SdkNotInstalledError,
3308
+ TMP_DIR_HINT,
1785
3309
  UnsupportedPlatformError,
1786
3310
  VERSION,
3311
+ applyChanges,
1787
3312
  attemptHdcConnection,
3313
+ buildGestureCommands,
3314
+ buildGestureUitestCommand,
3315
+ buildHap,
3316
+ buildHvigorwArgs,
3317
+ buildInputCommand,
3318
+ buildRawTouchCommand,
3319
+ captureBurst,
1788
3320
  consoleLogger,
3321
+ createInstallTempDir,
1789
3322
  createMaterial,
1790
3323
  createScaffold,
3324
+ createTempWorkDir,
3325
+ decideInstallMethod,
3326
+ decideReboot,
1791
3327
  decryptPwd,
3328
+ dedupEntries,
1792
3329
  defaultContext,
1793
3330
  defaultPaths,
3331
+ detectAaStartFailure,
1794
3332
  detectProjectSdkVersion,
1795
3333
  detectSigningConfigNames,
3334
+ diffEntryNames,
3335
+ diffHapAssets,
3336
+ discoverHaps,
1796
3337
  downloadAndInstallSdk,
1797
3338
  downloadFile,
3339
+ dumpLayout,
3340
+ dumpLog,
3341
+ dumpRenderService,
3342
+ dumpScreen,
3343
+ dumpWindow,
1798
3344
  encryptPwd,
3345
+ ensureFreeSpace,
3346
+ ensureOhModules,
3347
+ ensureOk,
3348
+ ensureRemoteOk,
1799
3349
  extractTarball,
1800
3350
  extractZipWithProgress,
1801
3351
  findAppProcessId,
1802
3352
  findCmdToolsSourceDir,
3353
+ findRunningProcess,
3354
+ forceStop,
3355
+ formatBytes,
1803
3356
  generateSigningConfigs,
1804
3357
  getBundleName,
1805
3358
  getCmdToolsBin,
1806
3359
  getCmdToolsDownloadUrl,
1807
3360
  getCmdToolsPath,
1808
3361
  getCmdToolsStatus,
3362
+ getDeviceInfo,
3363
+ getDisplaySize,
1809
3364
  getEmulatorDir,
3365
+ getFreeSpaceBytes,
1810
3366
  getHdcPath,
1811
3367
  getHvigorwPath,
1812
3368
  getInstalledSdks,
1813
3369
  getKey,
1814
3370
  getMainAbility,
1815
3371
  getOhosBaseSdkHome,
3372
+ getOhpmPath,
1816
3373
  getOsFolder,
1817
3374
  getSdkFilename,
1818
3375
  getSdkRootDir,
1819
3376
  getSupportedSdksForUi,
3377
+ hdcExec,
1820
3378
  installApp,
1821
3379
  installCmdTools,
1822
3380
  installEmulator,
1823
3381
  isCmdToolsInstalled,
1824
3382
  isEmulatorInstalled,
3383
+ isOutOfSpaceError,
3384
+ isRamBackedPath,
1825
3385
  isValidBundleName,
1826
3386
  isValidProjectName,
1827
3387
  launchApp,
3388
+ listAbilities,
3389
+ listDevices,
3390
+ listModules,
1828
3391
  listRunningProcesses,
1829
3392
  listTemplates,
1830
3393
  nonInteractivePrompter,
1831
3394
  noopLogger,
1832
3395
  noopProgress,
3396
+ paramGet,
3397
+ paramSet,
3398
+ parseBounds,
3399
+ parseCodelinterFindings,
3400
+ parseDeviceList,
1833
3401
  parseHilogLine,
3402
+ parseMainAbilityFromBmDump,
3403
+ parseRemoteExit,
1834
3404
  pickSigningKind,
3405
+ prepareSigning,
3406
+ pruneLayout,
1835
3407
  readJson5File,
1836
3408
  readJsonFile,
3409
+ reboot,
3410
+ recvFile,
1837
3411
  removeCmdTools,
1838
3412
  removeEmulator,
1839
3413
  removeSdk,
3414
+ removeTempWorkDir,
3415
+ resolveTmpRoot,
3416
+ runCodelinter,
1840
3417
  runHvigorw,
3418
+ runOhpm,
3419
+ runProcess,
3420
+ scopedLogger,
3421
+ selectDevice,
3422
+ sendFile,
3423
+ sendGesture,
3424
+ sendInput,
3425
+ sendRawTouch,
1841
3426
  setHilogLevel,
3427
+ shell,
3428
+ shellChecked,
1842
3429
  startEmulator,
1843
3430
  staticConfig,
1844
3431
  stopEmulator,
1845
3432
  streamHilog,
3433
+ takeScreenshot,
3434
+ toSpaceError,
3435
+ uninstallApp,
1846
3436
  validateTemplateLayout,
1847
3437
  verifySha256,
3438
+ waitForBoot,
3439
+ waitForBundle,
3440
+ waitForCondition,
3441
+ waitForLog,
3442
+ watchLog,
3443
+ waypointsToRawEvents,
1848
3444
  writeJson5File,
1849
3445
  writeJsonFile
1850
3446
  };