@oniroproject/oniro-app 0.15.0 → 0.16.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/README.md CHANGED
@@ -61,9 +61,20 @@ Install commands are idempotent (they skip when already present); pass `--force`
61
61
  | Command | Description |
62
62
  | --- | --- |
63
63
  | `sdk install <version> [--force] [--tmp-dir <path>]` | Download and install an OpenHarmony SDK (e.g. `6.1`). `--tmp-dir` overrides where download/extract temporaries go (see [Temporary space](#temporary-space)). |
64
+ | `sdk install --from-archive <path> [--force] [--tmp-dir <path>]` | Install an SDK from a local `.tar.gz` instead, such as the [full SDK](#full-sdk). Only the current OS's components are unpacked, and the API level is read from them; an SDK already installed at that level is replaced only with `--force`. |
64
65
  | `sdk list [--json]` | List known SDK versions; an `*` marks installed ones. |
65
66
  | `sdk remove <api>` | Remove an installed SDK by API level (e.g. `18`, `20`). |
66
67
 
68
+ ##### Full SDK
69
+
70
+ `sdk install <version>` downloads the public SDK, which leaves out system APIs (`@systemapi`). The full SDK is not on the release mirror: download an `ohos-sdk-full` archive from the [OpenHarmony daily builds](https://dcp.openharmony.cn/workbench/cicd/dailybuild/dailylist) and install it from the file:
71
+
72
+ ```bash
73
+ oniro-app sdk install --from-archive ~/Downloads/version-Master_Version-OpenHarmony_7.0.0.39-…-ohos-sdk-full_7.0-Release.tar.gz
74
+ ```
75
+
76
+ It lands in `<ONIRO_SDK_ROOT_DIR>/<os>/<api>` like a downloaded release, so projects with that `compileSdkVersion` build against it. Pass `--force` to replace a public SDK of the same API level, or set `ONIRO_SDK_ROOT_DIR` to a separate root to keep both.
77
+
67
78
  #### `cmdtools` — manage the OpenHarmony command-line tools (provides `hvigorw`, `ohpm`, `hdc`, `codelinter`)
68
79
 
69
80
  | Command | Description |
@@ -578,7 +589,7 @@ oniro-app sign --apl system_core --acls ohos.permission.ACCESSIBILITY_EXTENSION_
578
589
  --app-privilege-capabilities AllowAppUsePrivilegeExtension
579
590
  ```
580
591
 
581
- Apps like these usually also call system APIs (`@systemapi`), which the public SDK that `sdk install` downloads leaves out: the build fails with errors such as `'config' is not exported from Kit '@kit.AccessibilityKit'`. Build them against the full OpenHarmony SDK by unpacking its components into `<root>/<os>/<api>` of a separate SDK root and pointing `ONIRO_SDK_ROOT_DIR` at that root.
592
+ Apps like these usually also call system APIs (`@systemapi`), which the public SDK that `sdk install` downloads leaves out: the build fails with errors such as `'config' is not exported from Kit '@kit.AccessibilityKit'`. Build them against the full OpenHarmony SDK, installed with `sdk install --from-archive` (see [Full SDK](#full-sdk)).
582
593
 
583
594
  The `signingConfigs` block written to `build-profile.json5` mirrors the names used by the project's `products[*].signingConfig` (e.g. `applications/standard/systemui` uses `release`, `launcher` uses `default`). If the project has no products section, `default` is used, and a product that names no `signingConfig` is pointed at it.
584
595
 
package/dist/oniro-app.js CHANGED
@@ -2,17 +2,19 @@
2
2
 
3
3
  // src/bin/oniro-app.ts
4
4
  import { Command as Command4 } from "commander";
5
- import { OniroError as OniroError2 } from "@oniroproject/core";
5
+ import { OniroError as OniroError3 } from "@oniroproject/core";
6
6
 
7
7
  // src/commands/sdk.ts
8
8
  import * as fs from "fs";
9
9
  import * as path from "path";
10
10
  import {
11
11
  ALL_SDKS,
12
+ OHOS_DAILY_BUILDS_URL,
12
13
  downloadAndInstallSdk,
13
14
  getOsFolder,
14
15
  getSdkRootDir,
15
16
  getSupportedSdksForUi,
17
+ installSdkFromArchive,
16
18
  removeSdk
17
19
  } from "@oniroproject/core";
18
20
 
@@ -104,13 +106,42 @@ function getRuntime() {
104
106
  }
105
107
 
106
108
  // src/commands/sdk.ts
109
+ var FULL_SDK_TIP = `Tip: the full SDK (with system APIs) is not on the release mirror. Download an ohos-sdk-full archive from the OpenHarmony daily builds, ${OHOS_DAILY_BUILDS_URL}, and install it with \`oniro-app sdk install --from-archive <archive>\`.`;
107
110
  function registerSdkCommand(program) {
108
111
  const sdk = program.command("sdk").description("Manage OpenHarmony SDK installs.");
109
- sdk.command("install <version>").description("Download and install an OpenHarmony SDK. Skips if already installed.").option("--force", "Reinstall even if the SDK is already present.").option(
112
+ sdk.command("install [version]").description(
113
+ "Download and install an OpenHarmony SDK (skips if already installed), or install one from a local archive with --from-archive."
114
+ ).option(
115
+ "--from-archive <path>",
116
+ "Install from a local SDK .tar.gz instead of downloading, e.g. the full SDK. The API level is read from the archive."
117
+ ).option("--force", "Reinstall even if the SDK is already present.").option(
110
118
  "--tmp-dir <path>",
111
119
  "Directory for download/extract temporaries (default: ONIRO_TMP_DIR, else .oniro-tmp next to the install target). Use this when that filesystem is short on space."
112
- ).action(async (version, opts) => {
120
+ ).addHelpText("after", `
121
+ ${FULL_SDK_TIP}`).action(async (version, opts) => {
113
122
  const { config, logger, progress } = getRuntime();
123
+ if (opts.fromArchive) {
124
+ if (version) throw new Error("Pass either a version or --from-archive, not both.");
125
+ const archivePath = path.resolve(opts.fromArchive);
126
+ logger.info(`Installing OpenHarmony SDK from ${archivePath}...`);
127
+ const installed = await installSdkFromArchive({
128
+ config,
129
+ archivePath,
130
+ force: opts.force,
131
+ progress,
132
+ logger,
133
+ tmpDir: opts.tmpDir
134
+ });
135
+ const label = installed.version ? `SDK ${installed.version} (api ${installed.api})` : `SDK api ${installed.api}`;
136
+ logger.info(`${label} installed at ${installed.dir}.`);
137
+ return;
138
+ }
139
+ if (!version) {
140
+ throw new Error(
141
+ `Name an SDK version (${ALL_SDKS.map((s) => s.version).join(", ")}) or pass --from-archive <path>.
142
+ ` + FULL_SDK_TIP
143
+ );
144
+ }
114
145
  const release = ALL_SDKS.find((s) => s.version === version);
115
146
  if (!release) {
116
147
  throw new Error(
@@ -488,7 +519,17 @@ function registerAuthCommand(program) {
488
519
 
489
520
  // src/commands/app.ts
490
521
  import * as path4 from "path";
491
- import { installApp, launchApp, uninstallApp, forceStop, applyChanges, getBundleName } from "@oniroproject/core";
522
+ import {
523
+ installApp,
524
+ launchApp,
525
+ uninstallApp,
526
+ forceStop,
527
+ applyChanges,
528
+ existingHapOrUnsigned,
529
+ getBundleName,
530
+ readHapBundleName,
531
+ OniroError as OniroError2
532
+ } from "@oniroproject/core";
492
533
  function registerAppCommand(program) {
493
534
  const app = program.command("app").description("Install, launch, and manage built apps on a connected device/emulator.");
494
535
  app.command("install [project-dir-or-hap]").description(
@@ -516,16 +557,29 @@ function registerAppCommand(program) {
516
557
  }
517
558
  logger.info("App launched.");
518
559
  });
519
- app.command("apply [project-dir]").description(
520
- "Install a HAP and verify the running process took the change. Handles sign-info mismatch, asset-cache invalidation, and persistent-bundle restart."
521
- ).option("--bundle <bundle>", "Bundle name to apply changes to. Defaults to the bundleName in the project AppScope/app.json5.").option("--module <module>", "Module to resolve the HAP from (multi-module projects).").option("--hap <path>", "Explicit .hap path (relative to project-dir or absolute).").option("--installed-hap <path>", "Local path to the currently-installed HAP, to enable the asset-cache reboot.").option("--system", "Treat as a persistent/system bundle (refuses uninstall on sign-info mismatch).").option("--allow-uninstall", "Permit uninstalling a system bundle on sign-info mismatch (dangerous).").option("--device <serial>", "Target device serial.").option("--json", "Emit the result as JSON.").action(async (projectDir, opts) => {
560
+ app.command("apply [project-dir-or-hap]").description(
561
+ "Install a HAP (a project dir, default the current one, or a .hap file) and verify the running process took the change. Refuses a HAP of another bundle. Handles sign-info mismatch, asset-cache invalidation, and persistent-bundle restart."
562
+ ).option(
563
+ "--bundle <bundle>",
564
+ "Bundle name to apply changes to. Defaults to the bundleName in the .hap file, else in the project AppScope/app.json5."
565
+ ).option("--module <module>", "Module to resolve the HAP from (multi-module projects).").option("--hap <path>", "Explicit .hap path (relative to project-dir or absolute).").option("--installed-hap <path>", "Local path to the currently-installed HAP, to enable the asset-cache reboot.").option("--system", "Treat as a persistent/system bundle (refuses uninstall on sign-info mismatch).").option("--allow-uninstall", "Permit uninstalling a system bundle on sign-info mismatch (dangerous).").option("--device <serial>", "Target device serial.").option("--json", "Emit the result as JSON.").action(async (target, opts) => {
522
566
  const { config, logger } = getRuntime();
523
- const dir = path4.resolve(projectDir ?? process.cwd());
524
- const bundle = opts.bundle ?? getBundleName(dir);
567
+ const isHap = target !== void 0 && /\.hap$/i.test(target);
568
+ if (isHap && opts.hap) throw new OniroError2("Pass the HAP either as the argument or with --hap, not both.");
569
+ const dir = isHap ? process.cwd() : path4.resolve(target ?? process.cwd());
570
+ const hapPath = isHap ? path4.resolve(target) : opts.hap;
571
+ let bundle = opts.bundle;
572
+ if (!bundle && isHap) {
573
+ const hap = existingHapOrUnsigned(hapPath);
574
+ if (!hap) throw new OniroError2(`HAP not found at ${hapPath}.`);
575
+ bundle = await readHapBundleName(hap.path).catch(() => null) ?? void 0;
576
+ if (!bundle) throw new OniroError2(`Could not read the bundle name from ${hap.path}. Pass --bundle.`);
577
+ }
578
+ bundle ??= getBundleName(dir);
525
579
  const result = await applyChanges({
526
580
  config,
527
581
  bundle,
528
- hapPath: opts.hap,
582
+ hapPath,
529
583
  projectDir: dir,
530
584
  module: opts.module,
531
585
  installedHapPath: opts.installedHap,
@@ -685,16 +739,31 @@ function targetIndexFrom(opts) {
685
739
  return n;
686
740
  }
687
741
 
742
+ // src/lib/logSince.ts
743
+ function parseSince(value) {
744
+ if (value === void 0) return void 0;
745
+ if (value === "all") return Infinity;
746
+ const seconds = Number(value);
747
+ if (value.trim() === "" || !Number.isFinite(seconds) || seconds < 0) {
748
+ throw new Error(`--since takes a number of seconds or "all", not "${value}".`);
749
+ }
750
+ return seconds * 1e3;
751
+ }
752
+
688
753
  // src/commands/wait.ts
689
754
  function registerWaitCommand(program) {
690
755
  const wait = program.command("wait").description(
691
756
  "Wait for a device condition: a log line (--log), boot (--boot), a bundle to start (--bundle), or an on-screen element (--target / --target-id / --target-type)."
692
- ).option("--log <pattern>", "Resolve when a hilog line (tag: message) matches this regex.").option("--boot", "Resolve when the device is reachable (optionally also --pid-of).").option("--bundle <bundle>", "With --log: filter to this bundle. Alone: wait until this bundle is running.").option("--pid-of <name>", "With --boot: also require this process to have a pid.").option("--domain <domain>", "With --log: hilog domain filter (e.g. 0xD003900).");
757
+ ).option("--log <pattern>", "Resolve when a hilog line (tag: message) logged after the command starts matches this regex.").option(
758
+ "--since <seconds>",
759
+ 'With --log: also match lines logged up to N seconds before the command started, measured on the device clock; "all" matches everything still in the device log buffer.'
760
+ ).option("--boot", "Resolve when the device is reachable (optionally also --pid-of).").option("--bundle <bundle>", "With --log: filter to this bundle. Alone: wait until this bundle is running.").option("--pid-of <name>", "With --boot: also require this process to have a pid.").option("--domain <domain>", "With --log: hilog domain filter (e.g. 0xD003900).");
693
761
  addTargetOptions(wait, "Resolve when", { index: false });
694
762
  wait.option("--gone", "With --target*: resolve when no uncovered element matches any more.").option("--interval <ms>", "With --target*: poll interval, ms (each poll is a layout dump).", "500").option("--timeout <ms>", "Max wait, ms.", "30000").option("--device <serial>", "Target device serial.").option("--json", "With --log or --target*: emit the matched entry/element as JSON.").action(async (opts) => {
695
763
  const { config, logger } = getRuntime();
696
764
  const timeoutMs = Number(opts.timeout);
697
765
  const selector = selectorFrom(opts);
766
+ if (opts.since !== void 0 && !opts.log) throw new Error("--since applies only to --log.");
698
767
  if (opts.log) {
699
768
  const entry = await waitForLog({
700
769
  config,
@@ -703,11 +772,12 @@ function registerWaitCommand(program) {
703
772
  bundle: opts.bundle,
704
773
  domain: opts.domain,
705
774
  deviceSerial: opts.device,
775
+ lookbackMs: parseSince(opts.since),
706
776
  logger
707
777
  });
708
778
  if (opts.json) process.stdout.write(`${JSON.stringify(entry, null, 2)}
709
779
  `);
710
- else logger.info(`matched: ${entry.tag}: ${entry.message}`);
780
+ else logger.info(`matched: ${entry.time} ${entry.tag}: ${entry.message}`);
711
781
  return;
712
782
  }
713
783
  if (opts.boot) {
@@ -746,7 +816,10 @@ function registerWaitCommand(program) {
746
816
  // src/commands/watch.ts
747
817
  import { watchLog } from "@oniroproject/core";
748
818
  function registerWatchCommand(program) {
749
- program.command("watch").description("Collect hilog lines matching a pattern for a fixed duration.").option("--log <pattern>", "Regex tested against each line (tag: message).").option("--for <ms>", "Duration to watch, ms.", "10000").option("--bundle <bundle>", "Filter to this bundle.").option("--domain <domain>", "hilog domain filter (e.g. 0xD003900).").option("--no-dedup", "Do not collapse consecutive duplicate lines.").option("--device <serial>", "Target device serial.").option("--json", "Emit the entries as JSON.").action(async (opts) => {
819
+ program.command("watch").description("Collect the hilog lines matching a pattern that are logged during a fixed duration.").option("--log <pattern>", "Regex tested against each line (tag: message).").option("--for <ms>", "Duration to watch, ms.", "10000").option(
820
+ "--since <seconds>",
821
+ 'Also collect lines logged up to N seconds before the command started, measured on the device clock; "all" adds everything still in the device log buffer.'
822
+ ).option("--bundle <bundle>", "Filter to this bundle.").option("--domain <domain>", "hilog domain filter (e.g. 0xD003900).").option("--no-dedup", "Do not collapse consecutive duplicate lines.").option("--device <serial>", "Target device serial.").option("--json", "Emit the entries as JSON.").action(async (opts) => {
750
823
  const { config, logger } = getRuntime();
751
824
  if (!opts.log) throw new Error("watch requires --log <pattern>.");
752
825
  const entries = await watchLog({
@@ -757,6 +830,7 @@ function registerWatchCommand(program) {
757
830
  domain: opts.domain,
758
831
  dedup: opts.dedup,
759
832
  deviceSerial: opts.device,
833
+ lookbackMs: parseSince(opts.since),
760
834
  logger
761
835
  });
762
836
  if (opts.json) {
@@ -1194,6 +1268,7 @@ import {
1194
1268
  detectCmdToolsFlavour,
1195
1269
  detectRuntimeOs as detectRuntimeOs2,
1196
1270
  getCmdToolsPath,
1271
+ missingProjectRootFiles,
1197
1272
  runCodelinter
1198
1273
  } from "@oniroproject/core";
1199
1274
  function lintTotals(result) {
@@ -1208,8 +1283,10 @@ function lintTotals(result) {
1208
1283
  }
1209
1284
  function lintExitCode(result) {
1210
1285
  if (lintTotals(result).errors > 0) return 1;
1211
- const noReport = result.summary === null && result.findings.length === 0;
1212
- return noReport && result.code !== 0 ? result.code : 0;
1286
+ return lintFailed(result) ? result.code : 0;
1287
+ }
1288
+ function lintFailed(result) {
1289
+ return result.summary === null && result.findings.length === 0 && result.code !== 0;
1213
1290
  }
1214
1291
  function registerLintCommand(program) {
1215
1292
  program.command("lint [project-dir]").description(
@@ -1219,10 +1296,11 @@ function registerLintCommand(program) {
1219
1296
  const dir = path8.resolve(projectDir ?? process.cwd());
1220
1297
  const result = await runCodelinter({ config, projectDir: dir, files: opts.files, logger });
1221
1298
  const totals = lintTotals(result);
1299
+ const failed = lintFailed(result);
1222
1300
  if (opts.json) {
1223
1301
  process.stdout.write(`${JSON.stringify(result, null, 2)}
1224
1302
  `);
1225
- } else {
1303
+ } else if (!failed) {
1226
1304
  for (const f of result.findings) {
1227
1305
  const at = `${f.file}:${f.line}${f.column ? `:${f.column}` : ""}`;
1228
1306
  process.stdout.write(`${at} ${f.severity} ${f.message}${f.code ? ` [${f.code}]` : ""}
@@ -1233,9 +1311,13 @@ function registerLintCommand(program) {
1233
1311
  `
1234
1312
  );
1235
1313
  }
1236
- if (result.summary === null && result.findings.length === 0 && result.code !== 0) {
1237
- logger.error(`codelinter exited ${result.code} without a report:
1238
- ${result.raw.trim().split("\n").slice(-15).join("\n")}`);
1314
+ if (failed) {
1315
+ const missing = missingProjectRootFiles(dir);
1316
+ const why = missing.length ? ` It needs a DevEco project root, and ${dir} has no ${missing.join(", ")}.` : "";
1317
+ logger.error(
1318
+ `codelinter exited ${result.code} without a report, so nothing was linted.${why}
1319
+ ` + result.raw.trim().split("\n").slice(-15).join("\n")
1320
+ );
1239
1321
  }
1240
1322
  if (result.incomplete) {
1241
1323
  for (const n of result.notices) logger.warn(`codelinter: ${n}`);
@@ -1921,19 +2003,19 @@ function registerHdcCommand(program) {
1921
2003
  `);
1922
2004
  return;
1923
2005
  }
1924
- await new Promise((resolve9, reject) => {
2006
+ await new Promise((resolve10, reject) => {
1925
2007
  const child = spawnCompat(hdc, hdcPassthroughArgv(args, opts.device), { stdio: "inherit" });
1926
2008
  child.on("error", (err) => reject(new Error(`Failed to run ${hdc}: ${err.message}`)));
1927
2009
  child.on("close", (code) => {
1928
2010
  process.exitCode = code ?? 1;
1929
- resolve9();
2011
+ resolve10();
1930
2012
  });
1931
2013
  });
1932
2014
  });
1933
2015
  }
1934
2016
 
1935
2017
  // src/bin/oniro-app.ts
1936
- var CLI_VERSION = "0.15.0";
2018
+ var CLI_VERSION = "0.16.0";
1937
2019
  function buildProgram() {
1938
2020
  const program = new Command4().name("oniro-app").description(
1939
2021
  "Cross-platform CLI for Oniro/OpenHarmony app development.\n\nDesigned for non-interactive use (CI, scripts, agents): every command\ntakes explicit flags, results go to stdout, progress/logs go to stderr,\nand a non-zero exit code indicates failure."
@@ -1965,7 +2047,7 @@ async function main() {
1965
2047
  try {
1966
2048
  await program.parseAsync(process.argv);
1967
2049
  } catch (err) {
1968
- const message = err instanceof OniroError2 ? err.message : err instanceof Error ? err.message : String(err);
2050
+ const message = err instanceof OniroError3 ? err.message : err instanceof Error ? err.message : String(err);
1969
2051
  process.stderr.write(`[error] ${message}
1970
2052
  `);
1971
2053
  if (process.env.ONIRO_DEBUG === "1" && err instanceof Error && err.stack) {