@oniroproject/core 0.14.1 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -180,6 +180,7 @@ var ALL_SDKS = [
180
180
  { version: "7.0", api: "26", tarballStrip: { linuxWindows: 1, darwin: 3 }, archiveSuffix: "_20260829" }
181
181
  ];
182
182
  var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
183
+ var OHOS_DAILY_BUILDS_URL = "https://dcp.openharmony.cn/workbench/cicd/dailybuild/dailylist";
183
184
 
184
185
  // src/sdk/platform.ts
185
186
  import * as os2 from "os";
@@ -762,8 +763,17 @@ async function extractZipWithProgress(opts) {
762
763
  if (inc > 0) progress.report({ message: "Extracting: 100%", increment: inc });
763
764
  }
764
765
  }
765
- async function extractTarball(tarPath, dest, strip = 0) {
766
- await tar.x({ file: tarPath, cwd: dest, strip });
766
+ async function extractTarball(tarPath, dest, strip = 0, filter) {
767
+ await tar.x({ file: tarPath, cwd: dest, strip, ...filter ? { filter: (p) => filter(p) } : {} });
768
+ }
769
+ async function readZipEntry(zipPath, match) {
770
+ const zip = new StreamZip.async({ file: zipPath });
771
+ try {
772
+ const entry = Object.values(await zip.entries()).find((e) => !e.isDirectory && match(e.name));
773
+ return entry ? await zip.entryData(entry) : void 0;
774
+ } finally {
775
+ await zip.close();
776
+ }
767
777
  }
768
778
 
769
779
  // src/sdk/list.ts
@@ -869,34 +879,7 @@ async function downloadAndInstallSdk(opts) {
869
879
  `Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
870
880
  );
871
881
  }
872
- const zipFiles = fs10.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
873
- const componentsStart = 60;
874
- const componentsBudget = 35;
875
- const n = zipFiles.length;
876
- if (n === 0) {
877
- progress?.report({ message: "No SDK component ZIPs found.", increment: componentsBudget });
878
- } else {
879
- progress?.report({ message: "Extracting SDK components...", increment: 0 });
880
- }
881
- const base = n > 0 ? Math.floor(componentsBudget / n) : 0;
882
- let rem = n > 0 ? componentsBudget % n : 0;
883
- let cursor = componentsStart;
884
- for (const zipFile of zipFiles) {
885
- checkCancelled();
886
- logger.info(`Extracting component ${zipFile}`);
887
- const zipPath = path10.join(osContentPath, zipFile);
888
- const thisBudget = base + (rem > 0 ? 1 : 0);
889
- if (rem > 0) rem--;
890
- await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
891
- cursor += thisBudget;
892
- fs10.unlinkSync(zipPath);
893
- }
894
- progress?.report({ message: "Finalizing installation...", increment: 0 });
895
- if (fs10.existsSync(sdkInstallDir)) {
896
- fs10.rmSync(sdkInstallDir, { recursive: true, force: true });
897
- }
898
- movePath(osContentPath, sdkInstallDir);
899
- progress?.report({ message: "Finalizing installation...", increment: 3 });
882
+ await installComponents({ osContentPath, sdkInstallDir, progress, logger, start: 60, range: 35, checkCancelled });
900
883
  progress?.report({ message: "Cleaning up...", increment: 0 });
901
884
  removeTempWorkDir(tmpDir, tmpRoot);
902
885
  progress?.report({ message: "Cleaning up...", increment: 2 });
@@ -907,6 +890,115 @@ async function downloadAndInstallSdk(opts) {
907
890
  throw wrapped;
908
891
  }
909
892
  }
893
+ async function installComponents(opts) {
894
+ const { osContentPath, sdkInstallDir, progress, logger, checkCancelled } = opts;
895
+ const zipFiles = fs10.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
896
+ const n = zipFiles.length;
897
+ if (n === 0) {
898
+ progress?.report({ message: "No SDK component ZIPs found.", increment: opts.range });
899
+ } else {
900
+ progress?.report({ message: "Extracting SDK components...", increment: 0 });
901
+ }
902
+ const base = n > 0 ? Math.floor(opts.range / n) : 0;
903
+ let rem = n > 0 ? opts.range % n : 0;
904
+ let cursor = opts.start;
905
+ for (const zipFile of zipFiles) {
906
+ checkCancelled();
907
+ logger.info(`Extracting component ${zipFile}`);
908
+ const zipPath = path10.join(osContentPath, zipFile);
909
+ const thisBudget = base + (rem > 0 ? 1 : 0);
910
+ if (rem > 0) rem--;
911
+ await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
912
+ cursor += thisBudget;
913
+ fs10.unlinkSync(zipPath);
914
+ }
915
+ progress?.report({ message: "Finalizing installation...", increment: 0 });
916
+ if (fs10.existsSync(sdkInstallDir)) {
917
+ fs10.rmSync(sdkInstallDir, { recursive: true, force: true });
918
+ }
919
+ movePath(osContentPath, sdkInstallDir);
920
+ progress?.report({ message: "Finalizing installation...", increment: 3 });
921
+ }
922
+ async function installSdkFromArchive(opts) {
923
+ const { config, archivePath, progress, abortSignal } = opts;
924
+ const logger = opts.logger ?? noopLogger;
925
+ if (!fs10.statSync(archivePath, { throwIfNoEntry: false })?.isFile()) {
926
+ throw new OniroError(`SDK archive not found: ${archivePath}`);
927
+ }
928
+ const osFolder = getOsFolder();
929
+ const what = `the SDK install from '${path10.basename(archivePath)}'`;
930
+ const sdkRoot = getSdkRootDir(config);
931
+ const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
932
+ config,
933
+ override: opts.tmpDir,
934
+ installRoot: sdkRoot,
935
+ prefix: "oniro-sdk-",
936
+ logger
937
+ });
938
+ const extractDir = path10.join(tmpDir, "extract");
939
+ fs10.mkdirSync(extractDir);
940
+ const checkCancelled = () => {
941
+ if (abortSignal?.aborted) throw new CancelledError();
942
+ };
943
+ try {
944
+ progress?.report({ message: "Extracting SDK archive (this may take a while)...", increment: 0 });
945
+ await extractTarball(archivePath, extractDir, 0, (p) => p.split("/").includes(osFolder));
946
+ progress?.report({ message: "Extracting SDK archive (this may take a while)...", increment: 40 });
947
+ checkCancelled();
948
+ const osContentPath = findSdkOsDir(extractDir, osFolder);
949
+ if (!osContentPath) {
950
+ throw new OniroError(
951
+ `No '${osFolder}' SDK components (a '${osFolder}' folder of ZIPs) found in ${archivePath}. Is it an OpenHarmony SDK archive for this OS?`
952
+ );
953
+ }
954
+ const { api, version } = await readSdkComponentsMeta(osContentPath);
955
+ const sdkInstallDir = path10.join(sdkRoot, osFolder, api);
956
+ if (fs10.existsSync(sdkInstallDir) && !opts.force) {
957
+ throw new OniroError(
958
+ `An SDK for api ${api} is already installed at ${sdkInstallDir}; pass --force to replace it with this archive, or set ONIRO_SDK_ROOT_DIR to install it in a separate SDK root.`
959
+ );
960
+ }
961
+ fs10.mkdirSync(path10.dirname(sdkInstallDir), { recursive: true });
962
+ await installComponents({ osContentPath, sdkInstallDir, progress, logger, start: 40, range: 55, checkCancelled });
963
+ progress?.report({ message: "Cleaning up...", increment: 0 });
964
+ removeTempWorkDir(tmpDir, tmpRoot);
965
+ progress?.report({ message: "Cleaning up...", increment: 2 });
966
+ return { api, version, dir: sdkInstallDir };
967
+ } catch (err) {
968
+ removeTempWorkDir(tmpDir, tmpRoot);
969
+ throw toSpaceError(err, tmpRoot, what);
970
+ }
971
+ }
972
+ function findSdkOsDir(root, osFolder, maxDepth = 4) {
973
+ let level = [root];
974
+ for (let depth = 0; depth < maxDepth && level.length > 0; depth++) {
975
+ const next = [];
976
+ for (const dir of level) {
977
+ for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
978
+ if (!entry.isDirectory()) continue;
979
+ const p = path10.join(dir, entry.name);
980
+ if (entry.name === osFolder && fs10.readdirSync(p).some((n) => n.endsWith(".zip"))) return p;
981
+ next.push(p);
982
+ }
983
+ }
984
+ level = next;
985
+ }
986
+ return void 0;
987
+ }
988
+ async function readSdkComponentsMeta(dir) {
989
+ const zips = fs10.readdirSync(dir).filter((n) => n.endsWith(".zip")).sort((a, b) => Number(b.startsWith("ets-")) - Number(a.startsWith("ets-")));
990
+ for (const name of zips) {
991
+ const data = await readZipEntry(path10.join(dir, name), (e) => /^[^/]+\/oh-uni-package\.json$/.test(e));
992
+ if (!data) continue;
993
+ try {
994
+ const pkg = JSON.parse(data.toString("utf8"));
995
+ const api = String(pkg.apiVersion ?? "");
996
+ if (/^\d+$/.test(api)) return { api, version: String(pkg.version ?? "") };
997
+ } catch {
998
+ }
999
+ }
1000
+ throw new OniroError("No SDK component in the archive declares a numeric apiVersion (oh-uni-package.json).");
1001
+ }
910
1002
 
911
1003
  // src/cmdTools/install.ts
912
1004
  import * as fs11 from "fs";
@@ -1998,12 +2090,13 @@ async function listDevices(config, opts = {}) {
1998
2090
  });
1999
2091
  return parseDeviceList(res.stdout);
2000
2092
  }
2093
+ function isUsableDevice(device) {
2094
+ return device.status === "Connected" || device.status === "Unknown";
2095
+ }
2001
2096
  async function selectDevice(config, serial, opts = {}) {
2002
2097
  const resolved = resolveDeviceSerial(serial);
2003
2098
  if (resolved) return resolved;
2004
- const usable = (await listDevices(config, opts)).filter(
2005
- (d) => d.status === "Connected" || d.status === "Unknown"
2006
- );
2099
+ const usable = (await listDevices(config, opts)).filter(isUsableDevice);
2007
2100
  if (usable.length === 1) return usable[0].serial;
2008
2101
  if (usable.length === 0) {
2009
2102
  throw new OniroError("No connected device found. Connect a device/emulator or set ONIRO_DEVICE_SERIAL.");
@@ -2194,9 +2287,13 @@ function moduleOfHap(hapFileName) {
2194
2287
  const dash = hapFileName.indexOf("-");
2195
2288
  return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
2196
2289
  }
2290
+ function isAppProjectRoot(dir) {
2291
+ return fs17.existsSync(path18.join(dir, "AppScope", "app.json5"));
2292
+ }
2197
2293
  async function discoverHaps(opts) {
2198
2294
  const { projectDir } = opts;
2199
2295
  const maxDepth = opts.maxDepth ?? 8;
2296
+ const skipNestedProjects = isAppProjectRoot(projectDir);
2200
2297
  const result = {};
2201
2298
  const walk = async (dir, depth) => {
2202
2299
  if (depth > maxDepth) return;
@@ -2205,6 +2302,7 @@ async function discoverHaps(opts) {
2205
2302
  const full = path18.join(dir, e.name);
2206
2303
  if (e.isDirectory()) {
2207
2304
  if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
2305
+ if (skipNestedProjects && isAppProjectRoot(full)) continue;
2208
2306
  await walk(full, depth + 1);
2209
2307
  } else if (e.isFile() && e.name.endsWith(".hap")) {
2210
2308
  const moduleName = moduleOfHap(e.name);
@@ -3396,6 +3494,54 @@ async function resolveProcessId(config, bundle, deviceSerial) {
3396
3494
  const proc = await findRunningProcess({ config, bundle, deviceSerial });
3397
3495
  return proc?.pid;
3398
3496
  }
3497
+ async function readDeviceClock(config, deviceSerial) {
3498
+ try {
3499
+ const res = await shell({ config, command: "date +%Y-%m-%dT%H:%M:%S", deviceSerial, timeoutMs: 1e4 });
3500
+ const m = res.code === 0 ? /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/.exec(res.stdout) : null;
3501
+ if (!m) return null;
3502
+ const [year, month, day, hour, minute, second] = m.slice(1).map(Number);
3503
+ return Date.UTC(year, month - 1, day, hour, minute, second);
3504
+ } catch {
3505
+ return null;
3506
+ }
3507
+ }
3508
+ function hilogTimeMs(time, referenceMs) {
3509
+ const m = /^(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{3})$/.exec(time);
3510
+ if (!m) return null;
3511
+ const [month, day, hour, minute, second, ms] = m.slice(1).map(Number);
3512
+ const year = new Date(referenceMs).getUTCFullYear();
3513
+ let best = null;
3514
+ for (const y of [year - 1, year, year + 1]) {
3515
+ const t = Date.UTC(y, month - 1, day, hour, minute, second, ms);
3516
+ if (best === null || Math.abs(t - referenceMs) < Math.abs(best - referenceMs)) best = t;
3517
+ }
3518
+ return best;
3519
+ }
3520
+ async function recentEntryFilter(opts) {
3521
+ const passAll = { test: () => true, afterReconnect: async () => {
3522
+ } };
3523
+ const lookbackMs = opts.lookbackMs ?? 0;
3524
+ if (lookbackMs === Infinity) return passAll;
3525
+ const startedAt = Date.now();
3526
+ const now = await readDeviceClock(opts.config, opts.deviceSerial);
3527
+ if (now === null) {
3528
+ opts.logger.warn("Could not read the device clock, so lines already in the log buffer can match too.");
3529
+ return passAll;
3530
+ }
3531
+ let since = now - (Date.now() - startedAt) - lookbackMs;
3532
+ return {
3533
+ test: (entry) => {
3534
+ if (since === null) return true;
3535
+ const t = hilogTimeMs(entry.time, now);
3536
+ return t === null || t >= since;
3537
+ },
3538
+ afterReconnect: async () => {
3539
+ if (since === null) return;
3540
+ const later = await readDeviceClock(opts.config, opts.deviceSerial);
3541
+ if (later !== null && later < since) since = null;
3542
+ }
3543
+ };
3544
+ }
3399
3545
  function waitForLog(opts) {
3400
3546
  return new Promise((resolve7, reject) => {
3401
3547
  const deadline = Date.now() + opts.timeoutMs;
@@ -3403,6 +3549,8 @@ function waitForLog(opts) {
3403
3549
  let child = null;
3404
3550
  let reconnectTimer;
3405
3551
  let buf = "";
3552
+ let recent = { test: () => true, afterReconnect: async () => {
3553
+ } };
3406
3554
  const cleanup = () => {
3407
3555
  clearTimeout(timer);
3408
3556
  if (reconnectTimer) clearTimeout(reconnectTimer);
@@ -3443,7 +3591,7 @@ function waitForLog(opts) {
3443
3591
  buf = lines.pop() ?? "";
3444
3592
  for (const line of lines) {
3445
3593
  const entry = parseHilogLine(line);
3446
- if (entry && opts.pattern.test(entryText(entry))) {
3594
+ if (entry && recent.test(entry) && opts.pattern.test(entryText(entry))) {
3447
3595
  finish(entry);
3448
3596
  return;
3449
3597
  }
@@ -3453,13 +3601,21 @@ function waitForLog(opts) {
3453
3601
  if (settled || child !== stream) return;
3454
3602
  child = null;
3455
3603
  if (Date.now() < deadline) {
3456
- reconnectTimer = setTimeout(() => void spawnStream().catch(failWith), 1e3);
3604
+ reconnectTimer = setTimeout(() => void recent.afterReconnect().then(spawnStream).catch(failWith), 1e3);
3457
3605
  }
3458
3606
  };
3459
3607
  stream.once("close", onEnd);
3460
3608
  stream.once("error", onEnd);
3461
3609
  };
3462
- void spawnStream().catch(failWith);
3610
+ void (async () => {
3611
+ recent = await recentEntryFilter({
3612
+ config: opts.config,
3613
+ deviceSerial: opts.deviceSerial,
3614
+ lookbackMs: opts.lookbackMs,
3615
+ logger: opts.logger ?? noopLogger
3616
+ });
3617
+ await spawnStream();
3618
+ })().catch(failWith);
3463
3619
  });
3464
3620
  }
3465
3621
  function watchLog(opts) {
@@ -3468,6 +3624,8 @@ function watchLog(opts) {
3468
3624
  let settled = false;
3469
3625
  let child = null;
3470
3626
  let buf = "";
3627
+ let recent = { test: () => true, afterReconnect: async () => {
3628
+ } };
3471
3629
  const cleanup = () => {
3472
3630
  clearTimeout(timer);
3473
3631
  opts.abortSignal?.removeEventListener("abort", onAbort);
@@ -3491,6 +3649,13 @@ function watchLog(opts) {
3491
3649
  const timer = setTimeout(done, opts.durationMs);
3492
3650
  opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
3493
3651
  void (async () => {
3652
+ recent = await recentEntryFilter({
3653
+ config: opts.config,
3654
+ deviceSerial: opts.deviceSerial,
3655
+ lookbackMs: opts.lookbackMs,
3656
+ logger: opts.logger ?? noopLogger
3657
+ });
3658
+ if (settled) return;
3494
3659
  const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
3495
3660
  if (settled) return;
3496
3661
  const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
@@ -3501,7 +3666,7 @@ function watchLog(opts) {
3501
3666
  buf = lines.pop() ?? "";
3502
3667
  for (const line of lines) {
3503
3668
  const entry = parseHilogLine(line);
3504
- if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
3669
+ if (entry && recent.test(entry) && opts.pattern.test(entryText(entry))) matches.push(entry);
3505
3670
  }
3506
3671
  });
3507
3672
  })().catch((err) => {
@@ -3786,6 +3951,8 @@ async function ensureOhModules(opts) {
3786
3951
  }
3787
3952
 
3788
3953
  // src/build/codelinter.ts
3954
+ import * as fs26 from "fs";
3955
+ import * as path26 from "path";
3789
3956
  var FINDING_RE = /^(.*?):(\d+):(?:(\d+):)?\s*(error|warning|warn|suggestion|info)\b[:\s-]*(.*)$/i;
3790
3957
  var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
3791
3958
  var FILE_HEADER_RE = /^(.+\.\w+)\((\d+)\)$/;
@@ -3872,6 +4039,16 @@ function parseCodelinterReport(output) {
3872
4039
  function parseCodelinterFindings(output) {
3873
4040
  return parseCodelinterReport(output).findings;
3874
4041
  }
4042
+ var PROJECT_ROOT_FILES = [
4043
+ ["build-profile.json5"],
4044
+ ["hvigorfile.ts", "hvigorfile.js"],
4045
+ [path26.join("hvigor", "hvigor-config.json5")]
4046
+ ];
4047
+ function missingProjectRootFiles(projectDir) {
4048
+ return PROJECT_ROOT_FILES.filter((names) => !names.some((f) => fs26.existsSync(path26.join(projectDir, f)))).map(
4049
+ (names) => names[0]
4050
+ );
4051
+ }
3875
4052
  async function runCodelinter(opts) {
3876
4053
  const res = await runProcess({
3877
4054
  command: getCmdToolsBin(opts.config, "codelinter"),
@@ -3889,7 +4066,7 @@ async function runCodelinter(opts) {
3889
4066
  var DIAGNOSIS_OUTPUT_LIMIT = 2e6;
3890
4067
  function diagnoseBuildFailure(output) {
3891
4068
  if (/is not exported from Kit|has no exported member .*@systemapi/i.test(output)) {
3892
- return "The project calls system APIs (`@systemapi`), which the public SDK leaves out. Build against the full OpenHarmony SDK: unpack its components into <root>/<os>/<api> of a separate SDK root and set ONIRO_SDK_ROOT_DIR to that root. Installing such an app also needs privileged signing (`oniro-app sign --apl system_core --acls \u2026 --app-privilege-capabilities \u2026`).";
4069
+ return `The project calls system APIs (\`@systemapi\`), which the public SDK leaves out. Build against the full OpenHarmony SDK: download an \`ohos-sdk-full\` archive from ${OHOS_DAILY_BUILDS_URL} and install it with \`oniro-app sdk install --from-archive <archive> --force\` (this replaces the public SDK of that API level; set ONIRO_SDK_ROOT_DIR to keep the two apart). Installing such an app also needs privileged signing (\`oniro-app sign --apl system_core --acls \u2026 --app-privilege-capabilities \u2026\`).`;
3893
4070
  }
3894
4071
  if (/00308018|SDK path does not exist|SDK license agreement is not accepted/i.test(output)) {
3895
4072
  return "hvigor cannot find the SDK the project compiles against: check `oniro-app sdk list` against compileSdkVersion.";
@@ -3961,21 +4138,21 @@ function isValidBundleName(bundleName) {
3961
4138
  }
3962
4139
 
3963
4140
  // src/project/templates.ts
3964
- import * as fs26 from "fs";
3965
- import * as path26 from "path";
4141
+ import * as fs27 from "fs";
4142
+ import * as path27 from "path";
3966
4143
  function toHumanTemplateName(folderName) {
3967
4144
  return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
3968
4145
  }
3969
4146
  function listTemplates(templateRoot) {
3970
- if (!fs26.existsSync(templateRoot)) return [];
3971
- const entries = fs26.readdirSync(templateRoot, { withFileTypes: true });
4147
+ if (!fs27.existsSync(templateRoot)) return [];
4148
+ const entries = fs27.readdirSync(templateRoot, { withFileTypes: true });
3972
4149
  return entries.filter((e) => e.isDirectory()).map((e) => {
3973
- const dir = path26.join(templateRoot, e.name);
3974
- const metaPath = path26.join(dir, "template.json");
4150
+ const dir = path27.join(templateRoot, e.name);
4151
+ const metaPath = path27.join(dir, "template.json");
3975
4152
  let meta = {};
3976
- if (fs26.existsSync(metaPath)) {
4153
+ if (fs27.existsSync(metaPath)) {
3977
4154
  try {
3978
- meta = JSON.parse(fs26.readFileSync(metaPath, "utf8"));
4155
+ meta = JSON.parse(fs27.readFileSync(metaPath, "utf8"));
3979
4156
  } catch {
3980
4157
  }
3981
4158
  }
@@ -3990,57 +4167,57 @@ function listTemplates(templateRoot) {
3990
4167
  function validateTemplateLayout(templateDir, defaultModuleName) {
3991
4168
  const required = [
3992
4169
  "build-profile.json5",
3993
- path26.join("AppScope", "app.json5"),
3994
- path26.join(defaultModuleName, "src", "main", "module.json5"),
3995
- path26.join(defaultModuleName, "oh-package.json5"),
4170
+ path27.join("AppScope", "app.json5"),
4171
+ path27.join(defaultModuleName, "src", "main", "module.json5"),
4172
+ path27.join(defaultModuleName, "oh-package.json5"),
3996
4173
  "hvigorfile.ts"
3997
4174
  ];
3998
- return required.filter((rel) => !fs26.existsSync(path26.join(templateDir, rel)));
4175
+ return required.filter((rel) => !fs27.existsSync(path27.join(templateDir, rel)));
3999
4176
  }
4000
4177
 
4001
4178
  // src/project/createScaffold.ts
4002
- import * as fs28 from "fs";
4003
- import * as path27 from "path";
4179
+ import * as fs29 from "fs";
4180
+ import * as path28 from "path";
4004
4181
 
4005
4182
  // src/project/jsonHelpers.ts
4006
- import * as fs27 from "fs";
4183
+ import * as fs28 from "fs";
4007
4184
  import JSON55 from "json5";
4008
4185
  function readJson5File(filePath) {
4009
- return JSON55.parse(fs27.readFileSync(filePath, "utf8"));
4186
+ return JSON55.parse(fs28.readFileSync(filePath, "utf8"));
4010
4187
  }
4011
4188
  function writeJson5File(filePath, value) {
4012
- fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4189
+ fs28.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4013
4190
  }
4014
4191
  function readJsonFile(filePath) {
4015
- return JSON.parse(fs27.readFileSync(filePath, "utf8"));
4192
+ return JSON.parse(fs28.readFileSync(filePath, "utf8"));
4016
4193
  }
4017
4194
  function writeJsonFile(filePath, value) {
4018
- fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4195
+ fs28.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
4019
4196
  }
4020
4197
 
4021
4198
  // src/project/createScaffold.ts
4022
4199
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
4023
4200
  async function pathExists(p) {
4024
4201
  try {
4025
- await fs28.promises.access(p);
4202
+ await fs29.promises.access(p);
4026
4203
  return true;
4027
4204
  } catch {
4028
4205
  return false;
4029
4206
  }
4030
4207
  }
4031
4208
  async function copyDirRecursive(srcDir, destDir) {
4032
- await fs28.promises.mkdir(destDir, { recursive: true });
4033
- const entries = await fs28.promises.readdir(srcDir, { withFileTypes: true });
4209
+ await fs29.promises.mkdir(destDir, { recursive: true });
4210
+ const entries = await fs29.promises.readdir(srcDir, { withFileTypes: true });
4034
4211
  for (const entry of entries) {
4035
- const src = path27.join(srcDir, entry.name);
4036
- const dest = path27.join(destDir, entry.name);
4212
+ const src = path28.join(srcDir, entry.name);
4213
+ const dest = path28.join(destDir, entry.name);
4037
4214
  if (entry.isDirectory()) {
4038
4215
  await copyDirRecursive(src, dest);
4039
4216
  } else if (entry.isSymbolicLink()) {
4040
4217
  continue;
4041
4218
  } else if (entry.isFile()) {
4042
4219
  if (entry.name === "template.json") continue;
4043
- await fs28.promises.copyFile(src, dest);
4220
+ await fs29.promises.copyFile(src, dest);
4044
4221
  }
4045
4222
  }
4046
4223
  }
@@ -4048,9 +4225,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
4048
4225
  const stack = [projectDir];
4049
4226
  while (stack.length > 0) {
4050
4227
  const currentDir = stack.pop();
4051
- const entries = await fs28.promises.readdir(currentDir, { withFileTypes: true });
4228
+ const entries = await fs29.promises.readdir(currentDir, { withFileTypes: true });
4052
4229
  for (const entry of entries) {
4053
- const fullPath = path27.join(currentDir, entry.name);
4230
+ const fullPath = path28.join(currentDir, entry.name);
4054
4231
  if (entry.isDirectory()) {
4055
4232
  if (IGNORED_DIRS.has(entry.name)) continue;
4056
4233
  stack.push(fullPath);
@@ -4072,23 +4249,23 @@ function toJavaPropertiesPath(p) {
4072
4249
  function createOrUpdateLocalProperties(projectDir, sdkDir) {
4073
4250
  const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
4074
4251
  `;
4075
- fs28.writeFileSync(path27.join(projectDir, "local.properties"), content, "utf8");
4252
+ fs29.writeFileSync(path28.join(projectDir, "local.properties"), content, "utf8");
4076
4253
  }
4077
4254
  function renameIfExists(fromPath, toPath) {
4078
- if (fs28.existsSync(fromPath) && fromPath !== toPath) {
4079
- fs28.renameSync(fromPath, toPath);
4255
+ if (fs29.existsSync(fromPath) && fromPath !== toPath) {
4256
+ fs29.renameSync(fromPath, toPath);
4080
4257
  }
4081
4258
  }
4082
4259
  function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
4083
- const appJsonPath = path27.join(projectDir, "AppScope", "app.json5");
4084
- if (fs28.existsSync(appJsonPath)) {
4260
+ const appJsonPath = path28.join(projectDir, "AppScope", "app.json5");
4261
+ if (fs29.existsSync(appJsonPath)) {
4085
4262
  const appJson = readJson5File(appJsonPath);
4086
4263
  appJson.app = appJson.app ?? {};
4087
4264
  appJson.app.bundleName = args.bundleName;
4088
4265
  writeJson5File(appJsonPath, appJson);
4089
4266
  }
4090
- const stringsPath = path27.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
4091
- if (fs28.existsSync(stringsPath)) {
4267
+ const stringsPath = path28.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
4268
+ if (fs29.existsSync(stringsPath)) {
4092
4269
  const strings = readJsonFile(stringsPath);
4093
4270
  if (Array.isArray(strings.string)) {
4094
4271
  const appName = strings.string.find((s) => s.name === "app_name");
@@ -4096,8 +4273,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
4096
4273
  }
4097
4274
  writeJsonFile(stringsPath, strings);
4098
4275
  }
4099
- const buildProfilePath = path27.join(projectDir, "build-profile.json5");
4100
- if (fs28.existsSync(buildProfilePath)) {
4276
+ const buildProfilePath = path28.join(projectDir, "build-profile.json5");
4277
+ if (fs29.existsSync(buildProfilePath)) {
4101
4278
  const buildProfile = readJson5File(buildProfilePath);
4102
4279
  buildProfile.app = buildProfile.app ?? {};
4103
4280
  if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
@@ -4110,23 +4287,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
4110
4287
  }
4111
4288
  writeJson5File(buildProfilePath, buildProfile);
4112
4289
  }
4113
- const moduleJsonPath = path27.join(projectDir, args.moduleName, "src", "main", "module.json5");
4114
- if (fs28.existsSync(moduleJsonPath)) {
4290
+ const moduleJsonPath = path28.join(projectDir, args.moduleName, "src", "main", "module.json5");
4291
+ if (fs29.existsSync(moduleJsonPath)) {
4115
4292
  const moduleJson = readJson5File(moduleJsonPath);
4116
4293
  moduleJson.module = moduleJson.module ?? {};
4117
4294
  moduleJson.module.name = args.moduleName;
4118
4295
  writeJson5File(moduleJsonPath, moduleJson);
4119
4296
  }
4120
- const moduleOhPackagePath = path27.join(projectDir, args.moduleName, "oh-package.json5");
4121
- if (fs28.existsSync(moduleOhPackagePath)) {
4297
+ const moduleOhPackagePath = path28.join(projectDir, args.moduleName, "oh-package.json5");
4298
+ if (fs29.existsSync(moduleOhPackagePath)) {
4122
4299
  const pkg = readJson5File(moduleOhPackagePath);
4123
4300
  pkg.name = args.moduleName;
4124
4301
  writeJson5File(moduleOhPackagePath, pkg);
4125
4302
  }
4126
- const vscodeDir = path27.join(projectDir, ".vscode");
4127
- fs28.mkdirSync(vscodeDir, { recursive: true });
4303
+ const vscodeDir = path28.join(projectDir, ".vscode");
4304
+ fs29.mkdirSync(vscodeDir, { recursive: true });
4128
4305
  const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
4129
- writeJsonFile(path27.join(vscodeDir, "settings.json"), {
4306
+ writeJsonFile(path28.join(vscodeDir, "settings.json"), {
4130
4307
  "oniro.hapPath": hapPath,
4131
4308
  "files.associations": { "*.json5": "jsonc" }
4132
4309
  });
@@ -4143,7 +4320,7 @@ async function createScaffold(opts) {
4143
4320
  if (!opts.location || !await pathExists(opts.location)) {
4144
4321
  throw new OniroError(`Location does not exist: ${opts.location}`);
4145
4322
  }
4146
- const templateDir = path27.join(opts.templateRoot, opts.templateId);
4323
+ const templateDir = path28.join(opts.templateRoot, opts.templateId);
4147
4324
  if (!await pathExists(templateDir)) {
4148
4325
  throw new OniroError(`Template not found: ${templateDir}`);
4149
4326
  }
@@ -4178,17 +4355,17 @@ async function createScaffold(opts) {
4178
4355
  }
4179
4356
  }
4180
4357
  }
4181
- const projectDir = path27.join(opts.location, opts.projectName);
4358
+ const projectDir = path28.join(opts.location, opts.projectName);
4182
4359
  if (await pathExists(projectDir)) {
4183
4360
  if (!opts.overwrite) {
4184
4361
  throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
4185
4362
  }
4186
- await fs28.promises.rm(projectDir, { recursive: true, force: true });
4363
+ await fs29.promises.rm(projectDir, { recursive: true, force: true });
4187
4364
  }
4188
4365
  logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
4189
4366
  await copyDirRecursive(templateDir, projectDir);
4190
4367
  if (moduleName !== defaultModuleName) {
4191
- renameIfExists(path27.join(projectDir, defaultModuleName), path27.join(projectDir, moduleName));
4368
+ renameIfExists(path28.join(projectDir, defaultModuleName), path28.join(projectDir, moduleName));
4192
4369
  }
4193
4370
  updateTemplateConfigs(
4194
4371
  projectDir,
@@ -4205,11 +4382,11 @@ async function createScaffold(opts) {
4205
4382
  }
4206
4383
 
4207
4384
  // src/project/listModules.ts
4208
- import * as fs29 from "fs";
4209
- import * as path28 from "path";
4385
+ import * as fs30 from "fs";
4386
+ import * as path29 from "path";
4210
4387
  function listModules(opts) {
4211
- const buildProfile = path28.join(opts.projectDir, "build-profile.json5");
4212
- if (!fs29.existsSync(buildProfile)) {
4388
+ const buildProfile = path29.join(opts.projectDir, "build-profile.json5");
4389
+ if (!fs30.existsSync(buildProfile)) {
4213
4390
  throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
4214
4391
  }
4215
4392
  const parsed = readJson5File(buildProfile);
@@ -4221,12 +4398,12 @@ function listModules(opts) {
4221
4398
  }
4222
4399
 
4223
4400
  // src/project/abilities.ts
4224
- import * as fs30 from "fs";
4225
- import * as path29 from "path";
4401
+ import * as fs31 from "fs";
4402
+ import * as path30 from "path";
4226
4403
  function listAbilities(opts) {
4227
4404
  const moduleName = opts.moduleName ?? "entry";
4228
- const moduleJsonPath = path29.join(opts.projectDir, moduleName, "src", "main", "module.json5");
4229
- if (!fs30.existsSync(moduleJsonPath)) {
4405
+ const moduleJsonPath = path30.join(opts.projectDir, moduleName, "src", "main", "module.json5");
4406
+ if (!fs31.existsSync(moduleJsonPath)) {
4230
4407
  throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
4231
4408
  }
4232
4409
  const parsed = readJson5File(moduleJsonPath);
@@ -4234,12 +4411,12 @@ function listAbilities(opts) {
4234
4411
  }
4235
4412
 
4236
4413
  // src/project/prepareSigning.ts
4237
- import * as fs31 from "fs";
4238
- import * as path30 from "path";
4414
+ import * as fs32 from "fs";
4415
+ import * as path31 from "path";
4239
4416
  function hasSigningArtifacts(dir) {
4240
- if (!fs31.existsSync(path30.join(dir, "signatures"))) return false;
4241
- const buildProfile = path30.join(dir, "build-profile.json5");
4242
- if (!fs31.existsSync(buildProfile)) return false;
4417
+ if (!fs32.existsSync(path31.join(dir, "signatures"))) return false;
4418
+ const buildProfile = path31.join(dir, "build-profile.json5");
4419
+ if (!fs32.existsSync(buildProfile)) return false;
4243
4420
  try {
4244
4421
  const parsed = readJson5File(buildProfile);
4245
4422
  return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
@@ -4342,8 +4519,25 @@ async function diffHapAssets(opts) {
4342
4519
  }
4343
4520
 
4344
4521
  // src/install/apply.ts
4345
- import * as fs32 from "fs";
4346
- import * as path31 from "path";
4522
+ import * as fs33 from "fs";
4523
+ import * as path32 from "path";
4524
+
4525
+ // src/install/hapBundleName.ts
4526
+ function bundleNameFromHapConfig(text) {
4527
+ try {
4528
+ const parsed = JSON.parse(text.replace(/^/, ""));
4529
+ const name = parsed.app?.bundleName;
4530
+ return typeof name === "string" && name ? name : null;
4531
+ } catch {
4532
+ return null;
4533
+ }
4534
+ }
4535
+ async function readHapBundleName(hapPath) {
4536
+ const config = await readZipEntry(hapPath, (n) => n === "module.json") ?? await readZipEntry(hapPath, (n) => n === "config.json");
4537
+ return config ? bundleNameFromHapConfig(config.toString("utf8")) : null;
4538
+ }
4539
+
4540
+ // src/install/apply.ts
4347
4541
  function decideInstallMethod(s) {
4348
4542
  if (!s.signInfoInconsistent) return "replace";
4349
4543
  if (s.isSystemBundle && !s.allowUninstall) return "refuse";
@@ -4364,7 +4558,7 @@ async function pidOf(config, bundle, deviceSerial) {
4364
4558
  async function resolveHap(opts, logger) {
4365
4559
  let hap;
4366
4560
  if (opts.hapPath) {
4367
- const hapPath = !path31.isAbsolute(opts.hapPath) && opts.projectDir ? path31.join(opts.projectDir, opts.hapPath) : opts.hapPath;
4561
+ const hapPath = !path32.isAbsolute(opts.hapPath) && opts.projectDir ? path32.join(opts.projectDir, opts.hapPath) : opts.hapPath;
4368
4562
  hap = existingHapOrUnsigned(hapPath);
4369
4563
  if (!hap) throw new OniroError(`HAP not found at ${hapPath}.`);
4370
4564
  } else {
@@ -4373,7 +4567,7 @@ async function resolveHap(opts, logger) {
4373
4567
  if (!picked) throw new OniroError(`No HAP found${opts.module ? ` for module ${opts.module}` : ""}. Build first.`);
4374
4568
  hap = { path: picked.path, fallback: !picked.signed };
4375
4569
  }
4376
- if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path31.basename(hap.path)}.`);
4570
+ if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path32.basename(hap.path)}.`);
4377
4571
  return hap;
4378
4572
  }
4379
4573
  async function applyChanges(opts) {
@@ -4383,9 +4577,18 @@ async function applyChanges(opts) {
4383
4577
  const allowUninstall = opts.allowUninstall ?? false;
4384
4578
  const hap = await resolveHap(opts, logger);
4385
4579
  const hapPath = hap.path;
4580
+ const hapBundle = await readHapBundleName(hapPath).catch((err) => {
4581
+ logger.debug(`bundle name check skipped: ${err.message}`);
4582
+ return null;
4583
+ });
4584
+ if (hapBundle && hapBundle !== bundle) {
4585
+ throw new OniroError(
4586
+ `${path32.basename(hapPath)} (${hapPath}) is bundle ${hapBundle}, not ${bundle}, so installing it would not update ${bundle}. Choose the HAP explicitly, or name ${hapBundle} as the bundle if it is the app to update.`
4587
+ );
4588
+ }
4386
4589
  logger.info(`installing ${hapPath}`);
4387
4590
  let assetsChanged = false;
4388
- if (opts.installedHapPath && fs32.existsSync(opts.installedHapPath)) {
4591
+ if (opts.installedHapPath && fs33.existsSync(opts.installedHapPath)) {
4389
4592
  try {
4390
4593
  const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
4391
4594
  assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
@@ -4418,7 +4621,7 @@ async function applyChanges(opts) {
4418
4621
  }
4419
4622
  if (!await isBundleInstalled({ config, bundle, deviceSerial })) {
4420
4623
  throw new OniroError(
4421
- `hdc reported ${path31.basename(hapPath)} as installed, but bm dump does not list ${bundle}. Check that the HAP was built from this project (its bundleName must match).`
4624
+ `hdc reported ${path32.basename(hapPath)} as installed, but bm dump does not list ${bundle}. Check that the HAP was built from this project (its bundleName must match).`
4422
4625
  );
4423
4626
  }
4424
4627
  let postInstallPid = await pidOf(config, bundle, deviceSerial);
@@ -4495,9 +4698,9 @@ var AUTH_PATHS = {
4495
4698
  };
4496
4699
 
4497
4700
  // src/harmonyos/auth/tokenStore.ts
4498
- import * as fs33 from "fs";
4701
+ import * as fs34 from "fs";
4499
4702
  import * as os11 from "os";
4500
- import * as path32 from "path";
4703
+ import * as path33 from "path";
4501
4704
  import * as crypto3 from "crypto";
4502
4705
  var ALGORITHM = "aes-256-gcm";
4503
4706
  var KEY_LENGTH = 32;
@@ -4533,7 +4736,7 @@ function permissionHint(dir) {
4533
4736
  }
4534
4737
  function ensureDir(dir) {
4535
4738
  try {
4536
- fs33.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4739
+ fs34.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
4537
4740
  } catch (err) {
4538
4741
  if (err.code === "EACCES") {
4539
4742
  throw new OniroError(permissionHint(dir), err);
@@ -4544,17 +4747,17 @@ function ensureDir(dir) {
4544
4747
  function createTokenStore(opts) {
4545
4748
  const logger = opts.logger ?? noopLogger;
4546
4749
  const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
4547
- const tokenPath = path32.join(authDir, TOKEN_FILE);
4548
- const keyPath = path32.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4750
+ const tokenPath = path33.join(authDir, TOKEN_FILE);
4751
+ const keyPath = path33.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
4549
4752
  function loadOrCreateKey() {
4550
- if (fs33.existsSync(keyPath)) {
4551
- const existing = fs33.readFileSync(keyPath);
4753
+ if (fs34.existsSync(keyPath)) {
4754
+ const existing = fs34.readFileSync(keyPath);
4552
4755
  if (existing.length === KEY_LENGTH) return existing;
4553
4756
  logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
4554
4757
  }
4555
- ensureDir(path32.dirname(keyPath));
4758
+ ensureDir(path33.dirname(keyPath));
4556
4759
  const key = crypto3.randomBytes(KEY_LENGTH);
4557
- fs33.writeFileSync(keyPath, key, { mode: FILE_MODE });
4760
+ fs34.writeFileSync(keyPath, key, { mode: FILE_MODE });
4558
4761
  return key;
4559
4762
  }
4560
4763
  return {
@@ -4564,12 +4767,12 @@ function createTokenStore(opts) {
4564
4767
  const payload = Buffer.from(JSON.stringify(session), "utf8");
4565
4768
  const blob = encrypt2(loadOrCreateKey(), payload);
4566
4769
  ensureDir(authDir);
4567
- fs33.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4770
+ fs34.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
4568
4771
  },
4569
4772
  load() {
4570
- if (!fs33.existsSync(tokenPath)) return null;
4773
+ if (!fs34.existsSync(tokenPath)) return null;
4571
4774
  try {
4572
- const blob = JSON.parse(fs33.readFileSync(tokenPath, "utf8"));
4775
+ const blob = JSON.parse(fs34.readFileSync(tokenPath, "utf8"));
4573
4776
  if (!isEncryptedBlob(blob)) return null;
4574
4777
  const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
4575
4778
  return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
@@ -4581,7 +4784,7 @@ function createTokenStore(opts) {
4581
4784
  }
4582
4785
  logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
4583
4786
  try {
4584
- fs33.rmSync(tokenPath, { force: true });
4787
+ fs34.rmSync(tokenPath, { force: true });
4585
4788
  } catch {
4586
4789
  }
4587
4790
  return null;
@@ -4589,7 +4792,7 @@ function createTokenStore(opts) {
4589
4792
  },
4590
4793
  clear() {
4591
4794
  try {
4592
- fs33.rmSync(tokenPath, { force: true });
4795
+ fs34.rmSync(tokenPath, { force: true });
4593
4796
  } catch (err) {
4594
4797
  throw new OniroError(`Failed to remove ${tokenPath}.`, err);
4595
4798
  }
@@ -5025,7 +5228,7 @@ var SIGNING_ERRORS = {
5025
5228
  DEVICE_ADD: "AppGallery Connect did not register the device.",
5026
5229
  DEVICE_LIMIT: "This team has reached its device limit. Remove an unused device in AppGallery Connect and retry.",
5027
5230
  DEVICE_NAME_REPEAT: "A device with that name is already registered. Retry.",
5028
- 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.",
5231
+ DEVICE_NONE: "This team has no registered device and none could be registered from here, but a debug profile must name at least one. Connect a device that reports a UDID (`hdc shell bm get -u`), or register one in AppGallery Connect, and retry.",
5029
5232
  PROFILE_ADD: "AppGallery Connect did not issue the provisioning profile.",
5030
5233
  PROFILE_NAME_REPEAT: "A provisioning profile with that name already exists in AppGallery Connect. Retry.",
5031
5234
  PROVISION_LIMIT: "This team has reached its debug-profile limit. Delete unused profiles in AppGallery Connect and retry.",
@@ -5070,7 +5273,7 @@ function mapAgcError(operation, statusCode, reasonPhrase, body) {
5070
5273
  }
5071
5274
 
5072
5275
  // src/harmonyos/signing/profile.ts
5073
- import * as fs34 from "fs";
5276
+ import * as fs35 from "fs";
5074
5277
  import { X509Certificate } from "crypto";
5075
5278
  import forge from "node-forge";
5076
5279
  function extractProfileJson(buffer) {
@@ -5102,7 +5305,7 @@ function extractProfileJson(buffer) {
5102
5305
  function readProvisionProfile(profilePath) {
5103
5306
  let raw;
5104
5307
  try {
5105
- const json = extractProfileJson(fs34.readFileSync(profilePath));
5308
+ const json = extractProfileJson(fs35.readFileSync(profilePath));
5106
5309
  if (!json) return null;
5107
5310
  raw = JSON.parse(json);
5108
5311
  } catch {
@@ -5131,7 +5334,7 @@ function parseCertificate(pem) {
5131
5334
  }
5132
5335
  function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
5133
5336
  try {
5134
- const der = forge.util.createBuffer(fs34.readFileSync(p12Path).toString("binary"));
5337
+ const der = forge.util.createBuffer(fs35.readFileSync(p12Path).toString("binary"));
5135
5338
  const p12 = forge.pkcs12.pkcs12FromAsn1(forge.asn1.fromDer(der), keyPwd);
5136
5339
  const certBag = forge.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
5137
5340
  const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
@@ -5147,7 +5350,7 @@ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
5147
5350
  }
5148
5351
  }
5149
5352
  function verifySigningMaterial(opts) {
5150
- const pems = fs34.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
5353
+ const pems = fs35.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
5151
5354
  const chain = (pems ?? []).map(parseCertificate);
5152
5355
  const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
5153
5356
  if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
@@ -5166,14 +5369,14 @@ function verifySigningMaterial(opts) {
5166
5369
 
5167
5370
  // src/harmonyos/signing/autoSign.ts
5168
5371
  import * as crypto9 from "crypto";
5169
- import * as fs39 from "fs";
5170
- import * as path38 from "path";
5372
+ import * as fs40 from "fs";
5373
+ import * as path39 from "path";
5171
5374
  import JSON57 from "json5";
5172
5375
 
5173
5376
  // src/harmonyos/signing/agc.ts
5174
5377
  import * as crypto6 from "crypto";
5175
- import * as fs35 from "fs";
5176
- import * as path33 from "path";
5378
+ import * as fs36 from "fs";
5379
+ import * as path34 from "path";
5177
5380
  var ENDPOINTS = {
5178
5381
  CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
5179
5382
  CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
@@ -5259,8 +5462,8 @@ function createAgcClient(http3, auth) {
5259
5462
  if (target.sha256 && actual !== target.sha256.toLowerCase()) {
5260
5463
  throw new ChecksumMismatchError(target.sha256, actual);
5261
5464
  }
5262
- fs35.mkdirSync(path33.dirname(dest), { recursive: true, mode: 448 });
5263
- fs35.writeFileSync(dest, buffer, { mode: 384 });
5465
+ fs36.mkdirSync(path34.dirname(dest), { recursive: true, mode: 448 });
5466
+ fs36.writeFileSync(dest, buffer, { mode: 384 });
5264
5467
  }
5265
5468
  };
5266
5469
  }
@@ -5274,21 +5477,31 @@ function parseDeviceKind(stdout) {
5274
5477
  }
5275
5478
  async function collectLocalDevices(config, logger) {
5276
5479
  const devices = [];
5480
+ const skipped = [];
5277
5481
  for (const target of await listDevices(config, { logger, timeoutMs: 15e3 })) {
5278
- if (target.status !== "Connected") continue;
5482
+ const skip = (why) => {
5483
+ logger.warn(`[harmonyos] ${target.serial} ${why}`);
5484
+ skipped.push(target.serial);
5485
+ };
5486
+ if (!isUsableDevice(target)) {
5487
+ skip(
5488
+ target.status === "Unauthorized" ? "is unauthorized; accept the debugging prompt on the device to register it." : `is ${target.status.toLowerCase()}; skipping it.`
5489
+ );
5490
+ continue;
5491
+ }
5279
5492
  const shell2 = async (...args) => (await hdcExec({ config, deviceSerial: target.serial, args: ["shell", ...args], timeoutMs: 15e3, logger })).stdout;
5280
5493
  try {
5281
5494
  const udid = parseUdid(await shell2("bm", "get", "-u"));
5282
5495
  if (!udid) {
5283
- logger.warn(`[harmonyos] ${target.serial} did not report a UDID; skipping it.`);
5496
+ skip("did not report a UDID (`bm get -u`), so it cannot be registered.");
5284
5497
  continue;
5285
5498
  }
5286
5499
  devices.push({ serial: target.serial, udid, kind: parseDeviceKind(await shell2("getprop", "hw_sc.build.os.deviceType")) });
5287
5500
  } catch (err) {
5288
- logger.warn(`[harmonyos] Could not read ${target.serial}: ${err.message}`);
5501
+ skip(`could not be read: ${err.message}`);
5289
5502
  }
5290
5503
  }
5291
- return devices;
5504
+ return { devices, skipped };
5292
5505
  }
5293
5506
  async function registerDevices(agc, connected, logger) {
5294
5507
  let devices = await agc.listDevices();
@@ -5301,32 +5514,27 @@ async function registerDevices(agc, connected, logger) {
5301
5514
  if (unregistered.length > 0) devices = await agc.listDevices();
5302
5515
  const deviceIds = devices.map((d) => d.id);
5303
5516
  if (deviceIds.length === 0) throw new AgcError(SIGNING_ERRORS.DEVICE_NONE);
5304
- if (connected.length === 0) {
5305
- logger.warn(
5306
- `[harmonyos] No device is connected; the profile will name the ${deviceIds.length} device(s) already registered to this team.`
5307
- );
5308
- }
5309
5517
  return deviceIds;
5310
5518
  }
5311
5519
 
5312
5520
  // src/harmonyos/signing/keystore.ts
5313
5521
  import * as crypto7 from "crypto";
5314
- import * as fs37 from "fs";
5315
- import * as path35 from "path";
5522
+ import * as fs38 from "fs";
5523
+ import * as path36 from "path";
5316
5524
 
5317
5525
  // src/harmonyos/java.ts
5318
- import * as fs36 from "fs";
5319
- import * as path34 from "path";
5526
+ import * as fs37 from "fs";
5527
+ import * as path35 from "path";
5320
5528
  var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
5321
5529
  function findJava(sdk, env = process.env) {
5322
5530
  const candidates = [
5323
- env.JAVA_HOME && path34.join(env.JAVA_HOME, "bin", JAVA_BIN),
5324
- sdk && path34.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
5531
+ env.JAVA_HOME && path35.join(env.JAVA_HOME, "bin", JAVA_BIN),
5532
+ sdk && path35.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
5325
5533
  // macOS: the JBR is itself a bundle inside Studio's Contents.
5326
- sdk && path34.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
5327
- ...(env.PATH ?? "").split(path34.delimiter).map((dir) => dir && path34.join(dir, JAVA_BIN))
5534
+ sdk && path35.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
5535
+ ...(env.PATH ?? "").split(path35.delimiter).map((dir) => dir && path35.join(dir, JAVA_BIN))
5328
5536
  ];
5329
- const found = candidates.find((c) => !!c && fs36.existsSync(c));
5537
+ const found = candidates.find((c) => !!c && fs37.existsSync(c));
5330
5538
  if (found) return found;
5331
5539
  throw new OniroError(
5332
5540
  "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."
@@ -5340,13 +5548,13 @@ function redactArgs(args) {
5340
5548
  return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
5341
5549
  }
5342
5550
  async function generateKeystoreAndCsr(opts) {
5343
- const jar = path35.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
5344
- if (!fs37.existsSync(jar)) {
5551
+ const jar = path36.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
5552
+ if (!fs38.existsSync(jar)) {
5345
5553
  throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
5346
5554
  }
5347
5555
  const java = findJava(opts.sdk);
5348
5556
  const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
5349
- fs37.mkdirSync(path35.dirname(opts.p12Path), { recursive: true, mode: 448 });
5557
+ fs38.mkdirSync(path36.dirname(opts.p12Path), { recursive: true, mode: 448 });
5350
5558
  const run = async (what, args) => {
5351
5559
  const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
5352
5560
  opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
@@ -5360,28 +5568,28 @@ ${detail}` : ""}`);
5360
5568
  opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
5361
5569
  await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
5362
5570
  await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
5363
- return { keyPwd, csr: fs37.readFileSync(opts.csrPath, "utf8") };
5571
+ return { keyPwd, csr: fs38.readFileSync(opts.csrPath, "utf8") };
5364
5572
  }
5365
5573
 
5366
5574
  // src/harmonyos/signing/paths.ts
5367
5575
  import * as crypto8 from "crypto";
5368
- import * as path36 from "path";
5576
+ import * as path37 from "path";
5369
5577
  function sanitizeNamePart(raw) {
5370
5578
  const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
5371
5579
  return safe.slice(0, 64) || "default";
5372
5580
  }
5373
5581
  function resolveSigningMaterialPaths(config, productName, projectRoot) {
5374
5582
  const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
5375
- const base = path36.join(
5583
+ const base = path37.join(
5376
5584
  config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
5377
- `${sanitizeNamePart(productName)}_${path36.basename(projectRoot)}_${hash}=`
5585
+ `${sanitizeNamePart(productName)}_${path37.basename(projectRoot)}_${hash}=`
5378
5586
  );
5379
5587
  return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
5380
5588
  }
5381
5589
 
5382
5590
  // src/harmonyos/signing/project.ts
5383
- import * as fs38 from "fs";
5384
- import * as path37 from "path";
5591
+ import * as fs39 from "fs";
5592
+ import * as path38 from "path";
5385
5593
  import JSON56 from "json5";
5386
5594
 
5387
5595
  // src/harmonyos/signing/aclPermissionList.ts
@@ -5449,9 +5657,9 @@ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
5449
5657
 
5450
5658
  // src/harmonyos/signing/project.ts
5451
5659
  function readRequestedPermissions(moduleConfigPath) {
5452
- if (!fs38.existsSync(moduleConfigPath)) return [];
5660
+ if (!fs39.existsSync(moduleConfigPath)) return [];
5453
5661
  try {
5454
- const parsed = JSON56.parse(fs38.readFileSync(moduleConfigPath, "utf-8"));
5662
+ const parsed = JSON56.parse(fs39.readFileSync(moduleConfigPath, "utf-8"));
5455
5663
  return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
5456
5664
  } catch {
5457
5665
  return [];
@@ -5460,10 +5668,10 @@ function readRequestedPermissions(moduleConfigPath) {
5460
5668
  function collectAclPermissions(projectDir, logger = noopLogger) {
5461
5669
  const requested = /* @__PURE__ */ new Set();
5462
5670
  for (const module of listModules({ projectDir })) {
5463
- const moduleRoot = path37.resolve(projectDir, module.srcPath);
5671
+ const moduleRoot = path38.resolve(projectDir, module.srcPath);
5464
5672
  for (const sourceSet of ["main", "ohosTest"]) {
5465
5673
  for (const name of readRequestedPermissions(
5466
- path37.join(moduleRoot, "src", sourceSet, "module.json5")
5674
+ path38.join(moduleRoot, "src", sourceSet, "module.json5")
5467
5675
  )) {
5468
5676
  requested.add(name);
5469
5677
  }
@@ -5479,7 +5687,7 @@ function collectAclPermissions(projectDir, logger = noopLogger) {
5479
5687
  // src/harmonyos/signing/autoSign.ts
5480
5688
  function shouldRegenerate(input) {
5481
5689
  if (input.force) return "--force was passed";
5482
- const missing = Object.values(input.paths).find((file) => !fs39.existsSync(file));
5690
+ const missing = Object.values(input.paths).find((file) => !fs40.existsSync(file));
5483
5691
  if (missing) return `${missing} is missing`;
5484
5692
  const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
5485
5693
  if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
@@ -5518,16 +5726,16 @@ function writeSigningConfig(opts) {
5518
5726
  for (const product of app.products ?? []) {
5519
5727
  if (product.name === opts.productName) product.signingConfig = opts.productName;
5520
5728
  }
5521
- fs39.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5729
+ fs40.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
5522
5730
  }
5523
5731
  async function harmonyOsAutoSign(opts) {
5524
5732
  const logger = opts.logger ?? noopLogger;
5525
5733
  const productName = opts.productName ?? "default";
5526
- const projectDir = path38.resolve(opts.projectDir);
5527
- const buildProfilePath = path38.join(projectDir, "build-profile.json5");
5734
+ const projectDir = path39.resolve(opts.projectDir);
5735
+ const buildProfilePath = path39.join(projectDir, "build-profile.json5");
5528
5736
  let buildProfile;
5529
5737
  try {
5530
- buildProfile = JSON57.parse(fs39.readFileSync(buildProfilePath, "utf-8"));
5738
+ buildProfile = JSON57.parse(fs40.readFileSync(buildProfilePath, "utf-8"));
5531
5739
  } catch (err) {
5532
5740
  throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
5533
5741
  }
@@ -5542,7 +5750,7 @@ async function harmonyOsAutoSign(opts) {
5542
5750
  const aclPermissions = collectAclPermissions(projectDir, logger);
5543
5751
  const paths = resolveSigningMaterialPaths(opts.config, productName, projectDir);
5544
5752
  logger.info(`[harmonyos] Signing ${bundleName} (product '${productName}', team ${auth.teamId}).`);
5545
- const connected = await collectLocalDevices(opts.config, logger);
5753
+ const local = await collectLocalDevices(opts.config, logger);
5546
5754
  const result = (regenerated) => ({
5547
5755
  bundleName,
5548
5756
  teamId: auth.teamId,
@@ -5556,7 +5764,7 @@ async function harmonyOsAutoSign(opts) {
5556
5764
  productName,
5557
5765
  bundleName,
5558
5766
  teamId: auth.teamId,
5559
- connectedUdids: connected.map((d) => d.udid),
5767
+ connectedUdids: local.devices.map((d) => d.udid),
5560
5768
  aclPermissions,
5561
5769
  force: opts.force
5562
5770
  });
@@ -5566,8 +5774,12 @@ async function harmonyOsAutoSign(opts) {
5566
5774
  }
5567
5775
  logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
5568
5776
  const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
5569
- const deviceIds = await registerDevices(agc, connected, logger);
5570
- for (const file of Object.values(paths)) fs39.rmSync(file, { force: true });
5777
+ const deviceIds = await registerDevices(agc, local.devices, logger);
5778
+ if (local.devices.length === 0) {
5779
+ const cause = local.skipped.length > 0 ? "No connected device could be registered" : "No device is connected";
5780
+ logger.warn(`[harmonyos] ${cause}; the profile will name the ${deviceIds.length} device(s) already registered to this team.`);
5781
+ }
5782
+ for (const file of Object.values(paths)) fs40.rmSync(file, { force: true });
5571
5783
  const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
5572
5784
  const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
5573
5785
  if (stale) {
@@ -5598,10 +5810,10 @@ async function harmonyOsAutoSign(opts) {
5598
5810
  try {
5599
5811
  verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
5600
5812
  } catch (err) {
5601
- fs39.rmSync(paths.profilePath, { force: true });
5813
+ fs40.rmSync(paths.profilePath, { force: true });
5602
5814
  throw err;
5603
5815
  }
5604
- const materialDir = path38.join(path38.dirname(paths.p12Path), "material");
5816
+ const materialDir = path39.join(path39.dirname(paths.p12Path), "material");
5605
5817
  try {
5606
5818
  getKey(materialDir);
5607
5819
  } catch {
@@ -5613,7 +5825,7 @@ async function harmonyOsAutoSign(opts) {
5613
5825
  }
5614
5826
 
5615
5827
  // src/index.ts
5616
- var VERSION = "0.14.1";
5828
+ var VERSION = "0.15.1";
5617
5829
  export {
5618
5830
  ALL_SDKS,
5619
5831
  APL_VALUES,
@@ -5627,6 +5839,7 @@ export {
5627
5839
  HUAWEI_SITES,
5628
5840
  INSTALL_TMP_DIRNAME,
5629
5841
  InsufficientSpaceError,
5842
+ OHOS_DAILY_BUILDS_URL,
5630
5843
  OHOS_URL_BASE,
5631
5844
  OniroError,
5632
5845
  SIGNING_ERRORS,
@@ -5647,6 +5860,7 @@ export {
5647
5860
  buildHvigorwArgs,
5648
5861
  buildInputCommand,
5649
5862
  buildRawTouchCommand,
5863
+ bundleNameFromHapConfig,
5650
5864
  captureBurst,
5651
5865
  captureLog,
5652
5866
  cleanCodelinterOutput,
@@ -5700,6 +5914,7 @@ export {
5700
5914
  findLayoutNodes,
5701
5915
  findMissingProjectSdk,
5702
5916
  findRunningProcess,
5917
+ findSdkOsDir,
5703
5918
  flattenLayout,
5704
5919
  forceStop,
5705
5920
  formatBytes,
@@ -5733,16 +5948,19 @@ export {
5733
5948
  hdcTargetArgs,
5734
5949
  hdcTargetArgsFor,
5735
5950
  hideKeyboard,
5951
+ hilogTimeMs,
5736
5952
  installApp,
5737
5953
  installCmdTools,
5738
5954
  installEmulator,
5739
5955
  installFailureReason,
5956
+ installSdkFromArchive,
5740
5957
  isBundleInstalled,
5741
5958
  isCmdToolsInstalled,
5742
5959
  isEmulatorInstalled,
5743
5960
  isOutOfSpaceError,
5744
5961
  isRamBackedPath,
5745
5962
  isUnsignedHap,
5963
+ isUsableDevice,
5746
5964
  isValidBundleName,
5747
5965
  isValidProjectName,
5748
5966
  killProcessTree,
@@ -5753,6 +5971,7 @@ export {
5753
5971
  listRunningProcesses,
5754
5972
  listTemplates,
5755
5973
  locateTarget,
5974
+ missingProjectRootFiles,
5756
5975
  needsCmdWrapper,
5757
5976
  nonInteractivePrompter,
5758
5977
  noopLogger,
@@ -5776,9 +5995,11 @@ export {
5776
5995
  pickTarget,
5777
5996
  prepareSigning,
5778
5997
  pruneLayout,
5998
+ readHapBundleName,
5779
5999
  readJson5File,
5780
6000
  readJsonFile,
5781
6001
  readProvisionProfile,
6002
+ readZipEntry,
5782
6003
  reboot,
5783
6004
  recvFile,
5784
6005
  removeCmdTools,