@oniroproject/core 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -38,6 +38,8 @@ var defaultPaths = {
38
38
  sdkRootDir: () => path.join(os.homedir(), "setup-ohos-sdk"),
39
39
  cmdToolsPath: () => path.join(os.homedir(), "command-line-tools"),
40
40
  emulatorDir: () => path.join(os.homedir(), "oniro-emulator"),
41
+ harmonyosAuthDir: () => path.join(os.homedir(), ".oniro", "harmonyos"),
42
+ harmonyosSigningDir: () => path.join(os.homedir(), ".ohos", "config"),
41
43
  hapPath: "entry/build/default/outputs/default/entry-default-signed.hap",
42
44
  emulatorUrl: "https://github.com/eclipse-oniro4openharmony/device_board_oniro/releases/latest/download/oniro_emulator.zip",
43
45
  // The Huawei mirror only hosts a public Linux build of the command-line tools.
@@ -169,7 +171,8 @@ var ALL_SDKS = [
169
171
  { version: "5.1.0", api: "18", tarballStrip: { linuxWindows: 1, darwin: 3 } },
170
172
  { version: "5.1.1", api: "19", tarballStrip: { linuxWindows: 1, darwin: 3 } },
171
173
  { version: "6.0", api: "20", tarballStrip: { linuxWindows: 0, darwin: 3 } },
172
- { version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } }
174
+ { version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } },
175
+ { version: "7.0", api: "26", tarballStrip: { linuxWindows: 1, darwin: 3 }, archiveSuffix: "_20260829" }
173
176
  ];
174
177
  var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
175
178
 
