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