@oniroproject/core 0.6.2 → 0.8.0

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