@@ -185,31 +188,113 @@ function getOsFolder() {
185
188
  function getSdkFilename(version) {
186
189
  const platform7 = os2.platform();
187
190
  const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
188
- const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
191
+ const release = ALL_SDKS.find((r) => r.version === v);
192
+ const strips = release?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
193
+ const archive = (base) => `${base}${release?.archiveSuffix ?? ""}.tar.gz`;
189
194
  if (platform7 === "linux") {
190
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
195
+ return { filename: archive("ohos-sdk-windows_linux-public"), osFolder: "linux", strip: strips.linuxWindows };
191
196
  }
192
197
  if (platform7 === "darwin") {
193
198
  if (os2.arch() === "arm64") {
194
- return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
199
+ return { filename: archive("L2-SDK-MAC-M1-PUBLIC"), osFolder: "darwin", strip: strips.darwin };
195
200
  }
196
- return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
201
+ return { filename: archive("ohos-sdk-mac-public"), osFolder: "darwin", strip: strips.darwin };
197
202
  }
198
203
  if (platform7 === "win32") {
199
- return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
204
+ return { filename: archive("ohos-sdk-windows_linux-public"), osFolder: "windows", strip: strips.linuxWindows };
200
205
  }
201
206
  throw new UnsupportedPlatformError(platform7);
202
207
  }
203
208
 
204
209
  // src/sdk/paths.ts
210
+ import * as fs2 from "fs";
211
+ import * as os4 from "os";
212
+ import * as path3 from "path";
213
+
214
+ // src/harmonyos/sdk.ts
205
215
  import * as fs from "fs";
206
216
  import * as os3 from "os";
207
217
  import * as path2 from "path";
218
+ function readSdkPkg(sdkPath) {
219
+ let data;
220
+ try {
221
+ data = JSON.parse(fs.readFileSync(path2.join(sdkPath, "default", "sdk-pkg.json"), "utf8")).data;
222
+ } catch {
223
+ return {};
224
+ }
225
+ const text = (value) => typeof value === "string" || typeof value === "number" ? String(value) || void 0 : void 0;
226
+ const platformVersion = text(data?.platformVersion);
227
+ const apiVersion = text(data?.apiVersion);
228
+ return {
229
+ displayName: text(data?.displayName),
230
+ version: platformVersion && apiVersion ? `${platformVersion}(${apiVersion})` : void 0
231
+ };
232
+ }
233
+ function resolveSdk(root, requireHarmonyOs) {
234
+ const nested = path2.join(root, "sdk");
235
+ const sdkPath = [nested, root].find((dir) => fs.existsSync(path2.join(dir, "default", "openharmony")));
236
+ if (!sdkPath) return null;
237
+ const pkg = readSdkPkg(sdkPath);
238
+ if (requireHarmonyOs && !pkg.displayName?.startsWith("HarmonyOS")) return null;
239
+ return { sdkPath, installRoot: sdkPath === nested ? root : path2.dirname(root), ...pkg };
240
+ }
241
+ function studioCandidates() {
242
+ if (process.platform === "darwin") {
243
+ return [
244
+ "/Applications/DevEco-Studio.app/Contents",
245
+ path2.join(os3.homedir(), "Applications", "DevEco-Studio.app", "Contents")
246
+ ];
247
+ }
248
+ if (process.platform === "win32") {
249
+ return [path2.join(process.env.ProgramFiles ?? "C:\\Program Files", "Huawei", "DevEco Studio")];
250
+ }
251
+ return [];
252
+ }
253
+ function findHarmonyOsSdk(opts) {
254
+ const logger = opts.logger ?? noopLogger;
255
+ const configured = opts.config.get("harmonyosSdkPath", "");
256
+ if (configured) {
257
+ const sdk = resolveSdk(configured, false);
258
+ if (sdk) return sdk;
259
+ logger.warn(`ONIRO_HARMONYOS_SDK_PATH points at ${configured}, which holds no SDK; ignoring it.`);
260
+ }
261
+ const candidates = [
262
+ ...studioCandidates(),
263
+ opts.config.get("cmdToolsPath", defaultPaths.cmdToolsPath())
264
+ ];
265
+ for (const candidate of candidates) {
266
+ const sdk = resolveSdk(candidate, true);
267
+ if (sdk) {
268
+ logger.debug(`[harmonyos] Using the ${sdk.displayName} SDK at ${sdk.sdkPath}.`);
269
+ return sdk;
270
+ }
271
+ }
272
+ return null;
273
+ }
274
+ function requireHarmonyOsSdk(opts) {
275
+ const sdk = findHarmonyOsSdk(opts);
276
+ if (sdk) return sdk;
277
+ throw new OniroError(
278
+ "No HarmonyOS SDK found. It ships inside DevEco Studio or the HarmonyOS command-line tools and cannot be downloaded the way the OpenHarmony SDK is. Install one, then set ONIRO_HARMONYOS_SDK_PATH to the install (or its sdk directory)."
279
+ );
280
+ }
281
+ function findHarmonyOsTool(sdk, name) {
282
+ const names = process.platform === "win32" ? [`${name}.bat`, `${name}.cmd`, `${name}.exe`] : [name];
283
+ for (const dir of [path2.join(sdk.installRoot, "bin"), path2.join(sdk.installRoot, "tools", name, "bin")]) {
284
+ for (const candidate of names) {
285
+ const full = path2.join(dir, candidate);
286
+ if (fs.existsSync(full)) return full;
287
+ }
288
+ }
289
+ return null;
290
+ }
291
+
292
+ // src/sdk/paths.ts
208
293
  function getSdkRootDir(config) {
209
294
  return config.get("sdkRootDir", defaultPaths.sdkRootDir());
210
295
  }
211
296
  function getOhosBaseSdkHome(config) {
212
- return path2.join(getSdkRootDir(config), getOsFolder());
297
+ return path3.join(getSdkRootDir(config), getOsFolder());
213
298
  }
214
299
  function getCmdToolsPath(config) {
215
300
  return config.get("cmdToolsPath", defaultPaths.cmdToolsPath());
@@ -217,31 +302,31 @@ function getCmdToolsPath(config) {
217
302
  function getEmulatorDir(config) {
218
303
  return config.get("emulatorDir", defaultPaths.emulatorDir());
219
304
  }
220
- function toLongPath(p, platform7 = os3.platform()) {
305
+ function toLongPath(p, platform7 = os4.platform()) {
221
306
  if (platform7 !== "win32") return p;
222
307
  try {
223
- return fs.realpathSync.native(p);
308
+ return fs2.realpathSync.native(p);
224
309
  } catch {
225
310
  return p;
226
311
  }
227
312
  }
228
313
  function pickExisting(candidates) {
229
314
  for (const c of candidates) {
230
- if (fs.existsSync(c)) return c;
315
+ if (fs2.existsSync(c)) return c;
231
316
  }
232
317
  return candidates[0];
233
318
  }
234
319
  function getCmdToolsBin(config, name = "ohpm") {
235
- const binDir = path2.join(getCmdToolsPath(config), "bin");
236
- if (os3.platform() === "win32") {
320
+ const binDir = path3.join(getCmdToolsPath(config), "bin");
321
+ if (os4.platform() === "win32") {
237
322
  return pickExisting([
238
- path2.join(binDir, `${name}.exe`),
239
- path2.join(binDir, `${name}.cmd`),
240
- path2.join(binDir, `${name}.bat`),
241
- path2.join(binDir, name)
323
+ path3.join(binDir, `${name}.exe`),
324
+ path3.join(binDir, `${name}.cmd`),
325
+ path3.join(binDir, `${name}.bat`),
326
+ path3.join(binDir, name)
242
327
  ]);
243
328
  }
244
- return path2.join(binDir, name);
329
+ return path3.join(binDir, name);
245
330
  }
246
331
  function getOhpmPath(config) {
247
332
  return getCmdToolsBin(config, "ohpm");
@@ -249,45 +334,52 @@ function getOhpmPath(config) {
249
334
  function getHdcPath(config) {
250
335
  const override = config.get("hdcPath", "");
251
336
  if (override) return override;
252
- const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
253
- if (os3.platform() === "win32") {
337
+ const hdc = hdcInSdk(path3.join(getCmdToolsPath(config), "sdk"));
338
+ if (fs2.existsSync(hdc)) return hdc;
339
+ const harmonySdk = findHarmonyOsSdk({ config });
340
+ const harmonyHdc = harmonySdk ? hdcInSdk(harmonySdk.sdkPath) : void 0;
341
+ return harmonyHdc && fs2.existsSync(harmonyHdc) ? harmonyHdc : hdc;
342
+ }
343
+ function hdcInSdk(sdkDir) {
344
+ const base = path3.join(sdkDir, "default", "openharmony", "toolchains");
345
+ if (os4.platform() === "win32") {
254
346
  return pickExisting([
255
- path2.join(base, "hdc.exe"),
256
- path2.join(base, "hdc.bat"),
257
- path2.join(base, "hdc.cmd"),
258
- path2.join(base, "hdc")
347
+ path3.join(base, "hdc.exe"),
348
+ path3.join(base, "hdc.bat"),
349
+ path3.join(base, "hdc.cmd"),
350
+ path3.join(base, "hdc")
259
351
  ]);
260
352
  }
261
- return path2.join(base, "hdc");
353
+ return path3.join(base, "hdc");
262
354
  }
263
- function getHvigorwPath(config, projectDir, platform7 = os3.platform()) {
355
+ function getHvigorwPath(config, projectDir, platform7 = os4.platform()) {
264
356
  const posixFirst = ["hvigorw", "hvigorw.bat", "hvigorw.cmd"];
265
357
  const windowsFirst = ["hvigorw.bat", "hvigorw.cmd", "hvigorw"];
266
358
  const names = platform7 === "win32" ? windowsFirst : posixFirst;
267
- const local = pickExisting(names.map((n) => path2.join(projectDir, n)));
268
- if (fs.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
359
+ const local = pickExisting(names.map((n) => path3.join(projectDir, n)));
360
+ if (fs2.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
269
361
  return local;
270
362
  }
271
363
  return getCmdToolsBin(config, "hvigorw");
272
364
  }
273
365
  function isProjectLocalHvigorwWorking(projectDir) {
274
- const wrapper = path2.join(projectDir, "hvigor", "hvigor-wrapper.js");
275
- const nodeModules = path2.join(projectDir, "hvigor", "node_modules");
276
- return fs.existsSync(wrapper) && fs.existsSync(nodeModules);
366
+ const wrapper = path3.join(projectDir, "hvigor", "hvigor-wrapper.js");
367
+ const nodeModules = path3.join(projectDir, "hvigor", "node_modules");
368
+ return fs2.existsSync(wrapper) && fs2.existsSync(nodeModules);
277
369
  }
278
370
 
279
371
  // src/sdk/detectProjectSdk.ts
280
- import * as fs2 from "fs";
281
- import * as path3 from "path";
372
+ import * as fs3 from "fs";
373
+ import * as path4 from "path";
282
374
  import JSON5 from "json5";
283
375
  function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
284
- const buildProfilePath = path3.join(projectRoot, "build-profile.json5");
285
- if (!fs2.existsSync(buildProfilePath)) {
376
+ const buildProfilePath = path4.join(projectRoot, "build-profile.json5");
377
+ if (!fs3.existsSync(buildProfilePath)) {
286
378
  logger.warn(`build-profile.json5 not found at ${projectRoot}`);
287
379
  return void 0;
288
380
  }
289
381
  try {
290
- const content = fs2.readFileSync(buildProfilePath, "utf-8");
382
+ const content = fs3.readFileSync(buildProfilePath, "utf-8");
291
383
  const config = JSON5.parse(content);
292
384
  const product = config.app?.products?.[0];
293
385
  const v = product?.compileSdkVersion;
@@ -299,26 +391,26 @@ function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
299
391
  }
300
392
 
301
393
  // src/sdk/tmp.ts
302
- import * as fs3 from "fs";
303
- import * as os4 from "os";
304
- import * as path4 from "path";
394
+ import * as fs4 from "fs";
395
+ import * as os5 from "os";
396
+ import * as path5 from "path";
305
397
  var INSTALL_TMP_DIRNAME = ".oniro-tmp";
306
398
  var TMPFS_MAGIC = 16914836;
307
399
  var RAMFS_MAGIC = 2240043254;
308
- var HINT_EXAMPLE = os4.platform() === "win32" ? "D:\\oniro-tmp" : "~/.cache/oniro-tmp";
400
+ var HINT_EXAMPLE = os5.platform() === "win32" ? "D:\\oniro-tmp" : "~/.cache/oniro-tmp";
309
401
  var TMP_DIR_HINT = `Point the scratch directory at a filesystem with more room using --tmp-dir <path> (or the ONIRO_TMP_DIR environment variable), e.g. --tmp-dir ${HINT_EXAMPLE}.`;
310
402
  function resolveTmpRoot(config, override, installRoot) {
311
403
  const explicit = (override ?? "").trim();
312
- if (explicit) return path4.resolve(explicit);
404
+ if (explicit) return path5.resolve(explicit);
313
405
  const configured = config.get("tmpDir", "").trim();
314
- if (configured) return path4.resolve(configured);
315
- if (installRoot) return path4.join(path4.resolve(installRoot), INSTALL_TMP_DIRNAME);
316
- return os4.tmpdir();
406
+ if (configured) return path5.resolve(configured);
407
+ if (installRoot) return path5.join(path5.resolve(installRoot), INSTALL_TMP_DIRNAME);
408
+ return os5.tmpdir();
317
409
  }
318
410
  function createTempWorkDir(root, prefix) {
319
411
  try {
320
- fs3.mkdirSync(root, { recursive: true });
321
- return fs3.mkdtempSync(path4.join(root, prefix));
412
+ fs4.mkdirSync(root, { recursive: true });
413
+ return fs4.mkdtempSync(path5.join(root, prefix));
322
414
  } catch (err) {
323
415
  if (isOutOfSpaceError(err)) throw toSpaceError(err, root, "a temporary directory");
324
416
  throw new OniroError(
@@ -329,10 +421,10 @@ ${TMP_DIR_HINT}`,
329
421
  }
330
422
  }
331
423
  function removeTempWorkDir(dir, root) {
332
- fs3.rmSync(dir, { recursive: true, force: true });
333
- if (!root || path4.basename(root) !== INSTALL_TMP_DIRNAME) return;
424
+ fs4.rmSync(dir, { recursive: true, force: true });
425
+ if (!root || path5.basename(root) !== INSTALL_TMP_DIRNAME) return;
334
426
  try {
335
- fs3.rmdirSync(root);
427
+ fs4.rmdirSync(root);
336
428
  } catch {
337
429
  }
338
430
  }
@@ -342,18 +434,18 @@ function createInstallTempDir(opts) {
342
434
  try {
343
435
  return { dir: createTempWorkDir(root, opts.prefix), root };
344
436
  } catch (err) {
345
- const fallback = os4.tmpdir();
346
- if (requested || isOutOfSpaceError(err) || path4.resolve(fallback) === path4.resolve(root)) throw err;
437
+ const fallback = os5.tmpdir();
438
+ if (requested || isOutOfSpaceError(err) || path5.resolve(fallback) === path5.resolve(root)) throw err;
347
439
  const reason = err instanceof Error ? err.message.split(/\r?\n/)[0] : String(err);
348
440
  (opts.logger ?? noopLogger).warn(`${reason} Falling back to '${fallback}'.`);
349
441
  return { dir: createTempWorkDir(fallback, opts.prefix), root: fallback };
350
442
  }
351
443
  }
352
444
  function nearestExisting(dir) {
353
- let current = path4.resolve(dir);
445
+ let current = path5.resolve(dir);
354
446
  for (; ; ) {
355
- if (fs3.existsSync(current)) return current;
356
- const parent = path4.dirname(current);
447
+ if (fs4.existsSync(current)) return current;
448
+ const parent = path5.dirname(current);
357
449
  if (parent === current) return null;
358
450
  current = parent;
359
451
  }
@@ -362,7 +454,7 @@ function getFreeSpaceBytes(dir) {
362
454
  const target = nearestExisting(dir);
363
455
  if (!target) return null;
364
456
  try {
365
- const stats = fs3.statfsSync(target);
457
+ const stats = fs4.statfsSync(target);
366
458
  return Number(stats.bsize) * Number(stats.bavail);
367
459
  } catch {
368
460
  return null;
@@ -372,7 +464,7 @@ function isRamBackedPath(dir) {
372
464
  const target = nearestExisting(dir);
373
465
  if (!target) return false;
374
466
  try {
375
- const type = Number(fs3.statfsSync(target).type);
467
+ const type = Number(fs4.statfsSync(target).type);
376
468
  return type === TMPFS_MAGIC || type === RAMFS_MAGIC;
377
469
  } catch {
378
470
  return false;
@@ -436,16 +528,16 @@ function errorPath(err) {
436
528
  return null;
437
529
  }
438
530
  function isInside(child, parent) {
439
- const rel = path4.relative(path4.resolve(parent), path4.resolve(child));
440
- return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
531
+ const rel = path5.relative(path5.resolve(parent), path5.resolve(child));
532
+ return rel === "" || !rel.startsWith("..") && !path5.isAbsolute(rel);
441
533
  }
442
534
  function toSpaceError(err, tmpRoot, what) {
443
535
  if (err instanceof InsufficientSpaceError) return err;
444
536
  if (!isOutOfSpaceError(err)) return err;
445
537
  const failed = errorPath(err);
446
- const failedDir = failed ? path4.dirname(path4.resolve(failed)) : path4.resolve(tmpRoot);
538
+ const failedDir = failed ? path5.dirname(path5.resolve(failed)) : path5.resolve(tmpRoot);
447
539
  const inTmp = isInside(failedDir, tmpRoot);
448
- const dir = inTmp ? path4.resolve(tmpRoot) : failedDir;
540
+ const dir = inTmp ? path5.resolve(tmpRoot) : failedDir;
449
541
  return new InsufficientSpaceError(
450
542
  spaceMessage(dir, what, { freeBytes: getFreeSpaceBytes(dir), tmpDirHint: inTmp }),
451
543
  dir,
@@ -454,8 +546,8 @@ function toSpaceError(err, tmpRoot, what) {
454
546
  }
455
547
 
456
548
  // src/sdk/download.ts
457
- import * as fs4 from "fs";
458
- import * as path5 from "path";
549
+ import * as fs5 from "fs";
550
+ import * as path6 from "path";
459
551
  import * as crypto from "crypto";
460
552
  import { pipeline } from "stream";
461
553
  import { promisify } from "util";
@@ -465,26 +557,26 @@ var pipelineAsync = promisify(pipeline);
465
557
  async function downloadFile(opts) {
466
558
  const { url, dest, progress, abortSignal } = opts;
467
559
  const proto = url.startsWith("https") ? https : http;
468
- return new Promise((resolve4, reject) => {
560
+ return new Promise((resolve6, reject) => {
469
561
  const s = Math.max(0, Math.min(100, opts.start ?? 0));
470
562
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
471
563
  let settled = false;
472
564
  const done = (err) => {
473
565
  if (settled) return;
474
566
  settled = true;
475
- err ? reject(err) : resolve4();
567
+ err ? reject(err) : resolve6();
476
568
  };
477
569
  if (abortSignal?.aborted) {
478
570
  done(new CancelledError("Download cancelled before start."));
479
571
  return;
480
572
  }
481
- const destDir = path5.dirname(path5.resolve(dest));
573
+ const destDir = path6.dirname(path6.resolve(dest));
482
574
  const what = opts.what ?? `the download of '${url}'`;
483
575
  const writeFailed = (err) => isOutOfSpaceError(err) ? toSpaceError(err, opts.tmpRoot ?? destDir, what) : new OniroError(`Error writing '${dest}': ${err instanceof Error ? err.message : String(err)}`, err);
484
- const file = fs4.createWriteStream(dest);
576
+ const file = fs5.createWriteStream(dest);
485
577
  let activeResponse;
486
578
  const failWith = (err) => {
487
- file.close(() => fs4.rm(dest, { force: true }, () => done(err)));
579
+ file.close(() => fs5.rm(dest, { force: true }, () => done(err)));
488
580
  };
489
581
  file.on("error", (err) => {
490
582
  try {
@@ -556,9 +648,9 @@ async function downloadFile(opts) {
556
648
  });
557
649
  }
558
650
  async function verifySha256(filePath, sha256Path) {
559
- const expected = fs4.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
651
+ const expected = fs5.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
560
652
  const hash = crypto.createHash("sha256");
561
- await pipelineAsync(fs4.createReadStream(filePath), hash);
653
+ await pipelineAsync(fs5.createReadStream(filePath), hash);
562
654
  const actual = hash.digest("hex");
563
655
  if (actual !== expected) {
564
656
  throw new ChecksumMismatchError(expected, actual);
@@ -566,9 +658,9 @@ async function verifySha256(filePath, sha256Path) {
566
658
  }
567
659
 
568
660
  // src/sdk/extract.ts
569
- import * as fs5 from "fs";
570
- import * as os5 from "os";
571
- import * as path6 from "path";
661
+ import * as fs6 from "fs";
662
+ import * as os6 from "os";
663
+ import * as path7 from "path";
572
664
  import { pipeline as pipeline2 } from "stream";
573
665
  import { promisify as promisify2 } from "util";
574
666
  import * as tar from "tar";
@@ -585,12 +677,12 @@ async function extractZipWithProgress(opts) {
585
677
  const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
586
678
  let processed = 0;
587
679
  let lastOverall = Math.round(s);
588
- await fs5.promises.mkdir(dest, { recursive: true });
589
- const destRoot = path6.resolve(dest);
680
+ await fs6.promises.mkdir(dest, { recursive: true });
681
+ const destRoot = path7.resolve(dest);
590
682
  const safeResolveTarget = (entryName) => {
591
- const resolved = path6.resolve(destRoot, entryName);
592
- const rel = path6.relative(destRoot, resolved);
593
- if (rel.startsWith("..") || path6.isAbsolute(rel)) {
683
+ const resolved = path7.resolve(destRoot, entryName);
684
+ const rel = path7.relative(destRoot, resolved);
685
+ if (rel.startsWith("..") || path7.isAbsolute(rel)) {
594
686
  throw new OniroError(`Blocked ZIP entry with illegal path: ${entryName}`);
595
687
  }
596
688
  return resolved;
@@ -602,25 +694,25 @@ async function extractZipWithProgress(opts) {
602
694
  const attr = file.attr ? file.attr >>> 16 : 0;
603
695
  const isSymlink = "isSymbolicLink" in file ? Boolean(file.isSymbolicLink) : (attr & 61440) === 40960;
604
696
  if (file.isDirectory) {
605
- await fs5.promises.mkdir(targetPath, { recursive: true });
697
+ await fs6.promises.mkdir(targetPath, { recursive: true });
606
698
  } else if (isSymlink) {
607
- await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
699
+ await fs6.promises.mkdir(path7.dirname(targetPath), { recursive: true });
608
700
  const linkTargetBuffer = await zip.entryData(file);
609
701
  const linkTarget = linkTargetBuffer.toString("utf8");
610
702
  try {
611
- const isWin = os5.platform() === "win32";
612
- await fs5.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
703
+ const isWin = os6.platform() === "win32";
704
+ await fs6.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
613
705
  } catch (e) {
614
706
  logger.warn(`Failed to create symlink at ${targetPath}: ${String(e)}`);
615
707
  }
616
708
  } else {
617
- await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
709
+ await fs6.promises.mkdir(path7.dirname(targetPath), { recursive: true });
618
710
  const readStream = await zip.stream(file);
619
- const writeStream = fs5.createWriteStream(targetPath);
711
+ const writeStream = fs6.createWriteStream(targetPath);
620
712
  await pipelineAsync2(readStream, writeStream);
621
713
  if (attr > 0) {
622
714
  try {
623
- await fs5.promises.chmod(targetPath, attr & 511);
715
+ await fs6.promises.chmod(targetPath, attr & 511);
624
716
  } catch {
625
717
  }
626
718
  }
@@ -652,41 +744,41 @@ async function extractTarball(tarPath, dest, strip = 0) {
652
744
  }
653
745
 
654
746
  // src/sdk/list.ts
655
- import * as fs6 from "fs";
656
- import * as path7 from "path";
747
+ import * as fs7 from "fs";
748
+ import * as path8 from "path";
657
749
  function getSupportedSdksForUi(config) {
658
- const base = path7.join(getSdkRootDir(config), getOsFolder());
750
+ const base = path8.join(getSdkRootDir(config), getOsFolder());
659
751
  return ALL_SDKS.map((sdk) => ({
660
752
  version: sdk.version,
661
753
  api: sdk.api,
662
- installed: fs6.existsSync(path7.join(base, sdk.api))
754
+ installed: fs7.existsSync(path8.join(base, sdk.api))
663
755
  })).sort((a, b) => Number(b.api) - Number(a.api));
664
756
  }
665
757
  function getInstalledSdks(config) {
666
758
  const sdkRoot = getSdkRootDir(config);
667
759
  const versions = /* @__PURE__ */ new Set();
668
- if (!fs6.existsSync(sdkRoot)) return [];
760
+ if (!fs7.existsSync(sdkRoot)) return [];
669
761
  for (const osFolder of ["linux", "darwin", "windows"]) {
670
- const osPath = path7.join(sdkRoot, osFolder);
671
- if (!fs6.existsSync(osPath) || !fs6.statSync(osPath).isDirectory()) continue;
672
- for (const api of fs6.readdirSync(osPath)) {
673
- const apiPath = path7.join(osPath, api);
674
- if (fs6.statSync(apiPath).isDirectory()) versions.add(api);
762
+ const osPath = path8.join(sdkRoot, osFolder);
763
+ if (!fs7.existsSync(osPath) || !fs7.statSync(osPath).isDirectory()) continue;
764
+ for (const api of fs7.readdirSync(osPath)) {
765
+ const apiPath = path8.join(osPath, api);
766
+ if (fs7.statSync(apiPath).isDirectory()) versions.add(api);
675
767
  }
676
768
  }
677
769
  return ALL_SDKS.filter((sdk) => versions.has(sdk.api)).map((sdk) => sdk.version);
678
770
  }
679
771
 
680
772
  // src/sdk/remove.ts
681
- import * as fs7 from "fs";
682
- import * as path8 from "path";
773
+ import * as fs8 from "fs";
774
+ import * as path9 from "path";
683
775
  function removeSdk(config, api) {
684
776
  const sdkRoot = getSdkRootDir(config);
685
777
  let removed = false;
686
778
  for (const osFolder of ["linux", "darwin", "windows"]) {
687
- const sdkPath = path8.join(sdkRoot, osFolder, api);
688
- if (fs7.existsSync(sdkPath)) {
689
- fs7.rmSync(sdkPath, { recursive: true, force: true });
779
+ const sdkPath = path9.join(sdkRoot, osFolder, api);
780
+ if (fs8.existsSync(sdkPath)) {
781
+ fs8.rmSync(sdkPath, { recursive: true, force: true });
690
782
  removed = true;
691
783
  }
692
784
  }
@@ -694,18 +786,18 @@ function removeSdk(config, api) {
694
786
  }
695
787
 
696
788
  // src/sdk/install.ts
697
- import * as fs9 from "fs";
698
- import * as path9 from "path";
789
+ import * as fs10 from "fs";
790
+ import * as path10 from "path";
699
791
 
700
792
  // src/sdk/move.ts
701
- import * as fs8 from "fs";
793
+ import * as fs9 from "fs";
702
794
  function movePath(src, dest) {
703
795
  try {
704
- fs8.renameSync(src, dest);
796
+ fs9.renameSync(src, dest);
705
797
  } catch (err) {
706
798
  if (err.code !== "EXDEV") throw err;
707
- fs8.cpSync(src, dest, { recursive: true });
708
- fs8.rmSync(src, { recursive: true, force: true });
799
+ fs9.cpSync(src, dest, { recursive: true });
800
+ fs9.rmSync(src, { recursive: true, force: true });
709
801
  }
710
802
  }
711
803
 
@@ -726,12 +818,12 @@ async function downloadAndInstallSdk(opts) {
726
818
  prefix: "oniro-sdk-",
727
819
  logger
728
820
  });
729
- const tarPath = path9.join(tmpDir, filename);
730
- const sha256Path = path9.join(tmpDir, `${filename}.sha256`);
731
- const extractDir = path9.join(tmpDir, "extract");
732
- fs9.mkdirSync(extractDir);
733
- const sdkInstallDir = path9.join(sdkRoot, osFolder, api);
734
- fs9.mkdirSync(path9.dirname(sdkInstallDir), { recursive: true });
821
+ const tarPath = path10.join(tmpDir, filename);
822
+ const sha256Path = path10.join(tmpDir, `${filename}.sha256`);
823
+ const extractDir = path10.join(tmpDir, "extract");
824
+ fs10.mkdirSync(extractDir);
825
+ const sdkInstallDir = path10.join(sdkRoot, osFolder, api);
826
+ fs10.mkdirSync(path10.dirname(sdkInstallDir), { recursive: true });
735
827
  const checkCancelled = () => {
736
828
  if (abortSignal?.aborted) throw new CancelledError();
737
829
  };
@@ -748,13 +840,13 @@ async function downloadAndInstallSdk(opts) {
748
840
  await extractTarball(tarPath, extractDir, strip);
749
841
  progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
750
842
  checkCancelled();
751
- const osContentPath = path9.join(extractDir, osFolder);
752
- if (!fs9.existsSync(osContentPath)) {
843
+ const osContentPath = path10.join(extractDir, osFolder);
844
+ if (!fs10.existsSync(osContentPath)) {
753
845
  throw new OniroError(
754
846
  `Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
755
847
  );
756
848
  }
757
- const zipFiles = fs9.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
849
+ const zipFiles = fs10.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
758
850
  const componentsStart = 60;
759
851
  const componentsBudget = 35;
760
852
  const n = zipFiles.length;
@@ -769,16 +861,16 @@ async function downloadAndInstallSdk(opts) {
769
861
  for (const zipFile of zipFiles) {
770
862
  checkCancelled();
771
863
  logger.info(`Extracting component ${zipFile}`);
772
- const zipPath = path9.join(osContentPath, zipFile);
864
+ const zipPath = path10.join(osContentPath, zipFile);
773
865
  const thisBudget = base + (rem > 0 ? 1 : 0);
774
866
  if (rem > 0) rem--;
775
867
  await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
776
868
  cursor += thisBudget;
777
- fs9.unlinkSync(zipPath);
869
+ fs10.unlinkSync(zipPath);
778
870
  }
779
871
  progress?.report({ message: "Finalizing installation...", increment: 0 });
780
- if (fs9.existsSync(sdkInstallDir)) {
781
- fs9.rmSync(sdkInstallDir, { recursive: true, force: true });
872
+ if (fs10.existsSync(sdkInstallDir)) {
873
+ fs10.rmSync(sdkInstallDir, { recursive: true, force: true });
782
874
  }
783
875
  movePath(osContentPath, sdkInstallDir);
784
876
  progress?.report({ message: "Finalizing installation...", increment: 3 });
@@ -794,13 +886,13 @@ async function downloadAndInstallSdk(opts) {
794
886
  }
795
887
 
796
888
  // src/cmdTools/install.ts
797
- import * as fs10 from "fs";
798
- import * as os6 from "os";
799
- import * as path11 from "path";
889
+ import * as fs11 from "fs";
890
+ import * as os7 from "os";
891
+ import * as path12 from "path";
800
892
  import { spawnSync } from "child_process";
801
893
 
802
894
  // src/hdc/spawnCompat.ts
803
- import * as path10 from "path";
895
+ import * as path11 from "path";
804
896
  import { spawn } from "child_process";
805
897
  var WINDOWS_BATCH_RE = /\.(bat|cmd)$/i;
806
898
  var CMD_META_RE = /([()[\]%!^"`<>&|;, *?])/g;
@@ -824,7 +916,7 @@ function escapeCmdCommand(command) {
824
916
  function buildCmdWrapper(command, args) {
825
917
  assertCmdSafe(command, `command ${command}`);
826
918
  args.forEach((a, i) => assertCmdSafe(a, `argument ${i} (${a})`));
827
- const normalized = path10.normalize(command);
919
+ const normalized = path11.normalize(command);
828
920
  const double = CMD_SHIM_RE.test(normalized);
829
921
  const line = [escapeCmdCommand(normalized), ...args.map((a) => escapeCmdArgument(a, double))].join(" ");
830
922
  return {
@@ -872,7 +964,7 @@ function killProcessTree(child) {
872
964
  }
873
965
 
874
966
  // src/cmdTools/install.ts
875
- function getCmdToolsDownloadUrl(config, platform7 = os6.platform()) {
967
+ function getCmdToolsDownloadUrl(config, platform7 = os7.platform()) {
876
968
  if (platform7 === "linux") {
877
969
  return config.get("cmdToolsUrlLinux", defaultPaths.cmdToolsUrlLinux);
878
970
  }
@@ -898,16 +990,16 @@ function getCmdToolsDownloadUrl(config, platform7 = os6.platform()) {
898
990
  }
899
991
  function findCmdToolsSourceDir(extractPath) {
900
992
  const known = [
901
- path11.join(extractPath, "command-line-tools"),
902
- path11.join(extractPath, "oh-command-line-tools"),
903
- path11.join(extractPath, "commandline-tools")
993
+ path12.join(extractPath, "command-line-tools"),
994
+ path12.join(extractPath, "oh-command-line-tools"),
995
+ path12.join(extractPath, "commandline-tools")
904
996
  ];
905
997
  for (const c of known) {
906
- if (fs10.existsSync(c)) return c;
998
+ if (fs11.existsSync(c)) return c;
907
999
  }
908
- const entries = fs10.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path11.join(extractPath, e.name));
1000
+ const entries = fs11.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path12.join(extractPath, e.name));
909
1001
  for (const dir of entries) {
910
- if (fs10.existsSync(path11.join(dir, "bin"))) return dir;
1002
+ if (fs11.existsSync(path12.join(dir, "bin"))) return dir;
911
1003
  }
912
1004
  throw new OniroError("Could not locate command line tools folder in the extracted archive.");
913
1005
  }
@@ -919,12 +1011,12 @@ async function installCmdTools(opts) {
919
1011
  const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
920
1012
  config,
921
1013
  override: opts.tmpDir,
922
- installRoot: path11.dirname(CMD_PATH),
1014
+ installRoot: path12.dirname(CMD_PATH),
923
1015
  prefix: "oniro-cmdtools-",
924
1016
  logger
925
1017
  });
926
- const zipPath = opts.localZipPath ?? path11.join(tmpDir, "oh-command-line-tools.zip");
927
- const extractPath = path11.join(tmpDir, "oh-command-line-tools");
1018
+ const zipPath = opts.localZipPath ?? path12.join(tmpDir, "oh-command-line-tools.zip");
1019
+ const extractPath = path12.join(tmpDir, "oh-command-line-tools");
928
1020
  try {
929
1021
  if (!opts.localZipPath) {
930
1022
  const url = getCmdToolsDownloadUrl(config);
@@ -933,22 +1025,22 @@ async function installCmdTools(opts) {
933
1025
  }
934
1026
  progress?.report({ message: "Extracting tools...", increment: 0 });
935
1027
  await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
936
- fs10.mkdirSync(CMD_PATH, { recursive: true });
1028
+ fs11.mkdirSync(CMD_PATH, { recursive: true });
937
1029
  const srcDir = findCmdToolsSourceDir(extractPath);
938
- for (const entry of fs10.readdirSync(srcDir)) {
939
- const src = path11.join(srcDir, entry);
940
- const dest = path11.join(CMD_PATH, entry);
941
- if (fs10.statSync(src).isDirectory()) {
942
- if (fs10.existsSync(dest)) fs10.rmSync(dest, { recursive: true, force: true });
1030
+ for (const entry of fs11.readdirSync(srcDir)) {
1031
+ const src = path12.join(srcDir, entry);
1032
+ const dest = path12.join(CMD_PATH, entry);
1033
+ if (fs11.statSync(src).isDirectory()) {
1034
+ if (fs11.existsSync(dest)) fs11.rmSync(dest, { recursive: true, force: true });
943
1035
  movePath(src, dest);
944
1036
  } else {
945
- fs10.copyFileSync(src, dest);
1037
+ fs11.copyFileSync(src, dest);
946
1038
  }
947
1039
  }
948
- const binDir = path11.join(CMD_PATH, "bin");
949
- if (fs10.existsSync(binDir) && os6.platform() !== "win32") {
950
- for (const file of fs10.readdirSync(binDir)) {
951
- fs10.chmodSync(path11.join(binDir, file), 493);
1040
+ const binDir = path12.join(CMD_PATH, "bin");
1041
+ if (fs11.existsSync(binDir) && os7.platform() !== "win32") {
1042
+ for (const file of fs11.readdirSync(binDir)) {
1043
+ fs11.chmodSync(path12.join(binDir, file), 493);
952
1044
  }
953
1045
  }
954
1046
  progress?.report({ message: "Finalizing installation...", increment: 5 });
@@ -961,16 +1053,16 @@ async function installCmdTools(opts) {
961
1053
  }
962
1054
  function getCmdToolsStatus(config) {
963
1055
  const cmdPath = getCmdToolsPath(config);
964
- const binDir = path11.join(cmdPath, "bin");
965
- const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
966
- const bin = candidates.map((c) => path11.join(binDir, c)).find((p) => fs10.existsSync(p));
1056
+ const binDir = path12.join(cmdPath, "bin");
1057
+ const candidates = os7.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
1058
+ const bin = candidates.map((c) => path12.join(binDir, c)).find((p) => fs11.existsSync(p));
967
1059
  if (!bin) {
968
1060
  return { installed: false, status: "Not installed" };
969
1061
  }
970
1062
  try {
971
- const versionFile = path11.join(cmdPath, "version.txt");
972
- if (fs10.existsSync(versionFile)) {
973
- const content = fs10.readFileSync(versionFile, "utf8");
1063
+ const versionFile = path12.join(cmdPath, "version.txt");
1064
+ if (fs11.existsSync(versionFile)) {
1065
+ const content = fs11.readFileSync(versionFile, "utf8");
974
1066
  for (const line of content.split(/\r?\n/)) {
975
1067
  const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
976
1068
  if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
@@ -997,14 +1089,14 @@ function isCmdToolsInstalled(config) {
997
1089
  }
998
1090
  function removeCmdTools(config) {
999
1091
  const cmdPath = getCmdToolsPath(config);
1000
- if (fs10.existsSync(cmdPath)) {
1001
- fs10.rmSync(cmdPath, { recursive: true, force: true });
1092
+ if (fs11.existsSync(cmdPath)) {
1093
+ fs11.rmSync(cmdPath, { recursive: true, force: true });
1002
1094
  }
1003
1095
  }
1004
1096
 
1005
1097
  // src/sign/encryptKey.ts
1006
- import * as fs11 from "fs";
1007
- import * as path12 from "path";
1098
+ import * as fs12 from "fs";
1099
+ import * as path13 from "path";
1008
1100
  import * as crypto2 from "crypto";
1009
1101
  var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
1010
1102
  var DAEMON_SALT_KEY_LENGTH = 16;
@@ -1076,33 +1168,33 @@ function getRootKey(fdComponents, salt) {
1076
1168
  function createAndStoreKey(dir, keyLength) {
1077
1169
  const key = crypto2.randomBytes(keyLength);
1078
1170
  const hash = crypto2.createHash("sha256").update(key).digest("hex");
1079
- const filePath = path12.join(dir, hash);
1080
- fs11.writeFileSync(filePath, key);
1081
- fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1171
+ const filePath = path13.join(dir, hash);
1172
+ fs12.writeFileSync(filePath, key);
1173
+ fs12.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1082
1174
  return key;
1083
1175
  }
1084
1176
  function createAndStoreEnKey(rootKey, ceDir) {
1085
1177
  const workKey = crypto2.randomBytes(DAEMON_WORK_KEY_LENGTH);
1086
1178
  const encrypted = encrypt(rootKey, workKey);
1087
1179
  const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
1088
- const filePath = path12.join(ceDir, hash);
1089
- fs11.writeFileSync(filePath, encrypted);
1090
- fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1180
+ const filePath = path13.join(ceDir, hash);
1181
+ fs12.writeFileSync(filePath, encrypted);
1182
+ fs12.chmodSync(filePath, KEY_FILE_PERMISSIONS);
1091
1183
  }
1092
1184
  function createMaterial(materialPath) {
1093
- fs11.mkdirSync(materialPath, { recursive: true });
1094
- fs11.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
1095
- const fdDir = path12.join(materialPath, "fd");
1096
- const acDir = path12.join(materialPath, "ac");
1097
- const ceDir = path12.join(materialPath, "ce");
1098
- fs11.mkdirSync(fdDir, { recursive: true });
1099
- fs11.mkdirSync(acDir, { recursive: true });
1100
- fs11.mkdirSync(ceDir, { recursive: true });
1185
+ fs12.mkdirSync(materialPath, { recursive: true });
1186
+ fs12.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
1187
+ const fdDir = path13.join(materialPath, "fd");
1188
+ const acDir = path13.join(materialPath, "ac");
1189
+ const ceDir = path13.join(materialPath, "ce");
1190
+ fs12.mkdirSync(fdDir, { recursive: true });
1191
+ fs12.mkdirSync(acDir, { recursive: true });
1192
+ fs12.mkdirSync(ceDir, { recursive: true });
1101
1193
  const fdSubDirs = ["0", "1", "2"];
1102
1194
  const fdComponents = [];
1103
1195
  for (const sub of fdSubDirs) {
1104
- const subDir = path12.join(fdDir, sub);
1105
- fs11.mkdirSync(subDir, { recursive: true });
1196
+ const subDir = path13.join(fdDir, sub);
1197
+ fs12.mkdirSync(subDir, { recursive: true });
1106
1198
  const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
1107
1199
  fdComponents.push(comp);
1108
1200
  }
@@ -1111,24 +1203,24 @@ function createMaterial(materialPath) {
1111
1203
  createAndStoreEnKey(rootKey, ceDir);
1112
1204
  }
1113
1205
  function readSingleFile(dir) {
1114
- if (!fs11.existsSync(dir) || !fs11.statSync(dir).isDirectory()) {
1206
+ if (!fs12.existsSync(dir) || !fs12.statSync(dir).isDirectory()) {
1115
1207
  throw new Error(`Missing signing material directory: ${dir}`);
1116
1208
  }
1117
- const files = fs11.readdirSync(dir).filter((f) => f !== ".DS_Store");
1209
+ const files = fs12.readdirSync(dir).filter((f) => f !== ".DS_Store");
1118
1210
  if (files.length !== 1) {
1119
1211
  throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
1120
1212
  }
1121
- return fs11.readFileSync(path12.join(dir, files[0]));
1213
+ return fs12.readFileSync(path13.join(dir, files[0]));
1122
1214
  }
1123
1215
  function getKey(materialPath) {
1124
- if (!fs11.existsSync(materialPath) || !fs11.statSync(materialPath).isDirectory()) {
1216
+ if (!fs12.existsSync(materialPath) || !fs12.statSync(materialPath).isDirectory()) {
1125
1217
  throw new Error("Material directory does not exist.");
1126
1218
  }
1127
- const fdDir = path12.join(materialPath, "fd");
1128
- const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path12.join(fdDir, sub)));
1129
- const salt = readSingleFile(path12.join(materialPath, "ac"));
1219
+ const fdDir = path13.join(materialPath, "fd");
1220
+ const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path13.join(fdDir, sub)));
1221
+ const salt = readSingleFile(path13.join(materialPath, "ac"));
1130
1222
  const rootKey = getRootKey(fdComponents, salt);
1131
- const workMaterial = readSingleFile(path12.join(materialPath, "ce"));
1223
+ const workMaterial = readSingleFile(path13.join(materialPath, "ce"));
1132
1224
  return decrypt(rootKey, workMaterial);
1133
1225
  }
1134
1226
  function encryptPwd(password, materialPath) {
@@ -1143,8 +1235,8 @@ function decryptPwd(encryptedHex, materialPath) {
1143
1235
  }
1144
1236
 
1145
1237
  // src/sign/generateSigningConfigs.ts
1146
- import * as fs12 from "fs";
1147
- import * as path13 from "path";
1238
+ import * as fs13 from "fs";
1239
+ import * as path14 from "path";
1148
1240
  import { execFileSync } from "child_process";
1149
1241
  import JSON52 from "json5";
1150
1242
 
@@ -1214,17 +1306,17 @@ var SIGNING_KIND_CONFIG = {
1214
1306
  };
1215
1307
  function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
1216
1308
  logger.info("[sign] Copying signing material into project...");
1217
- const signaturesDir = path13.join(projectDir, "signatures");
1218
- fs12.mkdirSync(signaturesDir, { recursive: true });
1219
- fs12.copyFileSync(paths.keystore, path13.join(signaturesDir, "OpenHarmony.p12"));
1220
- fs12.copyFileSync(paths.profileCert, path13.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
1221
- fs12.copyFileSync(paths.unsignedProfileTemplate, path13.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
1309
+ const signaturesDir = path14.join(projectDir, "signatures");
1310
+ fs13.mkdirSync(signaturesDir, { recursive: true });
1311
+ fs13.copyFileSync(paths.keystore, path14.join(signaturesDir, "OpenHarmony.p12"));
1312
+ fs13.copyFileSync(paths.profileCert, path14.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
1313
+ fs13.copyFileSync(paths.unsignedProfileTemplate, path14.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
1222
1314
  if (kind === "application-release") {
1223
- const dest = path13.join(signaturesDir, "OpenHarmonyApplication.cer");
1315
+ const dest = path14.join(signaturesDir, "OpenHarmonyApplication.cer");
1224
1316
  if (applicationCertPath) {
1225
- fs12.copyFileSync(applicationCertPath, dest);
1317
+ fs13.copyFileSync(applicationCertPath, dest);
1226
1318
  } else {
1227
- fs12.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
1319
+ fs13.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
1228
1320
  }
1229
1321
  }
1230
1322
  }
@@ -1233,7 +1325,7 @@ function resolveAppFeature(apl, override) {
1233
1325
  return apl === "normal" ? "hos_normal_app" : "hos_system_app";
1234
1326
  }
1235
1327
  function extractLeafCertificate(chainPath) {
1236
- const certContent = fs12.readFileSync(chainPath, "utf-8");
1328
+ const certContent = fs13.readFileSync(chainPath, "utf-8");
1237
1329
  const parts = certContent.split("-----END CERTIFICATE-----");
1238
1330
  if (parts.length < 3) {
1239
1331
  throw new OniroError(`${chainPath} does not contain enough certificates.`);
@@ -1243,20 +1335,20 @@ function extractLeafCertificate(chainPath) {
1243
1335
  function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}) {
1244
1336
  const kind = options.kind ?? pickSigningKind(apl);
1245
1337
  logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
1246
- const appJsonPath = path13.join(projectDir, "AppScope/app.json5");
1247
- const profileTemplatePath = path13.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
1248
- if (!fs12.existsSync(appJsonPath)) {
1338
+ const appJsonPath = path14.join(projectDir, "AppScope/app.json5");
1339
+ const profileTemplatePath = path14.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
1340
+ if (!fs13.existsSync(appJsonPath)) {
1249
1341
  throw new OniroError(`${appJsonPath} does not exist.`);
1250
1342
  }
1251
1343
  let appJson;
1252
1344
  try {
1253
- appJson = JSON52.parse(fs12.readFileSync(appJsonPath, "utf-8"));
1345
+ appJson = JSON52.parse(fs13.readFileSync(appJsonPath, "utf-8"));
1254
1346
  } catch (e) {
1255
1347
  throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
1256
1348
  }
1257
1349
  let profileTemplate;
1258
1350
  try {
1259
- profileTemplate = JSON.parse(fs12.readFileSync(profileTemplatePath, "utf-8"));
1351
+ profileTemplate = JSON.parse(fs13.readFileSync(profileTemplatePath, "utf-8"));
1260
1352
  } catch (e) {
1261
1353
  throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
1262
1354
  }
@@ -1269,7 +1361,7 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
1269
1361
  profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
1270
1362
  profileTemplate["bundle-info"]["apl"] = apl;
1271
1363
  profileTemplate["bundle-info"]["app-feature"] = appFeature;
1272
- const distCertChainPath = path13.join(
1364
+ const distCertChainPath = path14.join(
1273
1365
  projectDir,
1274
1366
  "signatures",
1275
1367
  SIGNING_KIND_CONFIG[kind].certBasename
@@ -1279,13 +1371,13 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
1279
1371
  profileTemplate.acls = profileTemplate.acls ?? {};
1280
1372
  profileTemplate.acls["allowed-acls"] = options.acls;
1281
1373
  }
1282
- fs12.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1374
+ fs13.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
1283
1375
  }
1284
1376
  function generateP7bFile(projectDir, paths, logger, passwords) {
1285
1377
  logger.info("[sign] Generating P7b profile via hap-sign-tool...");
1286
- const signaturesDir = path13.join(projectDir, "signatures");
1287
- const profileTemplatePath = path13.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
1288
- const outputProfilePath = path13.join(signaturesDir, "app1-profile.p7b");
1378
+ const signaturesDir = path14.join(projectDir, "signatures");
1379
+ const profileTemplatePath = path14.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
1380
+ const outputProfilePath = path14.join(signaturesDir, "app1-profile.p7b");
1289
1381
  const args = [
1290
1382
  "-jar",
1291
1383
  paths.signTool,
@@ -1319,11 +1411,11 @@ function generateP7bFile(projectDir, paths, logger, passwords) {
1319
1411
  }
1320
1412
  }
1321
1413
  function detectSigningConfigNames(projectDir) {
1322
- const buildProfilePath = path13.join(projectDir, "build-profile.json5");
1323
- if (!fs12.existsSync(buildProfilePath)) return ["default"];
1414
+ const buildProfilePath = path14.join(projectDir, "build-profile.json5");
1415
+ if (!fs13.existsSync(buildProfilePath)) return ["default"];
1324
1416
  let parsed;
1325
1417
  try {
1326
- parsed = JSON52.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
1418
+ parsed = JSON52.parse(fs13.readFileSync(buildProfilePath, "utf-8"));
1327
1419
  } catch {
1328
1420
  return ["default"];
1329
1421
  }
@@ -1340,14 +1432,14 @@ function detectSigningConfigNames(projectDir) {
1340
1432
  }
1341
1433
  function updateBuildProfile(projectDir, kind, logger, passwords) {
1342
1434
  logger.info("[sign] Writing signing configs into build-profile.json5...");
1343
- const materialDir = path13.join(projectDir, "signatures", "material");
1344
- const buildProfilePath = path13.join(projectDir, "build-profile.json5");
1435
+ const materialDir = path14.join(projectDir, "signatures", "material");
1436
+ const buildProfilePath = path14.join(projectDir, "build-profile.json5");
1345
1437
  const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
1346
1438
  const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
1347
1439
  let buildProfile = { app: {} };
1348
- if (fs12.existsSync(buildProfilePath)) {
1440
+ if (fs13.existsSync(buildProfilePath)) {
1349
1441
  try {
1350
- buildProfile = JSON52.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
1442
+ buildProfile = JSON52.parse(fs13.readFileSync(buildProfilePath, "utf-8"));
1351
1443
  } catch (e) {
1352
1444
  throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
1353
1445
  }
@@ -1368,13 +1460,13 @@ function updateBuildProfile(projectDir, kind, logger, passwords) {
1368
1460
  storeFile: "./signatures/OpenHarmony.p12"
1369
1461
  }
1370
1462
  }));
1371
- fs12.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1463
+ fs13.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
1372
1464
  }
1373
1465
  function prepareMaterialDirectory(projectDir, logger) {
1374
1466
  logger.info("[sign] Generating fresh signing material...");
1375
- const materialDir = path13.join(projectDir, "signatures", "material");
1376
- if (fs12.existsSync(materialDir)) {
1377
- fs12.rmSync(materialDir, { recursive: true, force: true });
1467
+ const materialDir = path14.join(projectDir, "signatures", "material");
1468
+ if (fs13.existsSync(materialDir)) {
1469
+ fs13.rmSync(materialDir, { recursive: true, force: true });
1378
1470
  }
1379
1471
  createMaterial(materialDir);
1380
1472
  }
@@ -1397,15 +1489,15 @@ function generateSigningConfigs(options) {
1397
1489
  "Could not detect project SDK version (compileSdkVersion missing in build-profile.json5)."
1398
1490
  );
1399
1491
  }
1400
- const sdkPath = path13.join(sdkHome, String(sdkVersion));
1401
- if (!fs12.existsSync(sdkPath)) {
1492
+ const sdkPath = path14.join(sdkHome, String(sdkVersion));
1493
+ if (!fs13.existsSync(sdkPath)) {
1402
1494
  throw new OniroError(`SDK path does not exist: ${sdkPath}`);
1403
1495
  }
1404
1496
  const paths = {
1405
- signTool: path13.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
1406
- keystore: path13.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
1407
- profileCert: path13.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
1408
- unsignedProfileTemplate: path13.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
1497
+ signTool: path14.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
1498
+ keystore: path14.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
1499
+ profileCert: path14.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
1500
+ unsignedProfileTemplate: path14.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
1409
1501
  };
1410
1502
  const kind = pickSigningKind(apl);
1411
1503
  const passwords = options.passwords ?? DEFAULT_PASSWORDS;
@@ -1419,8 +1511,8 @@ function generateSigningConfigs(options) {
1419
1511
  }
1420
1512
 
1421
1513
  // src/emulator/install.ts
1422
- import * as fs13 from "fs";
1423
- import * as path14 from "path";
1514
+ import * as fs14 from "fs";
1515
+ import * as path15 from "path";
1424
1516
  async function installEmulator(opts) {
1425
1517
  const { config, progress, abortSignal } = opts;
1426
1518
  const logger = opts.logger ?? noopLogger;
@@ -1430,20 +1522,20 @@ async function installEmulator(opts) {
1430
1522
  const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
1431
1523
  config,
1432
1524
  override: opts.tmpDir,
1433
- installRoot: path14.dirname(emulatorDir),
1525
+ installRoot: path15.dirname(emulatorDir),
1434
1526
  prefix: "oniro-emulator-",
1435
1527
  logger
1436
1528
  });
1437
- const tmpZip = path14.join(tmpDir, "oniro_emulator.zip");
1529
+ const tmpZip = path15.join(tmpDir, "oniro_emulator.zip");
1438
1530
  try {
1439
- fs13.mkdirSync(emulatorDir, { recursive: true });
1531
+ fs14.mkdirSync(emulatorDir, { recursive: true });
1440
1532
  progress?.report({ message: "Downloading emulator...", increment: 0 });
1441
1533
  await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
1442
1534
  progress?.report({ message: "Extracting emulator...", increment: 0 });
1443
1535
  await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
1444
- const runSh = path14.join(emulatorDir, "images", "run.sh");
1445
- if (fs13.existsSync(runSh)) {
1446
- fs13.chmodSync(runSh, 493);
1536
+ const runSh = path15.join(emulatorDir, "images", "run.sh");
1537
+ if (fs14.existsSync(runSh)) {
1538
+ fs14.chmodSync(runSh, 493);
1447
1539
  }
1448
1540
  progress?.report({ message: "Finalizing installation...", increment: 5 });
1449
1541
  } catch (err) {
@@ -1453,24 +1545,24 @@ async function installEmulator(opts) {
1453
1545
  }
1454
1546
  }
1455
1547
  function isEmulatorInstalled(config) {
1456
- const imagesDir = path14.join(getEmulatorDir(config), "images");
1457
- return fs13.existsSync(path14.join(imagesDir, "run.sh")) || fs13.existsSync(path14.join(imagesDir, "run.bat"));
1548
+ const imagesDir = path15.join(getEmulatorDir(config), "images");
1549
+ return fs14.existsSync(path15.join(imagesDir, "run.sh")) || fs14.existsSync(path15.join(imagesDir, "run.bat"));
1458
1550
  }
1459
1551
  function removeEmulator(config) {
1460
1552
  const emulatorDir = getEmulatorDir(config);
1461
- if (fs13.existsSync(emulatorDir)) {
1462
- fs13.rmSync(emulatorDir, { recursive: true, force: true });
1553
+ if (fs14.existsSync(emulatorDir)) {
1554
+ fs14.rmSync(emulatorDir, { recursive: true, force: true });
1463
1555
  }
1464
1556
  }
1465
1557
 
1466
1558
  // src/emulator/lifecycle.ts
1467
- import * as fs15 from "fs";
1468
- import * as os7 from "os";
1469
- import * as path15 from "path";
1559
+ import * as fs16 from "fs";
1560
+ import * as os8 from "os";
1561
+ import * as path16 from "path";
1470
1562
  import { spawn as spawn2 } from "child_process";
1471
1563
 
1472
1564
  // src/hdc/exec.ts
1473
- import * as fs14 from "fs";
1565
+ import * as fs15 from "fs";
1474
1566
  function errorMessage(err) {
1475
1567
  return err instanceof Error ? err.message : String(err);
1476
1568
  }
@@ -1478,12 +1570,12 @@ function runProcess(opts) {
1478
1570
  const { command, args, cwd, env, onOutput } = opts;
1479
1571
  const timeoutMs = opts.timeoutMs ?? 6e4;
1480
1572
  const { abortSignal } = opts;
1481
- return new Promise((resolve4, reject) => {
1573
+ return new Promise((resolve6, reject) => {
1482
1574
  if (abortSignal?.aborted) {
1483
1575
  reject(new CancelledError(`Command aborted before start: ${command}`));
1484
1576
  return;
1485
1577
  }
1486
- if (needsCmdWrapper(command) && !fs14.existsSync(command)) {
1578
+ if (needsCmdWrapper(command) && !fs15.existsSync(command)) {
1487
1579
  reject(new OniroError(`Failed to spawn ${command}: ENOENT (no such file)`));
1488
1580
  return;
1489
1581
  }
@@ -1543,7 +1635,7 @@ ${stderr.trim()}` : "")
1543
1635
  );
1544
1636
  return;
1545
1637
  }
1546
- resolve4({ code: code ?? -1, stdout, stderr });
1638
+ resolve6({ code: code ?? -1, stdout, stderr });
1547
1639
  });
1548
1640
  });
1549
1641
  }
@@ -1591,7 +1683,7 @@ function ensureRemoteOk(result, command) {
1591
1683
  }
1592
1684
 
1593
1685
  // src/emulator/lifecycle.ts
1594
- var PID_FILE = path15.join(os7.tmpdir(), "oniro_emulator.pid");
1686
+ var PID_FILE = path16.join(os8.tmpdir(), "oniro_emulator.pid");
1595
1687
  async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
1596
1688
  try {
1597
1689
  await hdcExec({ config, args: ["start", "-r"], logger });
@@ -1604,24 +1696,24 @@ async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger
1604
1696
  }
1605
1697
  }
1606
1698
  function resolveLauncher(imagesPath, headless, connect) {
1607
- const runSh = path15.join(imagesPath, "run.sh");
1608
- const runBat = path15.join(imagesPath, "run.bat");
1699
+ const runSh = path16.join(imagesPath, "run.sh");
1700
+ const runBat = path16.join(imagesPath, "run.bat");
1609
1701
  const extra = [];
1610
1702
  if (headless) extra.push("--headless");
1611
1703
  if (connect) extra.push("--connect", connect);
1612
- if (os7.platform() === "win32") {
1613
- if (fs15.existsSync(runBat)) {
1704
+ if (os8.platform() === "win32") {
1705
+ if (fs16.existsSync(runBat)) {
1614
1706
  return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
1615
1707
  }
1616
- if (fs15.existsSync(runSh)) {
1708
+ if (fs16.existsSync(runSh)) {
1617
1709
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1618
1710
  }
1619
1711
  throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
1620
1712
  }
1621
- if (fs15.existsSync(runSh)) {
1713
+ if (fs16.existsSync(runSh)) {
1622
1714
  return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1623
1715
  }
1624
- if (fs15.existsSync(runBat)) {
1716
+ if (fs16.existsSync(runBat)) {
1625
1717
  return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
1626
1718
  }
1627
1719
  throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
@@ -1629,9 +1721,9 @@ function resolveLauncher(imagesPath, headless, connect) {
1629
1721
  async function startEmulator(opts) {
1630
1722
  const { config } = opts;
1631
1723
  const logger = opts.logger ?? noopLogger;
1632
- if (os7.platform() !== "win32" && fs15.existsSync(PID_FILE)) {
1724
+ if (os8.platform() !== "win32" && fs16.existsSync(PID_FILE)) {
1633
1725
  try {
1634
- const pid = Number(fs15.readFileSync(PID_FILE, "utf8").trim());
1726
+ const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
1635
1727
  if (Number.isFinite(pid) && pid > 0) {
1636
1728
  try {
1637
1729
  process.kill(pid, 0);
@@ -1647,13 +1739,13 @@ async function startEmulator(opts) {
1647
1739
  logger.warn(`Could not read PID file: ${err.message}`);
1648
1740
  }
1649
1741
  }
1650
- const imagesPath = path15.join(getEmulatorDir(config), "images");
1651
- if (!fs15.existsSync(imagesPath)) {
1742
+ const imagesPath = path16.join(getEmulatorDir(config), "images");
1743
+ if (!fs16.existsSync(imagesPath)) {
1652
1744
  throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
1653
1745
  }
1654
1746
  const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
1655
- const logPath = opts.logFile ?? (os7.platform() === "win32" ? "NUL" : "/dev/null");
1656
- const logFd = fs15.openSync(logPath, "a");
1747
+ const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
1748
+ const logFd = fs16.openSync(logPath, "a");
1657
1749
  let child;
1658
1750
  try {
1659
1751
  child = spawn2(launcher.command, [...launcher.args], {
@@ -1663,19 +1755,19 @@ async function startEmulator(opts) {
1663
1755
  windowsHide: true
1664
1756
  });
1665
1757
  } finally {
1666
- fs15.closeSync(logFd);
1758
+ fs16.closeSync(logFd);
1667
1759
  }
1668
1760
  if (!child.pid) {
1669
1761
  throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
1670
1762
  }
1671
- fs15.writeFileSync(PID_FILE, String(child.pid));
1763
+ fs16.writeFileSync(PID_FILE, String(child.pid));
1672
1764
  child.unref();
1673
1765
  logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1674
- const earlyExit = new Promise((resolve4) => {
1675
- child.once("exit", (code) => resolve4(code));
1676
- child.once("error", () => resolve4(-1));
1766
+ const earlyExit = new Promise((resolve6) => {
1767
+ child.once("exit", (code) => resolve6(code));
1768
+ child.once("error", () => resolve6(-1));
1677
1769
  });
1678
- const liveness = new Promise((resolve4) => setTimeout(() => resolve4("alive"), 2e3));
1770
+ const liveness = new Promise((resolve6) => setTimeout(() => resolve6("alive"), 2e3));
1679
1771
  const outcome = await Promise.race([earlyExit, liveness]);
1680
1772
  if (outcome !== "alive") {
1681
1773
  const tail = readTail(logPath, 4e3);
@@ -1715,24 +1807,24 @@ ${tail}` : ""}`);
1715
1807
  }
1716
1808
  function readTail(filePath, bytes) {
1717
1809
  try {
1718
- const stat = fs15.statSync(filePath);
1810
+ const stat = fs16.statSync(filePath);
1719
1811
  const start = Math.max(0, stat.size - bytes);
1720
- const fd = fs15.openSync(filePath, "r");
1812
+ const fd = fs16.openSync(filePath, "r");
1721
1813
  try {
1722
1814
  const buf = Buffer.alloc(stat.size - start);
1723
- fs15.readSync(fd, buf, 0, buf.length, start);
1815
+ fs16.readSync(fd, buf, 0, buf.length, start);
1724
1816
  return buf.toString("utf8");
1725
1817
  } finally {
1726
- fs15.closeSync(fd);
1818
+ fs16.closeSync(fd);
1727
1819
  }
1728
1820
  } catch {
1729
1821
  return "";
1730
1822
  }
1731
1823
  }
1732
1824
  async function stopEmulator(logger = noopLogger) {
1733
- if (fs15.existsSync(PID_FILE)) {
1825
+ if (fs16.existsSync(PID_FILE)) {
1734
1826
  try {
1735
- const pid = Number(fs15.readFileSync(PID_FILE, "utf8").trim());
1827
+ const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
1736
1828
  if (Number.isFinite(pid) && pid > 0) {
1737
1829
  try {
1738
1830
  process.kill(pid, "SIGTERM");
@@ -1745,13 +1837,13 @@ async function stopEmulator(logger = noopLogger) {
1745
1837
  logger.warn(`Could not read PID file: ${err.message}`);
1746
1838
  }
1747
1839
  }
1748
- const [killCmd, ...killArgs] = os7.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
1840
+ const [killCmd, ...killArgs] = os8.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
1749
1841
  try {
1750
1842
  await runProcess({ command: killCmd, args: killArgs, logger });
1751
1843
  } catch (err) {
1752
1844
  logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
1753
1845
  }
1754
- fs15.rm(PID_FILE, { force: true }, (err) => {
1846
+ fs16.rm(PID_FILE, { force: true }, (err) => {
1755
1847
  if (err) logger.warn(`Could not remove PID file: ${err.message}`);
1756
1848
  });
1757
1849
  }
@@ -1874,7 +1966,7 @@ async function getDisplaySize(config, opts = {}) {
1874
1966
 
1875
1967
  // src/hdc/wait.ts
1876
1968
  function delay(ms, signal) {
1877
- return new Promise((resolve4, reject) => {
1969
+ return new Promise((resolve6, reject) => {
1878
1970
  if (signal?.aborted) {
1879
1971
  reject(new CancelledError("Wait cancelled."));
1880
1972
  return;
@@ -1889,7 +1981,7 @@ function delay(ms, signal) {
1889
1981
  };
1890
1982
  const timer = setTimeout(() => {
1891
1983
  cleanup();
1892
- resolve4();
1984
+ resolve6();
1893
1985
  }, ms);
1894
1986
  signal?.addEventListener("abort", onAbort, { once: true });
1895
1987
  });
@@ -1918,20 +2010,20 @@ async function waitForCondition(opts) {
1918
2010
  }
1919
2011
 
1920
2012
  // src/hdc/app.ts
1921
- import * as fs17 from "fs";
1922
- import * as path17 from "path";
2013
+ import * as fs18 from "fs";
2014
+ import * as path18 from "path";
1923
2015
 
1924
2016
  // src/hdc/project.ts
1925
- import * as fs16 from "fs";
1926
- import * as path16 from "path";
2017
+ import * as fs17 from "fs";
2018
+ import * as path17 from "path";
1927
2019
  import JSON53 from "json5";
1928
2020
  function readJson5(filePath) {
1929
- const content = fs16.readFileSync(filePath, "utf-8");
2021
+ const content = fs17.readFileSync(filePath, "utf-8");
1930
2022
  return JSON53.parse(content);
1931
2023
  }
1932
2024
  function getBundleName(projectDir) {
1933
- const appJsonPath = path16.join(projectDir, "AppScope", "app.json5");
1934
- if (!fs16.existsSync(appJsonPath)) {
2025
+ const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
2026
+ if (!fs17.existsSync(appJsonPath)) {
1935
2027
  throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
1936
2028
  }
1937
2029
  const appJson = readJson5(appJsonPath);
@@ -1941,8 +2033,8 @@ function getBundleName(projectDir) {
1941
2033
  return appJson.app.bundleName;
1942
2034
  }
1943
2035
  function getMainAbility(projectDir, moduleName = "entry", abilityName) {
1944
- const moduleJsonPath = path16.join(projectDir, moduleName, "src", "main", "module.json5");
1945
- if (!fs16.existsSync(moduleJsonPath)) {
2036
+ const moduleJsonPath = path17.join(projectDir, moduleName, "src", "main", "module.json5");
2037
+ if (!fs17.existsSync(moduleJsonPath)) {
1946
2038
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
1947
2039
  }
1948
2040
  const moduleJson = readJson5(moduleJsonPath);
@@ -1965,8 +2057,8 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
1965
2057
  async function installApp(opts) {
1966
2058
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
1967
2059
  const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
1968
- const hapPath = path17.isAbsolute(relativeHapPath) ? relativeHapPath : path17.join(opts.projectDir, relativeHapPath);
1969
- if (!fs17.existsSync(hapPath)) {
2060
+ const hapPath = path18.isAbsolute(relativeHapPath) ? relativeHapPath : path18.join(opts.projectDir, relativeHapPath);
2061
+ if (!fs18.existsSync(hapPath)) {
1970
2062
  throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
1971
2063
  }
1972
2064
  const result = await hdcExec({
@@ -2055,7 +2147,7 @@ function listRunningProcesses(config, options = {}) {
2055
2147
  const logger = options.logger ?? noopLogger;
2056
2148
  const timeoutMs = options.timeoutMs ?? 1e3;
2057
2149
  const hdc = getHdcPath(config);
2058
- return new Promise((resolve4, reject) => {
2150
+ return new Promise((resolve6, reject) => {
2059
2151
  let proc;
2060
2152
  try {
2061
2153
  proc = spawnCompat(hdc, ["track-jpid"]);
@@ -2075,7 +2167,7 @@ function listRunningProcesses(config, options = {}) {
2075
2167
  if (options.targetProcessName && name === options.targetProcessName && !resolved) {
2076
2168
  resolved = true;
2077
2169
  killProcessTree(proc);
2078
- resolve4(pid);
2170
+ resolve6(pid);
2079
2171
  return;
2080
2172
  }
2081
2173
  }
@@ -2096,7 +2188,7 @@ function listRunningProcesses(config, options = {}) {
2096
2188
  if (options.targetProcessName) {
2097
2189
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2098
2190
  } else {
2099
- resolve4(processes);
2191
+ resolve6(processes);
2100
2192
  }
2101
2193
  }, timeoutMs);
2102
2194
  proc.on("close", () => {
@@ -2106,7 +2198,7 @@ function listRunningProcesses(config, options = {}) {
2106
2198
  if (options.targetProcessName) {
2107
2199
  reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
2108
2200
  } else {
2109
- resolve4(processes);
2201
+ resolve6(processes);
2110
2202
  }
2111
2203
  });
2112
2204
  });
@@ -2171,19 +2263,39 @@ async function isReachable(config, deviceSerial) {
2171
2263
  }
2172
2264
  }
2173
2265
  var TCP_SERIAL_RE = /^.+:\d+$/;
2174
- async function reconnectIfTcp(config, deviceSerial, logger) {
2175
- if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
2176
- try {
2177
- await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2178
- } catch {
2179
- }
2266
+ var WEDGED_POLLS_BEFORE_RESTART = 3;
2267
+ function tcpReconnector(config, deviceSerial, logger) {
2268
+ let wedgedPolls = 0;
2269
+ return async () => {
2270
+ if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
2271
+ let claimsConnected = false;
2272
+ try {
2273
+ const res = await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2274
+ claimsConnected = /already connected|repeat operation/i.test(`${res.stdout}
2275
+ ${res.stderr}`);
2276
+ } catch {
2277
+ }
2278
+ if (!claimsConnected || await isReachable(config, deviceSerial)) {
2279
+ wedgedPolls = 0;
2280
+ return;
2281
+ }
2282
+ if (++wedgedPolls < WEDGED_POLLS_BEFORE_RESTART) return;
2283
+ wedgedPolls = 0;
2284
+ logger.warn(`hdc reports ${deviceSerial} as connected but it does not answer; restarting the hdc server.`);
2285
+ try {
2286
+ await hdcExec({ config, args: ["start", "-r"], timeoutMs: 15e3, logger });
2287
+ await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
2288
+ } catch {
2289
+ }
2290
+ };
2180
2291
  }
2181
2292
  async function waitForBundle(opts) {
2182
2293
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2183
2294
  const timeoutMs = opts.timeoutMs ?? 18e4;
2295
+ const reconnectIfTcp = tcpReconnector(opts.config, opts.deviceSerial, logger);
2184
2296
  await waitForCondition({
2185
2297
  probe: async () => {
2186
- await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
2298
+ await reconnectIfTcp();
2187
2299
  return await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
2188
2300
  },
2189
2301
  timeoutMs,
@@ -2196,9 +2308,10 @@ async function waitForBundle(opts) {
2196
2308
  async function waitForBoot(opts) {
2197
2309
  const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
2198
2310
  const timeoutMs = opts.timeoutMs ?? 18e4;
2311
+ const reconnectIfTcp = tcpReconnector(opts.config, opts.deviceSerial, logger);
2199
2312
  await waitForCondition({
2200
2313
  probe: async () => {
2201
- await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
2314
+ await reconnectIfTcp();
2202
2315
  if (!await isReachable(opts.config, opts.deviceSerial)) return false;
2203
2316
  if (!opts.untilPidOf) return true;
2204
2317
  return await findRunningProcess({ config: opts.config, bundle: opts.untilPidOf, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
@@ -2250,12 +2363,12 @@ async function reboot(opts) {
2250
2363
  }
2251
2364
 
2252
2365
  // src/hdc/screen.ts
2253
- import * as fs18 from "fs";
2254
- import * as os8 from "os";
2255
- import * as path18 from "path";
2366
+ import * as fs19 from "fs";
2367
+ import * as os9 from "os";
2368
+ import * as path19 from "path";
2256
2369
  import { randomBytes as randomBytes2 } from "crypto";
2257
2370
  function tmpName(ext) {
2258
- return path18.join(os8.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
2371
+ return path19.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
2259
2372
  }
2260
2373
  async function takeScreenshot(opts) {
2261
2374
  const remote = "/data/local/tmp/oniro_screenshot.jpeg";
@@ -2265,11 +2378,11 @@ async function takeScreenshot(opts) {
2265
2378
  const local = tmpName("jpeg");
2266
2379
  try {
2267
2380
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2268
- const pixels = await fs18.promises.readFile(local);
2381
+ const pixels = await fs19.promises.readFile(local);
2269
2382
  const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
2270
2383
  return { pixels, width: size.width, height: size.height };
2271
2384
  } finally {
2272
- fs18.promises.unlink(local).catch(() => {
2385
+ fs19.promises.unlink(local).catch(() => {
2273
2386
  });
2274
2387
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2275
2388
  });
@@ -2288,9 +2401,9 @@ async function captureBurst(opts) {
2288
2401
  const local = tmpName(`burst_${i}.jpeg`);
2289
2402
  try {
2290
2403
  await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
2291
- frames.push(await fs18.promises.readFile(local));
2404
+ frames.push(await fs19.promises.readFile(local));
2292
2405
  } finally {
2293
- fs18.promises.unlink(local).catch(() => {
2406
+ fs19.promises.unlink(local).catch(() => {
2294
2407
  });
2295
2408
  }
2296
2409
  }
@@ -2416,9 +2529,9 @@ async function sendRawTouch(opts) {
2416
2529
  }
2417
2530
 
2418
2531
  // src/hdc/dump.ts
2419
- import * as fs19 from "fs";
2420
- import * as os9 from "os";
2421
- import * as path19 from "path";
2532
+ import * as fs20 from "fs";
2533
+ import * as os10 from "os";
2534
+ import * as path20 from "path";
2422
2535
  import { randomBytes as randomBytes3 } from "crypto";
2423
2536
  function parseBounds(s) {
2424
2537
  const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
@@ -2503,10 +2616,10 @@ async function dumpLayout(opts) {
2503
2616
  if (!m) throw new OniroError(`Could not parse dumpLayout output:
2504
2617
  ${dump.stdout}`);
2505
2618
  const remote = m[1];
2506
- const local = path19.join(os9.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
2619
+ const local = path20.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
2507
2620
  try {
2508
2621
  await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
2509
- const parsed = JSON.parse(await fs19.promises.readFile(local, "utf8"));
2622
+ const parsed = JSON.parse(await fs20.promises.readFile(local, "utf8"));
2510
2623
  const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
2511
2624
  if (opts.prune === false) {
2512
2625
  return { display, tree: parsed };
@@ -2515,7 +2628,7 @@ ${dump.stdout}`);
2515
2628
  const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
2516
2629
  return { display, tree };
2517
2630
  } finally {
2518
- fs19.promises.unlink(local).catch(() => {
2631
+ fs20.promises.unlink(local).catch(() => {
2519
2632
  });
2520
2633
  void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
2521
2634
  });
@@ -2526,7 +2639,7 @@ ${dump.stdout}`);
2526
2639
  function setHilogLevel(opts) {
2527
2640
  const logger = opts.logger ?? noopLogger;
2528
2641
  const hdc = getHdcPath(opts.config);
2529
- return new Promise((resolve4, reject) => {
2642
+ return new Promise((resolve6, reject) => {
2530
2643
  let child;
2531
2644
  try {
2532
2645
  child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
@@ -2546,7 +2659,7 @@ function setHilogLevel(opts) {
2546
2659
  });
2547
2660
  child.once("close", (code) => {
2548
2661
  if (code === 0) {
2549
- resolve4();
2662
+ resolve6();
2550
2663
  } else {
2551
2664
  const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
2552
2665
  logger.error(`[hilog] ${msg}`);
@@ -2601,7 +2714,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
2601
2714
  return proc?.pid;
2602
2715
  }
2603
2716
  function waitForLog(opts) {
2604
- return new Promise((resolve4, reject) => {
2717
+ return new Promise((resolve6, reject) => {
2605
2718
  const deadline = Date.now() + opts.timeoutMs;
2606
2719
  let settled = false;
2607
2720
  let child = null;
@@ -2620,7 +2733,7 @@ function waitForLog(opts) {
2620
2733
  if (settled) return;
2621
2734
  settled = true;
2622
2735
  cleanup();
2623
- resolve4(entry);
2736
+ resolve6(entry);
2624
2737
  };
2625
2738
  const fail = (err) => {
2626
2739
  if (settled) return;
@@ -2667,7 +2780,7 @@ function waitForLog(opts) {
2667
2780
  });
2668
2781
  }
2669
2782
  function watchLog(opts) {
2670
- return new Promise((resolve4, reject) => {
2783
+ return new Promise((resolve6, reject) => {
2671
2784
  const matches = [];
2672
2785
  let settled = false;
2673
2786
  let child = null;
@@ -2684,7 +2797,7 @@ function watchLog(opts) {
2684
2797
  if (settled) return;
2685
2798
  settled = true;
2686
2799
  cleanup();
2687
- resolve4(opts.dedup === false ? matches : dedupEntries(matches));
2800
+ resolve6(opts.dedup === false ? matches : dedupEntries(matches));
2688
2801
  };
2689
2802
  const onAbort = () => {
2690
2803
  if (settled) return;
@@ -2740,8 +2853,43 @@ async function dumpLog(opts) {
2740
2853
  }
2741
2854
 
2742
2855
  // src/build/runHvigorw.ts
2743
- import * as fs20 from "fs";
2744
- import * as path20 from "path";
2856
+ import * as fs22 from "fs";
2857
+ import * as path22 from "path";
2858
+
2859
+ // src/harmonyos/target.ts
2860
+ import * as fs21 from "fs";
2861
+ import * as path21 from "path";
2862
+ import JSON54 from "json5";
2863
+ function detectRuntimeOs(projectDir, productName) {
2864
+ let products = [];
2865
+ try {
2866
+ const parsed = JSON54.parse(fs21.readFileSync(path21.join(projectDir, "build-profile.json5"), "utf-8"));
2867
+ if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
2868
+ } catch {
2869
+ return "OpenHarmony";
2870
+ }
2871
+ const product = products.find((p) => p.name === productName) ?? products[0];
2872
+ return product?.runtimeOS === "HarmonyOS" ? "HarmonyOS" : "OpenHarmony";
2873
+ }
2874
+ var HARMONYOS_SDK_VERSIONS = /* @__PURE__ */ new Map([
2875
+ [12, "5.0.0"],
2876
+ [13, "5.0.1"],
2877
+ [14, "5.0.2"],
2878
+ [15, "5.0.3"],
2879
+ [16, "5.0.4"],
2880
+ [17, "5.0.5"],
2881
+ [18, "5.1.0"],
2882
+ [19, "5.1.1"],
2883
+ [20, "6.0.0"],
2884
+ [21, "6.0.1"],
2885
+ [24, "6.1.1"]
2886
+ ]);
2887
+ function harmonyOsSdkLabelForApi(api) {
2888
+ const version = HARMONYOS_SDK_VERSIONS.get(api);
2889
+ return version ? `${version}(${api})` : void 0;
2890
+ }
2891
+
2892
+ // src/build/runHvigorw.ts
2745
2893
  function buildHvigorwArgs(opts) {
2746
2894
  const args = [opts.task ?? "assembleHap", "--mode", "module"];
2747
2895
  args.push("-p", `product=${opts.product ?? "default"}`);
@@ -2756,28 +2904,36 @@ function runHvigorw(opts) {
2756
2904
  const { config } = opts;
2757
2905
  const projectDir = toLongPath(opts.projectDir);
2758
2906
  const logger = opts.logger ?? noopLogger;
2759
- if (!fs20.existsSync(path20.join(projectDir, "build-profile.json5"))) {
2907
+ if (!fs22.existsSync(path22.join(projectDir, "build-profile.json5"))) {
2760
2908
  throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
2761
2909
  }
2762
- if (!fs20.existsSync(getCmdToolsPath(config))) {
2763
- throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
2910
+ let hvigorw;
2911
+ let sdkEnv;
2912
+ if (detectRuntimeOs(projectDir, opts.product) === "HarmonyOS") {
2913
+ const sdk = requireHarmonyOsSdk({ config, logger });
2914
+ sdkEnv = { DEVECO_SDK_HOME: sdk.sdkPath };
2915
+ const resolved = getHvigorwPath(config, projectDir);
2916
+ hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
2917
+ logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
2918
+ } else {
2919
+ if (!fs22.existsSync(getCmdToolsPath(config))) {
2920
+ throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
2921
+ }
2922
+ sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
2923
+ hvigorw = getHvigorwPath(config, projectDir);
2764
2924
  }
2765
- const hvigorw = getHvigorwPath(config, projectDir);
2766
- if (!fs20.existsSync(hvigorw)) {
2925
+ if (!fs22.existsSync(hvigorw)) {
2767
2926
  throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
2768
2927
  }
2769
2928
  const args = buildHvigorwArgs(opts);
2770
2929
  if (process.platform !== "win32") {
2771
2930
  try {
2772
- fs20.chmodSync(hvigorw, 493);
2931
+ fs22.chmodSync(hvigorw, 493);
2773
2932
  } catch {
2774
2933
  }
2775
2934
  }
2776
- const env = {
2777
- ...process.env,
2778
- OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
2779
- };
2780
- return new Promise((resolve4, reject) => {
2935
+ const env = { ...process.env, ...sdkEnv };
2936
+ return new Promise((resolve6, reject) => {
2781
2937
  if (opts.abortSignal?.aborted) {
2782
2938
  reject(new CancelledError("hvigorw build cancelled."));
2783
2939
  return;
@@ -2821,15 +2977,15 @@ function runHvigorw(opts) {
2821
2977
  settled = true;
2822
2978
  cleanup();
2823
2979
  const exitCode = code ?? 1;
2824
- if (exitCode === 0) resolve4({ exitCode });
2980
+ if (exitCode === 0) resolve6({ exitCode });
2825
2981
  else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
2826
2982
  });
2827
2983
  });
2828
2984
  }
2829
2985
 
2830
2986
  // src/build/discoverHaps.ts
2831
- import * as fs21 from "fs";
2832
- import * as path21 from "path";
2987
+ import * as fs23 from "fs";
2988
+ import * as path23 from "path";
2833
2989
  function moduleOfHap(hapFileName) {
2834
2990
  const dash = hapFileName.indexOf("-");
2835
2991
  return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
@@ -2840,9 +2996,9 @@ async function discoverHaps(opts) {
2840
2996
  const result = {};
2841
2997
  const walk = async (dir, depth) => {
2842
2998
  if (depth > maxDepth) return;
2843
- const entries = await fs21.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2999
+ const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
2844
3000
  for (const e of entries) {
2845
- const full = path21.join(dir, e.name);
3001
+ const full = path23.join(dir, e.name);
2846
3002
  if (e.isDirectory()) {
2847
3003
  if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
2848
3004
  await walk(full, depth + 1);
@@ -2859,11 +3015,11 @@ async function discoverHaps(opts) {
2859
3015
  }
2860
3016
 
2861
3017
  // src/build/ohpm.ts
2862
- import * as fs22 from "fs";
2863
- import * as path22 from "path";
3018
+ import * as fs24 from "fs";
3019
+ import * as path24 from "path";
2864
3020
  function runOhpm(opts) {
2865
3021
  return runProcess({
2866
- command: getOhpmPath(opts.config),
3022
+ command: opts.ohpmPath ?? getOhpmPath(opts.config),
2867
3023
  args: [...opts.args],
2868
3024
  cwd: opts.projectDir,
2869
3025
  timeoutMs: opts.timeoutMs ?? 6e5,
@@ -2873,12 +3029,13 @@ function runOhpm(opts) {
2873
3029
  });
2874
3030
  }
2875
3031
  async function ensureOhModules(opts) {
2876
- if (fs22.existsSync(path22.join(opts.projectDir, "oh_modules"))) {
3032
+ if (fs24.existsSync(path24.join(opts.projectDir, "oh_modules"))) {
2877
3033
  return { installed: false };
2878
3034
  }
2879
3035
  const res = await runOhpm({
2880
3036
  config: opts.config,
2881
3037
  projectDir: opts.projectDir,
3038
+ ohpmPath: opts.ohpmPath,
2882
3039
  args: ["install", "--all"],
2883
3040
  abortSignal: opts.abortSignal,
2884
3041
  onOutput: opts.onOutput,
@@ -2926,10 +3083,13 @@ async function buildHap(opts) {
2926
3083
  const warnings = [];
2927
3084
  const start = Date.now();
2928
3085
  const projectDir = toLongPath(opts.projectDir);
3086
+ const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
2929
3087
  if (opts.autoInstallDeps !== false) {
3088
+ const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
2930
3089
  const r = await ensureOhModules({
2931
3090
  config: opts.config,
2932
3091
  projectDir,
3092
+ ohpmPath: (harmonySdk && findHarmonyOsTool(harmonySdk, "ohpm")) ?? void 0,
2933
3093
  abortSignal: opts.abortSignal,
2934
3094
  onOutput: opts.onOutput,
2935
3095
  logger: opts.logger
@@ -2952,7 +3112,7 @@ async function buildHap(opts) {
2952
3112
  const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
2953
3113
  if (!anySigned) {
2954
3114
  warnings.push(
2955
- "No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
3115
+ isHarmonyOs ? "No signed HAPs found \u2014 run `oniro-app sign --harmonyos` to have AppGallery Connect issue a certificate and profile." : "No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
2956
3116
  );
2957
3117
  }
2958
3118
  return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
@@ -2969,21 +3129,21 @@ function isValidBundleName(bundleName) {
2969
3129
  }
2970
3130
 
2971
3131
  // src/project/templates.ts
2972
- import * as fs23 from "fs";
2973
- import * as path23 from "path";
3132
+ import * as fs25 from "fs";
3133
+ import * as path25 from "path";
2974
3134
  function toHumanTemplateName(folderName) {
2975
3135
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
2976
3136
  }
2977
3137
  function listTemplates(templateRoot) {
2978
- if (!fs23.existsSync(templateRoot)) return [];
2979
- const entries = fs23.readdirSync(templateRoot, { withFileTypes: true });
3138
+ if (!fs25.existsSync(templateRoot)) return [];
3139
+ const entries = fs25.readdirSync(templateRoot, { withFileTypes: true });
2980
3140
  return entries.filter((e) => e.isDirectory()).map((e) => {
2981
- const dir = path23.join(templateRoot, e.name);
2982
- const metaPath = path23.join(dir, "template.json");
3141
+ const dir = path25.join(templateRoot, e.name);
3142
+ const metaPath = path25.join(dir, "template.json");
2983
3143
  let meta = {};
2984
- if (fs23.existsSync(metaPath)) {
3144
+ if (fs25.existsSync(metaPath)) {
2985
3145
  try {
2986
- meta = JSON.parse(fs23.readFileSync(metaPath, "utf8"));
3146
+ meta = JSON.parse(fs25.readFileSync(metaPath, "utf8"));
2987
3147
  } catch {
2988
3148
  }
2989
3149
  }
@@ -2998,57 +3158,57 @@ function listTemplates(templateRoot) {
2998
3158
  function validateTemplateLayout(templateDir, defaultModuleName) {
2999
3159
  const required = [
3000
3160
  "build-profile.json5",
3001
- path23.join("AppScope", "app.json5"),
3002
- path23.join(defaultModuleName, "src", "main", "module.json5"),
3003
- path23.join(defaultModuleName, "oh-package.json5"),
3161
+ path25.join("AppScope", "app.json5"),
3162
+ path25.join(defaultModuleName, "src", "main", "module.json5"),
3163
+ path25.join(defaultModuleName, "oh-package.json5"),
3004
3164
  "hvigorfile.ts"
3005
3165
  ];
3006
- return required.filter((rel) => !fs23.existsSync(path23.join(templateDir, rel)));
3166
+ return required.filter((rel) => !fs25.existsSync(path25.join(templateDir, rel)));
3007
3167
  }
3008
3168
 
3009
3169
  // src/project/createScaffold.ts
3010
- import * as fs25 from "fs";
3011
- import * as path24 from "path";
3170
+ import * as fs27 from "fs";
3171
+ import * as path26 from "path";
3012
3172
 
3013
3173
  // src/project/jsonHelpers.ts
3014
- import * as fs24 from "fs";
3015
- import JSON54 from "json5";
3174
+ import * as fs26 from "fs";
3175
+ import JSON55 from "json5";
3016
3176
  function readJson5File(filePath) {
3017
- return JSON54.parse(fs24.readFileSync(filePath, "utf8"));
3177
+ return JSON55.parse(fs26.readFileSync(filePath, "utf8"));
3018
3178
  }
3019
3179
  function writeJson5File(filePath, value) {
3020
- fs24.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3180
+ fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3021
3181
  }
3022
3182
  function readJsonFile(filePath) {
3023
- return JSON.parse(fs24.readFileSync(filePath, "utf8"));
3183
+ return JSON.parse(fs26.readFileSync(filePath, "utf8"));
3024
3184
  }
3025
3185
  function writeJsonFile(filePath, value) {
3026
- fs24.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3186
+ fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
3027
3187
  }
3028
3188
 
3029
3189
  // src/project/createScaffold.ts
3030
3190
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
3031
3191
  async function pathExists(p) {
3032
3192
  try {
3033
- await fs25.promises.access(p);
3193
+ await fs27.promises.access(p);
3034
3194
  return true;
3035
3195
  } catch {
3036
3196
  return false;
3037
3197
  }
3038
3198
  }
3039
3199
  async function copyDirRecursive(srcDir, destDir) {
3040
- await fs25.promises.mkdir(destDir, { recursive: true });
3041
- const entries = await fs25.promises.readdir(srcDir, { withFileTypes: true });
3200
+ await fs27.promises.mkdir(destDir, { recursive: true });
3201
+ const entries = await fs27.promises.readdir(srcDir, { withFileTypes: true });
3042
3202
  for (const entry of entries) {
3043
- const src = path24.join(srcDir, entry.name);
3044
- const dest = path24.join(destDir, entry.name);
3203
+ const src = path26.join(srcDir, entry.name);
3204
+ const dest = path26.join(destDir, entry.name);
3045
3205
  if (entry.isDirectory()) {
3046
3206
  await copyDirRecursive(src, dest);
3047
3207
  } else if (entry.isSymbolicLink()) {
3048
3208
  continue;
3049
3209
  } else if (entry.isFile()) {
3050
3210
  if (entry.name === "template.json") continue;
3051
- await fs25.promises.copyFile(src, dest);
3211
+ await fs27.promises.copyFile(src, dest);
3052
3212
  }
3053
3213
  }
3054
3214
  }
@@ -3056,9 +3216,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
3056
3216
  const stack = [projectDir];
3057
3217
  while (stack.length > 0) {
3058
3218
  const currentDir = stack.pop();
3059
- const entries = await fs25.promises.readdir(currentDir, { withFileTypes: true });
3219
+ const entries = await fs27.promises.readdir(currentDir, { withFileTypes: true });
3060
3220
  for (const entry of entries) {
3061
- const fullPath = path24.join(currentDir, entry.name);
3221
+ const fullPath = path26.join(currentDir, entry.name);
3062
3222
  if (entry.isDirectory()) {
3063
3223
  if (IGNORED_DIRS.has(entry.name)) continue;
3064
3224
  stack.push(fullPath);
@@ -3080,23 +3240,23 @@ function toJavaPropertiesPath(p) {
3080
3240
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
3081
3241
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
3082
3242
  `;
3083
- fs25.writeFileSync(path24.join(projectDir, "local.properties"), content, "utf8");
3243
+ fs27.writeFileSync(path26.join(projectDir, "local.properties"), content, "utf8");
3084
3244
  }
3085
3245
  function renameIfExists(fromPath, toPath) {
3086
- if (fs25.existsSync(fromPath) && fromPath !== toPath) {
3087
- fs25.renameSync(fromPath, toPath);
3246
+ if (fs27.existsSync(fromPath) && fromPath !== toPath) {
3247
+ fs27.renameSync(fromPath, toPath);
3088
3248
  }
3089
3249
  }
3090
3250
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3091
- const appJsonPath = path24.join(projectDir, "AppScope", "app.json5");
3092
- if (fs25.existsSync(appJsonPath)) {
3251
+ const appJsonPath = path26.join(projectDir, "AppScope", "app.json5");
3252
+ if (fs27.existsSync(appJsonPath)) {
3093
3253
  const appJson = readJson5File(appJsonPath);
3094
3254
  appJson.app = appJson.app ?? {};
3095
3255
  appJson.app.bundleName = args.bundleName;
3096
3256
  writeJson5File(appJsonPath, appJson);
3097
3257
  }
3098
- const stringsPath = path24.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3099
- if (fs25.existsSync(stringsPath)) {
3258
+ const stringsPath = path26.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
3259
+ if (fs27.existsSync(stringsPath)) {
3100
3260
  const strings = readJsonFile(stringsPath);
3101
3261
  if (Array.isArray(strings.string)) {
3102
3262
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -3104,13 +3264,13 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3104
3264
  }
3105
3265
  writeJsonFile(stringsPath, strings);
3106
3266
  }
3107
- const buildProfilePath = path24.join(projectDir, "build-profile.json5");
3108
- if (fs25.existsSync(buildProfilePath)) {
3267
+ const buildProfilePath = path26.join(projectDir, "build-profile.json5");
3268
+ if (fs27.existsSync(buildProfilePath)) {
3109
3269
  const buildProfile = readJson5File(buildProfilePath);
3110
3270
  buildProfile.app = buildProfile.app ?? {};
3111
3271
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
3112
- buildProfile.app.products[0].compileSdkVersion = args.sdkApi;
3113
- buildProfile.app.products[0].compatibleSdkVersion = args.sdkApi;
3272
+ buildProfile.app.products[0].compileSdkVersion = args.sdkVersion;
3273
+ buildProfile.app.products[0].compatibleSdkVersion = args.sdkVersion;
3114
3274
  }
3115
3275
  if (Array.isArray(buildProfile.modules) && buildProfile.modules.length > 0) {
3116
3276
  buildProfile.modules[0].name = args.moduleName;
@@ -3118,23 +3278,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
3118
3278
  }
3119
3279
  writeJson5File(buildProfilePath, buildProfile);
3120
3280
  }
3121
- const moduleJsonPath = path24.join(projectDir, args.moduleName, "src", "main", "module.json5");
3122
- if (fs25.existsSync(moduleJsonPath)) {
3281
+ const moduleJsonPath = path26.join(projectDir, args.moduleName, "src", "main", "module.json5");
3282
+ if (fs27.existsSync(moduleJsonPath)) {
3123
3283
  const moduleJson = readJson5File(moduleJsonPath);
3124
3284
  moduleJson.module = moduleJson.module ?? {};
3125
3285
  moduleJson.module.name = args.moduleName;
3126
3286
  writeJson5File(moduleJsonPath, moduleJson);
3127
3287
  }
3128
- const moduleOhPackagePath = path24.join(projectDir, args.moduleName, "oh-package.json5");
3129
- if (fs25.existsSync(moduleOhPackagePath)) {
3288
+ const moduleOhPackagePath = path26.join(projectDir, args.moduleName, "oh-package.json5");
3289
+ if (fs27.existsSync(moduleOhPackagePath)) {
3130
3290
  const pkg = readJson5File(moduleOhPackagePath);
3131
3291
  pkg.name = args.moduleName;
3132
3292
  writeJson5File(moduleOhPackagePath, pkg);
3133
3293
  }
3134
- const vscodeDir = path24.join(projectDir, ".vscode");
3135
- fs25.mkdirSync(vscodeDir, { recursive: true });
3294
+ const vscodeDir = path26.join(projectDir, ".vscode");
3295
+ fs27.mkdirSync(vscodeDir, { recursive: true });
3136
3296
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
3137
- writeJsonFile(path24.join(vscodeDir, "settings.json"), {
3297
+ writeJsonFile(path26.join(vscodeDir, "settings.json"), {
3138
3298
  "oniro.hapPath": hapPath,
3139
3299
  "files.associations": { "*.json5": "jsonc" }
3140
3300
  });
@@ -3151,7 +3311,7 @@ async function createScaffold(opts) {
3151
3311
  if (!opts.location || !await pathExists(opts.location)) {
3152
3312
  throw new OniroError(`Location does not exist: ${opts.location}`);
3153
3313
  }
3154
- const templateDir = path24.join(opts.templateRoot, opts.templateId);
3314
+ const templateDir = path26.join(opts.templateRoot, opts.templateId);
3155
3315
  if (!await pathExists(templateDir)) {
3156
3316
  throw new OniroError(`Template not found: ${templateDir}`);
3157
3317
  }
@@ -3164,38 +3324,60 @@ async function createScaffold(opts) {
3164
3324
  throw new OniroError(`Template '${opts.templateId}' is missing required files:
3165
3325
  - ${missing.join("\n- ")}`);
3166
3326
  }
3167
- const projectDir = path24.join(opts.location, opts.projectName);
3327
+ let sdkVersion = opts.sdkApi;
3328
+ let sdkBaseDir = getOhosBaseSdkHome(opts.config);
3329
+ if (detectRuntimeOs(templateDir) === "HarmonyOS") {
3330
+ const harmonySdk = findHarmonyOsSdk({ config: opts.config, logger });
3331
+ const label = harmonySdk?.version?.endsWith(`(${opts.sdkApi})`) ? harmonySdk.version : harmonyOsSdkLabelForApi(opts.sdkApi);
3332
+ if (!label) {
3333
+ throw new OniroError(
3334
+ `No HarmonyOS release is known for API ${opts.sdkApi}. Known: ${[...HARMONYOS_SDK_VERSIONS.keys()].join(", ")}` + (harmonySdk?.version ? `, and the installed SDK's ${harmonySdk.version}.` : ".")
3335
+ );
3336
+ }
3337
+ sdkVersion = label;
3338
+ if (!harmonySdk) {
3339
+ logger.warn("[create] No HarmonyOS SDK found; set ONIRO_HARMONYOS_SDK_PATH before building.");
3340
+ } else {
3341
+ sdkBaseDir = harmonySdk.sdkPath;
3342
+ if (harmonySdk.version && harmonySdk.version !== label) {
3343
+ logger.warn(
3344
+ `[create] The HarmonyOS SDK at ${harmonySdk.sdkPath} is ${harmonySdk.version}, so a project declaring ${label} will not build against it. Install ${label}, or create with the installed API level.`
3345
+ );
3346
+ }
3347
+ }
3348
+ }
3349
+ const projectDir = path26.join(opts.location, opts.projectName);
3168
3350
  if (await pathExists(projectDir)) {
3169
3351
  if (!opts.overwrite) {
3170
3352
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
3171
3353
  }
3172
- await fs25.promises.rm(projectDir, { recursive: true, force: true });
3354
+ await fs27.promises.rm(projectDir, { recursive: true, force: true });
3173
3355
  }
3174
3356
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
3175
3357
  await copyDirRecursive(templateDir, projectDir);
3176
3358
  if (moduleName !== defaultModuleName) {
3177
- renameIfExists(path24.join(projectDir, defaultModuleName), path24.join(projectDir, moduleName));
3359
+ renameIfExists(path26.join(projectDir, defaultModuleName), path26.join(projectDir, moduleName));
3178
3360
  }
3179
3361
  updateTemplateConfigs(
3180
3362
  projectDir,
3181
3363
  {
3182
3364
  projectName: opts.projectName,
3183
3365
  bundleName: opts.bundleName,
3184
- sdkApi: opts.sdkApi,
3366
+ sdkVersion,
3185
3367
  moduleName
3186
3368
  },
3187
- getOhosBaseSdkHome(opts.config)
3369
+ sdkBaseDir
3188
3370
  );
3189
3371
  await normalizeJson5ToJson(projectDir, logger);
3190
3372
  return { projectDir };
3191
3373
  }
3192
3374
 
3193
3375
  // src/project/listModules.ts
3194
- import * as fs26 from "fs";
3195
- import * as path25 from "path";
3376
+ import * as fs28 from "fs";
3377
+ import * as path27 from "path";
3196
3378
  function listModules(opts) {
3197
- const buildProfile = path25.join(opts.projectDir, "build-profile.json5");
3198
- if (!fs26.existsSync(buildProfile)) {
3379
+ const buildProfile = path27.join(opts.projectDir, "build-profile.json5");
3380
+ if (!fs28.existsSync(buildProfile)) {
3199
3381
  throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
3200
3382
  }
3201
3383
  const parsed = readJson5File(buildProfile);
@@ -3207,12 +3389,12 @@ function listModules(opts) {
3207
3389
  }
3208
3390
 
3209
3391
  // src/project/abilities.ts
3210
- import * as fs27 from "fs";
3211
- import * as path26 from "path";
3392
+ import * as fs29 from "fs";
3393
+ import * as path28 from "path";
3212
3394
  function listAbilities(opts) {
3213
3395
  const moduleName = opts.moduleName ?? "entry";
3214
- const moduleJsonPath = path26.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3215
- if (!fs27.existsSync(moduleJsonPath)) {
3396
+ const moduleJsonPath = path28.join(opts.projectDir, moduleName, "src", "main", "module.json5");
3397
+ if (!fs29.existsSync(moduleJsonPath)) {
3216
3398
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
3217
3399
  }
3218
3400
  const parsed = readJson5File(moduleJsonPath);
@@ -3220,12 +3402,12 @@ function listAbilities(opts) {
3220
3402
  }
3221
3403
 
3222
3404
  // src/project/prepareSigning.ts
3223
- import * as fs28 from "fs";
3224
- import * as path27 from "path";
3405
+ import * as fs30 from "fs";
3406
+ import * as path29 from "path";
3225
3407
  function hasSigningArtifacts(dir) {
3226
- if (!fs28.existsSync(path27.join(dir, "signatures"))) return false;
3227
- const buildProfile = path27.join(dir, "build-profile.json5");
3228
- if (!fs28.existsSync(buildProfile)) return false;
3408
+ if (!fs30.existsSync(path29.join(dir, "signatures"))) return false;
3409
+ const buildProfile = path29.join(dir, "build-profile.json5");
3410
+ if (!fs30.existsSync(buildProfile)) return false;
3229
3411
  try {
3230
3412
  const parsed = readJson5File(buildProfile);
3231
3413
  return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
@@ -3315,8 +3497,8 @@ async function diffHapAssets(opts) {
3315
3497
  }
3316
3498
 
3317
3499
  // src/install/apply.ts
3318
- import * as fs29 from "fs";
3319
- import * as path28 from "path";
3500
+ import * as fs31 from "fs";
3501
+ import * as path30 from "path";
3320
3502
  function decideInstallMethod(s) {
3321
3503
  if (!s.signInfoInconsistent) return "replace";
3322
3504
  if (s.isSystemBundle && !s.allowUninstall) return "refuse";
@@ -3337,7 +3519,7 @@ async function pidOf(config, bundle, deviceSerial) {
3337
3519
  async function resolveHapPath(opts) {
3338
3520
  let hapPath = opts.hapPath;
3339
3521
  if (hapPath) {
3340
- if (!path28.isAbsolute(hapPath) && opts.projectDir) hapPath = path28.join(opts.projectDir, hapPath);
3522
+ if (!path30.isAbsolute(hapPath) && opts.projectDir) hapPath = path30.join(opts.projectDir, hapPath);
3341
3523
  } else {
3342
3524
  if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
3343
3525
  const haps = await discoverHaps({ projectDir: opts.projectDir });
@@ -3356,7 +3538,7 @@ async function resolveHapPath(opts) {
3356
3538
  hapPath = withSigned[0][1].signed[0];
3357
3539
  }
3358
3540
  }
3359
- if (!fs29.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3541
+ if (!fs31.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
3360
3542
  return hapPath;
3361
3543
  }
3362
3544
  async function applyChanges(opts) {
@@ -3366,7 +3548,7 @@ async function applyChanges(opts) {
3366
3548
  const allowUninstall = opts.allowUninstall ?? false;
3367
3549
  const hapPath = await resolveHapPath(opts);
3368
3550
  let assetsChanged = false;
3369
- if (opts.installedHapPath && fs29.existsSync(opts.installedHapPath)) {
3551
+ if (opts.installedHapPath && fs31.existsSync(opts.installedHapPath)) {
3370
3552
  try {
3371
3553
  const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
3372
3554
  assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
@@ -3407,20 +3589,1201 @@ async function applyChanges(opts) {
3407
3589
  return { method, preInstallPid, postInstallPid, replaced: installMethod === "replace", cacheCleared };
3408
3590
  }
3409
3591
 
3592
+ // src/harmonyos/auth/endpoints.ts
3593
+ var HUAWEI_APP_ID = "1009";
3594
+ var HUAWEI_API_VERSION = "1.0.0";
3595
+ var LOGIN_TIMEOUT_MS = 6e5;
3596
+ var HUAWEI_SITES = {
3597
+ CN: {
3598
+ code: "CN",
3599
+ siteId: "1",
3600
+ siteParam: "CN",
3601
+ loginUrl: "https://cn.devecostudio.huawei.com",
3602
+ agcBaseUrl: "https://connect-api.cloud.huawei.com",
3603
+ language: "zh_CN"
3604
+ },
3605
+ SG: {
3606
+ code: "SG",
3607
+ siteId: "5",
3608
+ siteParam: "SG",
3609
+ loginUrl: "https://sg.devecostudio.huawei.com",
3610
+ agcBaseUrl: "https://connect-api-dra.cloud.huawei.com",
3611
+ language: "zh_CN"
3612
+ },
3613
+ EU: {
3614
+ code: "EU",
3615
+ siteId: "7",
3616
+ siteParam: "DE",
3617
+ loginUrl: "https://de.devecostudio.huawei.com",
3618
+ agcBaseUrl: "https://connect-api-dre.cloud.huawei.com",
3619
+ language: "de_DE"
3620
+ },
3621
+ RU: {
3622
+ code: "RU",
3623
+ siteId: "8",
3624
+ siteParam: "RU",
3625
+ loginUrl: "https://ru.devecostudio.huawei.com",
3626
+ agcBaseUrl: "https://connect-api-drru.cloud.huawei.com",
3627
+ language: "ru_RU"
3628
+ }
3629
+ };
3630
+ var AGC_TEAMS_URL = "https://connect-api.cloud.huawei.com/api/ups/user-permission-service/v1/user-team-list";
3631
+ function siteForId(siteId) {
3632
+ return Object.values(HUAWEI_SITES).find((s) => s.siteId === siteId) ?? null;
3633
+ }
3634
+ var AUTH_PATHS = {
3635
+ /** The browser authenticates here, then redirects to the loopback server. */
3636
+ AUTH_APPLY: "console/DevEcoIDE/apply",
3637
+ /** Exchanges the browser's temp token for a JWT. */
3638
+ TEMP_TOKEN_CHECK: "authrouter/auth/api/temptoken/check",
3639
+ /** Validates a JWT and, with `refresh`, mints an AGC access token. */
3640
+ JWT_TOKEN_CHECK: "authrouter/auth/api/jwToken/check",
3641
+ LOGIN_SUCCESS: "console/DevEcoCLI/loginSuccess",
3642
+ LOGIN_FAILED: "console/DevEcoCLI/loginFailed",
3643
+ LOGOUT: "authrouter/auth/api/logout",
3644
+ /**
3645
+ * Whether an account without real-name verification accepted the developer
3646
+ * agreement, which is what lets it sign. From DevEco Studio (`space.agr.url`).
3647
+ */
3648
+ UNREALNAME_AGREEMENT: "authrouter/unrealname/agreement"
3649
+ };
3650
+
3651
+ // src/harmonyos/auth/tokenStore.ts
3652
+ import * as fs32 from "fs";
3653
+ import * as os11 from "os";
3654
+ import * as path31 from "path";
3655
+ import * as crypto3 from "crypto";
3656
+ var ALGORITHM = "aes-256-gcm";
3657
+ var KEY_LENGTH = 32;
3658
+ var IV_LENGTH = 12;
3659
+ var TOKEN_FILE = "token.enc";
3660
+ var KEY_FILE = "kek.bin";
3661
+ var DIR_MODE = 448;
3662
+ var FILE_MODE = 384;
3663
+ function encrypt2(key, plaintext) {
3664
+ const iv = crypto3.randomBytes(IV_LENGTH);
3665
+ const cipher = crypto3.createCipheriv(ALGORITHM, key, iv);
3666
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
3667
+ return {
3668
+ version: 1,
3669
+ algorithm: ALGORITHM,
3670
+ ciphertext: ciphertext.toString("base64"),
3671
+ iv: iv.toString("base64"),
3672
+ authTag: cipher.getAuthTag().toString("base64")
3673
+ };
3674
+ }
3675
+ function decrypt2(key, blob) {
3676
+ const decipher = crypto3.createDecipheriv(ALGORITHM, key, Buffer.from(blob.iv, "base64"));
3677
+ decipher.setAuthTag(Buffer.from(blob.authTag, "base64"));
3678
+ return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "base64")), decipher.final()]);
3679
+ }
3680
+ function isEncryptedBlob(value) {
3681
+ if (!value || typeof value !== "object") return false;
3682
+ const b = value;
3683
+ return b.algorithm === ALGORITHM && typeof b.ciphertext === "string" && typeof b.iv === "string" && typeof b.authTag === "string";
3684
+ }
3685
+ function permissionHint(dir) {
3686
+ return process.platform === "win32" ? `Permission denied writing ${dir}. Grant write permission to that directory.` : `Permission denied writing ${dir}. Try: chown -R "$(whoami)" ${dir}`;
3687
+ }
3688
+ function ensureDir(dir) {
3689
+ try {
3690
+ fs32.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
3691
+ } catch (err) {
3692
+ if (err.code === "EACCES") {
3693
+ throw new OniroError(permissionHint(dir), err);
3694
+ }
3695
+ throw err;
3696
+ }
3697
+ }
3698
+ function createTokenStore(opts) {
3699
+ const logger = opts.logger ?? noopLogger;
3700
+ const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
3701
+ const tokenPath = path31.join(authDir, TOKEN_FILE);
3702
+ const keyPath = path31.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
3703
+ function loadOrCreateKey() {
3704
+ if (fs32.existsSync(keyPath)) {
3705
+ const existing = fs32.readFileSync(keyPath);
3706
+ if (existing.length === KEY_LENGTH) return existing;
3707
+ logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
3708
+ }
3709
+ ensureDir(path31.dirname(keyPath));
3710
+ const key = crypto3.randomBytes(KEY_LENGTH);
3711
+ fs32.writeFileSync(keyPath, key, { mode: FILE_MODE });
3712
+ return key;
3713
+ }
3714
+ return {
3715
+ tokenPath,
3716
+ save(session) {
3717
+ if (!session.jwtToken) throw new OniroError("Refusing to store an empty token.");
3718
+ const payload = Buffer.from(JSON.stringify(session), "utf8");
3719
+ const blob = encrypt2(loadOrCreateKey(), payload);
3720
+ ensureDir(authDir);
3721
+ fs32.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
3722
+ },
3723
+ load() {
3724
+ if (!fs32.existsSync(tokenPath)) return null;
3725
+ try {
3726
+ const blob = JSON.parse(fs32.readFileSync(tokenPath, "utf8"));
3727
+ if (!isEncryptedBlob(blob)) return null;
3728
+ const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
3729
+ return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
3730
+ } catch (err) {
3731
+ const code = err.code;
3732
+ if (code === "EACCES" || code === "EPERM") {
3733
+ logger.warn(`[harmonyos] Cannot read ${tokenPath}: ${permissionHint(authDir)}`);
3734
+ return null;
3735
+ }
3736
+ logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
3737
+ try {
3738
+ fs32.rmSync(tokenPath, { force: true });
3739
+ } catch {
3740
+ }
3741
+ return null;
3742
+ }
3743
+ },
3744
+ clear() {
3745
+ try {
3746
+ fs32.rmSync(tokenPath, { force: true });
3747
+ } catch (err) {
3748
+ throw new OniroError(`Failed to remove ${tokenPath}.`, err);
3749
+ }
3750
+ }
3751
+ };
3752
+ }
3753
+
3754
+ // src/harmonyos/auth/session.ts
3755
+ import * as crypto5 from "crypto";
3756
+
3757
+ // src/harmonyos/http.ts
3758
+ import { EnvHttpProxyAgent, fetch } from "undici";
3759
+ var DEFAULT_HEADERS = {
3760
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
3761
+ "accept-language": "zh-CN",
3762
+ accept: "application/json, text/plain, */*"
3763
+ };
3764
+ var DEFAULT_TIMEOUT_MS = 2e4;
3765
+ var SENSITIVE_PARAMS = /* @__PURE__ */ new Set(["temptoken", "jwttoken", "code", "oauth2token", "accesstoken"]);
3766
+ function redactUrl(url) {
3767
+ let parsed;
3768
+ try {
3769
+ parsed = new URL(url);
3770
+ } catch {
3771
+ return url;
3772
+ }
3773
+ for (const key of [...parsed.searchParams.keys()]) {
3774
+ if (SENSITIVE_PARAMS.has(key.toLowerCase())) parsed.searchParams.set(key, "***");
3775
+ }
3776
+ return parsed.toString();
3777
+ }
3778
+ function previewBody(body, limit = 300) {
3779
+ const trimmed = body.trim();
3780
+ if (!trimmed) return "(empty body)";
3781
+ if (/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(trimmed)) return "(a token-shaped value)";
3782
+ const collapsed = trimmed.replace(/\s+/g, " ");
3783
+ return collapsed.length > limit ? `${collapsed.slice(0, limit)}\u2026` : collapsed;
3784
+ }
3785
+ function describeResponse(response) {
3786
+ const status = `HTTP ${response.statusCode}${response.statusText ? ` ${response.statusText}` : ""}`;
3787
+ return `${status}: ${previewBody(response.data)}`;
3788
+ }
3789
+ function buildUrl(url, query) {
3790
+ if (!query) return url;
3791
+ const parsed = new URL(url);
3792
+ for (const [key, value] of Object.entries(query)) {
3793
+ if (value !== void 0 && value !== null) parsed.searchParams.set(key, String(value));
3794
+ }
3795
+ return parsed.toString();
3796
+ }
3797
+ function createHttpClient(opts = {}) {
3798
+ const logger = opts.logger ?? noopLogger;
3799
+ const dispatcher = new EnvHttpProxyAgent();
3800
+ async function send(method, url, options) {
3801
+ const target = buildUrl(url, options.query);
3802
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3803
+ const headers = { ...DEFAULT_HEADERS, ...options.headers };
3804
+ let body;
3805
+ if (options.body !== void 0) {
3806
+ body = JSON.stringify(options.body);
3807
+ headers["content-type"] = "application/json";
3808
+ }
3809
+ logger.debug(`[harmonyos] ${method} ${redactUrl(target)}`);
3810
+ try {
3811
+ return await fetch(target, {
3812
+ method,
3813
+ headers,
3814
+ body,
3815
+ signal: AbortSignal.timeout(timeoutMs),
3816
+ dispatcher
3817
+ });
3818
+ } catch (err) {
3819
+ const reason = err instanceof Error && err.name === "TimeoutError" ? `timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : String(err);
3820
+ throw new OniroError(
3821
+ `${method} ${redactUrl(target)} failed: ${reason}. Check the network connection and any proxy configuration (HTTPS_PROXY / HTTP_PROXY).`,
3822
+ err
3823
+ );
3824
+ }
3825
+ }
3826
+ async function request(method, url, options = {}) {
3827
+ const response = await send(method, url, options);
3828
+ return { data: await response.text(), statusCode: response.status, statusText: response.statusText };
3829
+ }
3830
+ return {
3831
+ get: (url, o) => request("GET", url, o),
3832
+ post: (url, o) => request("POST", url, o),
3833
+ delete: (url, o) => request("DELETE", url, o),
3834
+ async getBinary(url, o = {}) {
3835
+ const response = await send("GET", url, o);
3836
+ if (!response.ok) {
3837
+ throw new OniroError(`Download of ${redactUrl(response.url || url)} failed (HTTP ${response.status}).`);
3838
+ }
3839
+ return Buffer.from(await response.arrayBuffer());
3840
+ }
3841
+ };
3842
+ }
3843
+
3844
+ // src/harmonyos/auth/callbackServer.ts
3845
+ import * as http2 from "http";
3846
+ import * as crypto4 from "crypto";
3847
+ var CALLBACK_PATH = "/callback";
3848
+ var MAX_BODY_BYTES = 65536;
3849
+ function codeMatches(code, secret) {
3850
+ const a = Buffer.from(code ?? "", "utf8");
3851
+ const b = Buffer.from(secret, "utf8");
3852
+ return a.length === b.length && crypto4.timingSafeEqual(a, b);
3853
+ }
3854
+ function isQuit(quit) {
3855
+ return quit === "true" || quit === "access_denied" || quit === "quit";
3856
+ }
3857
+ async function startCallbackServer(opts) {
3858
+ let resolveCallback;
3859
+ let rejectCallback;
3860
+ const callback = new Promise((resolve6, reject) => {
3861
+ resolveCallback = resolve6;
3862
+ rejectCallback = reject;
3863
+ });
3864
+ callback.catch(() => {
3865
+ });
3866
+ const server = http2.createServer((req, res) => {
3867
+ const host = (req.headers.host ?? "").toLowerCase();
3868
+ const port2 = server.address()?.port;
3869
+ if (host !== `127.0.0.1:${port2}` && host !== `localhost:${port2}`) {
3870
+ res.writeHead(400);
3871
+ res.end("Bad Host");
3872
+ return;
3873
+ }
3874
+ const url = new URL(req.url ?? "", `http://${host}`);
3875
+ if (url.pathname !== CALLBACK_PATH) {
3876
+ res.writeHead(404);
3877
+ res.end("Not Found");
3878
+ return;
3879
+ }
3880
+ const finish = (params) => {
3881
+ if (!codeMatches(params.get("code"), opts.clientSecret)) {
3882
+ res.writeHead(400);
3883
+ res.end("Bad Request");
3884
+ return;
3885
+ }
3886
+ if (isQuit(params.get("quit"))) {
3887
+ res.writeHead(302, { Location: `${opts.baseUrl}/${opts.failedPath}` });
3888
+ res.end();
3889
+ rejectCallback(new CancelledError("Login was cancelled in the browser."));
3890
+ return;
3891
+ }
3892
+ const tempToken = params.get("tempToken");
3893
+ const siteId = params.get("siteId");
3894
+ if (!tempToken || !siteId) {
3895
+ res.writeHead(400);
3896
+ res.end("Bad Request");
3897
+ return;
3898
+ }
3899
+ res.writeHead(302, { Location: `${opts.baseUrl}/${opts.successPath}` });
3900
+ res.end();
3901
+ resolveCallback({ tempToken, siteId });
3902
+ };
3903
+ if (req.method === "POST") {
3904
+ let body = "";
3905
+ let size = 0;
3906
+ req.on("data", (chunk) => {
3907
+ size += chunk.length;
3908
+ if (size > MAX_BODY_BYTES) {
3909
+ req.destroy(new Error("Request body too large"));
3910
+ return;
3911
+ }
3912
+ body += chunk.toString();
3913
+ });
3914
+ req.on("end", () => finish(body.trim() ? new URLSearchParams(body) : url.searchParams));
3915
+ } else {
3916
+ finish(url.searchParams);
3917
+ }
3918
+ });
3919
+ server.keepAliveTimeout = 1;
3920
+ await new Promise((resolve6, reject) => {
3921
+ server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
3922
+ server.listen(0, "127.0.0.1", resolve6);
3923
+ });
3924
+ const port = server.address().port;
3925
+ return {
3926
+ port,
3927
+ waitForCallback(timeoutMs) {
3928
+ let timer;
3929
+ const timeout = new Promise((_, reject) => {
3930
+ timer = setTimeout(
3931
+ () => reject(new OniroError(`Timed out after ${Math.round(timeoutMs / 1e3)}s waiting for the browser login to complete.`)),
3932
+ timeoutMs
3933
+ );
3934
+ });
3935
+ return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
3936
+ },
3937
+ stop() {
3938
+ return new Promise((resolve6) => {
3939
+ server.closeAllConnections?.();
3940
+ const timer = setTimeout(resolve6, 100);
3941
+ server.close(() => {
3942
+ clearTimeout(timer);
3943
+ resolve6();
3944
+ });
3945
+ });
3946
+ }
3947
+ };
3948
+ }
3949
+
3950
+ // src/harmonyos/auth/browser.ts
3951
+ import { spawn as spawn3 } from "child_process";
3952
+ function isSafeUrl(url) {
3953
+ try {
3954
+ const parsed = new URL(url);
3955
+ return ["http:", "https:"].includes(parsed.protocol) && parsed.hostname !== "" && !url.includes('"');
3956
+ } catch {
3957
+ return false;
3958
+ }
3959
+ }
3960
+ function escapeForCmd(s) {
3961
+ return s.replace(/[&|<>()^%!]/g, (c) => `^${c}`);
3962
+ }
3963
+ function openBrowser(url) {
3964
+ if (!isSafeUrl(url)) {
3965
+ return Promise.reject(new OniroError(`Refusing to open unsafe URL: ${JSON.stringify(url)}`));
3966
+ }
3967
+ const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", '""', escapeForCmd(url)]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
3968
+ const child = spawn3(command, args, {
3969
+ stdio: "ignore",
3970
+ shell: false,
3971
+ detached: true,
3972
+ windowsHide: true
3973
+ });
3974
+ child.unref();
3975
+ return new Promise((resolve6, reject) => {
3976
+ child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
3977
+ child.on(
3978
+ "exit",
3979
+ (code) => code === 0 ? resolve6() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
3980
+ );
3981
+ });
3982
+ }
3983
+
3984
+ // src/harmonyos/auth/session.ts
3985
+ var NOT_SIGNED_IN = "Not signed in. Run `oniro-app auth login` first.";
3986
+ function createHarmonyOsSession(opts) {
3987
+ const logger = opts.logger ?? noopLogger;
3988
+ const http3 = opts.http ?? createHttpClient({ logger });
3989
+ const store = opts.tokenStore ?? createTokenStore({ config: opts.config, logger });
3990
+ async function exchangeTempToken(site, tempToken) {
3991
+ const response = await http3.get(`${site.loginUrl}/${AUTH_PATHS.TEMP_TOKEN_CHECK}`, {
3992
+ query: {
3993
+ // The page sometimes appends extra `&`-joined parameters to the token.
3994
+ tempToken: tempToken.split("&")[0],
3995
+ site: site.siteParam,
3996
+ version: HUAWEI_API_VERSION,
3997
+ appid: HUAWEI_APP_ID
3998
+ }
3999
+ });
4000
+ const jwtToken = response.data.trim();
4001
+ if (response.statusCode === 200 && /^[^.]+\.[^.]+\.[^.]+$/.test(jwtToken)) return jwtToken;
4002
+ throw new OniroError(
4003
+ `${site.loginUrl} would not exchange the login token for a session (${describeResponse(response)}). Login tokens are single-use and short-lived; run \`oniro-app auth login\` again.`
4004
+ );
4005
+ }
4006
+ async function userFromJwt(site, jwtToken, refresh) {
4007
+ const response = await http3.get(`${site.loginUrl}/${AUTH_PATHS.JWT_TOKEN_CHECK}`, {
4008
+ headers: { refresh: String(refresh), jwtToken }
4009
+ });
4010
+ let info;
4011
+ try {
4012
+ if (response.statusCode !== 200) throw new Error();
4013
+ info = JSON.parse(response.data);
4014
+ } catch {
4015
+ throw new OniroError(`Could not validate the session with ${site.loginUrl} (${describeResponse(response)}).`);
4016
+ }
4017
+ const user = info.userInfo;
4018
+ if (!info.status || !user?.accessToken) return null;
4019
+ return {
4020
+ userId: user.userId ?? "",
4021
+ userName: user.name ?? "",
4022
+ accessToken: user.accessToken,
4023
+ countryCode: user.nationalCode ?? "",
4024
+ siteCode: site.code,
4025
+ isRealName: String(user.realName) === "true"
4026
+ };
4027
+ }
4028
+ async function getUserInfo(o = {}) {
4029
+ const stored = store.load();
4030
+ const site = stored && siteForId(stored.siteId);
4031
+ const user = site ? await userFromJwt(site, stored.jwtToken, o.refresh ?? false) : null;
4032
+ if (stored && !user) {
4033
+ logger.debug("[harmonyos] The stored session is no longer valid; clearing it.");
4034
+ store.clear();
4035
+ }
4036
+ return user;
4037
+ }
4038
+ async function requireUser() {
4039
+ const user = await getUserInfo({ refresh: true });
4040
+ if (!user) throw new OniroError(NOT_SIGNED_IN);
4041
+ return user;
4042
+ }
4043
+ async function getDeveloperAgreement(user) {
4044
+ try {
4045
+ const response = await http3.get(`${HUAWEI_SITES[user.siteCode].loginUrl}/${AUTH_PATHS.UNREALNAME_AGREEMENT}`, {
4046
+ headers: { accessToken: user.accessToken }
4047
+ });
4048
+ const payload = JSON.parse(response.data);
4049
+ if (response.statusCode !== 200 || String(payload.success) !== "true" || !payload.body) return null;
4050
+ return { signed: payload.body.signedFlag === true, latest: payload.body.signLastestFlag === true };
4051
+ } catch {
4052
+ return null;
4053
+ }
4054
+ }
4055
+ return {
4056
+ tokenPath: store.tokenPath,
4057
+ getUserInfo,
4058
+ getDeveloperAgreement,
4059
+ async login(o = {}) {
4060
+ const start = HUAWEI_SITES.CN.loginUrl;
4061
+ const clientSecret = crypto5.randomUUID().replace(/-/g, "");
4062
+ const server = await startCallbackServer({
4063
+ clientSecret,
4064
+ baseUrl: start,
4065
+ successPath: AUTH_PATHS.LOGIN_SUCCESS,
4066
+ failedPath: AUTH_PATHS.LOGIN_FAILED
4067
+ });
4068
+ try {
4069
+ const loginUrl = `${start}/${AUTH_PATHS.AUTH_APPLY}?port=${server.port}&appid=${HUAWEI_APP_ID}&code=${clientSecret}`;
4070
+ opts.onLoginUrl?.(loginUrl);
4071
+ openBrowser(loginUrl).catch((err) => {
4072
+ logger.warn(`[harmonyos] Could not open a browser automatically: ${err.message}`);
4073
+ });
4074
+ const callback = await server.waitForCallback(o.timeoutMs ?? LOGIN_TIMEOUT_MS);
4075
+ const site = siteForId(callback.siteId);
4076
+ if (!site) {
4077
+ throw new OniroError(`The account belongs to a Huawei region oniro-app does not know (siteId ${callback.siteId}).`);
4078
+ }
4079
+ const jwtToken = await exchangeTempToken(site, callback.tempToken);
4080
+ const user = await userFromJwt(site, jwtToken, false);
4081
+ if (!user) throw new OniroError("Login did not yield a usable session. Try again.");
4082
+ store.save({ jwtToken, siteId: site.siteId });
4083
+ logger.info(`[harmonyos] Signed in as ${user.userName || user.userId} (${site.code}).`);
4084
+ return user;
4085
+ } finally {
4086
+ await server.stop();
4087
+ }
4088
+ },
4089
+ async logout() {
4090
+ const stored = store.load();
4091
+ if (!stored) return false;
4092
+ const site = siteForId(stored.siteId);
4093
+ try {
4094
+ if (site) {
4095
+ await http3.post(`${site.loginUrl}/${AUTH_PATHS.LOGOUT}`, {
4096
+ query: { jwtToken: stored.jwtToken },
4097
+ timeoutMs: 5e3
4098
+ });
4099
+ }
4100
+ } catch {
4101
+ } finally {
4102
+ store.clear();
4103
+ }
4104
+ return true;
4105
+ },
4106
+ async listTeams() {
4107
+ const user = await requireUser();
4108
+ const response = await http3.get(AGC_TEAMS_URL, {
4109
+ headers: {
4110
+ oauth2Token: user.accessToken,
4111
+ uid: user.userId,
4112
+ source: "cli",
4113
+ lang: HUAWEI_SITES[user.siteCode].language
4114
+ },
4115
+ timeoutMs: 15e3
4116
+ });
4117
+ if (response.statusCode === 401) {
4118
+ throw new OniroError("The session has expired. Run `oniro-app auth login` again.");
4119
+ }
4120
+ if (response.statusCode !== 200) {
4121
+ throw new OniroError(`Could not list AGC teams (${describeResponse(response)}).`);
4122
+ }
4123
+ return parseTeamList(response.data);
4124
+ },
4125
+ async resolveAgcAuth(o = {}) {
4126
+ const user = await requireUser();
4127
+ if (!user.isRealName) {
4128
+ const agreement = await getDeveloperAgreement(user);
4129
+ if (agreement && !(agreement.signed && agreement.latest)) {
4130
+ throw new OniroError(
4131
+ "This Huawei account has not accepted the current HUAWEI Developer Basic Service Agreement, which accounts without real-name verification need before AppGallery Connect issues signing material. Accept it in AppGallery Connect (https://developer.huawei.com/consumer/en/service/josp/agc/index.html) or by running automatic signing once in DevEco Studio, then try again."
4132
+ );
4133
+ }
4134
+ }
4135
+ return {
4136
+ uid: user.userId,
4137
+ // Personal accounts sign under their own user id; `--team-id` picks another team.
4138
+ teamId: o.teamId ?? user.userId,
4139
+ accessToken: user.accessToken,
4140
+ baseUrl: HUAWEI_SITES[user.siteCode].agcBaseUrl
4141
+ };
4142
+ }
4143
+ };
4144
+ }
4145
+ function parseTeamList(body) {
4146
+ let payload;
4147
+ try {
4148
+ payload = JSON.parse(body);
4149
+ } catch {
4150
+ return [];
4151
+ }
4152
+ if (payload.ret && payload.ret.code !== 0) {
4153
+ throw new OniroError(
4154
+ `AppGallery Connect refused the team list (code ${payload.ret.code}${payload.ret.msg ? `: ${payload.ret.msg}` : ""}).`
4155
+ );
4156
+ }
4157
+ if (!Array.isArray(payload.teams)) return [];
4158
+ return payload.teams.map((t) => ({ id: String(t?.id ?? ""), name: String(t?.name ?? "") })).filter((t) => t.id);
4159
+ }
4160
+
4161
+ // src/harmonyos/signing/errors.ts
4162
+ var AgcError = class extends OniroError {
4163
+ constructor(message, statusCode, retCode) {
4164
+ super(message);
4165
+ this.statusCode = statusCode;
4166
+ this.retCode = retCode;
4167
+ this.name = "AgcError";
4168
+ }
4169
+ statusCode;
4170
+ retCode;
4171
+ };
4172
+ var SIGNING_ERRORS = {
4173
+ FORBIDDEN: "This account has no AppGallery Connect permission for the selected team. Ask the team administrator for access, or pass --team-id for a team you do have access to (`oniro-app auth team list`).",
4174
+ UNAUTHORIZED: "AppGallery Connect rejected the access token. Run `oniro-app auth login` again.",
4175
+ NETWORK: "Could not reach AppGallery Connect. Check the network connection and any proxy configuration (HTTPS_PROXY / HTTP_PROXY).",
4176
+ USER_NOT_HARMONY: "This Huawei account is not enrolled for HarmonyOS development. Request HarmonyOS access for it in AppGallery Connect.",
4177
+ CERT_REQUEST: "AppGallery Connect did not issue the debug certificate.",
4178
+ CERT_LIMIT: "This team has reached its debug-certificate limit. Delete an unused certificate in AppGallery Connect and retry.",
4179
+ DEVICE_ADD: "AppGallery Connect did not register the device.",
4180
+ DEVICE_LIMIT: "This team has reached its device limit. Remove an unused device in AppGallery Connect and retry.",
4181
+ DEVICE_NAME_REPEAT: "A device with that name is already registered. Retry.",
4182
+ DEVICE_NONE: "No device is connected and none is registered to this team, but a debug profile must name at least one. Connect a device and retry.",
4183
+ PROFILE_ADD: "AppGallery Connect did not issue the provisioning profile.",
4184
+ PROFILE_NAME_REPEAT: "A provisioning profile with that name already exists in AppGallery Connect. Retry.",
4185
+ PROVISION_LIMIT: "This team has reached its debug-profile limit. Delete unused profiles in AppGallery Connect and retry.",
4186
+ CERT_INVALID: "AppGallery Connect returned an unreadable certificate. Retry with --force.",
4187
+ CERT_EXPIRED: "The signing certificate is outside its validity window. Check the system clock, then retry with --force.",
4188
+ CERT_PROFILE_MISMATCH: "The provisioning profile was not issued for the downloaded certificate. Retry with --force.",
4189
+ KEYSTORE_CERT_MISMATCH: "The local keystore does not hold the key the downloaded certificate was issued for. Retry with --force.",
4190
+ BUNDLE_NAME_INVALID: "Invalid bundle name. It must be 7\u2013128 characters, start with a letter, and have at least three dot-separated segments (for example com.example.app)."
4191
+ };
4192
+ var RET_CODE_ERRORS = {
4193
+ "205389872": SIGNING_ERRORS.CERT_LIMIT,
4194
+ "205389904": SIGNING_ERRORS.USER_NOT_HARMONY,
4195
+ "205389859": SIGNING_ERRORS.DEVICE_LIMIT,
4196
+ "205389857": SIGNING_ERRORS.DEVICE_NAME_REPEAT,
4197
+ "205389938": SIGNING_ERRORS.PROVISION_LIMIT,
4198
+ "205389830": SIGNING_ERRORS.PROFILE_NAME_REPEAT
4199
+ };
4200
+ var PROXY_BLOCKED = "Openproxy_Blocked_URL_list";
4201
+ function readRet(body) {
4202
+ try {
4203
+ let ret = JSON.parse(body).ret;
4204
+ if (typeof ret === "string") ret = JSON.parse(ret);
4205
+ return ret && typeof ret === "object" ? ret : null;
4206
+ } catch {
4207
+ return null;
4208
+ }
4209
+ }
4210
+ var FALLBACK = {
4211
+ cert: SIGNING_ERRORS.CERT_REQUEST,
4212
+ device: SIGNING_ERRORS.DEVICE_ADD,
4213
+ profile: SIGNING_ERRORS.PROFILE_ADD
4214
+ };
4215
+ function mapAgcError(operation, statusCode, reasonPhrase, body) {
4216
+ if (statusCode === 403) {
4217
+ return new AgcError(reasonPhrase === PROXY_BLOCKED ? SIGNING_ERRORS.NETWORK : SIGNING_ERRORS.FORBIDDEN, 403);
4218
+ }
4219
+ if (statusCode === 401) return new AgcError(SIGNING_ERRORS.UNAUTHORIZED, 401);
4220
+ const ret = readRet(body);
4221
+ const retCode = ret?.code === void 0 ? void 0 : String(ret.code);
4222
+ const msg = typeof ret?.msg === "string" && ret.msg.trim() ? ret.msg : void 0;
4223
+ return new AgcError(retCode && RET_CODE_ERRORS[retCode] || msg || FALLBACK[operation], statusCode, retCode);
4224
+ }
4225
+
4226
+ // src/harmonyos/signing/profile.ts
4227
+ import * as fs33 from "fs";
4228
+ import { X509Certificate } from "crypto";
4229
+ import forge from "node-forge";
4230
+ function extractProfileJson(buffer) {
4231
+ const text = buffer.toString("latin1");
4232
+ for (let start = text.indexOf("{"); start !== -1; start = text.indexOf("{", start + 1)) {
4233
+ let depth = 0;
4234
+ let inString = false;
4235
+ let escaped = false;
4236
+ for (let i = start; i < text.length; i++) {
4237
+ const ch = text[i];
4238
+ if (escaped) escaped = false;
4239
+ else if (inString) {
4240
+ if (ch === "\\") escaped = true;
4241
+ else if (ch === '"') inString = false;
4242
+ } else if (ch === '"') inString = true;
4243
+ else if (ch === "{") depth++;
4244
+ else if (ch === "}" && --depth === 0) {
4245
+ const json = Buffer.from(text.slice(start, i + 1), "latin1").toString("utf8");
4246
+ try {
4247
+ if ("bundle-info" in JSON.parse(json)) return json;
4248
+ } catch {
4249
+ }
4250
+ break;
4251
+ }
4252
+ }
4253
+ }
4254
+ return null;
4255
+ }
4256
+ function readProvisionProfile(profilePath) {
4257
+ let raw;
4258
+ try {
4259
+ const json = extractProfileJson(fs33.readFileSync(profilePath));
4260
+ if (!json) return null;
4261
+ raw = JSON.parse(json);
4262
+ } catch {
4263
+ return null;
4264
+ }
4265
+ const str = (v) => typeof v === "string" ? v : void 0;
4266
+ const strings = (v) => Array.isArray(v) ? v.filter((x2) => typeof x2 === "string") : [];
4267
+ const notAfter = raw.validity?.["not-after"];
4268
+ const bundle = raw["bundle-info"] ?? {};
4269
+ return {
4270
+ bundleName: str(bundle["bundle-name"]),
4271
+ developerId: str(bundle["developer-id"]),
4272
+ // Epoch seconds.
4273
+ notAfter: typeof notAfter === "number" ? new Date(notAfter * 1e3) : void 0,
4274
+ deviceIds: strings(raw["debug-info"]?.["device-ids"]).map((d) => d.toUpperCase()),
4275
+ aclPermissions: strings(raw.acls?.["allowed-acls"]),
4276
+ developmentCertificate: str(bundle["development-certificate"])
4277
+ };
4278
+ }
4279
+ function parseCertificate(pem) {
4280
+ try {
4281
+ return new X509Certificate(pem);
4282
+ } catch {
4283
+ throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
4284
+ }
4285
+ }
4286
+ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
4287
+ try {
4288
+ const der = forge.util.createBuffer(fs33.readFileSync(p12Path).toString("binary"));
4289
+ const p12 = forge.pkcs12.pkcs12FromAsn1(forge.asn1.fromDer(der), keyPwd);
4290
+ const certBag = forge.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
4291
+ const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
4292
+ return (p12.getBags({ bagType: certBag })[certBag] ?? []).some((bag) => {
4293
+ if (bag.attributes?.friendlyName?.[0]?.toLowerCase() !== keyAlias.toLowerCase()) return false;
4294
+ const asn1 = bag.cert ? forge.pki.certificateToAsn1(bag.cert) : bag.asn1;
4295
+ if (!asn1) return false;
4296
+ const bagCert = new X509Certificate(Buffer.from(forge.asn1.toDer(asn1).getBytes(), "binary"));
4297
+ return bagCert.publicKey.export({ type: "spki", format: "der" }).equals(wanted);
4298
+ });
4299
+ } catch {
4300
+ return false;
4301
+ }
4302
+ }
4303
+ function verifySigningMaterial(opts) {
4304
+ const pems = fs33.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
4305
+ const chain = (pems ?? []).map(parseCertificate);
4306
+ const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
4307
+ if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
4308
+ const now = opts.now ?? /* @__PURE__ */ new Date();
4309
+ if (chain.some((c) => now < new Date(c.validFrom) || now > new Date(c.validTo))) {
4310
+ throw new AgcError(SIGNING_ERRORS.CERT_EXPIRED);
4311
+ }
4312
+ const issuedFor = readProvisionProfile(opts.profilePath)?.developmentCertificate;
4313
+ if (!issuedFor || parseCertificate(issuedFor).fingerprint256 !== leaf.fingerprint256) {
4314
+ throw new AgcError(SIGNING_ERRORS.CERT_PROFILE_MISMATCH);
4315
+ }
4316
+ if (!keystoreHoldsKey(opts.p12Path, opts.keyAlias, opts.keyPwd, leaf)) {
4317
+ throw new AgcError(SIGNING_ERRORS.KEYSTORE_CERT_MISMATCH);
4318
+ }
4319
+ }
4320
+
4321
+ // src/harmonyos/signing/autoSign.ts
4322
+ import * as crypto9 from "crypto";
4323
+ import * as fs38 from "fs";
4324
+ import * as path37 from "path";
4325
+ import JSON57 from "json5";
4326
+
4327
+ // src/harmonyos/signing/agc.ts
4328
+ import * as crypto6 from "crypto";
4329
+ import * as fs34 from "fs";
4330
+ import * as path32 from "path";
4331
+ var ENDPOINTS = {
4332
+ CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
4333
+ CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
4334
+ CERT_DELETE: "/api/cps/harmony-cert-manage/v1/cert/delete",
4335
+ /** Exchanges an object id for a short-lived signed download URL. */
4336
+ OBJECT_URL: "/api/amis/app-manage/v1/objects/url/reapply",
4337
+ DEVICE_LIST: "/api/cps/device-manage/v1/device/list",
4338
+ DEVICE_ADD: "/api/cps/device-manage/v1/device/add",
4339
+ /** Debug ("test") provisioning profiles — the only kind oniro-app issues. */
4340
+ PROVISION_ADD: "/api/cps/provision-manage/v1/ide/test/provision/add",
4341
+ PROVISION_DELETE: "/api/cps/provision-manage/v1/provision/delete"
4342
+ };
4343
+ var DEVICE_TYPE_CODES = { liteWearable: 1, wearable: 2, tv: 3, phone: 4 };
4344
+ function createAgcClient(http3, auth) {
4345
+ async function call(operation, method, endpoint, opts = {}) {
4346
+ const response = await http3[method](`${auth.baseUrl}${endpoint}`, {
4347
+ ...opts,
4348
+ headers: { uid: auth.uid, teamId: auth.teamId, oauth2Token: auth.accessToken }
4349
+ });
4350
+ if (response.statusCode !== 200 || readRet(response.data)?.code !== 0) {
4351
+ throw mapAgcError(operation, response.statusCode, response.statusText, response.data);
4352
+ }
4353
+ return JSON.parse(response.data);
4354
+ }
4355
+ return {
4356
+ async listCertificates() {
4357
+ return (await call("cert", "post", ENDPOINTS.CERT_LIST)).certList ?? [];
4358
+ },
4359
+ async addCertificate(certName, csr) {
4360
+ await call("cert", "post", ENDPOINTS.CERT_ADD, { body: { csr, certName, certType: "1" } });
4361
+ },
4362
+ async deleteCertificate(certId) {
4363
+ await call("cert", "delete", ENDPOINTS.CERT_DELETE, { body: { certIds: [certId] } });
4364
+ },
4365
+ /** Every device registered to the team, across pages. */
4366
+ async listDevices() {
4367
+ const pageSize = 100;
4368
+ const devices = [];
4369
+ for (let start = 1; ; start++) {
4370
+ const page = await call("device", "get", ENDPOINTS.DEVICE_LIST, {
4371
+ query: { encodeFlag: 0, start, pageSize }
4372
+ });
4373
+ devices.push(...page.list ?? []);
4374
+ if (!page.list?.length || devices.length >= (page.totalCount ?? 0)) return devices;
4375
+ }
4376
+ },
4377
+ async addDevice(udid, deviceKind) {
4378
+ const deviceName = `oniro_app_device_${crypto6.randomBytes(4).toString("hex")}`;
4379
+ await call("device", "post", ENDPOINTS.DEVICE_ADD, {
4380
+ body: { deviceName, udid, deviceType: String(DEVICE_TYPE_CODES[deviceKind] ?? DEVICE_TYPE_CODES.phone) }
4381
+ });
4382
+ },
4383
+ /** Issue a debug profile; returns its id and object handle. */
4384
+ async addDebugProfile(opts) {
4385
+ return call("profile", "post", ENDPOINTS.PROVISION_ADD, {
4386
+ body: {
4387
+ provisionName: opts.provisionName,
4388
+ packageName: opts.bundleName,
4389
+ certList: [opts.certId],
4390
+ deviceList: opts.deviceIds,
4391
+ ...opts.aclPermissions.length ? { aclPermissionList: opts.aclPermissions } : {}
4392
+ }
4393
+ });
4394
+ },
4395
+ async deleteProfile(id) {
4396
+ await call("profile", "delete", ENDPOINTS.PROVISION_DELETE, { query: { id } });
4397
+ },
4398
+ /**
4399
+ * Download a certificate or profile object to `dest`, checking the digest AGC
4400
+ * supplies with the URL. Nothing is written unless it matches.
4401
+ */
4402
+ async download(operation, objectId, dest) {
4403
+ const { urlsInfo } = await call(
4404
+ operation,
4405
+ "post",
4406
+ ENDPOINTS.OBJECT_URL,
4407
+ { body: { sourceUrls: objectId } }
4408
+ );
4409
+ const target = urlsInfo?.[0];
4410
+ if (!target) throw mapAgcError(operation, 200, "", "");
4411
+ const buffer = await http3.getBinary(target.newUrl, { timeoutMs: 6e4 });
4412
+ const actual = crypto6.createHash("sha256").update(buffer).digest("hex");
4413
+ if (target.sha256 && actual !== target.sha256.toLowerCase()) {
4414
+ throw new ChecksumMismatchError(target.sha256, actual);
4415
+ }
4416
+ fs34.mkdirSync(path32.dirname(dest), { recursive: true, mode: 448 });
4417
+ fs34.writeFileSync(dest, buffer, { mode: 384 });
4418
+ }
4419
+ };
4420
+ }
4421
+
4422
+ // src/harmonyos/signing/devices.ts
4423
+ function parseUdid(stdout) {
4424
+ return stdout.match(/\b[A-Fa-f0-9]{64}\b/)?.[0].toUpperCase() ?? "";
4425
+ }
4426
+ function parseDeviceKind(stdout) {
4427
+ return ["liteWearable", "wearable", "tv"].find((kind) => stdout.includes(kind)) ?? "phone";
4428
+ }
4429
+ async function collectLocalDevices(config, logger) {
4430
+ const devices = [];
4431
+ for (const target of await listDevices(config, { logger, timeoutMs: 15e3 })) {
4432
+ if (target.status !== "Connected") continue;
4433
+ const shell2 = async (...args) => (await hdcExec({ config, deviceSerial: target.serial, args: ["shell", ...args], timeoutMs: 15e3, logger })).stdout;
4434
+ try {
4435
+ const udid = parseUdid(await shell2("bm", "get", "-u"));
4436
+ if (!udid) {
4437
+ logger.warn(`[harmonyos] ${target.serial} did not report a UDID; skipping it.`);
4438
+ continue;
4439
+ }
4440
+ devices.push({ serial: target.serial, udid, kind: parseDeviceKind(await shell2("getprop", "hw_sc.build.os.deviceType")) });
4441
+ } catch (err) {
4442
+ logger.warn(`[harmonyos] Could not read ${target.serial}: ${err.message}`);
4443
+ }
4444
+ }
4445
+ return devices;
4446
+ }
4447
+ async function registerDevices(agc, connected, logger) {
4448
+ let devices = await agc.listDevices();
4449
+ const registered = new Set(devices.map((d) => d.udid.toUpperCase()));
4450
+ const unregistered = connected.filter((d) => !registered.has(d.udid));
4451
+ for (const device of unregistered) {
4452
+ logger.info(`[harmonyos] Registering ${device.serial} with AppGallery Connect...`);
4453
+ await agc.addDevice(device.udid, device.kind);
4454
+ }
4455
+ if (unregistered.length > 0) devices = await agc.listDevices();
4456
+ const deviceIds = devices.map((d) => d.id);
4457
+ if (deviceIds.length === 0) throw new AgcError(SIGNING_ERRORS.DEVICE_NONE);
4458
+ if (connected.length === 0) {
4459
+ logger.warn(
4460
+ `[harmonyos] No device is connected; the profile will name the ${deviceIds.length} device(s) already registered to this team.`
4461
+ );
4462
+ }
4463
+ return deviceIds;
4464
+ }
4465
+
4466
+ // src/harmonyos/signing/keystore.ts
4467
+ import * as crypto7 from "crypto";
4468
+ import * as fs36 from "fs";
4469
+ import * as path34 from "path";
4470
+
4471
+ // src/harmonyos/java.ts
4472
+ import * as fs35 from "fs";
4473
+ import * as path33 from "path";
4474
+ var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
4475
+ function findJava(sdk, env = process.env) {
4476
+ const candidates = [
4477
+ env.JAVA_HOME && path33.join(env.JAVA_HOME, "bin", JAVA_BIN),
4478
+ sdk && path33.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
4479
+ // macOS: the JBR is itself a bundle inside Studio's Contents.
4480
+ sdk && path33.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
4481
+ ...(env.PATH ?? "").split(path33.delimiter).map((dir) => dir && path33.join(dir, JAVA_BIN))
4482
+ ];
4483
+ const found = candidates.find((c) => !!c && fs35.existsSync(c));
4484
+ if (found) return found;
4485
+ throw new OniroError(
4486
+ "No Java runtime found. HarmonyOS signing runs the SDK's hap-sign-tool.jar, which needs one: install a JDK and set JAVA_HOME, or install DevEco Studio, whose bundled runtime is used automatically."
4487
+ );
4488
+ }
4489
+
4490
+ // src/harmonyos/signing/keystore.ts
4491
+ var KEY_ALIAS = "debugKey";
4492
+ var SIGN_ALG = "SHA256withECDSA";
4493
+ function redactArgs(args) {
4494
+ return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
4495
+ }
4496
+ async function generateKeystoreAndCsr(opts) {
4497
+ const jar = path34.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
4498
+ if (!fs36.existsSync(jar)) {
4499
+ throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
4500
+ }
4501
+ const java = findJava(opts.sdk);
4502
+ const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
4503
+ fs36.mkdirSync(path34.dirname(opts.p12Path), { recursive: true, mode: 448 });
4504
+ const run = async (what, args) => {
4505
+ const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
4506
+ opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
4507
+ const result = await runProcess({ command: java, args: argv, timeoutMs: 12e4, logger: opts.logger });
4508
+ if (result.code !== 0) {
4509
+ const detail = (result.stderr || result.stdout || "").trim();
4510
+ throw new OniroError(`hap-sign-tool failed to ${what} (exit ${result.code}).${detail ? `
4511
+ ${detail}` : ""}`);
4512
+ }
4513
+ };
4514
+ opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
4515
+ await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
4516
+ await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
4517
+ return { keyPwd, csr: fs36.readFileSync(opts.csrPath, "utf8") };
4518
+ }
4519
+
4520
+ // src/harmonyos/signing/paths.ts
4521
+ import * as crypto8 from "crypto";
4522
+ import * as path35 from "path";
4523
+ function sanitizeNamePart(raw) {
4524
+ const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
4525
+ return safe.slice(0, 64) || "default";
4526
+ }
4527
+ function resolveSigningMaterialPaths(config, productName, projectRoot) {
4528
+ const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
4529
+ const base = path35.join(
4530
+ config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
4531
+ `${sanitizeNamePart(productName)}_${path35.basename(projectRoot)}_${hash}=`
4532
+ );
4533
+ return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
4534
+ }
4535
+
4536
+ // src/harmonyos/signing/project.ts
4537
+ import * as fs37 from "fs";
4538
+ import * as path36 from "path";
4539
+ import JSON56 from "json5";
4540
+
4541
+ // src/harmonyos/signing/aclPermissionList.ts
4542
+ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
4543
+ "ohos.permission.ACCESS_DDK_HID",
4544
+ "ohos.permission.ACCESS_DDK_SCSI_PERIPHERAL",
4545
+ "ohos.permission.ACCESS_DDK_USB",
4546
+ "ohos.permission.ACCESS_DDK_USB_SERIAL",
4547
+ "ohos.permission.ACCESS_DISK_PHY_INFO",
4548
+ "ohos.permission.ACCESS_FIDO2_ONLINEAUTH",
4549
+ "ohos.permission.ACCESS_NET_TRACE_INFO",
4550
+ "ohos.permission.ACCESS_USER_FULL_DISK",
4551
+ "ohos.permission.ACCESS_VIRTUAL_SCREEN",
4552
+ "ohos.permission.CUSTOMIZE_SAVE_BUTTON",
4553
+ "ohos.permission.CUSTOM_SCREEN_RECORDING",
4554
+ "ohos.permission.DLP_GET_HIDE_STATUS",
4555
+ "ohos.permission.FILE_ACCESS_PERSIST",
4556
+ "ohos.permission.GET_ABILITY_INFO",
4557
+ "ohos.permission.GET_ETHERNET_LOCAL_MAC",
4558
+ "ohos.permission.GET_IP_MAC_INFO",
4559
+ "ohos.permission.GET_WIFI_LOCAL_MAC",
4560
+ "ohos.permission.GET_WIFI_PEERS_MAC",
4561
+ "ohos.permission.HOOK_KEY_EVENT",
4562
+ "ohos.permission.INPUT_MONITORING",
4563
+ "ohos.permission.INTERCEPT_INPUT_EVENT",
4564
+ "ohos.permission.KEEP_BACKGROUND_RUNNING_SYSTEM",
4565
+ "ohos.permission.LINKTURBO",
4566
+ "ohos.permission.MANAGE_APN_SETTING",
4567
+ "ohos.permission.MANAGE_PASTEBOARD_APP_SHARE_OPTION",
4568
+ "ohos.permission.MANAGE_SCREEN_TIME_GUARD",
4569
+ "ohos.permission.MANAGE_UDMF_APP_SHARE_OPTION",
4570
+ "ohos.permission.PERSISTENT_BLUETOOTH_PEERS_MAC",
4571
+ "ohos.permission.PERSONAL_MANAGE_RESTRICTIONS",
4572
+ "ohos.permission.PRELOAD_FILE",
4573
+ "ohos.permission.READ_AUDIO",
4574
+ "ohos.permission.READ_CONTACTS",
4575
+ "ohos.permission.READ_IMAGEVIDEO",
4576
+ "ohos.permission.READ_LOCAL_DEVICE_NAME",
4577
+ "ohos.permission.READ_PASTEBOARD",
4578
+ "ohos.permission.READ_WRITE_DESKTOP_DIRECTORY",
4579
+ "ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY",
4580
+ "ohos.permission.READ_WRITE_DOWNLOAD_DIRECTORY",
4581
+ "ohos.permission.READ_WRITE_USB_DEV",
4582
+ "ohos.permission.READ_WRITE_USER_FILE",
4583
+ "ohos.permission.SET_PAC_URL",
4584
+ "ohos.permission.SET_SYSTEMSHARE_APPLAUNCHTRUSTLIST",
4585
+ "ohos.permission.SHORT_TERM_WRITE_IMAGEVIDEO",
4586
+ "ohos.permission.START_PROVISIONING_MESSAGE",
4587
+ "ohos.permission.SUBSCRIBE_NOTIFICATION",
4588
+ "ohos.permission.SYSTEM_FLOAT_WINDOW",
4589
+ "ohos.permission.USE_FLOAT_BALL",
4590
+ "ohos.permission.USE_FRAUD_APP_PICKER",
4591
+ "ohos.permission.USE_FRAUD_CALL_LOG_PICKER",
4592
+ "ohos.permission.USE_FRAUD_MESSAGES_PICKER",
4593
+ "ohos.permission.WEB_NATIVE_MESSAGING",
4594
+ "ohos.permission.WRITE_AUDIO",
4595
+ "ohos.permission.WRITE_CONTACTS",
4596
+ "ohos.permission.WRITE_IMAGEVIDEO",
4597
+ "ohos.permission.atomicService.MANAGE_STORAGE",
4598
+ "ohos.permission.kernel.ALLOW_EXECUTABLE_FORT_MEMORY",
4599
+ "ohos.permission.kernel.ALLOW_WRITABLE_CODE_MEMORY",
4600
+ "ohos.permission.kernel.DISABLE_CODE_MEMORY_PROTECTION",
4601
+ "ohos.permission.kernel.DISABLE_GOTPLT_RO_PROTECTION"
4602
+ ]);
4603
+
4604
+ // src/harmonyos/signing/project.ts
4605
+ function readRequestedPermissions(moduleConfigPath) {
4606
+ if (!fs37.existsSync(moduleConfigPath)) return [];
4607
+ try {
4608
+ const parsed = JSON56.parse(fs37.readFileSync(moduleConfigPath, "utf-8"));
4609
+ return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
4610
+ } catch {
4611
+ return [];
4612
+ }
4613
+ }
4614
+ function collectAclPermissions(projectDir, logger = noopLogger) {
4615
+ const requested = /* @__PURE__ */ new Set();
4616
+ for (const module of listModules({ projectDir })) {
4617
+ const moduleRoot = path36.resolve(projectDir, module.srcPath);
4618
+ for (const sourceSet of ["main", "ohosTest"]) {
4619
+ for (const name of readRequestedPermissions(
4620
+ path36.join(moduleRoot, "src", sourceSet, "module.json5")
4621
+ )) {
4622
+ requested.add(name);
4623
+ }
4624
+ }
4625
+ }
4626
+ const acls = [...requested].filter((p) => ACL_PERMISSIONS.has(p)).sort();
4627
+ if (acls.length > 0) {
4628
+ logger.info(`[harmonyos] Project requests ${acls.length} ACL permission(s): ${acls.join(", ")}`);
4629
+ }
4630
+ return acls;
4631
+ }
4632
+
4633
+ // src/harmonyos/signing/autoSign.ts
4634
+ function shouldRegenerate(input) {
4635
+ if (input.force) return "--force was passed";
4636
+ const missing = Object.values(input.paths).find((file) => !fs38.existsSync(file));
4637
+ if (missing) return `${missing} is missing`;
4638
+ const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
4639
+ if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
4640
+ return "build-profile.json5 has no signingConfig for this material";
4641
+ }
4642
+ const profile = readProvisionProfile(input.paths.profilePath);
4643
+ if (!profile) return "the provisioning profile could not be read";
4644
+ if (profile.bundleName !== input.bundleName) return `the profile is for bundle '${profile.bundleName}'`;
4645
+ if (profile.developerId && profile.developerId !== input.teamId) {
4646
+ return `the profile was issued to team '${profile.developerId}'`;
4647
+ }
4648
+ if (profile.notAfter && profile.notAfter <= (input.now ?? /* @__PURE__ */ new Date())) return "the profile has expired";
4649
+ const unnamed = input.connectedUdids.filter((udid) => !profile.deviceIds.includes(udid.toUpperCase()));
4650
+ if (unnamed.length > 0) return `${unnamed.length} connected device(s) are not named in the profile`;
4651
+ const acls = input.aclPermissions.filter((p) => !profile.aclPermissions.includes(p));
4652
+ if (acls.length > 0) return `the profile lacks ACL permission(s) ${acls.join(", ")}`;
4653
+ return null;
4654
+ }
4655
+ function writeSigningConfig(opts) {
4656
+ const app = opts.buildProfile.app ??= {};
4657
+ const entry = {
4658
+ name: opts.productName,
4659
+ // `type` is what makes hvigor treat this as HarmonyOS signing material.
4660
+ type: "HarmonyOS",
4661
+ material: {
4662
+ certpath: opts.paths.cerPath,
4663
+ keyAlias: KEY_ALIAS,
4664
+ keyPassword: opts.encryptedPassword,
4665
+ profile: opts.paths.profilePath,
4666
+ signAlg: SIGN_ALG,
4667
+ storeFile: opts.paths.p12Path,
4668
+ storePassword: opts.encryptedPassword
4669
+ }
4670
+ };
4671
+ app.signingConfigs = [...(app.signingConfigs ?? []).filter((c) => c.name !== opts.productName), entry];
4672
+ for (const product of app.products ?? []) {
4673
+ if (product.name === opts.productName) product.signingConfig = opts.productName;
4674
+ }
4675
+ fs38.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
4676
+ }
4677
+ async function harmonyOsAutoSign(opts) {
4678
+ const logger = opts.logger ?? noopLogger;
4679
+ const productName = opts.productName ?? "default";
4680
+ const projectDir = path37.resolve(opts.projectDir);
4681
+ const buildProfilePath = path37.join(projectDir, "build-profile.json5");
4682
+ let buildProfile;
4683
+ try {
4684
+ buildProfile = JSON57.parse(fs38.readFileSync(buildProfilePath, "utf-8"));
4685
+ } catch (err) {
4686
+ throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
4687
+ }
4688
+ const product = buildProfile.app?.products?.find((p) => p.name === productName);
4689
+ if (!product) throw new OniroError(`${buildProfilePath} declares no product named '${productName}'.`);
4690
+ const bundleName = product.bundleName || getBundleName(projectDir);
4691
+ if (!isValidBundleName(bundleName) || bundleName.split(".").length < 3 || bundleName.length < 7 || bundleName.length > 128) {
4692
+ throw new AgcError(SIGNING_ERRORS.BUNDLE_NAME_INVALID);
4693
+ }
4694
+ const sdk = requireHarmonyOsSdk({ config: opts.config, logger });
4695
+ const auth = await opts.session.resolveAgcAuth({ teamId: opts.teamId });
4696
+ const aclPermissions = collectAclPermissions(projectDir, logger);
4697
+ const paths = resolveSigningMaterialPaths(opts.config, productName, projectDir);
4698
+ logger.info(`[harmonyos] Signing ${bundleName} (product '${productName}', team ${auth.teamId}).`);
4699
+ const connected = await collectLocalDevices(opts.config, logger);
4700
+ const result = (regenerated) => ({
4701
+ bundleName,
4702
+ teamId: auth.teamId,
4703
+ paths,
4704
+ regenerated,
4705
+ deviceIds: readProvisionProfile(paths.profilePath)?.deviceIds ?? []
4706
+ });
4707
+ const reason = shouldRegenerate({
4708
+ paths,
4709
+ buildProfile,
4710
+ productName,
4711
+ bundleName,
4712
+ teamId: auth.teamId,
4713
+ connectedUdids: connected.map((d) => d.udid),
4714
+ aclPermissions,
4715
+ force: opts.force
4716
+ });
4717
+ if (!reason) {
4718
+ logger.info("[harmonyos] Reusing the existing signing material, which is still valid.");
4719
+ return result(false);
4720
+ }
4721
+ logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
4722
+ const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
4723
+ const deviceIds = await registerDevices(agc, connected, logger);
4724
+ for (const file of Object.values(paths)) fs38.rmSync(file, { force: true });
4725
+ const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
4726
+ const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
4727
+ if (stale) {
4728
+ logger.info(`[harmonyos] Replacing the previous '${certName}' certificate in AppGallery Connect...`);
4729
+ await agc.deleteCertificate(stale.id);
4730
+ }
4731
+ const { keyPwd, csr } = await generateKeystoreAndCsr({ sdk, p12Path: paths.p12Path, csrPath: paths.csrPath, logger });
4732
+ logger.info("[harmonyos] Requesting a debug certificate from AppGallery Connect...");
4733
+ await agc.addCertificate(certName, csr);
4734
+ const cert = (await agc.listCertificates()).find((c) => c.certName === certName);
4735
+ if (!cert) throw new AgcError(SIGNING_ERRORS.CERT_REQUEST);
4736
+ await agc.download("cert", cert.certObjectId, paths.cerPath);
4737
+ logger.info("[harmonyos] Issuing the debug provisioning profile...");
4738
+ const profile = await agc.addDebugProfile({
4739
+ provisionName: `oniro_${crypto9.randomBytes(6).toString("hex")}`,
4740
+ bundleName,
4741
+ certId: cert.id,
4742
+ deviceIds,
4743
+ aclPermissions
4744
+ });
4745
+ try {
4746
+ await agc.download("profile", profile.provisionFileUrl, paths.profilePath);
4747
+ } finally {
4748
+ await agc.deleteProfile(profile.id).catch((err) => {
4749
+ logger.debug(`[harmonyos] Could not delete profile ${profile.id} from AppGallery Connect: ${err.message}`);
4750
+ });
4751
+ }
4752
+ try {
4753
+ verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
4754
+ } catch (err) {
4755
+ fs38.rmSync(paths.profilePath, { force: true });
4756
+ throw err;
4757
+ }
4758
+ const materialDir = path37.join(path37.dirname(paths.p12Path), "material");
4759
+ try {
4760
+ getKey(materialDir);
4761
+ } catch {
4762
+ createMaterial(materialDir);
4763
+ }
4764
+ writeSigningConfig({ buildProfilePath, buildProfile, productName, paths, encryptedPassword: encryptPwd(keyPwd, materialDir) });
4765
+ logger.info(`[harmonyos] Wrote signingConfigs['${productName}'] to ${buildProfilePath}.`);
4766
+ return result(true);
4767
+ }
4768
+
3410
4769
  // src/index.ts
3411
- var VERSION = "0.10.0";
4770
+ var VERSION = "0.12.0";
3412
4771
  export {
3413
4772
  ALL_SDKS,
3414
4773
  APL_VALUES,
3415
4774
  APP_FEATURE_VALUES,
4775
+ AgcError,
3416
4776
  CancelledError,
3417
4777
  ChecksumMismatchError,
3418
4778
  CmdToolsNotInstalledError,
3419
4779
  CommandFailedError,
4780
+ HARMONYOS_SDK_VERSIONS,
4781
+ HUAWEI_SITES,
3420
4782
  INSTALL_TMP_DIRNAME,
3421
4783
  InsufficientSpaceError,
3422
4784
  OHOS_URL_BASE,
3423
4785
  OniroError,
4786
+ SIGNING_ERRORS,
3424
4787
  SdkNotInstalledError,
3425
4788
  TMP_DIR_HINT,
3426
4789
  UnsupportedPlatformError,
@@ -3436,10 +4799,12 @@ export {
3436
4799
  buildRawTouchCommand,
3437
4800
  captureBurst,
3438
4801
  consoleLogger,
4802
+ createHarmonyOsSession,
3439
4803
  createInstallTempDir,
3440
4804
  createMaterial,
3441
4805
  createScaffold,
3442
4806
  createTempWorkDir,
4807
+ createTokenStore,
3443
4808
  decideInstallMethod,
3444
4809
  decideReboot,
3445
4810
  decryptPwd,
@@ -3448,6 +4813,7 @@ export {
3448
4813
  defaultPaths,
3449
4814
  detectAaStartFailure,
3450
4815
  detectProjectSdkVersion,
4816
+ detectRuntimeOs,
3451
4817
  detectSigningConfigNames,
3452
4818
  diffEntryNames,
3453
4819
  diffHapAssets,
@@ -3470,6 +4836,7 @@ export {
3470
4836
  extractZipWithProgress,
3471
4837
  findAppProcessId,
3472
4838
  findCmdToolsSourceDir,
4839
+ findHarmonyOsSdk,
3473
4840
  findRunningProcess,
3474
4841
  forceStop,
3475
4842
  formatBytes,
@@ -3494,6 +4861,8 @@ export {
3494
4861
  getSdkFilename,
3495
4862
  getSdkRootDir,
3496
4863
  getSupportedSdksForUi,
4864
+ harmonyOsAutoSign,
4865
+ harmonyOsSdkLabelForApi,
3497
4866
  hdcExec,
3498
4867
  installApp,
3499
4868
  installCmdTools,
@@ -3528,12 +4897,14 @@ export {
3528
4897
  pruneLayout,
3529
4898
  readJson5File,
3530
4899
  readJsonFile,
4900
+ readProvisionProfile,
3531
4901
  reboot,
3532
4902
  recvFile,
3533
4903
  removeCmdTools,
3534
4904
  removeEmulator,
3535
4905
  removeSdk,
3536
4906
  removeTempWorkDir,
4907
+ requireHarmonyOsSdk,
3537
4908
  resolveTmpRoot,
3538
4909
  runCodelinter,
3539
4910
  runHvigorw,