@oniroproject/core 0.7.0 → 0.9.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
@@ -105,9 +105,9 @@ var CmdToolsNotInstalledError = class extends OniroError {
105
105
  expectedPath;
106
106
  };
107
107
  var UnsupportedPlatformError = class extends OniroError {
108
- constructor(platform6) {
109
- super(`Unsupported platform: ${platform6}.`);
110
- this.platform = platform6;
108
+ constructor(platform7) {
109
+ super(`Unsupported platform: ${platform7}.`);
110
+ this.platform = platform7;
111
111
  this.name = "UnsupportedPlatformError";
112
112
  }
113
113
  platform;
@@ -122,6 +122,14 @@ var ChecksumMismatchError = class extends OniroError {
122
122
  expected;
123
123
  actual;
124
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
+ };
125
133
  var CancelledError = class extends OniroError {
126
134
  constructor(message = "Operation cancelled.") {
127
135
  super(message);
@@ -168,29 +176,29 @@ var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
168
176
  // src/sdk/platform.ts
169
177
  import * as os2 from "os";
170
178
  function getOsFolder() {
171
- const platform6 = os2.platform();
172
- if (platform6 === "linux") return "linux";
173
- if (platform6 === "darwin") return "darwin";
174
- if (platform6 === "win32") return "windows";
175
- 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);
176
184
  }
177
185
  function getSdkFilename(version) {
178
- const platform6 = os2.platform();
186
+ const platform7 = os2.platform();
179
187
  const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
180
188
  const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
181
- if (platform6 === "linux") {
189
+ if (platform7 === "linux") {
182
190
  return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
183
191
  }
184
- if (platform6 === "darwin") {
192
+ if (platform7 === "darwin") {
185
193
  if (os2.arch() === "arm64") {
186
194
  return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
187
195
  }
188
196
  return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
189
197
  }
190
- if (platform6 === "win32") {
198
+ if (platform7 === "win32") {
191
199
  return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
192
200
  }
193
- throw new UnsupportedPlatformError(platform6);
201
+ throw new UnsupportedPlatformError(platform7);
194
202
  }
195
203
 
196
204
  // src/sdk/paths.ts
@@ -209,6 +217,14 @@ function getCmdToolsPath(config) {
209
217
  function getEmulatorDir(config) {
210
218
  return config.get("emulatorDir", defaultPaths.emulatorDir());
211
219
  }
220
+ function toLongPath(p, platform7 = os3.platform()) {
221
+ if (platform7 !== "win32") return p;
222
+ try {
223
+ return fs.realpathSync.native(p);
224
+ } catch {
225
+ return p;
226
+ }
227
+ }
212
228
  function pickExisting(candidates) {
213
229
  for (const c of candidates) {
214
230
  if (fs.existsSync(c)) return c;
@@ -231,6 +247,8 @@ function getOhpmPath(config) {
231
247
  return getCmdToolsBin(config, "ohpm");
232
248
  }
233
249
  function getHdcPath(config) {
250
+ const override = config.get("hdcPath", "");
251
+ if (override) return override;
234
252
  const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
235
253
  if (os3.platform() === "win32") {
236
254
  return pickExisting([
@@ -242,12 +260,11 @@ function getHdcPath(config) {
242
260
  }
243
261
  return path2.join(base, "hdc");
244
262
  }
245
- function getHvigorwPath(config, projectDir) {
246
- const local = pickExisting([
247
- path2.join(projectDir, "hvigorw"),
248
- path2.join(projectDir, "hvigorw.bat"),
249
- path2.join(projectDir, "hvigorw.cmd")
250
- ]);
263
+ function getHvigorwPath(config, projectDir, platform7 = os3.platform()) {
264
+ const posixFirst = ["hvigorw", "hvigorw.bat", "hvigorw.cmd"];
265
+ const windowsFirst = ["hvigorw.bat", "hvigorw.cmd", "hvigorw"];
266
+ const names = platform7 === "win32" ? windowsFirst : posixFirst;
267
+ const local = pickExisting(names.map((n) => path2.join(projectDir, n)));
251
268
  if (fs.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
252
269
  return local;
253
270
  }
@@ -281,8 +298,164 @@ function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
281
298
  }
282
299
  }
283
300
 
284
- // src/sdk/download.ts
301
+ // src/sdk/tmp.ts
285
302
  import * as fs3 from "fs";
303
+ import * as os4 from "os";
304
+ import * as path4 from "path";
305
+ var INSTALL_TMP_DIRNAME = ".oniro-tmp";
306
+ var TMPFS_MAGIC = 16914836;
307
+ var RAMFS_MAGIC = 2240043254;
308
+ var HINT_EXAMPLE = os4.platform() === "win32" ? "D:\\oniro-tmp" : "~/.cache/oniro-tmp";
309
+ 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}.`;
310
+ function resolveTmpRoot(config, override, installRoot) {
311
+ const explicit = (override ?? "").trim();
312
+ if (explicit) return path4.resolve(explicit);
313
+ const configured = config.get("tmpDir", "").trim();
314
+ if (configured) return path4.resolve(configured);
315
+ if (installRoot) return path4.join(path4.resolve(installRoot), INSTALL_TMP_DIRNAME);
316
+ return os4.tmpdir();
317
+ }
318
+ function createTempWorkDir(root, prefix) {
319
+ try {
320
+ fs3.mkdirSync(root, { recursive: true });
321
+ return fs3.mkdtempSync(path4.join(root, prefix));
322
+ } catch (err) {
323
+ if (isOutOfSpaceError(err)) throw toSpaceError(err, root, "a temporary directory");
324
+ throw new OniroError(
325
+ `Cannot create a temporary directory in '${root}': ${err instanceof Error ? err.message : String(err)}
326
+ ${TMP_DIR_HINT}`,
327
+ err
328
+ );
329
+ }
330
+ }
331
+ function removeTempWorkDir(dir, root) {
332
+ fs3.rmSync(dir, { recursive: true, force: true });
333
+ if (!root || path4.basename(root) !== INSTALL_TMP_DIRNAME) return;
334
+ try {
335
+ fs3.rmdirSync(root);
336
+ } catch {
337
+ }
338
+ }
339
+ function createInstallTempDir(opts) {
340
+ const requested = (opts.override ?? "").trim() || opts.config.get("tmpDir", "").trim();
341
+ const root = resolveTmpRoot(opts.config, opts.override, opts.installRoot);
342
+ try {
343
+ return { dir: createTempWorkDir(root, opts.prefix), root };
344
+ } catch (err) {
345
+ const fallback = os4.tmpdir();
346
+ if (requested || isOutOfSpaceError(err) || path4.resolve(fallback) === path4.resolve(root)) throw err;
347
+ const reason = err instanceof Error ? err.message.split("\n")[0] : String(err);
348
+ (opts.logger ?? noopLogger).warn(`${reason} Falling back to '${fallback}'.`);
349
+ return { dir: createTempWorkDir(fallback, opts.prefix), root: fallback };
350
+ }
351
+ }
352
+ function nearestExisting(dir) {
353
+ let current = path4.resolve(dir);
354
+ for (; ; ) {
355
+ if (fs3.existsSync(current)) return current;
356
+ const parent = path4.dirname(current);
357
+ if (parent === current) return null;
358
+ current = parent;
359
+ }
360
+ }
361
+ function getFreeSpaceBytes(dir) {
362
+ const target = nearestExisting(dir);
363
+ if (!target) return null;
364
+ try {
365
+ const stats = fs3.statfsSync(target);
366
+ return Number(stats.bsize) * Number(stats.bavail);
367
+ } catch {
368
+ return null;
369
+ }
370
+ }
371
+ function isRamBackedPath(dir) {
372
+ const target = nearestExisting(dir);
373
+ if (!target) return false;
374
+ try {
375
+ const type = Number(fs3.statfsSync(target).type);
376
+ return type === TMPFS_MAGIC || type === RAMFS_MAGIC;
377
+ } catch {
378
+ return false;
379
+ }
380
+ }
381
+ function formatBytes(bytes) {
382
+ const units = ["B", "KiB", "MiB", "GiB", "TiB"];
383
+ let value = Math.max(0, bytes);
384
+ let unit = 0;
385
+ while (value >= 1024 && unit < units.length - 1) {
386
+ value /= 1024;
387
+ unit++;
388
+ }
389
+ return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
390
+ }
391
+ function isOutOfSpaceError(err) {
392
+ if (err instanceof InsufficientSpaceError) return true;
393
+ const seen = /* @__PURE__ */ new Set();
394
+ let current = err;
395
+ while (current && typeof current === "object" && !seen.has(current)) {
396
+ seen.add(current);
397
+ const e = current;
398
+ if (e.code === "ENOSPC" || e.code === "EDQUOT") return true;
399
+ if (typeof e.message === "string" && /ENOSPC|EDQUOT|no space left on device/i.test(e.message)) return true;
400
+ current = e.cause;
401
+ }
402
+ return false;
403
+ }
404
+ function spaceMessage(dir, what, opts) {
405
+ const need = typeof opts.requiredBytes === "number" ? `needs about ${formatBytes(opts.requiredBytes)}` : null;
406
+ const free = typeof opts.freeBytes === "number" ? `${formatBytes(opts.freeBytes)} available` : null;
407
+ const amounts = [need, free].filter(Boolean).join(", ");
408
+ const lines = [`Not enough free space in '${dir}' for ${what}${amounts ? ` (${amounts})` : ""}.`];
409
+ if (isRamBackedPath(dir)) {
410
+ lines.push(
411
+ `'${dir}' is on a RAM-backed filesystem (tmpfs), so its capacity is limited by free memory, not by free disk.`
412
+ );
413
+ }
414
+ if (opts.tmpDirHint) lines.push(TMP_DIR_HINT);
415
+ return lines.join("\n");
416
+ }
417
+ function ensureFreeSpace(dir, requiredBytes, what) {
418
+ if (!Number.isFinite(requiredBytes) || requiredBytes <= 0) return;
419
+ const freeBytes = getFreeSpaceBytes(dir);
420
+ if (freeBytes === null || freeBytes >= requiredBytes) return;
421
+ throw new InsufficientSpaceError(
422
+ spaceMessage(dir, what, { requiredBytes, freeBytes, tmpDirHint: true }),
423
+ dir
424
+ );
425
+ }
426
+ function errorPath(err) {
427
+ const seen = /* @__PURE__ */ new Set();
428
+ let current = err;
429
+ while (current && typeof current === "object" && !seen.has(current)) {
430
+ seen.add(current);
431
+ const e = current;
432
+ if (typeof e.path === "string" && e.path) return e.path;
433
+ if (typeof e.dest === "string" && e.dest) return e.dest;
434
+ current = e.cause;
435
+ }
436
+ return null;
437
+ }
438
+ function isInside(child, parent) {
439
+ const rel = path4.relative(path4.resolve(parent), path4.resolve(child));
440
+ return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
441
+ }
442
+ function toSpaceError(err, tmpRoot, what) {
443
+ if (err instanceof InsufficientSpaceError) return err;
444
+ if (!isOutOfSpaceError(err)) return err;
445
+ const failed = errorPath(err);
446
+ const failedDir = failed ? path4.dirname(path4.resolve(failed)) : path4.resolve(tmpRoot);
447
+ const inTmp = isInside(failedDir, tmpRoot);
448
+ const dir = inTmp ? path4.resolve(tmpRoot) : failedDir;
449
+ return new InsufficientSpaceError(
450
+ spaceMessage(dir, what, { freeBytes: getFreeSpaceBytes(dir), tmpDirHint: inTmp }),
451
+ dir,
452
+ err
453
+ );
454
+ }
455
+
456
+ // src/sdk/download.ts
457
+ import * as fs4 from "fs";
458
+ import * as path5 from "path";
286
459
  import * as crypto from "crypto";
287
460
  import { pipeline } from "stream";
288
461
  import { promisify } from "util";
@@ -292,36 +465,57 @@ var pipelineAsync = promisify(pipeline);
292
465
  async function downloadFile(opts) {
293
466
  const { url, dest, progress, abortSignal } = opts;
294
467
  const proto = url.startsWith("https") ? https : http;
295
- return new Promise((resolve2, reject) => {
468
+ return new Promise((resolve4, reject) => {
296
469
  const s = Math.max(0, Math.min(100, opts.start ?? 0));
297
470
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
298
471
  let settled = false;
299
472
  const done = (err) => {
300
473
  if (settled) return;
301
474
  settled = true;
302
- err ? reject(err) : resolve2();
475
+ err ? reject(err) : resolve4();
303
476
  };
304
477
  if (abortSignal?.aborted) {
305
478
  done(new CancelledError("Download cancelled before start."));
306
479
  return;
307
480
  }
308
- const file = fs3.createWriteStream(dest);
481
+ const destDir = path5.dirname(path5.resolve(dest));
482
+ const what = opts.what ?? `the download of '${url}'`;
483
+ const writeFailed = (err) => isOutOfSpaceError(err) ? toSpaceError(err, opts.tmpRoot ?? destDir, what) : new OniroError(`Error writing '${dest}': ${err instanceof Error ? err.message : String(err)}`, err);
484
+ const file = fs4.createWriteStream(dest);
485
+ let activeResponse;
486
+ const failWith = (err) => {
487
+ file.close(() => fs4.rm(dest, { force: true }, () => done(err)));
488
+ };
489
+ file.on("error", (err) => {
490
+ try {
491
+ activeResponse?.destroy?.();
492
+ } catch {
493
+ }
494
+ failWith(writeFailed(err));
495
+ });
309
496
  const req = proto.get(url, (response) => {
497
+ activeResponse = response;
310
498
  if (response.statusCode !== 200) {
311
499
  try {
312
500
  response.destroy();
313
501
  } catch {
314
502
  }
315
- try {
316
- file.close();
317
- } catch {
318
- }
319
- fs3.unlink(dest, () => {
320
- });
321
- done(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
503
+ failWith(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
322
504
  return;
323
505
  }
324
506
  const total = parseInt(response.headers["content-length"] || "0", 10);
507
+ if (total > 0) {
508
+ try {
509
+ ensureFreeSpace(opts.tmpRoot ?? destDir, total, what);
510
+ } catch (err) {
511
+ try {
512
+ response.destroy();
513
+ } catch {
514
+ }
515
+ failWith(err);
516
+ return;
517
+ }
518
+ }
325
519
  let downloaded = 0;
326
520
  let lastOverall = Math.round(s);
327
521
  response.on("data", (chunk) => {
@@ -345,38 +539,26 @@ async function downloadFile(opts) {
345
539
  const inc = endOverall - lastOverall;
346
540
  if (inc > 0) progress.report({ message: "Downloading: 100%", increment: inc });
347
541
  }
348
- file.close((err) => err ? done(err) : done());
542
+ file.close((err) => err ? done(writeFailed(err)) : done());
349
543
  });
350
544
  abortSignal?.addEventListener("abort", () => {
351
545
  response.destroy();
352
- file.close();
353
- fs3.unlink(dest, () => {
354
- });
355
- done(new CancelledError("Download cancelled."));
546
+ failWith(new CancelledError("Download cancelled."));
356
547
  });
357
548
  });
358
549
  req.on("error", (err) => {
359
- try {
360
- file.close();
361
- } catch {
362
- }
363
- fs3.unlink(dest, () => {
364
- });
365
- done(new OniroError(`Error downloading '${url}': ${err.message}`, err));
550
+ failWith(new OniroError(`Error downloading '${url}': ${err.message}`, err));
366
551
  });
367
552
  abortSignal?.addEventListener("abort", () => {
368
553
  req.destroy();
369
- file.close();
370
- fs3.unlink(dest, () => {
371
- });
372
- done(new CancelledError("Download cancelled."));
554
+ failWith(new CancelledError("Download cancelled."));
373
555
  });
374
556
  });
375
557
  }
376
558
  async function verifySha256(filePath, sha256Path) {
377
- const expected = fs3.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
559
+ const expected = fs4.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
378
560
  const hash = crypto.createHash("sha256");
379
- await pipelineAsync(fs3.createReadStream(filePath), hash);
561
+ await pipelineAsync(fs4.createReadStream(filePath), hash);
380
562
  const actual = hash.digest("hex");
381
563
  if (actual !== expected) {
382
564
  throw new ChecksumMismatchError(expected, actual);
@@ -384,9 +566,9 @@ async function verifySha256(filePath, sha256Path) {
384
566
  }
385
567
 
386
568
  // src/sdk/extract.ts
387
- import * as fs4 from "fs";
388
- import * as os4 from "os";
389
- import * as path4 from "path";
569
+ import * as fs5 from "fs";
570
+ import * as os5 from "os";
571
+ import * as path6 from "path";
390
572
  import { pipeline as pipeline2 } from "stream";
391
573
  import { promisify as promisify2 } from "util";
392
574
  import * as tar from "tar";
@@ -403,12 +585,12 @@ async function extractZipWithProgress(opts) {
403
585
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
404
586
  let processed = 0;
405
587
  let lastOverall = Math.round(s);
406
- await fs4.promises.mkdir(dest, { recursive: true });
407
- const destRoot = path4.resolve(dest);
588
+ await fs5.promises.mkdir(dest, { recursive: true });
589
+ const destRoot = path6.resolve(dest);
408
590
  const safeResolveTarget = (entryName) => {
409
- const resolved = path4.resolve(destRoot, entryName);
410
- const rel = path4.relative(destRoot, resolved);
411
- if (rel.startsWith("..") || path4.isAbsolute(rel)) {
591
+ const resolved = path6.resolve(destRoot, entryName);
592
+ const rel = path6.relative(destRoot, resolved);
593
+ if (rel.startsWith("..") || path6.isAbsolute(rel)) {
412
594
  throw new OniroError(`Blocked ZIP entry with illegal path: ${entryName}`);
413
595
  }
414
596
  return resolved;
@@ -420,25 +602,25 @@ async function extractZipWithProgress(opts) {
420
602
  const attr = file.attr ? file.attr >>> 16 : 0;
421
603
  const isSymlink = "isSymbolicLink" in file ? Boolean(file.isSymbolicLink) : (attr & 61440) === 40960;
422
604
  if (file.isDirectory) {
423
- await fs4.promises.mkdir(targetPath, { recursive: true });
605
+ await fs5.promises.mkdir(targetPath, { recursive: true });
424
606
  } else if (isSymlink) {
425
- await fs4.promises.mkdir(path4.dirname(targetPath), { recursive: true });
607
+ await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
426
608
  const linkTargetBuffer = await zip.entryData(file);
427
609
  const linkTarget = linkTargetBuffer.toString("utf8");
428
610
  try {
429
- const isWin = os4.platform() === "win32";
430
- await fs4.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
611
+ const isWin = os5.platform() === "win32";
612
+ await fs5.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
431
613
  } catch (e) {
432
614
  logger.warn(`Failed to create symlink at ${targetPath}: ${String(e)}`);
433
615
  }
434
616
  } else {
435
- await fs4.promises.mkdir(path4.dirname(targetPath), { recursive: true });
617
+ await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
436
618
  const readStream = await zip.stream(file);
437
- const writeStream = fs4.createWriteStream(targetPath);
619
+ const writeStream = fs5.createWriteStream(targetPath);
438
620
  await pipelineAsync2(readStream, writeStream);
439
621
  if (attr > 0) {
440
622
  try {
441
- await fs4.promises.chmod(targetPath, attr & 511);
623
+ await fs5.promises.chmod(targetPath, attr & 511);
442
624
  } catch {
443
625
  }
444
626
  }
@@ -470,41 +652,41 @@ async function extractTarball(tarPath, dest, strip = 0) {
470
652
  }
471
653
 
472
654
  // src/sdk/list.ts
473
- import * as fs5 from "fs";
474
- import * as path5 from "path";
655
+ import * as fs6 from "fs";
656
+ import * as path7 from "path";
475
657
  function getSupportedSdksForUi(config) {
476
- const base = path5.join(getSdkRootDir(config), getOsFolder());
658
+ const base = path7.join(getSdkRootDir(config), getOsFolder());
477
659
  return ALL_SDKS.map((sdk) => ({
478
660
  version: sdk.version,
479
661
  api: sdk.api,
480
- installed: fs5.existsSync(path5.join(base, sdk.api))
662
+ installed: fs6.existsSync(path7.join(base, sdk.api))
481
663
  })).sort((a, b) => Number(b.api) - Number(a.api));
482
664
  }
483
665
  function getInstalledSdks(config) {
484
666
  const sdkRoot = getSdkRootDir(config);
485
667
  const versions = /* @__PURE__ */ new Set();
486
- if (!fs5.existsSync(sdkRoot)) return [];
668
+ if (!fs6.existsSync(sdkRoot)) return [];
487
669
  for (const osFolder of ["linux", "darwin", "windows"]) {
488
- const osPath = path5.join(sdkRoot, osFolder);
489
- if (!fs5.existsSync(osPath) || !fs5.statSync(osPath).isDirectory()) continue;
490
- for (const api of fs5.readdirSync(osPath)) {
491
- const apiPath = path5.join(osPath, api);
492
- if (fs5.statSync(apiPath).isDirectory()) versions.add(api);
670
+ const osPath = path7.join(sdkRoot, osFolder);
671
+ if (!fs6.existsSync(osPath) || !fs6.statSync(osPath).isDirectory()) continue;
672
+ for (const api of fs6.readdirSync(osPath)) {
673
+ const apiPath = path7.join(osPath, api);
674
+ if (fs6.statSync(apiPath).isDirectory()) versions.add(api);
493
675
  }
494
676
  }
495
677
  return ALL_SDKS.filter((sdk) => versions.has(sdk.api)).map((sdk) => sdk.version);
496
678
  }
497
679
 
498
680
  // src/sdk/remove.ts
499
- import * as fs6 from "fs";
500
- import * as path6 from "path";
681
+ import * as fs7 from "fs";
682
+ import * as path8 from "path";
501
683
  function removeSdk(config, api) {
502
684
  const sdkRoot = getSdkRootDir(config);
503
685
  let removed = false;
504
686
  for (const osFolder of ["linux", "darwin", "windows"]) {
505
- const sdkPath = path6.join(sdkRoot, osFolder, api);
506
- if (fs6.existsSync(sdkPath)) {
507
- fs6.rmSync(sdkPath, { recursive: true, force: true });
687
+ const sdkPath = path8.join(sdkRoot, osFolder, api);
688
+ if (fs7.existsSync(sdkPath)) {
689
+ fs7.rmSync(sdkPath, { recursive: true, force: true });
508
690
  removed = true;
509
691
  }
510
692
  }
@@ -512,19 +694,18 @@ function removeSdk(config, api) {
512
694
  }
513
695
 
514
696
  // src/sdk/install.ts
515
- import * as fs8 from "fs";
516
- import * as os5 from "os";
517
- import * as path7 from "path";
697
+ import * as fs9 from "fs";
698
+ import * as path9 from "path";
518
699
 
519
700
  // src/sdk/move.ts
520
- import * as fs7 from "fs";
701
+ import * as fs8 from "fs";
521
702
  function movePath(src, dest) {
522
703
  try {
523
- fs7.renameSync(src, dest);
704
+ fs8.renameSync(src, dest);
524
705
  } catch (err) {
525
706
  if (err.code !== "EXDEV") throw err;
526
- fs7.cpSync(src, dest, { recursive: true });
527
- fs7.rmSync(src, { recursive: true, force: true });
707
+ fs8.cpSync(src, dest, { recursive: true });
708
+ fs8.rmSync(src, { recursive: true, force: true });
528
709
  }
529
710
  }
530
711
 
@@ -536,21 +717,29 @@ async function downloadAndInstallSdk(opts) {
536
717
  const urlBase = config.get("sdkUrlBase", OHOS_URL_BASE);
537
718
  const downloadUrl = `${urlBase}/${version}-Release/${filename}`;
538
719
  const sha256Url = `${downloadUrl}.sha256`;
539
- const tmpDir = fs8.mkdtempSync(path7.join(os5.tmpdir(), "oniro-sdk-"));
540
- const tarPath = path7.join(tmpDir, filename);
541
- const sha256Path = path7.join(tmpDir, `${filename}.sha256`);
542
- const extractDir = path7.join(tmpDir, "extract");
543
- fs8.mkdirSync(extractDir);
544
- const sdkInstallDir = path7.join(getSdkRootDir(config), osFolder, api);
545
- fs8.mkdirSync(path7.dirname(sdkInstallDir), { recursive: true });
720
+ const what = `the SDK ${version} install`;
721
+ const sdkRoot = getSdkRootDir(config);
722
+ const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
723
+ config,
724
+ override: opts.tmpDir,
725
+ installRoot: sdkRoot,
726
+ prefix: "oniro-sdk-",
727
+ logger
728
+ });
729
+ const tarPath = path9.join(tmpDir, filename);
730
+ const sha256Path = path9.join(tmpDir, `${filename}.sha256`);
731
+ const extractDir = path9.join(tmpDir, "extract");
732
+ fs9.mkdirSync(extractDir);
733
+ const sdkInstallDir = path9.join(sdkRoot, osFolder, api);
734
+ fs9.mkdirSync(path9.dirname(sdkInstallDir), { recursive: true });
546
735
  const checkCancelled = () => {
547
736
  if (abortSignal?.aborted) throw new CancelledError();
548
737
  };
549
738
  try {
550
739
  progress?.report({ message: "Downloading SDK archive...", increment: 0 });
551
- await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35 });
740
+ await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35, what, tmpRoot });
552
741
  progress?.report({ message: "Downloading checksum...", increment: 0 });
553
- await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10 });
742
+ await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10, what, tmpRoot });
554
743
  progress?.report({ message: "Verifying checksum...", increment: 0 });
555
744
  await verifySha256(tarPath, sha256Path);
556
745
  progress?.report({ message: "Verifying checksum...", increment: 5 });
@@ -559,13 +748,13 @@ async function downloadAndInstallSdk(opts) {
559
748
  await extractTarball(tarPath, extractDir, strip);
560
749
  progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
561
750
  checkCancelled();
562
- const osContentPath = path7.join(extractDir, osFolder);
563
- if (!fs8.existsSync(osContentPath)) {
751
+ const osContentPath = path9.join(extractDir, osFolder);
752
+ if (!fs9.existsSync(osContentPath)) {
564
753
  throw new OniroError(
565
754
  `Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
566
755
  );
567
756
  }
568
- const zipFiles = fs8.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
757
+ const zipFiles = fs9.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
569
758
  const componentsStart = 60;
570
759
  const componentsBudget = 35;
571
760
  const n = zipFiles.length;
@@ -580,39 +769,114 @@ async function downloadAndInstallSdk(opts) {
580
769
  for (const zipFile of zipFiles) {
581
770
  checkCancelled();
582
771
  logger.info(`Extracting component ${zipFile}`);
583
- const zipPath = path7.join(osContentPath, zipFile);
772
+ const zipPath = path9.join(osContentPath, zipFile);
584
773
  const thisBudget = base + (rem > 0 ? 1 : 0);
585
774
  if (rem > 0) rem--;
586
775
  await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
587
776
  cursor += thisBudget;
588
- fs8.unlinkSync(zipPath);
777
+ fs9.unlinkSync(zipPath);
589
778
  }
590
779
  progress?.report({ message: "Finalizing installation...", increment: 0 });
591
- if (fs8.existsSync(sdkInstallDir)) {
592
- fs8.rmSync(sdkInstallDir, { recursive: true, force: true });
780
+ if (fs9.existsSync(sdkInstallDir)) {
781
+ fs9.rmSync(sdkInstallDir, { recursive: true, force: true });
593
782
  }
594
783
  movePath(osContentPath, sdkInstallDir);
595
784
  progress?.report({ message: "Finalizing installation...", increment: 3 });
596
785
  progress?.report({ message: "Cleaning up...", increment: 0 });
597
- fs8.rmSync(tmpDir, { recursive: true, force: true });
786
+ removeTempWorkDir(tmpDir, tmpRoot);
598
787
  progress?.report({ message: "Cleaning up...", increment: 2 });
599
788
  } catch (err) {
600
- logger.error(`SDK install failed: ${err instanceof Error ? err.message : String(err)}`);
601
- fs8.rmSync(tmpDir, { recursive: true, force: true });
602
- throw err;
789
+ const wrapped = toSpaceError(err, tmpRoot, what);
790
+ logger.error(`SDK install failed: ${wrapped instanceof Error ? wrapped.message : String(wrapped)}`);
791
+ removeTempWorkDir(tmpDir, tmpRoot);
792
+ throw wrapped;
603
793
  }
604
794
  }
605
795
 
606
796
  // src/cmdTools/install.ts
607
- import * as fs9 from "fs";
797
+ import * as fs10 from "fs";
608
798
  import * as os6 from "os";
609
- import * as path8 from "path";
610
- import { execFileSync } from "child_process";
611
- function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
612
- if (platform6 === "linux") {
799
+ import * as path11 from "path";
800
+ import { spawnSync } from "child_process";
801
+
802
+ // src/hdc/spawnCompat.ts
803
+ import * as path10 from "path";
804
+ import { spawn } from "child_process";
805
+ var WINDOWS_BATCH_RE = /\.(bat|cmd)$/i;
806
+ var CMD_META_RE = /([()[\]%!^"`<>&|;, *?])/g;
807
+ var CMD_SHIM_RE = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i;
808
+ var UNSAFE_CMD_CHARS_RE = /["\r\n\0]/;
809
+ function needsCmdWrapper(command, platform7 = process.platform) {
810
+ return platform7 === "win32" && WINDOWS_BATCH_RE.test(command);
811
+ }
812
+ function escapeCmdArgument(arg, doubleEscapeMetaChars = false) {
813
+ let out = String(arg);
814
+ out = out.replace(/(\\*)"/g, '$1$1\\"');
815
+ out = out.replace(/(\\*)$/, "$1$1");
816
+ out = `"${out}"`;
817
+ out = out.replace(CMD_META_RE, "^$1");
818
+ if (doubleEscapeMetaChars) out = out.replace(CMD_META_RE, "^$1");
819
+ return out;
820
+ }
821
+ function escapeCmdCommand(command) {
822
+ return command.replace(CMD_META_RE, "^$1");
823
+ }
824
+ function buildCmdWrapper(command, args) {
825
+ assertCmdSafe(command, `command ${command}`);
826
+ args.forEach((a, i) => assertCmdSafe(a, `argument ${i} (${a})`));
827
+ const normalized = path10.normalize(command);
828
+ const double = CMD_SHIM_RE.test(normalized);
829
+ const line = [escapeCmdCommand(normalized), ...args.map((a) => escapeCmdArgument(a, double))].join(" ");
830
+ return {
831
+ command: process.env.ComSpec ?? process.env.COMSPEC ?? "cmd.exe",
832
+ args: ["/d", "/s", "/c", `"${line}"`]
833
+ };
834
+ }
835
+ function assertCmdSafe(value, label) {
836
+ if (UNSAFE_CMD_CHARS_RE.test(value)) {
837
+ throw new OniroError(
838
+ `Cannot run a Windows batch wrapper with a quote, newline or NUL in the ${label}: cmd.exe cannot pass it through unambiguously.`
839
+ );
840
+ }
841
+ }
842
+ function spawnCompat(command, args, options = {}) {
843
+ if (!needsCmdWrapper(command)) {
844
+ return spawn(command, [...args], { ...options, shell: false });
845
+ }
846
+ const wrapped = buildCmdWrapper(command, args);
847
+ return spawn(wrapped.command, wrapped.args, {
848
+ ...options,
849
+ shell: false,
850
+ // The line is already escaped; stop Node from re-quoting it.
851
+ windowsVerbatimArguments: true
852
+ });
853
+ }
854
+ function killProcessTree(child) {
855
+ if (process.platform === "win32" && typeof child.pid === "number") {
856
+ try {
857
+ const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
858
+ killer.on("error", () => {
859
+ try {
860
+ child.kill("SIGKILL");
861
+ } catch {
862
+ }
863
+ });
864
+ return;
865
+ } catch {
866
+ }
867
+ }
868
+ try {
869
+ child.kill("SIGKILL");
870
+ } catch {
871
+ }
872
+ }
873
+
874
+ // src/cmdTools/install.ts
875
+ function getCmdToolsDownloadUrl(config, platform7 = os6.platform()) {
876
+ if (platform7 === "linux") {
613
877
  return config.get("cmdToolsUrlLinux", defaultPaths.cmdToolsUrlLinux);
614
878
  }
615
- if (platform6 === "win32") {
879
+ if (platform7 === "win32") {
616
880
  const url = config.get("cmdToolsUrlWindows", "");
617
881
  if (!url) {
618
882
  throw new OniroError(
@@ -621,7 +885,7 @@ function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
621
885
  }
622
886
  return url;
623
887
  }
624
- if (platform6 === "darwin") {
888
+ if (platform7 === "darwin") {
625
889
  const url = config.get("cmdToolsUrlMac", "");
626
890
  if (!url) {
627
891
  throw new OniroError(
@@ -630,20 +894,20 @@ function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
630
894
  }
631
895
  return url;
632
896
  }
633
- throw new UnsupportedPlatformError(platform6);
897
+ throw new UnsupportedPlatformError(platform7);
634
898
  }
635
899
  function findCmdToolsSourceDir(extractPath) {
636
900
  const known = [
637
- path8.join(extractPath, "command-line-tools"),
638
- path8.join(extractPath, "oh-command-line-tools"),
639
- path8.join(extractPath, "commandline-tools")
901
+ path11.join(extractPath, "command-line-tools"),
902
+ path11.join(extractPath, "oh-command-line-tools"),
903
+ path11.join(extractPath, "commandline-tools")
640
904
  ];
641
905
  for (const c of known) {
642
- if (fs9.existsSync(c)) return c;
906
+ if (fs10.existsSync(c)) return c;
643
907
  }
644
- const entries = fs9.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path8.join(extractPath, e.name));
908
+ const entries = fs10.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path11.join(extractPath, e.name));
645
909
  for (const dir of entries) {
646
- if (fs9.existsSync(path8.join(dir, "bin"))) return dir;
910
+ if (fs10.existsSync(path11.join(dir, "bin"))) return dir;
647
911
  }
648
912
  throw new OniroError("Could not locate command line tools folder in the extracted archive.");
649
913
  }
@@ -651,53 +915,62 @@ async function installCmdTools(opts) {
651
915
  const { config, progress, abortSignal } = opts;
652
916
  const logger = opts.logger ?? noopLogger;
653
917
  const CMD_PATH = getCmdToolsPath(config);
654
- const tmpDir = fs9.mkdtempSync(path8.join(os6.tmpdir(), "oniro-cmdtools-"));
655
- const zipPath = opts.localZipPath ?? path8.join(tmpDir, "oh-command-line-tools.zip");
656
- const extractPath = path8.join(tmpDir, "oh-command-line-tools");
918
+ const what = "the command-line tools install";
919
+ const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
920
+ config,
921
+ override: opts.tmpDir,
922
+ installRoot: path11.dirname(CMD_PATH),
923
+ prefix: "oniro-cmdtools-",
924
+ logger
925
+ });
926
+ const zipPath = opts.localZipPath ?? path11.join(tmpDir, "oh-command-line-tools.zip");
927
+ const extractPath = path11.join(tmpDir, "oh-command-line-tools");
657
928
  try {
658
929
  if (!opts.localZipPath) {
659
930
  const url = getCmdToolsDownloadUrl(config);
660
931
  progress?.report({ message: "Downloading command line tools...", increment: 0 });
661
- await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50 });
932
+ await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
662
933
  }
663
934
  progress?.report({ message: "Extracting tools...", increment: 0 });
664
935
  await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
665
- fs9.mkdirSync(CMD_PATH, { recursive: true });
936
+ fs10.mkdirSync(CMD_PATH, { recursive: true });
666
937
  const srcDir = findCmdToolsSourceDir(extractPath);
667
- for (const entry of fs9.readdirSync(srcDir)) {
668
- const src = path8.join(srcDir, entry);
669
- const dest = path8.join(CMD_PATH, entry);
670
- if (fs9.statSync(src).isDirectory()) {
671
- if (fs9.existsSync(dest)) fs9.rmSync(dest, { recursive: true, force: true });
938
+ for (const entry of fs10.readdirSync(srcDir)) {
939
+ const src = path11.join(srcDir, entry);
940
+ const dest = path11.join(CMD_PATH, entry);
941
+ if (fs10.statSync(src).isDirectory()) {
942
+ if (fs10.existsSync(dest)) fs10.rmSync(dest, { recursive: true, force: true });
672
943
  movePath(src, dest);
673
944
  } else {
674
- fs9.copyFileSync(src, dest);
945
+ fs10.copyFileSync(src, dest);
675
946
  }
676
947
  }
677
- const binDir = path8.join(CMD_PATH, "bin");
678
- if (fs9.existsSync(binDir) && os6.platform() !== "win32") {
679
- for (const file of fs9.readdirSync(binDir)) {
680
- fs9.chmodSync(path8.join(binDir, file), 493);
948
+ const binDir = path11.join(CMD_PATH, "bin");
949
+ if (fs10.existsSync(binDir) && os6.platform() !== "win32") {
950
+ for (const file of fs10.readdirSync(binDir)) {
951
+ fs10.chmodSync(path11.join(binDir, file), 493);
681
952
  }
682
953
  }
683
954
  progress?.report({ message: "Finalizing installation...", increment: 5 });
684
955
  progress?.report({ message: "Cleaning up...", increment: 0 });
956
+ } catch (err) {
957
+ throw toSpaceError(err, tmpRoot, what);
685
958
  } finally {
686
- fs9.rmSync(tmpDir, { recursive: true, force: true });
959
+ removeTempWorkDir(tmpDir, tmpRoot);
687
960
  }
688
961
  }
689
962
  function getCmdToolsStatus(config) {
690
963
  const cmdPath = getCmdToolsPath(config);
691
- const binDir = path8.join(cmdPath, "bin");
964
+ const binDir = path11.join(cmdPath, "bin");
692
965
  const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
693
- const bin = candidates.map((c) => path8.join(binDir, c)).find((p) => fs9.existsSync(p));
966
+ const bin = candidates.map((c) => path11.join(binDir, c)).find((p) => fs10.existsSync(p));
694
967
  if (!bin) {
695
968
  return { installed: false, status: "Not installed" };
696
969
  }
697
970
  try {
698
- const versionFile = path8.join(cmdPath, "version.txt");
699
- if (fs9.existsSync(versionFile)) {
700
- const content = fs9.readFileSync(versionFile, "utf8");
971
+ const versionFile = path11.join(cmdPath, "version.txt");
972
+ if (fs10.existsSync(versionFile)) {
973
+ const content = fs10.readFileSync(versionFile, "utf8");
701
974
  for (const line of content.split(/\r?\n/)) {
702
975
  const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
703
976
  if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
@@ -706,7 +979,14 @@ function getCmdToolsStatus(config) {
706
979
  } catch {
707
980
  }
708
981
  try {
709
- const version = execFileSync(bin, ["-v"], { encoding: "utf8" }).trim();
982
+ const wrapped = needsCmdWrapper(bin);
983
+ const probe = wrapped ? buildCmdWrapper(bin, ["-v"]) : { command: bin, args: ["-v"] };
984
+ const res = spawnSync(probe.command, probe.args, {
985
+ encoding: "utf8",
986
+ windowsVerbatimArguments: wrapped
987
+ });
988
+ if (res.error || res.status !== 0) throw res.error ?? new Error(`exit ${String(res.status)}`);
989
+ const version = res.stdout.trim();
710
990
  return { installed: true, status: `Installed (${version})` };
711
991
  } catch {
712
992
  return { installed: true, status: "Installed (version unknown)" };
@@ -717,14 +997,14 @@ function isCmdToolsInstalled(config) {
717
997
  }
718
998
  function removeCmdTools(config) {
719
999
  const cmdPath = getCmdToolsPath(config);
720
- if (fs9.existsSync(cmdPath)) {
721
- fs9.rmSync(cmdPath, { recursive: true, force: true });
1000
+ if (fs10.existsSync(cmdPath)) {
1001
+ fs10.rmSync(cmdPath, { recursive: true, force: true });
722
1002
  }
723
1003
  }
724
1004
 
725
1005
  // src/sign/encryptKey.ts
726
- import * as fs10 from "fs";
727
- import * as path9 from "path";
1006
+ import * as fs11 from "fs";
1007
+ import * as path12 from "path";
728
1008
  import * as crypto2 from "crypto";
729
1009
  var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
730
1010
  var DAEMON_SALT_KEY_LENGTH = 16;
@@ -796,33 +1076,33 @@ function getRootKey(fdComponents, salt) {
796
1076
  function createAndStoreKey(dir, keyLength) {
797
1077
  const key = crypto2.randomBytes(keyLength);
798
1078
  const hash = crypto2.createHash("sha256").update(key).digest("hex");
799
- const filePath = path9.join(dir, hash);
800
- fs10.writeFileSync(filePath, key);
801
- fs10.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1079
+ const filePath = path12.join(dir, hash);
1080
+ fs11.writeFileSync(filePath, key);
1081
+ fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
802
1082
  return key;
803
1083
  }
804
1084
  function createAndStoreEnKey(rootKey, ceDir) {
805
1085
  const workKey = crypto2.randomBytes(DAEMON_WORK_KEY_LENGTH);
806
1086
  const encrypted = encrypt(rootKey, workKey);
807
1087
  const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
808
- const filePath = path9.join(ceDir, hash);
809
- fs10.writeFileSync(filePath, encrypted);
810
- fs10.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1088
+ const filePath = path12.join(ceDir, hash);
1089
+ fs11.writeFileSync(filePath, encrypted);
1090
+ fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
811
1091
  }
812
1092
  function createMaterial(materialPath) {
813
- fs10.mkdirSync(materialPath, { recursive: true });
814
- fs10.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
815
- const fdDir = path9.join(materialPath, "fd");
816
- const acDir = path9.join(materialPath, "ac");
817
- const ceDir = path9.join(materialPath, "ce");
818
- fs10.mkdirSync(fdDir, { recursive: true });
819
- fs10.mkdirSync(acDir, { recursive: true });
820
- fs10.mkdirSync(ceDir, { recursive: true });
1093
+ fs11.mkdirSync(materialPath, { recursive: true });
1094
+ fs11.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
1095
+ const fdDir = path12.join(materialPath, "fd");
1096
+ const acDir = path12.join(materialPath, "ac");
1097
+ const ceDir = path12.join(materialPath, "ce");
1098
+ fs11.mkdirSync(fdDir, { recursive: true });
1099
+ fs11.mkdirSync(acDir, { recursive: true });
1100
+ fs11.mkdirSync(ceDir, { recursive: true });
821
1101
  const fdSubDirs = ["0", "1", "2"];
822
1102
  const fdComponents = [];
823
1103
  for (const sub of fdSubDirs) {
824
- const subDir = path9.join(fdDir, sub);
825
- fs10.mkdirSync(subDir, { recursive: true });
1104
+ const subDir = path12.join(fdDir, sub);
1105
+ fs11.mkdirSync(subDir, { recursive: true });
826
1106
  const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
827
1107
  fdComponents.push(comp);
828
1108
  }
@@ -831,24 +1111,24 @@ function createMaterial(materialPath) {
831
1111
  createAndStoreEnKey(rootKey, ceDir);
832
1112
  }
833
1113
  function readSingleFile(dir) {
834
- if (!fs10.existsSync(dir) || !fs10.statSync(dir).isDirectory()) {
1114
+ if (!fs11.existsSync(dir) || !fs11.statSync(dir).isDirectory()) {
835
1115
  throw new Error(`Missing signing material directory: ${dir}`);
836
1116
  }
837
- const files = fs10.readdirSync(dir).filter((f) => f !== ".DS_Store");
1117
+ const files = fs11.readdirSync(dir).filter((f) => f !== ".DS_Store");
838
1118
  if (files.length !== 1) {
839
1119
  throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
840
1120
  }
841
- return fs10.readFileSync(path9.join(dir, files[0]));
1121
+ return fs11.readFileSync(path12.join(dir, files[0]));
842
1122
  }
843
1123
  function getKey(materialPath) {
844
- if (!fs10.existsSync(materialPath) || !fs10.statSync(materialPath).isDirectory()) {
1124
+ if (!fs11.existsSync(materialPath) || !fs11.statSync(materialPath).isDirectory()) {
845
1125
  throw new Error("Material directory does not exist.");
846
1126
  }
847
- const fdDir = path9.join(materialPath, "fd");
848
- const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path9.join(fdDir, sub)));
849
- const salt = readSingleFile(path9.join(materialPath, "ac"));
1127
+ const fdDir = path12.join(materialPath, "fd");
1128
+ const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path12.join(fdDir, sub)));
1129
+ const salt = readSingleFile(path12.join(materialPath, "ac"));
850
1130
  const rootKey = getRootKey(fdComponents, salt);
851
- const workMaterial = readSingleFile(path9.join(materialPath, "ce"));
1131
+ const workMaterial = readSingleFile(path12.join(materialPath, "ce"));
852
1132
  return decrypt(rootKey, workMaterial);
853
1133
  }
854
1134
  function encryptPwd(password, materialPath) {
@@ -863,9 +1143,9 @@ function decryptPwd(encryptedHex, materialPath) {
863
1143
  }
864
1144
 
865
1145
  // src/sign/generateSigningConfigs.ts
866
- import * as fs11 from "fs";
867
- import * as path10 from "path";
868
- import { execFileSync as execFileSync2 } from "child_process";
1146
+ import * as fs12 from "fs";
1147
+ import * as path13 from "path";
1148
+ import { execFileSync } from "child_process";
869
1149
  import JSON52 from "json5";
870
1150
 
871
1151
  // src/sign/applicationCert.ts
@@ -934,17 +1214,17 @@ var SIGNING_KIND_CONFIG = {
934
1214
  };
935
1215
  function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
936
1216
  logger.info("[sign] Copying signing material into project...");
937
- const signaturesDir = path10.join(projectDir, "signatures");
938
- fs11.mkdirSync(signaturesDir, { recursive: true });
939
- fs11.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
940
- fs11.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
941
- fs11.copyFileSync(paths.unsignedProfileTemplate, path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
1217
+ const signaturesDir = path13.join(projectDir, "signatures");
1218
+ fs12.mkdirSync(signaturesDir, { recursive: true });
1219
+ fs12.copyFileSync(paths.keystore, path13.join(signaturesDir, "OpenHarmony.p12"));
1220
+ fs12.copyFileSync(paths.profileCert, path13.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
1221
+ fs12.copyFileSync(paths.unsignedProfileTemplate, path13.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
942
1222
  if (kind === "application-release") {
943
- const dest = path10.join(signaturesDir, "OpenHarmonyApplication.cer");
1223
+ const dest = path13.join(signaturesDir, "OpenHarmonyApplication.cer");
944
1224
  if (applicationCertPath) {
945
- fs11.copyFileSync(applicationCertPath, dest);
1225
+ fs12.copyFileSync(applicationCertPath, dest);
946
1226
  } else {
947
- fs11.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
1227
+ fs12.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
948
1228
  }
949
1229
  }
950
1230
  }
@@ -953,7 +1233,7 @@ function resolveAppFeature(apl, override) {
953
1233
  return apl === "normal" ? "hos_normal_app" : "hos_system_app";
954
1234
  }
955
1235
  function extractLeafCertificate(chainPath) {
956
- const certContent = fs11.readFileSync(chainPath, "utf-8");
1236
+ const certContent = fs12.readFileSync(chainPath, "utf-8");
957
1237
  const parts = certContent.split("-----END CERTIFICATE-----");
958
1238
  if (parts.length < 3) {
959
1239
  throw new OniroError(`${chainPath} does not contain enough certificates.`);
@@ -963,20 +1243,20 @@ function extractLeafCertificate(chainPath) {
963
1243
  function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}) {
964
1244
  const kind = options.kind ?? pickSigningKind(apl);
965
1245
  logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
966
- const appJsonPath = path10.join(projectDir, "AppScope/app.json5");
967
- const profileTemplatePath = path10.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
968
- if (!fs11.existsSync(appJsonPath)) {
1246
+ const appJsonPath = path13.join(projectDir, "AppScope/app.json5");
1247
+ const profileTemplatePath = path13.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
1248
+ if (!fs12.existsSync(appJsonPath)) {
969
1249
  throw new OniroError(`${appJsonPath} does not exist.`);
970
1250
  }
971
1251
  let appJson;
972
1252
  try {
973
- appJson = JSON52.parse(fs11.readFileSync(appJsonPath, "utf-8"));
1253
+ appJson = JSON52.parse(fs12.readFileSync(appJsonPath, "utf-8"));
974
1254
  } catch (e) {
975
1255
  throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
976
1256
  }
977
1257
  let profileTemplate;
978
1258
  try {
979
- profileTemplate = JSON.parse(fs11.readFileSync(profileTemplatePath, "utf-8"));
1259
+ profileTemplate = JSON.parse(fs12.readFileSync(profileTemplatePath, "utf-8"));
980
1260
  } catch (e) {
981
1261
  throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
982
1262
  }
@@ -989,7 +1269,7 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
989
1269
  profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
990
1270
  profileTemplate["bundle-info"]["apl"] = apl;
991
1271
  profileTemplate["bundle-info"]["app-feature"] = appFeature;
992
- const distCertChainPath = path10.join(
1272
+ const distCertChainPath = path13.join(
993
1273
  projectDir,
994
1274
  "signatures",
995
1275
  SIGNING_KIND_CONFIG[kind].certBasename
@@ -999,13 +1279,13 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
999
1279
  profileTemplate.acls = profileTemplate.acls ?? {};
1000
1280
  profileTemplate.acls["allowed-acls"] = options.acls;
1001
1281
  }
1002
- fs11.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1282
+ fs12.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1003
1283
  }
1004
1284
  function generateP7bFile(projectDir, paths, logger, passwords) {
1005
1285
  logger.info("[sign] Generating P7b profile via hap-sign-tool...");
1006
- const signaturesDir = path10.join(projectDir, "signatures");
1007
- const profileTemplatePath = path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
1008
- const outputProfilePath = path10.join(signaturesDir, "app1-profile.p7b");
1286
+ const signaturesDir = path13.join(projectDir, "signatures");
1287
+ const profileTemplatePath = path13.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
1288
+ const outputProfilePath = path13.join(signaturesDir, "app1-profile.p7b");
1009
1289
  const args = [
1010
1290
  "-jar",
1011
1291
  paths.signTool,
@@ -1030,7 +1310,7 @@ function generateP7bFile(projectDir, paths, logger, passwords) {
1030
1310
  passwords.store
1031
1311
  ];
1032
1312
  try {
1033
- execFileSync2("java", args, { stdio: "inherit" });
1313
+ execFileSync("java", args, { stdio: "inherit" });
1034
1314
  } catch (e) {
1035
1315
  throw new OniroError(
1036
1316
  `hap-sign-tool failed. Ensure a JDK (with keytool/java) is on PATH. Underlying error: ${e.message}`,
@@ -1039,11 +1319,11 @@ function generateP7bFile(projectDir, paths, logger, passwords) {
1039
1319
  }
1040
1320
  }
1041
1321
  function detectSigningConfigNames(projectDir) {
1042
- const buildProfilePath = path10.join(projectDir, "build-profile.json5");
1043
- if (!fs11.existsSync(buildProfilePath)) return ["default"];
1322
+ const buildProfilePath = path13.join(projectDir, "build-profile.json5");
1323
+ if (!fs12.existsSync(buildProfilePath)) return ["default"];
1044
1324
  let parsed;
1045
1325
  try {
1046
- parsed = JSON52.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
1326
+ parsed = JSON52.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
1047
1327
  } catch {
1048
1328
  return ["default"];
1049
1329
  }
@@ -1060,14 +1340,14 @@ function detectSigningConfigNames(projectDir) {
1060
1340
  }
1061
1341
  function updateBuildProfile(projectDir, kind, logger, passwords) {
1062
1342
  logger.info("[sign] Writing signing configs into build-profile.json5...");
1063
- const materialDir = path10.join(projectDir, "signatures", "material");
1064
- const buildProfilePath = path10.join(projectDir, "build-profile.json5");
1343
+ const materialDir = path13.join(projectDir, "signatures", "material");
1344
+ const buildProfilePath = path13.join(projectDir, "build-profile.json5");
1065
1345
  const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
1066
1346
  const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
1067
1347
  let buildProfile = { app: {} };
1068
- if (fs11.existsSync(buildProfilePath)) {
1348
+ if (fs12.existsSync(buildProfilePath)) {
1069
1349
  try {
1070
- buildProfile = JSON52.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
1350
+ buildProfile = JSON52.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
1071
1351
  } catch (e) {
1072
1352
  throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
1073
1353
  }
@@ -1088,13 +1368,13 @@ function updateBuildProfile(projectDir, kind, logger, passwords) {
1088
1368
  storeFile: "./signatures/OpenHarmony.p12"
1089
1369
  }
1090
1370
  }));
1091
- fs11.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1371
+ fs12.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1092
1372
  }
1093
1373
  function prepareMaterialDirectory(projectDir, logger) {
1094
1374
  logger.info("[sign] Generating fresh signing material...");
1095
- const materialDir = path10.join(projectDir, "signatures", "material");
1096
- if (fs11.existsSync(materialDir)) {
1097
- fs11.rmSync(materialDir, { recursive: true, force: true });
1375
+ const materialDir = path13.join(projectDir, "signatures", "material");
1376
+ if (fs12.existsSync(materialDir)) {
1377
+ fs12.rmSync(materialDir, { recursive: true, force: true });
1098
1378
  }
1099
1379
  createMaterial(materialDir);
1100
1380
  }
@@ -1117,15 +1397,15 @@ function generateSigningConfigs(options) {
1117
1397
  "Could not detect project SDK version (compileSdkVersion missing in build-profile.json5)."
1118
1398
  );
1119
1399
  }
1120
- const sdkPath = path10.join(sdkHome, String(sdkVersion));
1121
- if (!fs11.existsSync(sdkPath)) {
1400
+ const sdkPath = path13.join(sdkHome, String(sdkVersion));
1401
+ if (!fs12.existsSync(sdkPath)) {
1122
1402
  throw new OniroError(`SDK path does not exist: ${sdkPath}`);
1123
1403
  }
1124
1404
  const paths = {
1125
- signTool: path10.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
1126
- keystore: path10.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
1127
- profileCert: path10.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
1128
- unsignedProfileTemplate: path10.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
1405
+ signTool: path13.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
1406
+ keystore: path13.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
1407
+ profileCert: path13.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
1408
+ unsignedProfileTemplate: path13.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
1129
1409
  };
1130
1410
  const kind = pickSigningKind(apl);
1131
1411
  const passwords = options.passwords ?? DEFAULT_PASSWORDS;
@@ -1139,60 +1419,81 @@ function generateSigningConfigs(options) {
1139
1419
  }
1140
1420
 
1141
1421
  // src/emulator/install.ts
1142
- import * as fs12 from "fs";
1143
- import * as os7 from "os";
1144
- import * as path11 from "path";
1422
+ import * as fs13 from "fs";
1423
+ import * as path14 from "path";
1145
1424
  async function installEmulator(opts) {
1146
1425
  const { config, progress, abortSignal } = opts;
1147
1426
  const logger = opts.logger ?? noopLogger;
1148
1427
  const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
1149
1428
  const emulatorDir = getEmulatorDir(config);
1150
- const tmpDir = fs12.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
1151
- const tmpZip = path11.join(tmpDir, "oniro_emulator.zip");
1429
+ const what = "the emulator install";
1430
+ const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
1431
+ config,
1432
+ override: opts.tmpDir,
1433
+ installRoot: path14.dirname(emulatorDir),
1434
+ prefix: "oniro-emulator-",
1435
+ logger
1436
+ });
1437
+ const tmpZip = path14.join(tmpDir, "oniro_emulator.zip");
1152
1438
  try {
1153
- fs12.mkdirSync(emulatorDir, { recursive: true });
1439
+ fs13.mkdirSync(emulatorDir, { recursive: true });
1154
1440
  progress?.report({ message: "Downloading emulator...", increment: 0 });
1155
- await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
1441
+ await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
1156
1442
  progress?.report({ message: "Extracting emulator...", increment: 0 });
1157
1443
  await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
1158
- const runSh = path11.join(emulatorDir, "images", "run.sh");
1159
- if (fs12.existsSync(runSh)) {
1160
- fs12.chmodSync(runSh, 493);
1444
+ const runSh = path14.join(emulatorDir, "images", "run.sh");
1445
+ if (fs13.existsSync(runSh)) {
1446
+ fs13.chmodSync(runSh, 493);
1161
1447
  }
1162
1448
  progress?.report({ message: "Finalizing installation...", increment: 5 });
1449
+ } catch (err) {
1450
+ throw toSpaceError(err, tmpRoot, what);
1163
1451
  } finally {
1164
- fs12.rmSync(tmpDir, { recursive: true, force: true });
1452
+ removeTempWorkDir(tmpDir, tmpRoot);
1165
1453
  }
1166
1454
  }
1167
1455
  function isEmulatorInstalled(config) {
1168
- const imagesDir = path11.join(getEmulatorDir(config), "images");
1169
- return fs12.existsSync(path11.join(imagesDir, "run.sh")) || fs12.existsSync(path11.join(imagesDir, "run.bat"));
1456
+ const imagesDir = path14.join(getEmulatorDir(config), "images");
1457
+ return fs13.existsSync(path14.join(imagesDir, "run.sh")) || fs13.existsSync(path14.join(imagesDir, "run.bat"));
1170
1458
  }
1171
1459
  function removeEmulator(config) {
1172
1460
  const emulatorDir = getEmulatorDir(config);
1173
- if (fs12.existsSync(emulatorDir)) {
1174
- fs12.rmSync(emulatorDir, { recursive: true, force: true });
1461
+ if (fs13.existsSync(emulatorDir)) {
1462
+ fs13.rmSync(emulatorDir, { recursive: true, force: true });
1175
1463
  }
1176
1464
  }
1177
1465
 
1178
1466
  // src/emulator/lifecycle.ts
1179
- import * as fs13 from "fs";
1180
- import * as os8 from "os";
1181
- import * as path12 from "path";
1467
+ import * as fs15 from "fs";
1468
+ import * as os7 from "os";
1469
+ import * as path15 from "path";
1182
1470
  import { spawn as spawn2 } from "child_process";
1183
1471
 
1184
1472
  // src/hdc/exec.ts
1185
- import { spawn } from "child_process";
1473
+ import * as fs14 from "fs";
1474
+ function errorMessage(err) {
1475
+ return err instanceof Error ? err.message : String(err);
1476
+ }
1186
1477
  function runProcess(opts) {
1187
1478
  const { command, args, cwd, env, onOutput } = opts;
1188
1479
  const timeoutMs = opts.timeoutMs ?? 6e4;
1189
1480
  const { abortSignal } = opts;
1190
- return new Promise((resolve2, reject) => {
1481
+ return new Promise((resolve4, reject) => {
1191
1482
  if (abortSignal?.aborted) {
1192
1483
  reject(new CancelledError(`Command aborted before start: ${command}`));
1193
1484
  return;
1194
1485
  }
1195
- const child = spawn(command, [...args], { cwd, env, shell: false });
1486
+ if (needsCmdWrapper(command) && !fs14.existsSync(command)) {
1487
+ reject(new OniroError(`Failed to spawn ${command}: ENOENT (no such file)`));
1488
+ return;
1489
+ }
1490
+ let child;
1491
+ try {
1492
+ child = spawnCompat(command, args, { cwd, env });
1493
+ } catch (err) {
1494
+ reject(new OniroError(`Failed to spawn ${command}: ${errorMessage(err)}`, err));
1495
+ return;
1496
+ }
1196
1497
  let stdout = "";
1197
1498
  let stderr = "";
1198
1499
  let settled = false;
@@ -1205,26 +1506,20 @@ function runProcess(opts) {
1205
1506
  if (settled) return;
1206
1507
  settled = true;
1207
1508
  cleanup();
1208
- try {
1209
- child.kill("SIGKILL");
1210
- } catch {
1211
- }
1509
+ killProcessTree(child);
1212
1510
  reject(new CancelledError(`Command aborted: ${command} ${args.join(" ")}`));
1213
1511
  };
1214
1512
  const timer = setTimeout(() => {
1215
1513
  timedOut = true;
1216
- try {
1217
- child.kill("SIGKILL");
1218
- } catch {
1219
- }
1514
+ killProcessTree(child);
1220
1515
  }, timeoutMs);
1221
1516
  abortSignal?.addEventListener("abort", onAbort, { once: true });
1222
- child.stdout.on("data", (d) => {
1517
+ child.stdout?.on("data", (d) => {
1223
1518
  const s = d.toString();
1224
1519
  stdout += s;
1225
1520
  onOutput?.(s, "stdout");
1226
1521
  });
1227
- child.stderr.on("data", (d) => {
1522
+ child.stderr?.on("data", (d) => {
1228
1523
  const s = d.toString();
1229
1524
  stderr += s;
1230
1525
  onOutput?.(s, "stderr");
@@ -1248,7 +1543,7 @@ ${stderr.trim()}` : "")
1248
1543
  );
1249
1544
  return;
1250
1545
  }
1251
- resolve2({ code: code ?? -1, stdout, stderr });
1546
+ resolve4({ code: code ?? -1, stdout, stderr });
1252
1547
  });
1253
1548
  });
1254
1549
  }
@@ -1274,9 +1569,29 @@ function ensureOk(result, command) {
1274
1569
  }
1275
1570
  return result;
1276
1571
  }
1572
+ var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
1573
+ function parseRemoteExit(stdout) {
1574
+ let code = null;
1575
+ for (const m of stdout.matchAll(REMOTE_RC_RE)) code = Number(m[1]);
1576
+ if (code === null) return null;
1577
+ return { code, stdout: stdout.replace(REMOTE_RC_RE, "").replace(/[ \t\r\n]+$/, "") };
1578
+ }
1579
+ async function shellChecked(opts) {
1580
+ const { command, ...rest } = opts;
1581
+ const res = await hdcExec({ ...rest, args: ["shell", `${command}; echo "__ONIRO_RC=$?__"`] });
1582
+ const parsed = parseRemoteExit(res.stdout);
1583
+ if (!parsed) return res;
1584
+ return { code: parsed.code, stdout: parsed.stdout, stderr: res.stderr };
1585
+ }
1586
+ function ensureRemoteOk(result, command) {
1587
+ if (result.code !== 0) {
1588
+ throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
1589
+ }
1590
+ return result;
1591
+ }
1277
1592
 
1278
1593
  // src/emulator/lifecycle.ts
1279
- var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
1594
+ var PID_FILE = path15.join(os7.tmpdir(), "oniro_emulator.pid");
1280
1595
  async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
1281
1596
  try {
1282
1597
  await hdcExec({ config, args: ["start", "-r"], logger });
@@ -1289,24 +1604,24 @@ async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger
1289
1604
  }
1290
1605
  }
1291
1606
  function resolveLauncher(imagesPath, headless, connect) {
1292
- const runSh = path12.join(imagesPath, "run.sh");
1293
- const runBat = path12.join(imagesPath, "run.bat");
1607
+ const runSh = path15.join(imagesPath, "run.sh");
1608
+ const runBat = path15.join(imagesPath, "run.bat");
1294
1609
  const extra = [];
1295
1610
  if (headless) extra.push("--headless");
1296
1611
  if (connect) extra.push("--connect", connect);
1297
- if (os8.platform() === "win32") {
1298
- if (fs13.existsSync(runBat)) {
1612
+ if (os7.platform() === "win32") {
1613
+ if (fs15.existsSync(runBat)) {
1299
1614
  return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
1300
1615
  }
1301
- if (fs13.existsSync(runSh)) {
1616
+ if (fs15.existsSync(runSh)) {
1302
1617
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1303
1618
  }
1304
1619
  throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
1305
1620
  }
1306
- if (fs13.existsSync(runSh)) {
1621
+ if (fs15.existsSync(runSh)) {
1307
1622
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1308
1623
  }
1309
- if (fs13.existsSync(runBat)) {
1624
+ if (fs15.existsSync(runBat)) {
1310
1625
  return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
1311
1626
  }
1312
1627
  throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
@@ -1314,9 +1629,9 @@ function resolveLauncher(imagesPath, headless, connect) {
1314
1629
  async function startEmulator(opts) {
1315
1630
  const { config } = opts;
1316
1631
  const logger = opts.logger ?? noopLogger;
1317
- if (os8.platform() !== "win32" && fs13.existsSync(PID_FILE)) {
1632
+ if (os7.platform() !== "win32" && fs15.existsSync(PID_FILE)) {
1318
1633
  try {
1319
- const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
1634
+ const pid = Number(fs15.readFileSync(PID_FILE, "utf8").trim());
1320
1635
  if (Number.isFinite(pid) && pid > 0) {
1321
1636
  try {
1322
1637
  process.kill(pid, 0);
@@ -1332,13 +1647,13 @@ async function startEmulator(opts) {
1332
1647
  logger.warn(`Could not read PID file: ${err.message}`);
1333
1648
  }
1334
1649
  }
1335
- const imagesPath = path12.join(getEmulatorDir(config), "images");
1336
- if (!fs13.existsSync(imagesPath)) {
1650
+ const imagesPath = path15.join(getEmulatorDir(config), "images");
1651
+ if (!fs15.existsSync(imagesPath)) {
1337
1652
  throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
1338
1653
  }
1339
1654
  const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
1340
- const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
1341
- const logFd = fs13.openSync(logPath, "a");
1655
+ const logPath = opts.logFile ?? (os7.platform() === "win32" ? "NUL" : "/dev/null");
1656
+ const logFd = fs15.openSync(logPath, "a");
1342
1657
  let child;
1343
1658
  try {
1344
1659
  child = spawn2(launcher.command, [...launcher.args], {
@@ -1348,19 +1663,19 @@ async function startEmulator(opts) {
1348
1663
  windowsHide: true
1349
1664
  });
1350
1665
  } finally {
1351
- fs13.closeSync(logFd);
1666
+ fs15.closeSync(logFd);
1352
1667
  }
1353
1668
  if (!child.pid) {
1354
1669
  throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
1355
1670
  }
1356
- fs13.writeFileSync(PID_FILE, String(child.pid));
1671
+ fs15.writeFileSync(PID_FILE, String(child.pid));
1357
1672
  child.unref();
1358
1673
  logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1359
- const earlyExit = new Promise((resolve2) => {
1360
- child.once("exit", (code) => resolve2(code));
1361
- child.once("error", () => resolve2(-1));
1674
+ const earlyExit = new Promise((resolve4) => {
1675
+ child.once("exit", (code) => resolve4(code));
1676
+ child.once("error", () => resolve4(-1));
1362
1677
  });
1363
- const liveness = new Promise((resolve2) => setTimeout(() => resolve2("alive"), 2e3));
1678
+ const liveness = new Promise((resolve4) => setTimeout(() => resolve4("alive"), 2e3));
1364
1679
  const outcome = await Promise.race([earlyExit, liveness]);
1365
1680
  if (outcome !== "alive") {
1366
1681
  const tail = readTail(logPath, 4e3);
@@ -1400,24 +1715,24 @@ ${tail}` : ""}`);
1400
1715
  }
1401
1716
  function readTail(filePath, bytes) {
1402
1717
  try {
1403
- const stat = fs13.statSync(filePath);
1718
+ const stat = fs15.statSync(filePath);
1404
1719
  const start = Math.max(0, stat.size - bytes);
1405
- const fd = fs13.openSync(filePath, "r");
1720
+ const fd = fs15.openSync(filePath, "r");
1406
1721
  try {
1407
1722
  const buf = Buffer.alloc(stat.size - start);
1408
- fs13.readSync(fd, buf, 0, buf.length, start);
1723
+ fs15.readSync(fd, buf, 0, buf.length, start);
1409
1724
  return buf.toString("utf8");
1410
1725
  } finally {
1411
- fs13.closeSync(fd);
1726
+ fs15.closeSync(fd);
1412
1727
  }
1413
1728
  } catch {
1414
1729
  return "";
1415
1730
  }
1416
1731
  }
1417
1732
  async function stopEmulator(logger = noopLogger) {
1418
- if (fs13.existsSync(PID_FILE)) {
1733
+ if (fs15.existsSync(PID_FILE)) {
1419
1734
  try {
1420
- const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
1735
+ const pid = Number(fs15.readFileSync(PID_FILE, "utf8").trim());
1421
1736
  if (Number.isFinite(pid) && pid > 0) {
1422
1737
  try {
1423
1738
  process.kill(pid, "SIGTERM");
@@ -1430,13 +1745,13 @@ async function stopEmulator(logger = noopLogger) {
1430
1745
  logger.warn(`Could not read PID file: ${err.message}`);
1431
1746
  }
1432
1747
  }
1433
- const [killCmd, ...killArgs] = os8.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
1748
+ const [killCmd, ...killArgs] = os7.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
1434
1749
  try {
1435
1750
  await runProcess({ command: killCmd, args: killArgs, logger });
1436
1751
  } catch (err) {
1437
1752
  logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
1438
1753
  }
1439
- fs13.rm(PID_FILE, { force: true }, (err) => {
1754
+ fs15.rm(PID_FILE, { force: true }, (err) => {
1440
1755
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
1441
1756
  });
1442
1757
  }
@@ -1559,7 +1874,7 @@ async function getDisplaySize(config, opts = {}) {
1559
1874
 
1560
1875
  // src/hdc/wait.ts
1561
1876
  function delay(ms, signal) {
1562
- return new Promise((resolve2, reject) => {
1877
+ return new Promise((resolve4, reject) => {
1563
1878
  if (signal?.aborted) {
1564
1879
  reject(new CancelledError("Wait cancelled."));
1565
1880
  return;
@@ -1574,7 +1889,7 @@ function delay(ms, signal) {
1574
1889
  };
1575
1890
  const timer = setTimeout(() => {
1576
1891
  cleanup();
1577
- resolve2();
1892
+ resolve4();
1578
1893
  }, ms);
1579
1894
  signal?.addEventListener("abort", onAbort, { once: true });
1580
1895
  });
@@ -1603,21 +1918,20 @@ async function waitForCondition(opts) {
1603
1918
  }
1604
1919
 
1605
1920
  // src/hdc/app.ts
1606
- import * as fs15 from "fs";
1607
- import * as path14 from "path";
1608
- import { spawn as spawn3 } from "child_process";
1921
+ import * as fs17 from "fs";
1922
+ import * as path17 from "path";
1609
1923
 
1610
1924
  // src/hdc/project.ts
1611
- import * as fs14 from "fs";
1612
- import * as path13 from "path";
1925
+ import * as fs16 from "fs";
1926
+ import * as path16 from "path";
1613
1927
  import JSON53 from "json5";
1614
1928
  function readJson5(filePath) {
1615
- const content = fs14.readFileSync(filePath, "utf-8");
1929
+ const content = fs16.readFileSync(filePath, "utf-8");
1616
1930
  return JSON53.parse(content);
1617
1931
  }
1618
1932
  function getBundleName(projectDir) {
1619
- const appJsonPath = path13.join(projectDir, "AppScope", "app.json5");
1620
- if (!fs14.existsSync(appJsonPath)) {
1933
+ const appJsonPath = path16.join(projectDir, "AppScope", "app.json5");
1934
+ if (!fs16.existsSync(appJsonPath)) {
1621
1935
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
1622
1936
  }
1623
1937
  const appJson = readJson5(appJsonPath);
@@ -1627,8 +1941,8 @@ function getBundleName(projectDir) {
1627
1941
  return appJson.app.bundleName;
1628
1942
  }
1629
1943
  function getMainAbility(projectDir, moduleName = "entry", abilityName) {
1630
- const moduleJsonPath = path13.join(projectDir, moduleName, "src", "main", "module.json5");
1631
- if (!fs14.existsSync(moduleJsonPath)) {
1944
+ const moduleJsonPath = path16.join(projectDir, moduleName, "src", "main", "module.json5");
1945
+ if (!fs16.existsSync(moduleJsonPath)) {
1632
1946
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
1633
1947
  }
1634
1948
  const moduleJson = readJson5(moduleJsonPath);
@@ -1651,8 +1965,8 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
1651
1965
  async function installApp(opts) {
1652
1966
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1653
1967
  const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
1654
- const hapPath = path14.isAbsolute(relativeHapPath) ? relativeHapPath : path14.join(opts.projectDir, relativeHapPath);
1655
- if (!fs15.existsSync(hapPath)) {
1968
+ const hapPath = path17.isAbsolute(relativeHapPath) ? relativeHapPath : path17.join(opts.projectDir, relativeHapPath);
1969
+ if (!fs17.existsSync(hapPath)) {
1656
1970
  throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
1657
1971
  }
1658
1972
  const result = await hdcExec({ config: opts.config, args: ["install", hapPath], timeoutMs: 3e5 });
@@ -1669,13 +1983,59 @@ async function installApp(opts) {
1669
1983
  function detectAaStartFailure(output) {
1670
1984
  return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
1671
1985
  }
1986
+ function parseMainAbilityFromBmDump(stdout) {
1987
+ const start = stdout.indexOf("{");
1988
+ if (start < 0) return null;
1989
+ let info;
1990
+ try {
1991
+ info = JSON.parse(stdout.slice(start));
1992
+ } catch {
1993
+ return null;
1994
+ }
1995
+ const mods = Array.isArray(info?.hapModuleInfos) ? info.hapModuleInfos : [];
1996
+ const abilityOf = (m) => m?.mainElementName || m?.mainAbility || void 0;
1997
+ const isEntry = (m) => m?.moduleType === 1 || m?.moduleType === "1" || m?.moduleType === "entry";
1998
+ const entry = mods.find((m) => isEntry(m) && abilityOf(m));
1999
+ const any = mods.find((m) => abilityOf(m));
2000
+ return entry && abilityOf(entry) || any && abilityOf(any) || null;
2001
+ }
2002
+ async function resolveInstalledMainAbility(opts) {
2003
+ const res = await shell({
2004
+ config: opts.config,
2005
+ command: `bm dump -n ${opts.bundle}`,
2006
+ deviceSerial: opts.deviceSerial,
2007
+ timeoutMs: 15e3,
2008
+ logger: opts.logger
2009
+ });
2010
+ const ability = parseMainAbilityFromBmDump(res.stdout);
2011
+ if (!ability) {
2012
+ throw new OniroError(
2013
+ `Could not determine the main ability for installed bundle ${opts.bundle} (is it installed?). Pass an explicit ability instead.`
2014
+ );
2015
+ }
2016
+ return ability;
2017
+ }
1672
2018
  async function launchApp(opts) {
1673
2019
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1674
- const bundleName = getBundleName(opts.projectDir);
1675
- const mainAbility = getMainAbility(opts.projectDir, opts.moduleName, opts.abilityName);
2020
+ let bundleName;
2021
+ let mainAbility;
2022
+ if (opts.bundleName) {
2023
+ bundleName = opts.bundleName;
2024
+ mainAbility = opts.abilityName ?? await resolveInstalledMainAbility({
2025
+ config: opts.config,
2026
+ bundle: bundleName,
2027
+ deviceSerial: opts.deviceSerial,
2028
+ logger: opts.logger
2029
+ });
2030
+ } else {
2031
+ if (!opts.projectDir) throw new OniroError("launchApp requires either bundleName or projectDir.");
2032
+ bundleName = getBundleName(opts.projectDir);
2033
+ mainAbility = getMainAbility(opts.projectDir, opts.moduleName, opts.abilityName);
2034
+ }
1676
2035
  const result = await hdcExec({
1677
2036
  config: opts.config,
1678
- args: ["shell", "aa", "start", "-a", mainAbility, "-b", bundleName]
2037
+ args: ["shell", "aa", "start", "-a", mainAbility, "-b", bundleName],
2038
+ deviceSerial: opts.deviceSerial
1679
2039
  });
1680
2040
  if (result.stdout.trim()) logger.info(result.stdout.trim());
1681
2041
  if (result.stderr.trim()) logger.warn(result.stderr.trim());
@@ -1690,11 +2050,17 @@ function listRunningProcesses(config, options = {}) {
1690
2050
  const logger = options.logger ?? noopLogger;
1691
2051
  const timeoutMs = options.timeoutMs ?? 1e3;
1692
2052
  const hdc = getHdcPath(config);
1693
- return new Promise((resolve2, reject) => {
1694
- const proc = spawn3(hdc, ["track-jpid"]);
2053
+ return new Promise((resolve4, reject) => {
2054
+ let proc;
2055
+ try {
2056
+ proc = spawnCompat(hdc, ["track-jpid"]);
2057
+ } catch (err) {
2058
+ reject(new OniroError(`Failed to spawn ${hdc}: ${err instanceof Error ? err.message : String(err)}`, err));
2059
+ return;
2060
+ }
1695
2061
  const processes = [];
1696
2062
  let resolved = false;
1697
- proc.stdout.on("data", (data) => {
2063
+ proc.stdout?.on("data", (data) => {
1698
2064
  for (const line of data.toString().split("\n").filter(Boolean)) {
1699
2065
  const match = line.match(/^(\d+)\s+(.+)$/);
1700
2066
  if (!match) continue;
@@ -1703,13 +2069,13 @@ function listRunningProcesses(config, options = {}) {
1703
2069
  processes.push({ pid, name });
1704
2070
  if (options.targetProcessName && name === options.targetProcessName && !resolved) {
1705
2071
  resolved = true;
1706
- proc.kill();
1707
- resolve2(pid);
2072
+ killProcessTree(proc);
2073
+ resolve4(pid);
1708
2074
  return;
1709
2075
  }
1710
2076
  }
1711
2077
  });
1712
- proc.stderr.on("data", (data) => {
2078
+ proc.stderr?.on("data", (data) => {
1713
2079
  logger.warn(`[hdc track-jpid] ${data.toString()}`);
1714
2080
  });
1715
2081
  proc.on("error", (err) => {
@@ -1721,11 +2087,11 @@ function listRunningProcesses(config, options = {}) {
1721
2087
  const timeout = setTimeout(() => {
1722
2088
  if (resolved) return;
1723
2089
  resolved = true;
1724
- proc.kill();
2090
+ killProcessTree(proc);
1725
2091
  if (options.targetProcessName) {
1726
2092
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
1727
2093
  } else {
1728
- resolve2(processes);
2094
+ resolve4(processes);
1729
2095
  }
1730
2096
  }, timeoutMs);
1731
2097
  proc.on("close", () => {
@@ -1735,7 +2101,7 @@ function listRunningProcesses(config, options = {}) {
1735
2101
  if (options.targetProcessName) {
1736
2102
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
1737
2103
  } else {
1738
- resolve2(processes);
2104
+ resolve4(processes);
1739
2105
  }
1740
2106
  });
1741
2107
  });
@@ -1755,8 +2121,17 @@ async function findRunningProcess(opts) {
1755
2121
  logger: opts.logger
1756
2122
  });
1757
2123
  const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
1758
- if (res.code !== 0 || !pid || !/^\d+$/.test(pid)) return null;
1759
- return { pid, name: opts.bundle };
2124
+ if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
2125
+ try {
2126
+ const list = await listRunningProcesses(opts.config, { timeoutMs: 1200, logger: opts.logger });
2127
+ if (Array.isArray(list)) {
2128
+ const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
2129
+ const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
2130
+ if (match) return match;
2131
+ }
2132
+ } catch {
2133
+ }
2134
+ return null;
1760
2135
  }
1761
2136
  async function uninstallApp(opts) {
1762
2137
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
@@ -1870,12 +2245,12 @@ async function reboot(opts) {
1870
2245
  }
1871
2246
 
1872
2247
  // src/hdc/screen.ts
1873
- import * as fs16 from "fs";
1874
- import * as os9 from "os";
1875
- import * as path15 from "path";
2248
+ import * as fs18 from "fs";
2249
+ import * as os8 from "os";
2250
+ import * as path18 from "path";
1876
2251
  import { randomBytes as randomBytes2 } from "crypto";
1877
2252
  function tmpName(ext) {
1878
- return path15.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
2253
+ return path18.join(os8.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
1879
2254
  }
1880
2255
  async function takeScreenshot(opts) {
1881
2256
  const remote = "/data/local/tmp/oniro_screenshot.jpeg";
@@ -1885,11 +2260,11 @@ async function takeScreenshot(opts) {
1885
2260
  const local = tmpName("jpeg");
1886
2261
  try {
1887
2262
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
1888
- const pixels = await fs16.promises.readFile(local);
2263
+ const pixels = await fs18.promises.readFile(local);
1889
2264
  const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
1890
2265
  return { pixels, width: size.width, height: size.height };
1891
2266
  } finally {
1892
- fs16.promises.unlink(local).catch(() => {
2267
+ fs18.promises.unlink(local).catch(() => {
1893
2268
  });
1894
2269
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
1895
2270
  });
@@ -1908,9 +2283,9 @@ async function captureBurst(opts) {
1908
2283
  const local = tmpName(`burst_${i}.jpeg`);
1909
2284
  try {
1910
2285
  await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
1911
- frames.push(await fs16.promises.readFile(local));
2286
+ frames.push(await fs18.promises.readFile(local));
1912
2287
  } finally {
1913
- fs16.promises.unlink(local).catch(() => {
2288
+ fs18.promises.unlink(local).catch(() => {
1914
2289
  });
1915
2290
  }
1916
2291
  }
@@ -1954,8 +2329,8 @@ function buildInputCommand(opts) {
1954
2329
  }
1955
2330
  async function sendInput(opts) {
1956
2331
  const cmd = buildInputCommand(opts);
1957
- const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
1958
- ensureOk(res, `hdc shell ${cmd}`);
2332
+ const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2333
+ ensureRemoteOk(res, `hdc shell ${cmd}`);
1959
2334
  }
1960
2335
  function buildGestureCommands(waypoints) {
1961
2336
  if (waypoints.length < 2) {
@@ -1998,32 +2373,47 @@ function waypointsToRawEvents(waypoints, holdStartMs = 0, holdEndMs = 0) {
1998
2373
  events.push({ type: "up", x: last.x, y: last.y, t: last.t + holdStartMs + holdEndMs });
1999
2374
  return events;
2000
2375
  }
2376
+ var UITEST_VELOCITY_MIN = 200;
2377
+ var UITEST_VELOCITY_MAX = 4e4;
2378
+ function buildGestureUitestCommand(waypoints, opts = {}) {
2379
+ if (waypoints.length < 2) throw new OniroError("sendGesture requires at least two waypoints.");
2380
+ const a = waypoints[0];
2381
+ const b = waypoints[waypoints.length - 1];
2382
+ const dist = Math.hypot(b.x - a.x, b.y - a.y);
2383
+ const dtMs = b.t - a.t;
2384
+ const op = (opts.holdStartMs ?? 0) > 0 ? "drag" : "swipe";
2385
+ let cmd = `uitest uiInput ${op} ${a.x} ${a.y} ${b.x} ${b.y}`;
2386
+ if (dtMs > 0 && dist > 0) {
2387
+ const v = Math.round(dist / (dtMs / 1e3));
2388
+ cmd += ` ${Math.max(UITEST_VELOCITY_MIN, Math.min(UITEST_VELOCITY_MAX, v))}`;
2389
+ }
2390
+ return cmd;
2391
+ }
2001
2392
  async function sendGesture(opts) {
2002
- const wantsHold = (opts.holdStartMs ?? 0) > 0 || (opts.holdEndMs ?? 0) > 0;
2003
- if (wantsHold) {
2004
- await sendRawTouch({
2005
- config: opts.config,
2006
- events: waypointsToRawEvents(opts.waypoints, opts.holdStartMs, opts.holdEndMs),
2007
- deviceSerial: opts.deviceSerial,
2008
- logger: opts.logger
2009
- });
2010
- return;
2393
+ if ((opts.holdEndMs ?? 0) > 0) {
2394
+ opts.logger?.warn(
2395
+ "sendGesture: hold-end has no uitest equivalent and is ignored; use sendRawTouch for a trailing hold."
2396
+ );
2011
2397
  }
2012
- for (const cmd of buildGestureCommands(opts.waypoints)) {
2013
- const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2014
- ensureOk(res, `hdc shell ${cmd}`);
2398
+ if (opts.waypoints.length > 2) {
2399
+ opts.logger?.debug(
2400
+ `sendGesture: ${opts.waypoints.length} waypoints collapsed to first\u2192last (uitest has no polyline path).`
2401
+ );
2015
2402
  }
2403
+ const cmd = buildGestureUitestCommand(opts.waypoints, { holdStartMs: opts.holdStartMs });
2404
+ const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2405
+ ensureRemoteOk(res, `hdc shell ${cmd}`);
2016
2406
  }
2017
2407
  async function sendRawTouch(opts) {
2018
2408
  const cmd = buildRawTouchCommand(opts.events);
2019
- const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2020
- ensureOk(res, `hdc shell ${cmd}`);
2409
+ const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
2410
+ ensureRemoteOk(res, `hdc shell ${cmd}`);
2021
2411
  }
2022
2412
 
2023
2413
  // src/hdc/dump.ts
2024
- import * as fs17 from "fs";
2025
- import * as os10 from "os";
2026
- import * as path16 from "path";
2414
+ import * as fs19 from "fs";
2415
+ import * as os9 from "os";
2416
+ import * as path19 from "path";
2027
2417
  import { randomBytes as randomBytes3 } from "crypto";
2028
2418
  function parseBounds(s) {
2029
2419
  const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
@@ -2108,10 +2498,10 @@ async function dumpLayout(opts) {
2108
2498
  if (!m) throw new OniroError(`Could not parse dumpLayout output:
2109
2499
  ${dump.stdout}`);
2110
2500
  const remote = m[1];
2111
- const local = path16.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
2501
+ const local = path19.join(os9.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
2112
2502
  try {
2113
2503
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2114
- const parsed = JSON.parse(await fs17.promises.readFile(local, "utf8"));
2504
+ const parsed = JSON.parse(await fs19.promises.readFile(local, "utf8"));
2115
2505
  const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
2116
2506
  if (opts.prune === false) {
2117
2507
  return { display, tree: parsed };
@@ -2120,7 +2510,7 @@ ${dump.stdout}`);
2120
2510
  const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
2121
2511
  return { display, tree };
2122
2512
  } finally {
2123
- fs17.promises.unlink(local).catch(() => {
2513
+ fs19.promises.unlink(local).catch(() => {
2124
2514
  });
2125
2515
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2126
2516
  });
@@ -2128,14 +2518,21 @@ ${dump.stdout}`);
2128
2518
  }
2129
2519
 
2130
2520
  // src/hdc/hilog.ts
2131
- import { spawn as spawn4 } from "child_process";
2132
2521
  function setHilogLevel(opts) {
2133
2522
  const logger = opts.logger ?? noopLogger;
2134
2523
  const hdc = getHdcPath(opts.config);
2135
- return new Promise((resolve2, reject) => {
2136
- const child = spawn4(hdc, ["shell", "hilog", "-b", opts.level]);
2524
+ return new Promise((resolve4, reject) => {
2525
+ let child;
2526
+ try {
2527
+ child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
2528
+ } catch (err) {
2529
+ const msg = err instanceof Error ? err.message : String(err);
2530
+ logger.error(`[hilog] failed to spawn hdc: ${msg}`);
2531
+ reject(err instanceof Error ? err : new Error(msg));
2532
+ return;
2533
+ }
2137
2534
  let stderr = "";
2138
- child.stderr.on("data", (chunk) => {
2535
+ child.stderr?.on("data", (chunk) => {
2139
2536
  stderr += chunk.toString();
2140
2537
  });
2141
2538
  child.once("error", (err) => {
@@ -2144,7 +2541,7 @@ function setHilogLevel(opts) {
2144
2541
  });
2145
2542
  child.once("close", (code) => {
2146
2543
  if (code === 0) {
2147
- resolve2();
2544
+ resolve4();
2148
2545
  } else {
2149
2546
  const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
2150
2547
  logger.error(`[hilog] ${msg}`);
@@ -2164,7 +2561,7 @@ function streamHilog(opts) {
2164
2561
  if (opts.domain !== void 0 && `${opts.domain}` !== "") {
2165
2562
  args.push("-D", `${opts.domain}`);
2166
2563
  }
2167
- return spawn4(hdc, args);
2564
+ return spawnCompat(hdc, args);
2168
2565
  }
2169
2566
  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+(.*)$/;
2170
2567
  function parseHilogLine(line) {
@@ -2199,7 +2596,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
2199
2596
  return proc?.pid;
2200
2597
  }
2201
2598
  function waitForLog(opts) {
2202
- return new Promise((resolve2, reject) => {
2599
+ return new Promise((resolve4, reject) => {
2203
2600
  const deadline = Date.now() + opts.timeoutMs;
2204
2601
  let settled = false;
2205
2602
  let child = null;
@@ -2210,10 +2607,7 @@ function waitForLog(opts) {
2210
2607
  if (reconnectTimer) clearTimeout(reconnectTimer);
2211
2608
  opts.abortSignal?.removeEventListener("abort", onAbort);
2212
2609
  if (child) {
2213
- try {
2214
- child.kill("SIGKILL");
2215
- } catch {
2216
- }
2610
+ killProcessTree(child);
2217
2611
  child = null;
2218
2612
  }
2219
2613
  };
@@ -2221,7 +2615,7 @@ function waitForLog(opts) {
2221
2615
  if (settled) return;
2222
2616
  settled = true;
2223
2617
  cleanup();
2224
- resolve2(entry);
2618
+ resolve4(entry);
2225
2619
  };
2226
2620
  const fail = (err) => {
2227
2621
  if (settled) return;
@@ -2229,6 +2623,7 @@ function waitForLog(opts) {
2229
2623
  cleanup();
2230
2624
  reject(err);
2231
2625
  };
2626
+ const failWith = (err) => fail(err instanceof Error ? err : new Error(String(err)));
2232
2627
  const onAbort = () => fail(new CancelledError("waitForLog cancelled."));
2233
2628
  const timer = setTimeout(
2234
2629
  () => fail(new OniroError(`No log line matched ${String(opts.pattern)} within ${opts.timeoutMs}ms.`)),
@@ -2258,17 +2653,17 @@ function waitForLog(opts) {
2258
2653
  if (settled || child !== stream) return;
2259
2654
  child = null;
2260
2655
  if (Date.now() < deadline) {
2261
- reconnectTimer = setTimeout(() => void spawnStream(), 1e3);
2656
+ reconnectTimer = setTimeout(() => void spawnStream().catch(failWith), 1e3);
2262
2657
  }
2263
2658
  };
2264
2659
  stream.once("close", onEnd);
2265
2660
  stream.once("error", onEnd);
2266
2661
  };
2267
- void spawnStream();
2662
+ void spawnStream().catch(failWith);
2268
2663
  });
2269
2664
  }
2270
2665
  function watchLog(opts) {
2271
- return new Promise((resolve2, reject) => {
2666
+ return new Promise((resolve4, reject) => {
2272
2667
  const matches = [];
2273
2668
  let settled = false;
2274
2669
  let child = null;
@@ -2277,10 +2672,7 @@ function watchLog(opts) {
2277
2672
  clearTimeout(timer);
2278
2673
  opts.abortSignal?.removeEventListener("abort", onAbort);
2279
2674
  if (child) {
2280
- try {
2281
- child.kill("SIGKILL");
2282
- } catch {
2283
- }
2675
+ killProcessTree(child);
2284
2676
  child = null;
2285
2677
  }
2286
2678
  };
@@ -2288,7 +2680,7 @@ function watchLog(opts) {
2288
2680
  if (settled) return;
2289
2681
  settled = true;
2290
2682
  cleanup();
2291
- resolve2(opts.dedup === false ? matches : dedupEntries(matches));
2683
+ resolve4(opts.dedup === false ? matches : dedupEntries(matches));
2292
2684
  };
2293
2685
  const onAbort = () => {
2294
2686
  if (settled) return;
@@ -2313,7 +2705,12 @@ function watchLog(opts) {
2313
2705
  if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
2314
2706
  }
2315
2707
  });
2316
- })();
2708
+ })().catch((err) => {
2709
+ if (settled) return;
2710
+ settled = true;
2711
+ cleanup();
2712
+ reject(err instanceof Error ? err : new Error(String(err)));
2713
+ });
2317
2714
  });
2318
2715
  }
2319
2716
  async function dumpLog(opts) {
@@ -2340,9 +2737,8 @@ async function dumpLog(opts) {
2340
2737
  }
2341
2738
 
2342
2739
  // src/build/runHvigorw.ts
2343
- import * as fs18 from "fs";
2344
- import * as path17 from "path";
2345
- import { spawn as spawn5 } from "child_process";
2740
+ import * as fs20 from "fs";
2741
+ import * as path20 from "path";
2346
2742
  function buildHvigorwArgs(opts) {
2347
2743
  const args = [opts.task ?? "assembleHap", "--mode", "module"];
2348
2744
  args.push("-p", `product=${opts.product ?? "default"}`);
@@ -2354,19 +2750,23 @@ function buildHvigorwArgs(opts) {
2354
2750
  return args;
2355
2751
  }
2356
2752
  function runHvigorw(opts) {
2357
- const { config, projectDir } = opts;
2753
+ const { config } = opts;
2754
+ const projectDir = toLongPath(opts.projectDir);
2358
2755
  const logger = opts.logger ?? noopLogger;
2359
- if (!fs18.existsSync(path17.join(projectDir, "build-profile.json5"))) {
2756
+ if (!fs20.existsSync(path20.join(projectDir, "build-profile.json5"))) {
2360
2757
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
2361
2758
  }
2362
- if (!fs18.existsSync(getCmdToolsPath(config))) {
2759
+ if (!fs20.existsSync(getCmdToolsPath(config))) {
2363
2760
  throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
2364
2761
  }
2365
2762
  const hvigorw = getHvigorwPath(config, projectDir);
2763
+ if (!fs20.existsSync(hvigorw)) {
2764
+ throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
2765
+ }
2366
2766
  const args = buildHvigorwArgs(opts);
2367
2767
  if (process.platform !== "win32") {
2368
2768
  try {
2369
- fs18.chmodSync(hvigorw, 493);
2769
+ fs20.chmodSync(hvigorw, 493);
2370
2770
  } catch {
2371
2771
  }
2372
2772
  }
@@ -2374,32 +2774,35 @@ function runHvigorw(opts) {
2374
2774
  ...process.env,
2375
2775
  OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
2376
2776
  };
2377
- return new Promise((resolve2, reject) => {
2777
+ return new Promise((resolve4, reject) => {
2378
2778
  if (opts.abortSignal?.aborted) {
2379
2779
  reject(new CancelledError("hvigorw build cancelled."));
2380
2780
  return;
2381
2781
  }
2382
2782
  logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
2383
- const child = spawn5(hvigorw, args, { cwd: projectDir, env, shell: false });
2783
+ let child;
2784
+ try {
2785
+ child = spawnCompat(hvigorw, args, { cwd: projectDir, env });
2786
+ } catch (err) {
2787
+ reject(new OniroError(`Failed to start hvigorw: ${err instanceof Error ? err.message : String(err)}`, err));
2788
+ return;
2789
+ }
2384
2790
  let settled = false;
2385
2791
  const cleanup = () => opts.abortSignal?.removeEventListener("abort", onAbort);
2386
2792
  const onAbort = () => {
2387
2793
  if (settled) return;
2388
2794
  settled = true;
2389
2795
  cleanup();
2390
- try {
2391
- child.kill("SIGKILL");
2392
- } catch {
2393
- }
2796
+ killProcessTree(child);
2394
2797
  reject(new CancelledError("hvigorw build cancelled."));
2395
2798
  };
2396
2799
  opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
2397
- child.stdout.on("data", (chunk) => {
2800
+ child.stdout?.on("data", (chunk) => {
2398
2801
  const text = chunk.toString();
2399
2802
  opts.onOutput?.(text, "stdout");
2400
2803
  logger.info(text.trimEnd());
2401
2804
  });
2402
- child.stderr.on("data", (chunk) => {
2805
+ child.stderr?.on("data", (chunk) => {
2403
2806
  const text = chunk.toString();
2404
2807
  opts.onOutput?.(text, "stderr");
2405
2808
  logger.warn(text.trimEnd());
@@ -2415,19 +2818,18 @@ function runHvigorw(opts) {
2415
2818
  settled = true;
2416
2819
  cleanup();
2417
2820
  const exitCode = code ?? 1;
2418
- if (exitCode === 0) resolve2({ exitCode });
2821
+ if (exitCode === 0) resolve4({ exitCode });
2419
2822
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
2420
2823
  });
2421
2824
  });
2422
2825
  }
2423
2826
 
2424
2827
  // src/build/discoverHaps.ts
2425
- import * as fs19 from "fs";
2426
- import * as path18 from "path";
2427
- function moduleOf(projectDir, hapPath) {
2428
- const rel = path18.relative(projectDir, hapPath);
2429
- const first = rel.split(path18.sep)[0];
2430
- return first && first !== ".." ? first : "(root)";
2828
+ import * as fs21 from "fs";
2829
+ import * as path21 from "path";
2830
+ function moduleOfHap(hapFileName) {
2831
+ const dash = hapFileName.indexOf("-");
2832
+ return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
2431
2833
  }
2432
2834
  async function discoverHaps(opts) {
2433
2835
  const { projectDir } = opts;
@@ -2435,14 +2837,14 @@ async function discoverHaps(opts) {
2435
2837
  const result = {};
2436
2838
  const walk = async (dir, depth) => {
2437
2839
  if (depth > maxDepth) return;
2438
- const entries = await fs19.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2840
+ const entries = await fs21.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2439
2841
  for (const e of entries) {
2440
- const full = path18.join(dir, e.name);
2842
+ const full = path21.join(dir, e.name);
2441
2843
  if (e.isDirectory()) {
2442
2844
  if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
2443
2845
  await walk(full, depth + 1);
2444
2846
  } else if (e.isFile() && e.name.endsWith(".hap")) {
2445
- const moduleName = moduleOf(projectDir, full);
2847
+ const moduleName = moduleOfHap(e.name);
2446
2848
  const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
2447
2849
  if (e.name.includes("unsigned")) bucket.unsigned.push(full);
2448
2850
  else if (e.name.includes("signed")) bucket.signed.push(full);
@@ -2454,8 +2856,8 @@ async function discoverHaps(opts) {
2454
2856
  }
2455
2857
 
2456
2858
  // src/build/ohpm.ts
2457
- import * as fs20 from "fs";
2458
- import * as path19 from "path";
2859
+ import * as fs22 from "fs";
2860
+ import * as path22 from "path";
2459
2861
  function runOhpm(opts) {
2460
2862
  return runProcess({
2461
2863
  command: getOhpmPath(opts.config),
@@ -2468,7 +2870,7 @@ function runOhpm(opts) {
2468
2870
  });
2469
2871
  }
2470
2872
  async function ensureOhModules(opts) {
2471
- if (fs20.existsSync(path19.join(opts.projectDir, "oh_modules"))) {
2873
+ if (fs22.existsSync(path22.join(opts.projectDir, "oh_modules"))) {
2472
2874
  return { installed: false };
2473
2875
  }
2474
2876
  const res = await runOhpm({
@@ -2520,10 +2922,11 @@ async function buildHap(opts) {
2520
2922
  const logger = scopedLogger(opts.logger ?? noopLogger, "build");
2521
2923
  const warnings = [];
2522
2924
  const start = Date.now();
2925
+ const projectDir = toLongPath(opts.projectDir);
2523
2926
  if (opts.autoInstallDeps !== false) {
2524
2927
  const r = await ensureOhModules({
2525
2928
  config: opts.config,
2526
- projectDir: opts.projectDir,
2929
+ projectDir,
2527
2930
  abortSignal: opts.abortSignal,
2528
2931
  onOutput: opts.onOutput,
2529
2932
  logger: opts.logger
@@ -2532,7 +2935,7 @@ async function buildHap(opts) {
2532
2935
  }
2533
2936
  const { exitCode } = await runHvigorw({
2534
2937
  config: opts.config,
2535
- projectDir: opts.projectDir,
2938
+ projectDir,
2536
2939
  product: opts.product,
2537
2940
  module: opts.module,
2538
2941
  buildMode: opts.mode,
@@ -2542,7 +2945,7 @@ async function buildHap(opts) {
2542
2945
  onOutput: opts.onOutput,
2543
2946
  logger: opts.logger
2544
2947
  });
2545
- const discoveredHaps = await discoverHaps({ projectDir: opts.projectDir });
2948
+ const discoveredHaps = await discoverHaps({ projectDir });
2546
2949
  const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
2547
2950
  if (!anySigned) {
2548
2951
  warnings.push(
@@ -2563,21 +2966,21 @@ function isValidBundleName(bundleName) {
2563
2966
  }
2564
2967
 
2565
2968
  // src/project/templates.ts
2566
- import * as fs21 from "fs";
2567
- import * as path20 from "path";
2969
+ import * as fs23 from "fs";
2970
+ import * as path23 from "path";
2568
2971
  function toHumanTemplateName(folderName) {
2569
2972
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
2570
2973
  }
2571
2974
  function listTemplates(templateRoot) {
2572
- if (!fs21.existsSync(templateRoot)) return [];
2573
- const entries = fs21.readdirSync(templateRoot, { withFileTypes: true });
2975
+ if (!fs23.existsSync(templateRoot)) return [];
2976
+ const entries = fs23.readdirSync(templateRoot, { withFileTypes: true });
2574
2977
  return entries.filter((e) => e.isDirectory()).map((e) => {
2575
- const dir = path20.join(templateRoot, e.name);
2576
- const metaPath = path20.join(dir, "template.json");
2978
+ const dir = path23.join(templateRoot, e.name);
2979
+ const metaPath = path23.join(dir, "template.json");
2577
2980
  let meta = {};
2578
- if (fs21.existsSync(metaPath)) {
2981
+ if (fs23.existsSync(metaPath)) {
2579
2982
  try {
2580
- meta = JSON.parse(fs21.readFileSync(metaPath, "utf8"));
2983
+ meta = JSON.parse(fs23.readFileSync(metaPath, "utf8"));
2581
2984
  } catch {
2582
2985
  }
2583
2986
  }
@@ -2592,57 +2995,57 @@ function listTemplates(templateRoot) {
2592
2995
  function validateTemplateLayout(templateDir, defaultModuleName) {
2593
2996
  const required = [
2594
2997
  "build-profile.json5",
2595
- path20.join("AppScope", "app.json5"),
2596
- path20.join(defaultModuleName, "src", "main", "module.json5"),
2597
- path20.join(defaultModuleName, "oh-package.json5"),
2998
+ path23.join("AppScope", "app.json5"),
2999
+ path23.join(defaultModuleName, "src", "main", "module.json5"),
3000
+ path23.join(defaultModuleName, "oh-package.json5"),
2598
3001
  "hvigorfile.ts"
2599
3002
  ];
2600
- return required.filter((rel) => !fs21.existsSync(path20.join(templateDir, rel)));
3003
+ return required.filter((rel) => !fs23.existsSync(path23.join(templateDir, rel)));
2601
3004
  }
2602
3005
 
2603
3006
  // src/project/createScaffold.ts
2604
- import * as fs23 from "fs";
2605
- import * as path21 from "path";
3007
+ import * as fs25 from "fs";
3008
+ import * as path24 from "path";
2606
3009
 
2607
3010
  // src/project/jsonHelpers.ts
2608
- import * as fs22 from "fs";
3011
+ import * as fs24 from "fs";
2609
3012
  import JSON54 from "json5";
2610
3013
  function readJson5File(filePath) {
2611
- return JSON54.parse(fs22.readFileSync(filePath, "utf8"));
3014
+ return JSON54.parse(fs24.readFileSync(filePath, "utf8"));
2612
3015
  }
2613
3016
  function writeJson5File(filePath, value) {
2614
- fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3017
+ fs24.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
2615
3018
  }
2616
3019
  function readJsonFile(filePath) {
2617
- return JSON.parse(fs22.readFileSync(filePath, "utf8"));
3020
+ return JSON.parse(fs24.readFileSync(filePath, "utf8"));
2618
3021
  }
2619
3022
  function writeJsonFile(filePath, value) {
2620
- fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3023
+ fs24.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
2621
3024
  }
2622
3025
 
2623
3026
  // src/project/createScaffold.ts
2624
3027
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
2625
3028
  async function pathExists(p) {
2626
3029
  try {
2627
- await fs23.promises.access(p);
3030
+ await fs25.promises.access(p);
2628
3031
  return true;
2629
3032
  } catch {
2630
3033
  return false;
2631
3034
  }
2632
3035
  }
2633
3036
  async function copyDirRecursive(srcDir, destDir) {
2634
- await fs23.promises.mkdir(destDir, { recursive: true });
2635
- const entries = await fs23.promises.readdir(srcDir, { withFileTypes: true });
3037
+ await fs25.promises.mkdir(destDir, { recursive: true });
3038
+ const entries = await fs25.promises.readdir(srcDir, { withFileTypes: true });
2636
3039
  for (const entry of entries) {
2637
- const src = path21.join(srcDir, entry.name);
2638
- const dest = path21.join(destDir, entry.name);
3040
+ const src = path24.join(srcDir, entry.name);
3041
+ const dest = path24.join(destDir, entry.name);
2639
3042
  if (entry.isDirectory()) {
2640
3043
  await copyDirRecursive(src, dest);
2641
3044
  } else if (entry.isSymbolicLink()) {
2642
3045
  continue;
2643
3046
  } else if (entry.isFile()) {
2644
3047
  if (entry.name === "template.json") continue;
2645
- await fs23.promises.copyFile(src, dest);
3048
+ await fs25.promises.copyFile(src, dest);
2646
3049
  }
2647
3050
  }
2648
3051
  }
@@ -2650,9 +3053,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
2650
3053
  const stack = [projectDir];
2651
3054
  while (stack.length > 0) {
2652
3055
  const currentDir = stack.pop();
2653
- const entries = await fs23.promises.readdir(currentDir, { withFileTypes: true });
3056
+ const entries = await fs25.promises.readdir(currentDir, { withFileTypes: true });
2654
3057
  for (const entry of entries) {
2655
- const fullPath = path21.join(currentDir, entry.name);
3058
+ const fullPath = path24.join(currentDir, entry.name);
2656
3059
  if (entry.isDirectory()) {
2657
3060
  if (IGNORED_DIRS.has(entry.name)) continue;
2658
3061
  stack.push(fullPath);
@@ -2674,23 +3077,23 @@ function toJavaPropertiesPath(p) {
2674
3077
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
2675
3078
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
2676
3079
  `;
2677
- fs23.writeFileSync(path21.join(projectDir, "local.properties"), content, "utf8");
3080
+ fs25.writeFileSync(path24.join(projectDir, "local.properties"), content, "utf8");
2678
3081
  }
2679
3082
  function renameIfExists(fromPath, toPath) {
2680
- if (fs23.existsSync(fromPath) && fromPath !== toPath) {
2681
- fs23.renameSync(fromPath, toPath);
3083
+ if (fs25.existsSync(fromPath) && fromPath !== toPath) {
3084
+ fs25.renameSync(fromPath, toPath);
2682
3085
  }
2683
3086
  }
2684
3087
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
2685
- const appJsonPath = path21.join(projectDir, "AppScope", "app.json5");
2686
- if (fs23.existsSync(appJsonPath)) {
3088
+ const appJsonPath = path24.join(projectDir, "AppScope", "app.json5");
3089
+ if (fs25.existsSync(appJsonPath)) {
2687
3090
  const appJson = readJson5File(appJsonPath);
2688
3091
  appJson.app = appJson.app ?? {};
2689
3092
  appJson.app.bundleName = args.bundleName;
2690
3093
  writeJson5File(appJsonPath, appJson);
2691
3094
  }
2692
- const stringsPath = path21.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
2693
- if (fs23.existsSync(stringsPath)) {
3095
+ const stringsPath = path24.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3096
+ if (fs25.existsSync(stringsPath)) {
2694
3097
  const strings = readJsonFile(stringsPath);
2695
3098
  if (Array.isArray(strings.string)) {
2696
3099
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -2698,8 +3101,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
2698
3101
  }
2699
3102
  writeJsonFile(stringsPath, strings);
2700
3103
  }
2701
- const buildProfilePath = path21.join(projectDir, "build-profile.json5");
2702
- if (fs23.existsSync(buildProfilePath)) {
3104
+ const buildProfilePath = path24.join(projectDir, "build-profile.json5");
3105
+ if (fs25.existsSync(buildProfilePath)) {
2703
3106
  const buildProfile = readJson5File(buildProfilePath);
2704
3107
  buildProfile.app = buildProfile.app ?? {};
2705
3108
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -2712,23 +3115,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
2712
3115
  }
2713
3116
  writeJson5File(buildProfilePath, buildProfile);
2714
3117
  }
2715
- const moduleJsonPath = path21.join(projectDir, args.moduleName, "src", "main", "module.json5");
2716
- if (fs23.existsSync(moduleJsonPath)) {
3118
+ const moduleJsonPath = path24.join(projectDir, args.moduleName, "src", "main", "module.json5");
3119
+ if (fs25.existsSync(moduleJsonPath)) {
2717
3120
  const moduleJson = readJson5File(moduleJsonPath);
2718
3121
  moduleJson.module = moduleJson.module ?? {};
2719
3122
  moduleJson.module.name = args.moduleName;
2720
3123
  writeJson5File(moduleJsonPath, moduleJson);
2721
3124
  }
2722
- const moduleOhPackagePath = path21.join(projectDir, args.moduleName, "oh-package.json5");
2723
- if (fs23.existsSync(moduleOhPackagePath)) {
3125
+ const moduleOhPackagePath = path24.join(projectDir, args.moduleName, "oh-package.json5");
3126
+ if (fs25.existsSync(moduleOhPackagePath)) {
2724
3127
  const pkg = readJson5File(moduleOhPackagePath);
2725
3128
  pkg.name = args.moduleName;
2726
3129
  writeJson5File(moduleOhPackagePath, pkg);
2727
3130
  }
2728
- const vscodeDir = path21.join(projectDir, ".vscode");
2729
- fs23.mkdirSync(vscodeDir, { recursive: true });
3131
+ const vscodeDir = path24.join(projectDir, ".vscode");
3132
+ fs25.mkdirSync(vscodeDir, { recursive: true });
2730
3133
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
2731
- writeJsonFile(path21.join(vscodeDir, "settings.json"), {
3134
+ writeJsonFile(path24.join(vscodeDir, "settings.json"), {
2732
3135
  "oniro.hapPath": hapPath,
2733
3136
  "files.associations": { "*.json5": "jsonc" }
2734
3137
  });
@@ -2745,7 +3148,7 @@ async function createScaffold(opts) {
2745
3148
  if (!opts.location || !await pathExists(opts.location)) {
2746
3149
  throw new OniroError(`Location does not exist: ${opts.location}`);
2747
3150
  }
2748
- const templateDir = path21.join(opts.templateRoot, opts.templateId);
3151
+ const templateDir = path24.join(opts.templateRoot, opts.templateId);
2749
3152
  if (!await pathExists(templateDir)) {
2750
3153
  throw new OniroError(`Template not found: ${templateDir}`);
2751
3154
  }
@@ -2758,17 +3161,17 @@ async function createScaffold(opts) {
2758
3161
  throw new OniroError(`Template '${opts.templateId}' is missing required files:
2759
3162
  - ${missing.join("\n- ")}`);
2760
3163
  }
2761
- const projectDir = path21.join(opts.location, opts.projectName);
3164
+ const projectDir = path24.join(opts.location, opts.projectName);
2762
3165
  if (await pathExists(projectDir)) {
2763
3166
  if (!opts.overwrite) {
2764
3167
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
2765
3168
  }
2766
- await fs23.promises.rm(projectDir, { recursive: true, force: true });
3169
+ await fs25.promises.rm(projectDir, { recursive: true, force: true });
2767
3170
  }
2768
3171
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
2769
3172
  await copyDirRecursive(templateDir, projectDir);
2770
3173
  if (moduleName !== defaultModuleName) {
2771
- renameIfExists(path21.join(projectDir, defaultModuleName), path21.join(projectDir, moduleName));
3174
+ renameIfExists(path24.join(projectDir, defaultModuleName), path24.join(projectDir, moduleName));
2772
3175
  }
2773
3176
  updateTemplateConfigs(
2774
3177
  projectDir,
@@ -2785,11 +3188,11 @@ async function createScaffold(opts) {
2785
3188
  }
2786
3189
 
2787
3190
  // src/project/listModules.ts
2788
- import * as fs24 from "fs";
2789
- import * as path22 from "path";
3191
+ import * as fs26 from "fs";
3192
+ import * as path25 from "path";
2790
3193
  function listModules(opts) {
2791
- const buildProfile = path22.join(opts.projectDir, "build-profile.json5");
2792
- if (!fs24.existsSync(buildProfile)) {
3194
+ const buildProfile = path25.join(opts.projectDir, "build-profile.json5");
3195
+ if (!fs26.existsSync(buildProfile)) {
2793
3196
  throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
2794
3197
  }
2795
3198
  const parsed = readJson5File(buildProfile);
@@ -2801,12 +3204,12 @@ function listModules(opts) {
2801
3204
  }
2802
3205
 
2803
3206
  // src/project/abilities.ts
2804
- import * as fs25 from "fs";
2805
- import * as path23 from "path";
3207
+ import * as fs27 from "fs";
3208
+ import * as path26 from "path";
2806
3209
  function listAbilities(opts) {
2807
3210
  const moduleName = opts.moduleName ?? "entry";
2808
- const moduleJsonPath = path23.join(opts.projectDir, moduleName, "src", "main", "module.json5");
2809
- if (!fs25.existsSync(moduleJsonPath)) {
3211
+ const moduleJsonPath = path26.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3212
+ if (!fs27.existsSync(moduleJsonPath)) {
2810
3213
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
2811
3214
  }
2812
3215
  const parsed = readJson5File(moduleJsonPath);
@@ -2814,12 +3217,12 @@ function listAbilities(opts) {
2814
3217
  }
2815
3218
 
2816
3219
  // src/project/prepareSigning.ts
2817
- import * as fs26 from "fs";
2818
- import * as path24 from "path";
3220
+ import * as fs28 from "fs";
3221
+ import * as path27 from "path";
2819
3222
  function hasSigningArtifacts(dir) {
2820
- if (!fs26.existsSync(path24.join(dir, "signatures"))) return false;
2821
- const buildProfile = path24.join(dir, "build-profile.json5");
2822
- if (!fs26.existsSync(buildProfile)) return false;
3223
+ if (!fs28.existsSync(path27.join(dir, "signatures"))) return false;
3224
+ const buildProfile = path27.join(dir, "build-profile.json5");
3225
+ if (!fs28.existsSync(buildProfile)) return false;
2823
3226
  try {
2824
3227
  const parsed = readJson5File(buildProfile);
2825
3228
  return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
@@ -2909,8 +3312,8 @@ async function diffHapAssets(opts) {
2909
3312
  }
2910
3313
 
2911
3314
  // src/install/apply.ts
2912
- import * as fs27 from "fs";
2913
- import * as path25 from "path";
3315
+ import * as fs29 from "fs";
3316
+ import * as path28 from "path";
2914
3317
  function decideInstallMethod(s) {
2915
3318
  if (!s.signInfoInconsistent) return "replace";
2916
3319
  if (s.isSystemBundle && !s.allowUninstall) return "refuse";
@@ -2931,7 +3334,7 @@ async function pidOf(config, bundle, deviceSerial) {
2931
3334
  async function resolveHapPath(opts) {
2932
3335
  let hapPath = opts.hapPath;
2933
3336
  if (hapPath) {
2934
- if (!path25.isAbsolute(hapPath) && opts.projectDir) hapPath = path25.join(opts.projectDir, hapPath);
3337
+ if (!path28.isAbsolute(hapPath) && opts.projectDir) hapPath = path28.join(opts.projectDir, hapPath);
2935
3338
  } else {
2936
3339
  if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
2937
3340
  const haps = await discoverHaps({ projectDir: opts.projectDir });
@@ -2950,7 +3353,7 @@ async function resolveHapPath(opts) {
2950
3353
  hapPath = withSigned[0][1].signed[0];
2951
3354
  }
2952
3355
  }
2953
- if (!fs27.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3356
+ if (!fs29.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
2954
3357
  return hapPath;
2955
3358
  }
2956
3359
  async function applyChanges(opts) {
@@ -2960,7 +3363,7 @@ async function applyChanges(opts) {
2960
3363
  const allowUninstall = opts.allowUninstall ?? false;
2961
3364
  const hapPath = await resolveHapPath(opts);
2962
3365
  let assetsChanged = false;
2963
- if (opts.installedHapPath && fs27.existsSync(opts.installedHapPath)) {
3366
+ if (opts.installedHapPath && fs29.existsSync(opts.installedHapPath)) {
2964
3367
  try {
2965
3368
  const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
2966
3369
  assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
@@ -3002,7 +3405,7 @@ async function applyChanges(opts) {
3002
3405
  }
3003
3406
 
3004
3407
  // src/index.ts
3005
- var VERSION = "0.7.0";
3408
+ var VERSION = "0.9.0";
3006
3409
  export {
3007
3410
  ALL_SDKS,
3008
3411
  APL_VALUES,
@@ -3011,22 +3414,29 @@ export {
3011
3414
  ChecksumMismatchError,
3012
3415
  CmdToolsNotInstalledError,
3013
3416
  CommandFailedError,
3417
+ INSTALL_TMP_DIRNAME,
3418
+ InsufficientSpaceError,
3014
3419
  OHOS_URL_BASE,
3015
3420
  OniroError,
3016
3421
  SdkNotInstalledError,
3422
+ TMP_DIR_HINT,
3017
3423
  UnsupportedPlatformError,
3018
3424
  VERSION,
3019
3425
  applyChanges,
3020
3426
  attemptHdcConnection,
3427
+ buildCmdWrapper,
3021
3428
  buildGestureCommands,
3429
+ buildGestureUitestCommand,
3022
3430
  buildHap,
3023
3431
  buildHvigorwArgs,
3024
3432
  buildInputCommand,
3025
3433
  buildRawTouchCommand,
3026
3434
  captureBurst,
3027
3435
  consoleLogger,
3436
+ createInstallTempDir,
3028
3437
  createMaterial,
3029
3438
  createScaffold,
3439
+ createTempWorkDir,
3030
3440
  decideInstallMethod,
3031
3441
  decideReboot,
3032
3442
  decryptPwd,
@@ -3047,14 +3457,19 @@ export {
3047
3457
  dumpScreen,
3048
3458
  dumpWindow,
3049
3459
  encryptPwd,
3460
+ ensureFreeSpace,
3050
3461
  ensureOhModules,
3051
3462
  ensureOk,
3463
+ ensureRemoteOk,
3464
+ escapeCmdArgument,
3465
+ escapeCmdCommand,
3052
3466
  extractTarball,
3053
3467
  extractZipWithProgress,
3054
3468
  findAppProcessId,
3055
3469
  findCmdToolsSourceDir,
3056
3470
  findRunningProcess,
3057
3471
  forceStop,
3472
+ formatBytes,
3058
3473
  generateSigningConfigs,
3059
3474
  getBundleName,
3060
3475
  getCmdToolsBin,
@@ -3064,6 +3479,7 @@ export {
3064
3479
  getDeviceInfo,
3065
3480
  getDisplaySize,
3066
3481
  getEmulatorDir,
3482
+ getFreeSpaceBytes,
3067
3483
  getHdcPath,
3068
3484
  getHvigorwPath,
3069
3485
  getInstalledSdks,
@@ -3081,14 +3497,18 @@ export {
3081
3497
  installEmulator,
3082
3498
  isCmdToolsInstalled,
3083
3499
  isEmulatorInstalled,
3500
+ isOutOfSpaceError,
3501
+ isRamBackedPath,
3084
3502
  isValidBundleName,
3085
3503
  isValidProjectName,
3504
+ killProcessTree,
3086
3505
  launchApp,
3087
3506
  listAbilities,
3088
3507
  listDevices,
3089
3508
  listModules,
3090
3509
  listRunningProcesses,
3091
3510
  listTemplates,
3511
+ needsCmdWrapper,
3092
3512
  nonInteractivePrompter,
3093
3513
  noopLogger,
3094
3514
  noopProgress,
@@ -3098,6 +3518,8 @@ export {
3098
3518
  parseCodelinterFindings,
3099
3519
  parseDeviceList,
3100
3520
  parseHilogLine,
3521
+ parseMainAbilityFromBmDump,
3522
+ parseRemoteExit,
3101
3523
  pickSigningKind,
3102
3524
  prepareSigning,
3103
3525
  pruneLayout,
@@ -3108,6 +3530,8 @@ export {
3108
3530
  removeCmdTools,
3109
3531
  removeEmulator,
3110
3532
  removeSdk,
3533
+ removeTempWorkDir,
3534
+ resolveTmpRoot,
3111
3535
  runCodelinter,
3112
3536
  runHvigorw,
3113
3537
  runOhpm,
@@ -3120,11 +3544,15 @@ export {
3120
3544
  sendRawTouch,
3121
3545
  setHilogLevel,
3122
3546
  shell,
3547
+ shellChecked,
3548
+ spawnCompat,
3123
3549
  startEmulator,
3124
3550
  staticConfig,
3125
3551
  stopEmulator,
3126
3552
  streamHilog,
3127
3553
  takeScreenshot,
3554
+ toLongPath,
3555
+ toSpaceError,
3128
3556
  uninstallApp,
3129
3557
  validateTemplateLayout,
3130
3558
  verifySha256,
@@ -3133,6 +3561,7 @@ export {
3133
3561
  waitForCondition,
3134
3562
  waitForLog,
3135
3563
  watchLog,
3564
+ waypointsToRawEvents,
3136
3565
  writeJson5File,
3137
3566
  writeJsonFile
3138
3567
  };