@oniroproject/core 0.7.0 → 0.9.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 +18 -6
- package/dist/index.cjs +899 -444
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +301 -18
- package/dist/index.d.ts +301 -18
- package/dist/index.js +874 -445
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -105,9 +105,9 @@ var CmdToolsNotInstalledError = class extends OniroError {
|
|
|
105
105
|
expectedPath;
|
|
106
106
|
};
|
|
107
107
|
var UnsupportedPlatformError = class extends OniroError {
|
|
108
|
-
constructor(
|
|
109
|
-
super(`Unsupported platform: ${
|
|
110
|
-
this.platform =
|
|
108
|
+
constructor(platform7) {
|
|
109
|
+
super(`Unsupported platform: ${platform7}.`);
|
|
110
|
+
this.platform = platform7;
|
|
111
111
|
this.name = "UnsupportedPlatformError";
|
|
112
112
|
}
|
|
113
113
|
platform;
|
|
@@ -122,6 +122,14 @@ var ChecksumMismatchError = class extends OniroError {
|
|
|
122
122
|
expected;
|
|
123
123
|
actual;
|
|
124
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
|
+
};
|
|
125
133
|
var CancelledError = class extends OniroError {
|
|
126
134
|
constructor(message = "Operation cancelled.") {
|
|
127
135
|
super(message);
|
|
@@ -168,29 +176,29 @@ var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
|
|
|
168
176
|
// src/sdk/platform.ts
|
|
169
177
|
import * as os2 from "os";
|
|
170
178
|
function getOsFolder() {
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
173
|
-
if (
|
|
174
|
-
if (
|
|
175
|
-
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);
|
|
176
184
|
}
|
|
177
185
|
function getSdkFilename(version) {
|
|
178
|
-
const
|
|
186
|
+
const platform7 = os2.platform();
|
|
179
187
|
const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
|
|
180
188
|
const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
|
|
181
|
-
if (
|
|
189
|
+
if (platform7 === "linux") {
|
|
182
190
|
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
|
|
183
191
|
}
|
|
184
|
-
if (
|
|
192
|
+
if (platform7 === "darwin") {
|
|
185
193
|
if (os2.arch() === "arm64") {
|
|
186
194
|
return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
|
|
187
195
|
}
|
|
188
196
|
return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
|
|
189
197
|
}
|
|
190
|
-
if (
|
|
198
|
+
if (platform7 === "win32") {
|
|
191
199
|
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
|
|
192
200
|
}
|
|
193
|
-
throw new UnsupportedPlatformError(
|
|
201
|
+
throw new UnsupportedPlatformError(platform7);
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
// src/sdk/paths.ts
|
|
@@ -209,6 +217,14 @@ function getCmdToolsPath(config) {
|
|
|
209
217
|
function getEmulatorDir(config) {
|
|
210
218
|
return config.get("emulatorDir", defaultPaths.emulatorDir());
|
|
211
219
|
}
|
|
220
|
+
function toLongPath(p, platform7 = os3.platform()) {
|
|
221
|
+
if (platform7 !== "win32") return p;
|
|
222
|
+
try {
|
|
223
|
+
return fs.realpathSync.native(p);
|
|
224
|
+
} catch {
|
|
225
|
+
return p;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
212
228
|
function pickExisting(candidates) {
|
|
213
229
|
for (const c of candidates) {
|
|
214
230
|
if (fs.existsSync(c)) return c;
|
|
@@ -231,6 +247,8 @@ function getOhpmPath(config) {
|
|
|
231
247
|
return getCmdToolsBin(config, "ohpm");
|
|
232
248
|
}
|
|
233
249
|
function getHdcPath(config) {
|
|
250
|
+
const override = config.get("hdcPath", "");
|
|
251
|
+
if (override) return override;
|
|
234
252
|
const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
|
|
235
253
|
if (os3.platform() === "win32") {
|
|
236
254
|
return pickExisting([
|
|
@@ -242,12 +260,11 @@ function getHdcPath(config) {
|
|
|
242
260
|
}
|
|
243
261
|
return path2.join(base, "hdc");
|
|
244
262
|
}
|
|
245
|
-
function getHvigorwPath(config, projectDir) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
]);
|
|
263
|
+
function getHvigorwPath(config, projectDir, platform7 = os3.platform()) {
|
|
264
|
+
const posixFirst = ["hvigorw", "hvigorw.bat", "hvigorw.cmd"];
|
|
265
|
+
const windowsFirst = ["hvigorw.bat", "hvigorw.cmd", "hvigorw"];
|
|
266
|
+
const names = platform7 === "win32" ? windowsFirst : posixFirst;
|
|
267
|
+
const local = pickExisting(names.map((n) => path2.join(projectDir, n)));
|
|
251
268
|
if (fs.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
|
|
252
269
|
return local;
|
|
253
270
|
}
|
|
@@ -281,8 +298,164 @@ function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
|
|
|
281
298
|
}
|
|
282
299
|
}
|
|
283
300
|
|
|
284
|
-
// src/sdk/
|
|
301
|
+
// src/sdk/tmp.ts
|
|
285
302
|
import * as fs3 from "fs";
|
|
303
|
+
import * as os4 from "os";
|
|
304
|
+
import * as path4 from "path";
|
|
305
|
+
var INSTALL_TMP_DIRNAME = ".oniro-tmp";
|
|
306
|
+
var TMPFS_MAGIC = 16914836;
|
|
307
|
+
var RAMFS_MAGIC = 2240043254;
|
|
308
|
+
var HINT_EXAMPLE = os4.platform() === "win32" ? "D:\\oniro-tmp" : "~/.cache/oniro-tmp";
|
|
309
|
+
var TMP_DIR_HINT = `Point the scratch directory at a filesystem with more room using --tmp-dir <path> (or the ONIRO_TMP_DIR environment variable), e.g. --tmp-dir ${HINT_EXAMPLE}.`;
|
|
310
|
+
function resolveTmpRoot(config, override, installRoot) {
|
|
311
|
+
const explicit = (override ?? "").trim();
|
|
312
|
+
if (explicit) return path4.resolve(explicit);
|
|
313
|
+
const configured = config.get("tmpDir", "").trim();
|
|
314
|
+
if (configured) return path4.resolve(configured);
|
|
315
|
+
if (installRoot) return path4.join(path4.resolve(installRoot), INSTALL_TMP_DIRNAME);
|
|
316
|
+
return os4.tmpdir();
|
|
317
|
+
}
|
|
318
|
+
function createTempWorkDir(root, prefix) {
|
|
319
|
+
try {
|
|
320
|
+
fs3.mkdirSync(root, { recursive: true });
|
|
321
|
+
return fs3.mkdtempSync(path4.join(root, prefix));
|
|
322
|
+
} catch (err) {
|
|
323
|
+
if (isOutOfSpaceError(err)) throw toSpaceError(err, root, "a temporary directory");
|
|
324
|
+
throw new OniroError(
|
|
325
|
+
`Cannot create a temporary directory in '${root}': ${err instanceof Error ? err.message : String(err)}
|
|
326
|
+
${TMP_DIR_HINT}`,
|
|
327
|
+
err
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function removeTempWorkDir(dir, root) {
|
|
332
|
+
fs3.rmSync(dir, { recursive: true, force: true });
|
|
333
|
+
if (!root || path4.basename(root) !== INSTALL_TMP_DIRNAME) return;
|
|
334
|
+
try {
|
|
335
|
+
fs3.rmdirSync(root);
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function createInstallTempDir(opts) {
|
|
340
|
+
const requested = (opts.override ?? "").trim() || opts.config.get("tmpDir", "").trim();
|
|
341
|
+
const root = resolveTmpRoot(opts.config, opts.override, opts.installRoot);
|
|
342
|
+
try {
|
|
343
|
+
return { dir: createTempWorkDir(root, opts.prefix), root };
|
|
344
|
+
} catch (err) {
|
|
345
|
+
const fallback = os4.tmpdir();
|
|
346
|
+
if (requested || isOutOfSpaceError(err) || path4.resolve(fallback) === path4.resolve(root)) throw err;
|
|
347
|
+
const reason = err instanceof Error ? err.message.split("\n")[0] : String(err);
|
|
348
|
+
(opts.logger ?? noopLogger).warn(`${reason} Falling back to '${fallback}'.`);
|
|
349
|
+
return { dir: createTempWorkDir(fallback, opts.prefix), root: fallback };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function nearestExisting(dir) {
|
|
353
|
+
let current = path4.resolve(dir);
|
|
354
|
+
for (; ; ) {
|
|
355
|
+
if (fs3.existsSync(current)) return current;
|
|
356
|
+
const parent = path4.dirname(current);
|
|
357
|
+
if (parent === current) return null;
|
|
358
|
+
current = parent;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function getFreeSpaceBytes(dir) {
|
|
362
|
+
const target = nearestExisting(dir);
|
|
363
|
+
if (!target) return null;
|
|
364
|
+
try {
|
|
365
|
+
const stats = fs3.statfsSync(target);
|
|
366
|
+
return Number(stats.bsize) * Number(stats.bavail);
|
|
367
|
+
} catch {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function isRamBackedPath(dir) {
|
|
372
|
+
const target = nearestExisting(dir);
|
|
373
|
+
if (!target) return false;
|
|
374
|
+
try {
|
|
375
|
+
const type = Number(fs3.statfsSync(target).type);
|
|
376
|
+
return type === TMPFS_MAGIC || type === RAMFS_MAGIC;
|
|
377
|
+
} catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function formatBytes(bytes) {
|
|
382
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
383
|
+
let value = Math.max(0, bytes);
|
|
384
|
+
let unit = 0;
|
|
385
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
386
|
+
value /= 1024;
|
|
387
|
+
unit++;
|
|
388
|
+
}
|
|
389
|
+
return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
390
|
+
}
|
|
391
|
+
function isOutOfSpaceError(err) {
|
|
392
|
+
if (err instanceof InsufficientSpaceError) return true;
|
|
393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
394
|
+
let current = err;
|
|
395
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
396
|
+
seen.add(current);
|
|
397
|
+
const e = current;
|
|
398
|
+
if (e.code === "ENOSPC" || e.code === "EDQUOT") return true;
|
|
399
|
+
if (typeof e.message === "string" && /ENOSPC|EDQUOT|no space left on device/i.test(e.message)) return true;
|
|
400
|
+
current = e.cause;
|
|
401
|
+
}
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
function spaceMessage(dir, what, opts) {
|
|
405
|
+
const need = typeof opts.requiredBytes === "number" ? `needs about ${formatBytes(opts.requiredBytes)}` : null;
|
|
406
|
+
const free = typeof opts.freeBytes === "number" ? `${formatBytes(opts.freeBytes)} available` : null;
|
|
407
|
+
const amounts = [need, free].filter(Boolean).join(", ");
|
|
408
|
+
const lines = [`Not enough free space in '${dir}' for ${what}${amounts ? ` (${amounts})` : ""}.`];
|
|
409
|
+
if (isRamBackedPath(dir)) {
|
|
410
|
+
lines.push(
|
|
411
|
+
`'${dir}' is on a RAM-backed filesystem (tmpfs), so its capacity is limited by free memory, not by free disk.`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (opts.tmpDirHint) lines.push(TMP_DIR_HINT);
|
|
415
|
+
return lines.join("\n");
|
|
416
|
+
}
|
|
417
|
+
function ensureFreeSpace(dir, requiredBytes, what) {
|
|
418
|
+
if (!Number.isFinite(requiredBytes) || requiredBytes <= 0) return;
|
|
419
|
+
const freeBytes = getFreeSpaceBytes(dir);
|
|
420
|
+
if (freeBytes === null || freeBytes >= requiredBytes) return;
|
|
421
|
+
throw new InsufficientSpaceError(
|
|
422
|
+
spaceMessage(dir, what, { requiredBytes, freeBytes, tmpDirHint: true }),
|
|
423
|
+
dir
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
function errorPath(err) {
|
|
427
|
+
const seen = /* @__PURE__ */ new Set();
|
|
428
|
+
let current = err;
|
|
429
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
430
|
+
seen.add(current);
|
|
431
|
+
const e = current;
|
|
432
|
+
if (typeof e.path === "string" && e.path) return e.path;
|
|
433
|
+
if (typeof e.dest === "string" && e.dest) return e.dest;
|
|
434
|
+
current = e.cause;
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
function isInside(child, parent) {
|
|
439
|
+
const rel = path4.relative(path4.resolve(parent), path4.resolve(child));
|
|
440
|
+
return rel === "" || !rel.startsWith("..") && !path4.isAbsolute(rel);
|
|
441
|
+
}
|
|
442
|
+
function toSpaceError(err, tmpRoot, what) {
|
|
443
|
+
if (err instanceof InsufficientSpaceError) return err;
|
|
444
|
+
if (!isOutOfSpaceError(err)) return err;
|
|
445
|
+
const failed = errorPath(err);
|
|
446
|
+
const failedDir = failed ? path4.dirname(path4.resolve(failed)) : path4.resolve(tmpRoot);
|
|
447
|
+
const inTmp = isInside(failedDir, tmpRoot);
|
|
448
|
+
const dir = inTmp ? path4.resolve(tmpRoot) : failedDir;
|
|
449
|
+
return new InsufficientSpaceError(
|
|
450
|
+
spaceMessage(dir, what, { freeBytes: getFreeSpaceBytes(dir), tmpDirHint: inTmp }),
|
|
451
|
+
dir,
|
|
452
|
+
err
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/sdk/download.ts
|
|
457
|
+
import * as fs4 from "fs";
|
|
458
|
+
import * as path5 from "path";
|
|
286
459
|
import * as crypto from "crypto";
|
|
287
460
|
import { pipeline } from "stream";
|
|
288
461
|
import { promisify } from "util";
|
|
@@ -292,36 +465,57 @@ var pipelineAsync = promisify(pipeline);
|
|
|
292
465
|
async function downloadFile(opts) {
|
|
293
466
|
const { url, dest, progress, abortSignal } = opts;
|
|
294
467
|
const proto = url.startsWith("https") ? https : http;
|
|
295
|
-
return new Promise((
|
|
468
|
+
return new Promise((resolve4, reject) => {
|
|
296
469
|
const s = Math.max(0, Math.min(100, opts.start ?? 0));
|
|
297
470
|
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
298
471
|
let settled = false;
|
|
299
472
|
const done = (err) => {
|
|
300
473
|
if (settled) return;
|
|
301
474
|
settled = true;
|
|
302
|
-
err ? reject(err) :
|
|
475
|
+
err ? reject(err) : resolve4();
|
|
303
476
|
};
|
|
304
477
|
if (abortSignal?.aborted) {
|
|
305
478
|
done(new CancelledError("Download cancelled before start."));
|
|
306
479
|
return;
|
|
307
480
|
}
|
|
308
|
-
const
|
|
481
|
+
const destDir = path5.dirname(path5.resolve(dest));
|
|
482
|
+
const what = opts.what ?? `the download of '${url}'`;
|
|
483
|
+
const writeFailed = (err) => isOutOfSpaceError(err) ? toSpaceError(err, opts.tmpRoot ?? destDir, what) : new OniroError(`Error writing '${dest}': ${err instanceof Error ? err.message : String(err)}`, err);
|
|
484
|
+
const file = fs4.createWriteStream(dest);
|
|
485
|
+
let activeResponse;
|
|
486
|
+
const failWith = (err) => {
|
|
487
|
+
file.close(() => fs4.rm(dest, { force: true }, () => done(err)));
|
|
488
|
+
};
|
|
489
|
+
file.on("error", (err) => {
|
|
490
|
+
try {
|
|
491
|
+
activeResponse?.destroy?.();
|
|
492
|
+
} catch {
|
|
493
|
+
}
|
|
494
|
+
failWith(writeFailed(err));
|
|
495
|
+
});
|
|
309
496
|
const req = proto.get(url, (response) => {
|
|
497
|
+
activeResponse = response;
|
|
310
498
|
if (response.statusCode !== 200) {
|
|
311
499
|
try {
|
|
312
500
|
response.destroy();
|
|
313
501
|
} catch {
|
|
314
502
|
}
|
|
315
|
-
|
|
316
|
-
file.close();
|
|
317
|
-
} catch {
|
|
318
|
-
}
|
|
319
|
-
fs3.unlink(dest, () => {
|
|
320
|
-
});
|
|
321
|
-
done(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
|
|
503
|
+
failWith(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
|
|
322
504
|
return;
|
|
323
505
|
}
|
|
324
506
|
const total = parseInt(response.headers["content-length"] || "0", 10);
|
|
507
|
+
if (total > 0) {
|
|
508
|
+
try {
|
|
509
|
+
ensureFreeSpace(opts.tmpRoot ?? destDir, total, what);
|
|
510
|
+
} catch (err) {
|
|
511
|
+
try {
|
|
512
|
+
response.destroy();
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
failWith(err);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
325
519
|
let downloaded = 0;
|
|
326
520
|
let lastOverall = Math.round(s);
|
|
327
521
|
response.on("data", (chunk) => {
|
|
@@ -345,38 +539,26 @@ async function downloadFile(opts) {
|
|
|
345
539
|
const inc = endOverall - lastOverall;
|
|
346
540
|
if (inc > 0) progress.report({ message: "Downloading: 100%", increment: inc });
|
|
347
541
|
}
|
|
348
|
-
file.close((err) => err ? done(err) : done());
|
|
542
|
+
file.close((err) => err ? done(writeFailed(err)) : done());
|
|
349
543
|
});
|
|
350
544
|
abortSignal?.addEventListener("abort", () => {
|
|
351
545
|
response.destroy();
|
|
352
|
-
|
|
353
|
-
fs3.unlink(dest, () => {
|
|
354
|
-
});
|
|
355
|
-
done(new CancelledError("Download cancelled."));
|
|
546
|
+
failWith(new CancelledError("Download cancelled."));
|
|
356
547
|
});
|
|
357
548
|
});
|
|
358
549
|
req.on("error", (err) => {
|
|
359
|
-
|
|
360
|
-
file.close();
|
|
361
|
-
} catch {
|
|
362
|
-
}
|
|
363
|
-
fs3.unlink(dest, () => {
|
|
364
|
-
});
|
|
365
|
-
done(new OniroError(`Error downloading '${url}': ${err.message}`, err));
|
|
550
|
+
failWith(new OniroError(`Error downloading '${url}': ${err.message}`, err));
|
|
366
551
|
});
|
|
367
552
|
abortSignal?.addEventListener("abort", () => {
|
|
368
553
|
req.destroy();
|
|
369
|
-
|
|
370
|
-
fs3.unlink(dest, () => {
|
|
371
|
-
});
|
|
372
|
-
done(new CancelledError("Download cancelled."));
|
|
554
|
+
failWith(new CancelledError("Download cancelled."));
|
|
373
555
|
});
|
|
374
556
|
});
|
|
375
557
|
}
|
|
376
558
|
async function verifySha256(filePath, sha256Path) {
|
|
377
|
-
const expected =
|
|
559
|
+
const expected = fs4.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
|
|
378
560
|
const hash = crypto.createHash("sha256");
|
|
379
|
-
await pipelineAsync(
|
|
561
|
+
await pipelineAsync(fs4.createReadStream(filePath), hash);
|
|
380
562
|
const actual = hash.digest("hex");
|
|
381
563
|
if (actual !== expected) {
|
|
382
564
|
throw new ChecksumMismatchError(expected, actual);
|
|
@@ -384,9 +566,9 @@ async function verifySha256(filePath, sha256Path) {
|
|
|
384
566
|
}
|
|
385
567
|
|
|
386
568
|
// src/sdk/extract.ts
|
|
387
|
-
import * as
|
|
388
|
-
import * as
|
|
389
|
-
import * as
|
|
569
|
+
import * as fs5 from "fs";
|
|
570
|
+
import * as os5 from "os";
|
|
571
|
+
import * as path6 from "path";
|
|
390
572
|
import { pipeline as pipeline2 } from "stream";
|
|
391
573
|
import { promisify as promisify2 } from "util";
|
|
392
574
|
import * as tar from "tar";
|
|
@@ -403,12 +585,12 @@ async function extractZipWithProgress(opts) {
|
|
|
403
585
|
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
404
586
|
let processed = 0;
|
|
405
587
|
let lastOverall = Math.round(s);
|
|
406
|
-
await
|
|
407
|
-
const destRoot =
|
|
588
|
+
await fs5.promises.mkdir(dest, { recursive: true });
|
|
589
|
+
const destRoot = path6.resolve(dest);
|
|
408
590
|
const safeResolveTarget = (entryName) => {
|
|
409
|
-
const resolved =
|
|
410
|
-
const rel =
|
|
411
|
-
if (rel.startsWith("..") ||
|
|
591
|
+
const resolved = path6.resolve(destRoot, entryName);
|
|
592
|
+
const rel = path6.relative(destRoot, resolved);
|
|
593
|
+
if (rel.startsWith("..") || path6.isAbsolute(rel)) {
|
|
412
594
|
throw new OniroError(`Blocked ZIP entry with illegal path: ${entryName}`);
|
|
413
595
|
}
|
|
414
596
|
return resolved;
|
|
@@ -420,25 +602,25 @@ async function extractZipWithProgress(opts) {
|
|
|
420
602
|
const attr = file.attr ? file.attr >>> 16 : 0;
|
|
421
603
|
const isSymlink = "isSymbolicLink" in file ? Boolean(file.isSymbolicLink) : (attr & 61440) === 40960;
|
|
422
604
|
if (file.isDirectory) {
|
|
423
|
-
await
|
|
605
|
+
await fs5.promises.mkdir(targetPath, { recursive: true });
|
|
424
606
|
} else if (isSymlink) {
|
|
425
|
-
await
|
|
607
|
+
await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
|
|
426
608
|
const linkTargetBuffer = await zip.entryData(file);
|
|
427
609
|
const linkTarget = linkTargetBuffer.toString("utf8");
|
|
428
610
|
try {
|
|
429
|
-
const isWin =
|
|
430
|
-
await
|
|
611
|
+
const isWin = os5.platform() === "win32";
|
|
612
|
+
await fs5.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
|
|
431
613
|
} catch (e) {
|
|
432
614
|
logger.warn(`Failed to create symlink at ${targetPath}: ${String(e)}`);
|
|
433
615
|
}
|
|
434
616
|
} else {
|
|
435
|
-
await
|
|
617
|
+
await fs5.promises.mkdir(path6.dirname(targetPath), { recursive: true });
|
|
436
618
|
const readStream = await zip.stream(file);
|
|
437
|
-
const writeStream =
|
|
619
|
+
const writeStream = fs5.createWriteStream(targetPath);
|
|
438
620
|
await pipelineAsync2(readStream, writeStream);
|
|
439
621
|
if (attr > 0) {
|
|
440
622
|
try {
|
|
441
|
-
await
|
|
623
|
+
await fs5.promises.chmod(targetPath, attr & 511);
|
|
442
624
|
} catch {
|
|
443
625
|
}
|
|
444
626
|
}
|
|
@@ -470,41 +652,41 @@ async function extractTarball(tarPath, dest, strip = 0) {
|
|
|
470
652
|
}
|
|
471
653
|
|
|
472
654
|
// src/sdk/list.ts
|
|
473
|
-
import * as
|
|
474
|
-
import * as
|
|
655
|
+
import * as fs6 from "fs";
|
|
656
|
+
import * as path7 from "path";
|
|
475
657
|
function getSupportedSdksForUi(config) {
|
|
476
|
-
const base =
|
|
658
|
+
const base = path7.join(getSdkRootDir(config), getOsFolder());
|
|
477
659
|
return ALL_SDKS.map((sdk) => ({
|
|
478
660
|
version: sdk.version,
|
|
479
661
|
api: sdk.api,
|
|
480
|
-
installed:
|
|
662
|
+
installed: fs6.existsSync(path7.join(base, sdk.api))
|
|
481
663
|
})).sort((a, b) => Number(b.api) - Number(a.api));
|
|
482
664
|
}
|
|
483
665
|
function getInstalledSdks(config) {
|
|
484
666
|
const sdkRoot = getSdkRootDir(config);
|
|
485
667
|
const versions = /* @__PURE__ */ new Set();
|
|
486
|
-
if (!
|
|
668
|
+
if (!fs6.existsSync(sdkRoot)) return [];
|
|
487
669
|
for (const osFolder of ["linux", "darwin", "windows"]) {
|
|
488
|
-
const osPath =
|
|
489
|
-
if (!
|
|
490
|
-
for (const api of
|
|
491
|
-
const apiPath =
|
|
492
|
-
if (
|
|
670
|
+
const osPath = path7.join(sdkRoot, osFolder);
|
|
671
|
+
if (!fs6.existsSync(osPath) || !fs6.statSync(osPath).isDirectory()) continue;
|
|
672
|
+
for (const api of fs6.readdirSync(osPath)) {
|
|
673
|
+
const apiPath = path7.join(osPath, api);
|
|
674
|
+
if (fs6.statSync(apiPath).isDirectory()) versions.add(api);
|
|
493
675
|
}
|
|
494
676
|
}
|
|
495
677
|
return ALL_SDKS.filter((sdk) => versions.has(sdk.api)).map((sdk) => sdk.version);
|
|
496
678
|
}
|
|
497
679
|
|
|
498
680
|
// src/sdk/remove.ts
|
|
499
|
-
import * as
|
|
500
|
-
import * as
|
|
681
|
+
import * as fs7 from "fs";
|
|
682
|
+
import * as path8 from "path";
|
|
501
683
|
function removeSdk(config, api) {
|
|
502
684
|
const sdkRoot = getSdkRootDir(config);
|
|
503
685
|
let removed = false;
|
|
504
686
|
for (const osFolder of ["linux", "darwin", "windows"]) {
|
|
505
|
-
const sdkPath =
|
|
506
|
-
if (
|
|
507
|
-
|
|
687
|
+
const sdkPath = path8.join(sdkRoot, osFolder, api);
|
|
688
|
+
if (fs7.existsSync(sdkPath)) {
|
|
689
|
+
fs7.rmSync(sdkPath, { recursive: true, force: true });
|
|
508
690
|
removed = true;
|
|
509
691
|
}
|
|
510
692
|
}
|
|
@@ -512,19 +694,18 @@ function removeSdk(config, api) {
|
|
|
512
694
|
}
|
|
513
695
|
|
|
514
696
|
// src/sdk/install.ts
|
|
515
|
-
import * as
|
|
516
|
-
import * as
|
|
517
|
-
import * as path7 from "path";
|
|
697
|
+
import * as fs9 from "fs";
|
|
698
|
+
import * as path9 from "path";
|
|
518
699
|
|
|
519
700
|
// src/sdk/move.ts
|
|
520
|
-
import * as
|
|
701
|
+
import * as fs8 from "fs";
|
|
521
702
|
function movePath(src, dest) {
|
|
522
703
|
try {
|
|
523
|
-
|
|
704
|
+
fs8.renameSync(src, dest);
|
|
524
705
|
} catch (err) {
|
|
525
706
|
if (err.code !== "EXDEV") throw err;
|
|
526
|
-
|
|
527
|
-
|
|
707
|
+
fs8.cpSync(src, dest, { recursive: true });
|
|
708
|
+
fs8.rmSync(src, { recursive: true, force: true });
|
|
528
709
|
}
|
|
529
710
|
}
|
|
530
711
|
|
|
@@ -536,21 +717,29 @@ async function downloadAndInstallSdk(opts) {
|
|
|
536
717
|
const urlBase = config.get("sdkUrlBase", OHOS_URL_BASE);
|
|
537
718
|
const downloadUrl = `${urlBase}/${version}-Release/${filename}`;
|
|
538
719
|
const sha256Url = `${downloadUrl}.sha256`;
|
|
539
|
-
const
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
720
|
+
const what = `the SDK ${version} install`;
|
|
721
|
+
const sdkRoot = getSdkRootDir(config);
|
|
722
|
+
const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
|
|
723
|
+
config,
|
|
724
|
+
override: opts.tmpDir,
|
|
725
|
+
installRoot: sdkRoot,
|
|
726
|
+
prefix: "oniro-sdk-",
|
|
727
|
+
logger
|
|
728
|
+
});
|
|
729
|
+
const tarPath = path9.join(tmpDir, filename);
|
|
730
|
+
const sha256Path = path9.join(tmpDir, `${filename}.sha256`);
|
|
731
|
+
const extractDir = path9.join(tmpDir, "extract");
|
|
732
|
+
fs9.mkdirSync(extractDir);
|
|
733
|
+
const sdkInstallDir = path9.join(sdkRoot, osFolder, api);
|
|
734
|
+
fs9.mkdirSync(path9.dirname(sdkInstallDir), { recursive: true });
|
|
546
735
|
const checkCancelled = () => {
|
|
547
736
|
if (abortSignal?.aborted) throw new CancelledError();
|
|
548
737
|
};
|
|
549
738
|
try {
|
|
550
739
|
progress?.report({ message: "Downloading SDK archive...", increment: 0 });
|
|
551
|
-
await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35 });
|
|
740
|
+
await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35, what, tmpRoot });
|
|
552
741
|
progress?.report({ message: "Downloading checksum...", increment: 0 });
|
|
553
|
-
await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10 });
|
|
742
|
+
await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10, what, tmpRoot });
|
|
554
743
|
progress?.report({ message: "Verifying checksum...", increment: 0 });
|
|
555
744
|
await verifySha256(tarPath, sha256Path);
|
|
556
745
|
progress?.report({ message: "Verifying checksum...", increment: 5 });
|
|
@@ -559,13 +748,13 @@ async function downloadAndInstallSdk(opts) {
|
|
|
559
748
|
await extractTarball(tarPath, extractDir, strip);
|
|
560
749
|
progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
|
|
561
750
|
checkCancelled();
|
|
562
|
-
const osContentPath =
|
|
563
|
-
if (!
|
|
751
|
+
const osContentPath = path9.join(extractDir, osFolder);
|
|
752
|
+
if (!fs9.existsSync(osContentPath)) {
|
|
564
753
|
throw new OniroError(
|
|
565
754
|
`Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
|
|
566
755
|
);
|
|
567
756
|
}
|
|
568
|
-
const zipFiles =
|
|
757
|
+
const zipFiles = fs9.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
|
|
569
758
|
const componentsStart = 60;
|
|
570
759
|
const componentsBudget = 35;
|
|
571
760
|
const n = zipFiles.length;
|
|
@@ -580,39 +769,114 @@ async function downloadAndInstallSdk(opts) {
|
|
|
580
769
|
for (const zipFile of zipFiles) {
|
|
581
770
|
checkCancelled();
|
|
582
771
|
logger.info(`Extracting component ${zipFile}`);
|
|
583
|
-
const zipPath =
|
|
772
|
+
const zipPath = path9.join(osContentPath, zipFile);
|
|
584
773
|
const thisBudget = base + (rem > 0 ? 1 : 0);
|
|
585
774
|
if (rem > 0) rem--;
|
|
586
775
|
await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
|
|
587
776
|
cursor += thisBudget;
|
|
588
|
-
|
|
777
|
+
fs9.unlinkSync(zipPath);
|
|
589
778
|
}
|
|
590
779
|
progress?.report({ message: "Finalizing installation...", increment: 0 });
|
|
591
|
-
if (
|
|
592
|
-
|
|
780
|
+
if (fs9.existsSync(sdkInstallDir)) {
|
|
781
|
+
fs9.rmSync(sdkInstallDir, { recursive: true, force: true });
|
|
593
782
|
}
|
|
594
783
|
movePath(osContentPath, sdkInstallDir);
|
|
595
784
|
progress?.report({ message: "Finalizing installation...", increment: 3 });
|
|
596
785
|
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
597
|
-
|
|
786
|
+
removeTempWorkDir(tmpDir, tmpRoot);
|
|
598
787
|
progress?.report({ message: "Cleaning up...", increment: 2 });
|
|
599
788
|
} catch (err) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
789
|
+
const wrapped = toSpaceError(err, tmpRoot, what);
|
|
790
|
+
logger.error(`SDK install failed: ${wrapped instanceof Error ? wrapped.message : String(wrapped)}`);
|
|
791
|
+
removeTempWorkDir(tmpDir, tmpRoot);
|
|
792
|
+
throw wrapped;
|
|
603
793
|
}
|
|
604
794
|
}
|
|
605
795
|
|
|
606
796
|
// src/cmdTools/install.ts
|
|
607
|
-
import * as
|
|
797
|
+
import * as fs10 from "fs";
|
|
608
798
|
import * as os6 from "os";
|
|
609
|
-
import * as
|
|
610
|
-
import {
|
|
611
|
-
|
|
612
|
-
|
|
799
|
+
import * as path11 from "path";
|
|
800
|
+
import { spawnSync } from "child_process";
|
|
801
|
+
|
|
802
|
+
// src/hdc/spawnCompat.ts
|
|
803
|
+
import * as path10 from "path";
|
|
804
|
+
import { spawn } from "child_process";
|
|
805
|
+
var WINDOWS_BATCH_RE = /\.(bat|cmd)$/i;
|
|
806
|
+
var CMD_META_RE = /([()[\]%!^"`<>&|;, *?])/g;
|
|
807
|
+
var CMD_SHIM_RE = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i;
|
|
808
|
+
var UNSAFE_CMD_CHARS_RE = /["\r\n\0]/;
|
|
809
|
+
function needsCmdWrapper(command, platform7 = process.platform) {
|
|
810
|
+
return platform7 === "win32" && WINDOWS_BATCH_RE.test(command);
|
|
811
|
+
}
|
|
812
|
+
function escapeCmdArgument(arg, doubleEscapeMetaChars = false) {
|
|
813
|
+
let out = String(arg);
|
|
814
|
+
out = out.replace(/(\\*)"/g, '$1$1\\"');
|
|
815
|
+
out = out.replace(/(\\*)$/, "$1$1");
|
|
816
|
+
out = `"${out}"`;
|
|
817
|
+
out = out.replace(CMD_META_RE, "^$1");
|
|
818
|
+
if (doubleEscapeMetaChars) out = out.replace(CMD_META_RE, "^$1");
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
function escapeCmdCommand(command) {
|
|
822
|
+
return command.replace(CMD_META_RE, "^$1");
|
|
823
|
+
}
|
|
824
|
+
function buildCmdWrapper(command, args) {
|
|
825
|
+
assertCmdSafe(command, `command ${command}`);
|
|
826
|
+
args.forEach((a, i) => assertCmdSafe(a, `argument ${i} (${a})`));
|
|
827
|
+
const normalized = path10.normalize(command);
|
|
828
|
+
const double = CMD_SHIM_RE.test(normalized);
|
|
829
|
+
const line = [escapeCmdCommand(normalized), ...args.map((a) => escapeCmdArgument(a, double))].join(" ");
|
|
830
|
+
return {
|
|
831
|
+
command: process.env.ComSpec ?? process.env.COMSPEC ?? "cmd.exe",
|
|
832
|
+
args: ["/d", "/s", "/c", `"${line}"`]
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function assertCmdSafe(value, label) {
|
|
836
|
+
if (UNSAFE_CMD_CHARS_RE.test(value)) {
|
|
837
|
+
throw new OniroError(
|
|
838
|
+
`Cannot run a Windows batch wrapper with a quote, newline or NUL in the ${label}: cmd.exe cannot pass it through unambiguously.`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function spawnCompat(command, args, options = {}) {
|
|
843
|
+
if (!needsCmdWrapper(command)) {
|
|
844
|
+
return spawn(command, [...args], { ...options, shell: false });
|
|
845
|
+
}
|
|
846
|
+
const wrapped = buildCmdWrapper(command, args);
|
|
847
|
+
return spawn(wrapped.command, wrapped.args, {
|
|
848
|
+
...options,
|
|
849
|
+
shell: false,
|
|
850
|
+
// The line is already escaped; stop Node from re-quoting it.
|
|
851
|
+
windowsVerbatimArguments: true
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
function killProcessTree(child) {
|
|
855
|
+
if (process.platform === "win32" && typeof child.pid === "number") {
|
|
856
|
+
try {
|
|
857
|
+
const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
858
|
+
killer.on("error", () => {
|
|
859
|
+
try {
|
|
860
|
+
child.kill("SIGKILL");
|
|
861
|
+
} catch {
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
return;
|
|
865
|
+
} catch {
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
try {
|
|
869
|
+
child.kill("SIGKILL");
|
|
870
|
+
} catch {
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/cmdTools/install.ts
|
|
875
|
+
function getCmdToolsDownloadUrl(config, platform7 = os6.platform()) {
|
|
876
|
+
if (platform7 === "linux") {
|
|
613
877
|
return config.get("cmdToolsUrlLinux", defaultPaths.cmdToolsUrlLinux);
|
|
614
878
|
}
|
|
615
|
-
if (
|
|
879
|
+
if (platform7 === "win32") {
|
|
616
880
|
const url = config.get("cmdToolsUrlWindows", "");
|
|
617
881
|
if (!url) {
|
|
618
882
|
throw new OniroError(
|
|
@@ -621,7 +885,7 @@ function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
|
|
|
621
885
|
}
|
|
622
886
|
return url;
|
|
623
887
|
}
|
|
624
|
-
if (
|
|
888
|
+
if (platform7 === "darwin") {
|
|
625
889
|
const url = config.get("cmdToolsUrlMac", "");
|
|
626
890
|
if (!url) {
|
|
627
891
|
throw new OniroError(
|
|
@@ -630,20 +894,20 @@ function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
|
|
|
630
894
|
}
|
|
631
895
|
return url;
|
|
632
896
|
}
|
|
633
|
-
throw new UnsupportedPlatformError(
|
|
897
|
+
throw new UnsupportedPlatformError(platform7);
|
|
634
898
|
}
|
|
635
899
|
function findCmdToolsSourceDir(extractPath) {
|
|
636
900
|
const known = [
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
901
|
+
path11.join(extractPath, "command-line-tools"),
|
|
902
|
+
path11.join(extractPath, "oh-command-line-tools"),
|
|
903
|
+
path11.join(extractPath, "commandline-tools")
|
|
640
904
|
];
|
|
641
905
|
for (const c of known) {
|
|
642
|
-
if (
|
|
906
|
+
if (fs10.existsSync(c)) return c;
|
|
643
907
|
}
|
|
644
|
-
const entries =
|
|
908
|
+
const entries = fs10.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path11.join(extractPath, e.name));
|
|
645
909
|
for (const dir of entries) {
|
|
646
|
-
if (
|
|
910
|
+
if (fs10.existsSync(path11.join(dir, "bin"))) return dir;
|
|
647
911
|
}
|
|
648
912
|
throw new OniroError("Could not locate command line tools folder in the extracted archive.");
|
|
649
913
|
}
|
|
@@ -651,53 +915,62 @@ async function installCmdTools(opts) {
|
|
|
651
915
|
const { config, progress, abortSignal } = opts;
|
|
652
916
|
const logger = opts.logger ?? noopLogger;
|
|
653
917
|
const CMD_PATH = getCmdToolsPath(config);
|
|
654
|
-
const
|
|
655
|
-
const
|
|
656
|
-
|
|
918
|
+
const what = "the command-line tools install";
|
|
919
|
+
const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
|
|
920
|
+
config,
|
|
921
|
+
override: opts.tmpDir,
|
|
922
|
+
installRoot: path11.dirname(CMD_PATH),
|
|
923
|
+
prefix: "oniro-cmdtools-",
|
|
924
|
+
logger
|
|
925
|
+
});
|
|
926
|
+
const zipPath = opts.localZipPath ?? path11.join(tmpDir, "oh-command-line-tools.zip");
|
|
927
|
+
const extractPath = path11.join(tmpDir, "oh-command-line-tools");
|
|
657
928
|
try {
|
|
658
929
|
if (!opts.localZipPath) {
|
|
659
930
|
const url = getCmdToolsDownloadUrl(config);
|
|
660
931
|
progress?.report({ message: "Downloading command line tools...", increment: 0 });
|
|
661
|
-
await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50 });
|
|
932
|
+
await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
|
|
662
933
|
}
|
|
663
934
|
progress?.report({ message: "Extracting tools...", increment: 0 });
|
|
664
935
|
await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
|
|
665
|
-
|
|
936
|
+
fs10.mkdirSync(CMD_PATH, { recursive: true });
|
|
666
937
|
const srcDir = findCmdToolsSourceDir(extractPath);
|
|
667
|
-
for (const entry of
|
|
668
|
-
const src =
|
|
669
|
-
const dest =
|
|
670
|
-
if (
|
|
671
|
-
if (
|
|
938
|
+
for (const entry of fs10.readdirSync(srcDir)) {
|
|
939
|
+
const src = path11.join(srcDir, entry);
|
|
940
|
+
const dest = path11.join(CMD_PATH, entry);
|
|
941
|
+
if (fs10.statSync(src).isDirectory()) {
|
|
942
|
+
if (fs10.existsSync(dest)) fs10.rmSync(dest, { recursive: true, force: true });
|
|
672
943
|
movePath(src, dest);
|
|
673
944
|
} else {
|
|
674
|
-
|
|
945
|
+
fs10.copyFileSync(src, dest);
|
|
675
946
|
}
|
|
676
947
|
}
|
|
677
|
-
const binDir =
|
|
678
|
-
if (
|
|
679
|
-
for (const file of
|
|
680
|
-
|
|
948
|
+
const binDir = path11.join(CMD_PATH, "bin");
|
|
949
|
+
if (fs10.existsSync(binDir) && os6.platform() !== "win32") {
|
|
950
|
+
for (const file of fs10.readdirSync(binDir)) {
|
|
951
|
+
fs10.chmodSync(path11.join(binDir, file), 493);
|
|
681
952
|
}
|
|
682
953
|
}
|
|
683
954
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
684
955
|
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
956
|
+
} catch (err) {
|
|
957
|
+
throw toSpaceError(err, tmpRoot, what);
|
|
685
958
|
} finally {
|
|
686
|
-
|
|
959
|
+
removeTempWorkDir(tmpDir, tmpRoot);
|
|
687
960
|
}
|
|
688
961
|
}
|
|
689
962
|
function getCmdToolsStatus(config) {
|
|
690
963
|
const cmdPath = getCmdToolsPath(config);
|
|
691
|
-
const binDir =
|
|
964
|
+
const binDir = path11.join(cmdPath, "bin");
|
|
692
965
|
const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
|
|
693
|
-
const bin = candidates.map((c) =>
|
|
966
|
+
const bin = candidates.map((c) => path11.join(binDir, c)).find((p) => fs10.existsSync(p));
|
|
694
967
|
if (!bin) {
|
|
695
968
|
return { installed: false, status: "Not installed" };
|
|
696
969
|
}
|
|
697
970
|
try {
|
|
698
|
-
const versionFile =
|
|
699
|
-
if (
|
|
700
|
-
const content =
|
|
971
|
+
const versionFile = path11.join(cmdPath, "version.txt");
|
|
972
|
+
if (fs10.existsSync(versionFile)) {
|
|
973
|
+
const content = fs10.readFileSync(versionFile, "utf8");
|
|
701
974
|
for (const line of content.split(/\r?\n/)) {
|
|
702
975
|
const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
|
|
703
976
|
if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
|
|
@@ -706,7 +979,14 @@ function getCmdToolsStatus(config) {
|
|
|
706
979
|
} catch {
|
|
707
980
|
}
|
|
708
981
|
try {
|
|
709
|
-
const
|
|
982
|
+
const wrapped = needsCmdWrapper(bin);
|
|
983
|
+
const probe = wrapped ? buildCmdWrapper(bin, ["-v"]) : { command: bin, args: ["-v"] };
|
|
984
|
+
const res = spawnSync(probe.command, probe.args, {
|
|
985
|
+
encoding: "utf8",
|
|
986
|
+
windowsVerbatimArguments: wrapped
|
|
987
|
+
});
|
|
988
|
+
if (res.error || res.status !== 0) throw res.error ?? new Error(`exit ${String(res.status)}`);
|
|
989
|
+
const version = res.stdout.trim();
|
|
710
990
|
return { installed: true, status: `Installed (${version})` };
|
|
711
991
|
} catch {
|
|
712
992
|
return { installed: true, status: "Installed (version unknown)" };
|
|
@@ -717,14 +997,14 @@ function isCmdToolsInstalled(config) {
|
|
|
717
997
|
}
|
|
718
998
|
function removeCmdTools(config) {
|
|
719
999
|
const cmdPath = getCmdToolsPath(config);
|
|
720
|
-
if (
|
|
721
|
-
|
|
1000
|
+
if (fs10.existsSync(cmdPath)) {
|
|
1001
|
+
fs10.rmSync(cmdPath, { recursive: true, force: true });
|
|
722
1002
|
}
|
|
723
1003
|
}
|
|
724
1004
|
|
|
725
1005
|
// src/sign/encryptKey.ts
|
|
726
|
-
import * as
|
|
727
|
-
import * as
|
|
1006
|
+
import * as fs11 from "fs";
|
|
1007
|
+
import * as path12 from "path";
|
|
728
1008
|
import * as crypto2 from "crypto";
|
|
729
1009
|
var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
|
|
730
1010
|
var DAEMON_SALT_KEY_LENGTH = 16;
|
|
@@ -796,33 +1076,33 @@ function getRootKey(fdComponents, salt) {
|
|
|
796
1076
|
function createAndStoreKey(dir, keyLength) {
|
|
797
1077
|
const key = crypto2.randomBytes(keyLength);
|
|
798
1078
|
const hash = crypto2.createHash("sha256").update(key).digest("hex");
|
|
799
|
-
const filePath =
|
|
800
|
-
|
|
801
|
-
|
|
1079
|
+
const filePath = path12.join(dir, hash);
|
|
1080
|
+
fs11.writeFileSync(filePath, key);
|
|
1081
|
+
fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
802
1082
|
return key;
|
|
803
1083
|
}
|
|
804
1084
|
function createAndStoreEnKey(rootKey, ceDir) {
|
|
805
1085
|
const workKey = crypto2.randomBytes(DAEMON_WORK_KEY_LENGTH);
|
|
806
1086
|
const encrypted = encrypt(rootKey, workKey);
|
|
807
1087
|
const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
|
|
808
|
-
const filePath =
|
|
809
|
-
|
|
810
|
-
|
|
1088
|
+
const filePath = path12.join(ceDir, hash);
|
|
1089
|
+
fs11.writeFileSync(filePath, encrypted);
|
|
1090
|
+
fs11.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
811
1091
|
}
|
|
812
1092
|
function createMaterial(materialPath) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const fdDir =
|
|
816
|
-
const acDir =
|
|
817
|
-
const ceDir =
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1093
|
+
fs11.mkdirSync(materialPath, { recursive: true });
|
|
1094
|
+
fs11.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
|
|
1095
|
+
const fdDir = path12.join(materialPath, "fd");
|
|
1096
|
+
const acDir = path12.join(materialPath, "ac");
|
|
1097
|
+
const ceDir = path12.join(materialPath, "ce");
|
|
1098
|
+
fs11.mkdirSync(fdDir, { recursive: true });
|
|
1099
|
+
fs11.mkdirSync(acDir, { recursive: true });
|
|
1100
|
+
fs11.mkdirSync(ceDir, { recursive: true });
|
|
821
1101
|
const fdSubDirs = ["0", "1", "2"];
|
|
822
1102
|
const fdComponents = [];
|
|
823
1103
|
for (const sub of fdSubDirs) {
|
|
824
|
-
const subDir =
|
|
825
|
-
|
|
1104
|
+
const subDir = path12.join(fdDir, sub);
|
|
1105
|
+
fs11.mkdirSync(subDir, { recursive: true });
|
|
826
1106
|
const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
|
|
827
1107
|
fdComponents.push(comp);
|
|
828
1108
|
}
|
|
@@ -831,24 +1111,24 @@ function createMaterial(materialPath) {
|
|
|
831
1111
|
createAndStoreEnKey(rootKey, ceDir);
|
|
832
1112
|
}
|
|
833
1113
|
function readSingleFile(dir) {
|
|
834
|
-
if (!
|
|
1114
|
+
if (!fs11.existsSync(dir) || !fs11.statSync(dir).isDirectory()) {
|
|
835
1115
|
throw new Error(`Missing signing material directory: ${dir}`);
|
|
836
1116
|
}
|
|
837
|
-
const files =
|
|
1117
|
+
const files = fs11.readdirSync(dir).filter((f) => f !== ".DS_Store");
|
|
838
1118
|
if (files.length !== 1) {
|
|
839
1119
|
throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
|
|
840
1120
|
}
|
|
841
|
-
return
|
|
1121
|
+
return fs11.readFileSync(path12.join(dir, files[0]));
|
|
842
1122
|
}
|
|
843
1123
|
function getKey(materialPath) {
|
|
844
|
-
if (!
|
|
1124
|
+
if (!fs11.existsSync(materialPath) || !fs11.statSync(materialPath).isDirectory()) {
|
|
845
1125
|
throw new Error("Material directory does not exist.");
|
|
846
1126
|
}
|
|
847
|
-
const fdDir =
|
|
848
|
-
const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(
|
|
849
|
-
const salt = readSingleFile(
|
|
1127
|
+
const fdDir = path12.join(materialPath, "fd");
|
|
1128
|
+
const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path12.join(fdDir, sub)));
|
|
1129
|
+
const salt = readSingleFile(path12.join(materialPath, "ac"));
|
|
850
1130
|
const rootKey = getRootKey(fdComponents, salt);
|
|
851
|
-
const workMaterial = readSingleFile(
|
|
1131
|
+
const workMaterial = readSingleFile(path12.join(materialPath, "ce"));
|
|
852
1132
|
return decrypt(rootKey, workMaterial);
|
|
853
1133
|
}
|
|
854
1134
|
function encryptPwd(password, materialPath) {
|
|
@@ -863,9 +1143,9 @@ function decryptPwd(encryptedHex, materialPath) {
|
|
|
863
1143
|
}
|
|
864
1144
|
|
|
865
1145
|
// src/sign/generateSigningConfigs.ts
|
|
866
|
-
import * as
|
|
867
|
-
import * as
|
|
868
|
-
import { execFileSync
|
|
1146
|
+
import * as fs12 from "fs";
|
|
1147
|
+
import * as path13 from "path";
|
|
1148
|
+
import { execFileSync } from "child_process";
|
|
869
1149
|
import JSON52 from "json5";
|
|
870
1150
|
|
|
871
1151
|
// src/sign/applicationCert.ts
|
|
@@ -934,17 +1214,17 @@ var SIGNING_KIND_CONFIG = {
|
|
|
934
1214
|
};
|
|
935
1215
|
function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
|
|
936
1216
|
logger.info("[sign] Copying signing material into project...");
|
|
937
|
-
const signaturesDir =
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1217
|
+
const signaturesDir = path13.join(projectDir, "signatures");
|
|
1218
|
+
fs12.mkdirSync(signaturesDir, { recursive: true });
|
|
1219
|
+
fs12.copyFileSync(paths.keystore, path13.join(signaturesDir, "OpenHarmony.p12"));
|
|
1220
|
+
fs12.copyFileSync(paths.profileCert, path13.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
|
|
1221
|
+
fs12.copyFileSync(paths.unsignedProfileTemplate, path13.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
|
|
942
1222
|
if (kind === "application-release") {
|
|
943
|
-
const dest =
|
|
1223
|
+
const dest = path13.join(signaturesDir, "OpenHarmonyApplication.cer");
|
|
944
1224
|
if (applicationCertPath) {
|
|
945
|
-
|
|
1225
|
+
fs12.copyFileSync(applicationCertPath, dest);
|
|
946
1226
|
} else {
|
|
947
|
-
|
|
1227
|
+
fs12.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
|
|
948
1228
|
}
|
|
949
1229
|
}
|
|
950
1230
|
}
|
|
@@ -953,7 +1233,7 @@ function resolveAppFeature(apl, override) {
|
|
|
953
1233
|
return apl === "normal" ? "hos_normal_app" : "hos_system_app";
|
|
954
1234
|
}
|
|
955
1235
|
function extractLeafCertificate(chainPath) {
|
|
956
|
-
const certContent =
|
|
1236
|
+
const certContent = fs12.readFileSync(chainPath, "utf-8");
|
|
957
1237
|
const parts = certContent.split("-----END CERTIFICATE-----");
|
|
958
1238
|
if (parts.length < 3) {
|
|
959
1239
|
throw new OniroError(`${chainPath} does not contain enough certificates.`);
|
|
@@ -963,20 +1243,20 @@ function extractLeafCertificate(chainPath) {
|
|
|
963
1243
|
function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}) {
|
|
964
1244
|
const kind = options.kind ?? pickSigningKind(apl);
|
|
965
1245
|
logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
|
|
966
|
-
const appJsonPath =
|
|
967
|
-
const profileTemplatePath =
|
|
968
|
-
if (!
|
|
1246
|
+
const appJsonPath = path13.join(projectDir, "AppScope/app.json5");
|
|
1247
|
+
const profileTemplatePath = path13.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
|
|
1248
|
+
if (!fs12.existsSync(appJsonPath)) {
|
|
969
1249
|
throw new OniroError(`${appJsonPath} does not exist.`);
|
|
970
1250
|
}
|
|
971
1251
|
let appJson;
|
|
972
1252
|
try {
|
|
973
|
-
appJson = JSON52.parse(
|
|
1253
|
+
appJson = JSON52.parse(fs12.readFileSync(appJsonPath, "utf-8"));
|
|
974
1254
|
} catch (e) {
|
|
975
1255
|
throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
|
|
976
1256
|
}
|
|
977
1257
|
let profileTemplate;
|
|
978
1258
|
try {
|
|
979
|
-
profileTemplate = JSON.parse(
|
|
1259
|
+
profileTemplate = JSON.parse(fs12.readFileSync(profileTemplatePath, "utf-8"));
|
|
980
1260
|
} catch (e) {
|
|
981
1261
|
throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
|
|
982
1262
|
}
|
|
@@ -989,7 +1269,7 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
989
1269
|
profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
|
|
990
1270
|
profileTemplate["bundle-info"]["apl"] = apl;
|
|
991
1271
|
profileTemplate["bundle-info"]["app-feature"] = appFeature;
|
|
992
|
-
const distCertChainPath =
|
|
1272
|
+
const distCertChainPath = path13.join(
|
|
993
1273
|
projectDir,
|
|
994
1274
|
"signatures",
|
|
995
1275
|
SIGNING_KIND_CONFIG[kind].certBasename
|
|
@@ -999,13 +1279,13 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
999
1279
|
profileTemplate.acls = profileTemplate.acls ?? {};
|
|
1000
1280
|
profileTemplate.acls["allowed-acls"] = options.acls;
|
|
1001
1281
|
}
|
|
1002
|
-
|
|
1282
|
+
fs12.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
|
|
1003
1283
|
}
|
|
1004
1284
|
function generateP7bFile(projectDir, paths, logger, passwords) {
|
|
1005
1285
|
logger.info("[sign] Generating P7b profile via hap-sign-tool...");
|
|
1006
|
-
const signaturesDir =
|
|
1007
|
-
const profileTemplatePath =
|
|
1008
|
-
const outputProfilePath =
|
|
1286
|
+
const signaturesDir = path13.join(projectDir, "signatures");
|
|
1287
|
+
const profileTemplatePath = path13.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
|
|
1288
|
+
const outputProfilePath = path13.join(signaturesDir, "app1-profile.p7b");
|
|
1009
1289
|
const args = [
|
|
1010
1290
|
"-jar",
|
|
1011
1291
|
paths.signTool,
|
|
@@ -1030,7 +1310,7 @@ function generateP7bFile(projectDir, paths, logger, passwords) {
|
|
|
1030
1310
|
passwords.store
|
|
1031
1311
|
];
|
|
1032
1312
|
try {
|
|
1033
|
-
|
|
1313
|
+
execFileSync("java", args, { stdio: "inherit" });
|
|
1034
1314
|
} catch (e) {
|
|
1035
1315
|
throw new OniroError(
|
|
1036
1316
|
`hap-sign-tool failed. Ensure a JDK (with keytool/java) is on PATH. Underlying error: ${e.message}`,
|
|
@@ -1039,11 +1319,11 @@ function generateP7bFile(projectDir, paths, logger, passwords) {
|
|
|
1039
1319
|
}
|
|
1040
1320
|
}
|
|
1041
1321
|
function detectSigningConfigNames(projectDir) {
|
|
1042
|
-
const buildProfilePath =
|
|
1043
|
-
if (!
|
|
1322
|
+
const buildProfilePath = path13.join(projectDir, "build-profile.json5");
|
|
1323
|
+
if (!fs12.existsSync(buildProfilePath)) return ["default"];
|
|
1044
1324
|
let parsed;
|
|
1045
1325
|
try {
|
|
1046
|
-
parsed = JSON52.parse(
|
|
1326
|
+
parsed = JSON52.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
|
|
1047
1327
|
} catch {
|
|
1048
1328
|
return ["default"];
|
|
1049
1329
|
}
|
|
@@ -1060,14 +1340,14 @@ function detectSigningConfigNames(projectDir) {
|
|
|
1060
1340
|
}
|
|
1061
1341
|
function updateBuildProfile(projectDir, kind, logger, passwords) {
|
|
1062
1342
|
logger.info("[sign] Writing signing configs into build-profile.json5...");
|
|
1063
|
-
const materialDir =
|
|
1064
|
-
const buildProfilePath =
|
|
1343
|
+
const materialDir = path13.join(projectDir, "signatures", "material");
|
|
1344
|
+
const buildProfilePath = path13.join(projectDir, "build-profile.json5");
|
|
1065
1345
|
const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
|
|
1066
1346
|
const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
|
|
1067
1347
|
let buildProfile = { app: {} };
|
|
1068
|
-
if (
|
|
1348
|
+
if (fs12.existsSync(buildProfilePath)) {
|
|
1069
1349
|
try {
|
|
1070
|
-
buildProfile = JSON52.parse(
|
|
1350
|
+
buildProfile = JSON52.parse(fs12.readFileSync(buildProfilePath, "utf-8"));
|
|
1071
1351
|
} catch (e) {
|
|
1072
1352
|
throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
|
|
1073
1353
|
}
|
|
@@ -1088,13 +1368,13 @@ function updateBuildProfile(projectDir, kind, logger, passwords) {
|
|
|
1088
1368
|
storeFile: "./signatures/OpenHarmony.p12"
|
|
1089
1369
|
}
|
|
1090
1370
|
}));
|
|
1091
|
-
|
|
1371
|
+
fs12.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
|
|
1092
1372
|
}
|
|
1093
1373
|
function prepareMaterialDirectory(projectDir, logger) {
|
|
1094
1374
|
logger.info("[sign] Generating fresh signing material...");
|
|
1095
|
-
const materialDir =
|
|
1096
|
-
if (
|
|
1097
|
-
|
|
1375
|
+
const materialDir = path13.join(projectDir, "signatures", "material");
|
|
1376
|
+
if (fs12.existsSync(materialDir)) {
|
|
1377
|
+
fs12.rmSync(materialDir, { recursive: true, force: true });
|
|
1098
1378
|
}
|
|
1099
1379
|
createMaterial(materialDir);
|
|
1100
1380
|
}
|
|
@@ -1117,15 +1397,15 @@ function generateSigningConfigs(options) {
|
|
|
1117
1397
|
"Could not detect project SDK version (compileSdkVersion missing in build-profile.json5)."
|
|
1118
1398
|
);
|
|
1119
1399
|
}
|
|
1120
|
-
const sdkPath =
|
|
1121
|
-
if (!
|
|
1400
|
+
const sdkPath = path13.join(sdkHome, String(sdkVersion));
|
|
1401
|
+
if (!fs12.existsSync(sdkPath)) {
|
|
1122
1402
|
throw new OniroError(`SDK path does not exist: ${sdkPath}`);
|
|
1123
1403
|
}
|
|
1124
1404
|
const paths = {
|
|
1125
|
-
signTool:
|
|
1126
|
-
keystore:
|
|
1127
|
-
profileCert:
|
|
1128
|
-
unsignedProfileTemplate:
|
|
1405
|
+
signTool: path13.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
|
|
1406
|
+
keystore: path13.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
|
|
1407
|
+
profileCert: path13.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
|
|
1408
|
+
unsignedProfileTemplate: path13.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
|
|
1129
1409
|
};
|
|
1130
1410
|
const kind = pickSigningKind(apl);
|
|
1131
1411
|
const passwords = options.passwords ?? DEFAULT_PASSWORDS;
|
|
@@ -1139,60 +1419,81 @@ function generateSigningConfigs(options) {
|
|
|
1139
1419
|
}
|
|
1140
1420
|
|
|
1141
1421
|
// src/emulator/install.ts
|
|
1142
|
-
import * as
|
|
1143
|
-
import * as
|
|
1144
|
-
import * as path11 from "path";
|
|
1422
|
+
import * as fs13 from "fs";
|
|
1423
|
+
import * as path14 from "path";
|
|
1145
1424
|
async function installEmulator(opts) {
|
|
1146
1425
|
const { config, progress, abortSignal } = opts;
|
|
1147
1426
|
const logger = opts.logger ?? noopLogger;
|
|
1148
1427
|
const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
|
|
1149
1428
|
const emulatorDir = getEmulatorDir(config);
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1429
|
+
const what = "the emulator install";
|
|
1430
|
+
const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
|
|
1431
|
+
config,
|
|
1432
|
+
override: opts.tmpDir,
|
|
1433
|
+
installRoot: path14.dirname(emulatorDir),
|
|
1434
|
+
prefix: "oniro-emulator-",
|
|
1435
|
+
logger
|
|
1436
|
+
});
|
|
1437
|
+
const tmpZip = path14.join(tmpDir, "oniro_emulator.zip");
|
|
1152
1438
|
try {
|
|
1153
|
-
|
|
1439
|
+
fs13.mkdirSync(emulatorDir, { recursive: true });
|
|
1154
1440
|
progress?.report({ message: "Downloading emulator...", increment: 0 });
|
|
1155
|
-
await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
|
|
1441
|
+
await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
|
|
1156
1442
|
progress?.report({ message: "Extracting emulator...", increment: 0 });
|
|
1157
1443
|
await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
|
|
1158
|
-
const runSh =
|
|
1159
|
-
if (
|
|
1160
|
-
|
|
1444
|
+
const runSh = path14.join(emulatorDir, "images", "run.sh");
|
|
1445
|
+
if (fs13.existsSync(runSh)) {
|
|
1446
|
+
fs13.chmodSync(runSh, 493);
|
|
1161
1447
|
}
|
|
1162
1448
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
1449
|
+
} catch (err) {
|
|
1450
|
+
throw toSpaceError(err, tmpRoot, what);
|
|
1163
1451
|
} finally {
|
|
1164
|
-
|
|
1452
|
+
removeTempWorkDir(tmpDir, tmpRoot);
|
|
1165
1453
|
}
|
|
1166
1454
|
}
|
|
1167
1455
|
function isEmulatorInstalled(config) {
|
|
1168
|
-
const imagesDir =
|
|
1169
|
-
return
|
|
1456
|
+
const imagesDir = path14.join(getEmulatorDir(config), "images");
|
|
1457
|
+
return fs13.existsSync(path14.join(imagesDir, "run.sh")) || fs13.existsSync(path14.join(imagesDir, "run.bat"));
|
|
1170
1458
|
}
|
|
1171
1459
|
function removeEmulator(config) {
|
|
1172
1460
|
const emulatorDir = getEmulatorDir(config);
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
1461
|
+
if (fs13.existsSync(emulatorDir)) {
|
|
1462
|
+
fs13.rmSync(emulatorDir, { recursive: true, force: true });
|
|
1175
1463
|
}
|
|
1176
1464
|
}
|
|
1177
1465
|
|
|
1178
1466
|
// src/emulator/lifecycle.ts
|
|
1179
|
-
import * as
|
|
1180
|
-
import * as
|
|
1181
|
-
import * as
|
|
1467
|
+
import * as fs15 from "fs";
|
|
1468
|
+
import * as os7 from "os";
|
|
1469
|
+
import * as path15 from "path";
|
|
1182
1470
|
import { spawn as spawn2 } from "child_process";
|
|
1183
1471
|
|
|
1184
1472
|
// src/hdc/exec.ts
|
|
1185
|
-
import
|
|
1473
|
+
import * as fs14 from "fs";
|
|
1474
|
+
function errorMessage(err) {
|
|
1475
|
+
return err instanceof Error ? err.message : String(err);
|
|
1476
|
+
}
|
|
1186
1477
|
function runProcess(opts) {
|
|
1187
1478
|
const { command, args, cwd, env, onOutput } = opts;
|
|
1188
1479
|
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1189
1480
|
const { abortSignal } = opts;
|
|
1190
|
-
return new Promise((
|
|
1481
|
+
return new Promise((resolve4, reject) => {
|
|
1191
1482
|
if (abortSignal?.aborted) {
|
|
1192
1483
|
reject(new CancelledError(`Command aborted before start: ${command}`));
|
|
1193
1484
|
return;
|
|
1194
1485
|
}
|
|
1195
|
-
|
|
1486
|
+
if (needsCmdWrapper(command) && !fs14.existsSync(command)) {
|
|
1487
|
+
reject(new OniroError(`Failed to spawn ${command}: ENOENT (no such file)`));
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
let child;
|
|
1491
|
+
try {
|
|
1492
|
+
child = spawnCompat(command, args, { cwd, env });
|
|
1493
|
+
} catch (err) {
|
|
1494
|
+
reject(new OniroError(`Failed to spawn ${command}: ${errorMessage(err)}`, err));
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1196
1497
|
let stdout = "";
|
|
1197
1498
|
let stderr = "";
|
|
1198
1499
|
let settled = false;
|
|
@@ -1205,26 +1506,20 @@ function runProcess(opts) {
|
|
|
1205
1506
|
if (settled) return;
|
|
1206
1507
|
settled = true;
|
|
1207
1508
|
cleanup();
|
|
1208
|
-
|
|
1209
|
-
child.kill("SIGKILL");
|
|
1210
|
-
} catch {
|
|
1211
|
-
}
|
|
1509
|
+
killProcessTree(child);
|
|
1212
1510
|
reject(new CancelledError(`Command aborted: ${command} ${args.join(" ")}`));
|
|
1213
1511
|
};
|
|
1214
1512
|
const timer = setTimeout(() => {
|
|
1215
1513
|
timedOut = true;
|
|
1216
|
-
|
|
1217
|
-
child.kill("SIGKILL");
|
|
1218
|
-
} catch {
|
|
1219
|
-
}
|
|
1514
|
+
killProcessTree(child);
|
|
1220
1515
|
}, timeoutMs);
|
|
1221
1516
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1222
|
-
child.stdout
|
|
1517
|
+
child.stdout?.on("data", (d) => {
|
|
1223
1518
|
const s = d.toString();
|
|
1224
1519
|
stdout += s;
|
|
1225
1520
|
onOutput?.(s, "stdout");
|
|
1226
1521
|
});
|
|
1227
|
-
child.stderr
|
|
1522
|
+
child.stderr?.on("data", (d) => {
|
|
1228
1523
|
const s = d.toString();
|
|
1229
1524
|
stderr += s;
|
|
1230
1525
|
onOutput?.(s, "stderr");
|
|
@@ -1248,7 +1543,7 @@ ${stderr.trim()}` : "")
|
|
|
1248
1543
|
);
|
|
1249
1544
|
return;
|
|
1250
1545
|
}
|
|
1251
|
-
|
|
1546
|
+
resolve4({ code: code ?? -1, stdout, stderr });
|
|
1252
1547
|
});
|
|
1253
1548
|
});
|
|
1254
1549
|
}
|
|
@@ -1274,9 +1569,29 @@ function ensureOk(result, command) {
|
|
|
1274
1569
|
}
|
|
1275
1570
|
return result;
|
|
1276
1571
|
}
|
|
1572
|
+
var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
|
|
1573
|
+
function parseRemoteExit(stdout) {
|
|
1574
|
+
let code = null;
|
|
1575
|
+
for (const m of stdout.matchAll(REMOTE_RC_RE)) code = Number(m[1]);
|
|
1576
|
+
if (code === null) return null;
|
|
1577
|
+
return { code, stdout: stdout.replace(REMOTE_RC_RE, "").replace(/[ \t\r\n]+$/, "") };
|
|
1578
|
+
}
|
|
1579
|
+
async function shellChecked(opts) {
|
|
1580
|
+
const { command, ...rest } = opts;
|
|
1581
|
+
const res = await hdcExec({ ...rest, args: ["shell", `${command}; echo "__ONIRO_RC=$?__"`] });
|
|
1582
|
+
const parsed = parseRemoteExit(res.stdout);
|
|
1583
|
+
if (!parsed) return res;
|
|
1584
|
+
return { code: parsed.code, stdout: parsed.stdout, stderr: res.stderr };
|
|
1585
|
+
}
|
|
1586
|
+
function ensureRemoteOk(result, command) {
|
|
1587
|
+
if (result.code !== 0) {
|
|
1588
|
+
throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
|
|
1589
|
+
}
|
|
1590
|
+
return result;
|
|
1591
|
+
}
|
|
1277
1592
|
|
|
1278
1593
|
// src/emulator/lifecycle.ts
|
|
1279
|
-
var PID_FILE =
|
|
1594
|
+
var PID_FILE = path15.join(os7.tmpdir(), "oniro_emulator.pid");
|
|
1280
1595
|
async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
|
|
1281
1596
|
try {
|
|
1282
1597
|
await hdcExec({ config, args: ["start", "-r"], logger });
|
|
@@ -1289,24 +1604,24 @@ async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger
|
|
|
1289
1604
|
}
|
|
1290
1605
|
}
|
|
1291
1606
|
function resolveLauncher(imagesPath, headless, connect) {
|
|
1292
|
-
const runSh =
|
|
1293
|
-
const runBat =
|
|
1607
|
+
const runSh = path15.join(imagesPath, "run.sh");
|
|
1608
|
+
const runBat = path15.join(imagesPath, "run.bat");
|
|
1294
1609
|
const extra = [];
|
|
1295
1610
|
if (headless) extra.push("--headless");
|
|
1296
1611
|
if (connect) extra.push("--connect", connect);
|
|
1297
|
-
if (
|
|
1298
|
-
if (
|
|
1612
|
+
if (os7.platform() === "win32") {
|
|
1613
|
+
if (fs15.existsSync(runBat)) {
|
|
1299
1614
|
return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
|
|
1300
1615
|
}
|
|
1301
|
-
if (
|
|
1616
|
+
if (fs15.existsSync(runSh)) {
|
|
1302
1617
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1303
1618
|
}
|
|
1304
1619
|
throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
|
|
1305
1620
|
}
|
|
1306
|
-
if (
|
|
1621
|
+
if (fs15.existsSync(runSh)) {
|
|
1307
1622
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1308
1623
|
}
|
|
1309
|
-
if (
|
|
1624
|
+
if (fs15.existsSync(runBat)) {
|
|
1310
1625
|
return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
|
|
1311
1626
|
}
|
|
1312
1627
|
throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
|
|
@@ -1314,9 +1629,9 @@ function resolveLauncher(imagesPath, headless, connect) {
|
|
|
1314
1629
|
async function startEmulator(opts) {
|
|
1315
1630
|
const { config } = opts;
|
|
1316
1631
|
const logger = opts.logger ?? noopLogger;
|
|
1317
|
-
if (
|
|
1632
|
+
if (os7.platform() !== "win32" && fs15.existsSync(PID_FILE)) {
|
|
1318
1633
|
try {
|
|
1319
|
-
const pid = Number(
|
|
1634
|
+
const pid = Number(fs15.readFileSync(PID_FILE, "utf8").trim());
|
|
1320
1635
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1321
1636
|
try {
|
|
1322
1637
|
process.kill(pid, 0);
|
|
@@ -1332,13 +1647,13 @@ async function startEmulator(opts) {
|
|
|
1332
1647
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1333
1648
|
}
|
|
1334
1649
|
}
|
|
1335
|
-
const imagesPath =
|
|
1336
|
-
if (!
|
|
1650
|
+
const imagesPath = path15.join(getEmulatorDir(config), "images");
|
|
1651
|
+
if (!fs15.existsSync(imagesPath)) {
|
|
1337
1652
|
throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
|
|
1338
1653
|
}
|
|
1339
1654
|
const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
|
|
1340
|
-
const logPath = opts.logFile ?? (
|
|
1341
|
-
const logFd =
|
|
1655
|
+
const logPath = opts.logFile ?? (os7.platform() === "win32" ? "NUL" : "/dev/null");
|
|
1656
|
+
const logFd = fs15.openSync(logPath, "a");
|
|
1342
1657
|
let child;
|
|
1343
1658
|
try {
|
|
1344
1659
|
child = spawn2(launcher.command, [...launcher.args], {
|
|
@@ -1348,19 +1663,19 @@ async function startEmulator(opts) {
|
|
|
1348
1663
|
windowsHide: true
|
|
1349
1664
|
});
|
|
1350
1665
|
} finally {
|
|
1351
|
-
|
|
1666
|
+
fs15.closeSync(logFd);
|
|
1352
1667
|
}
|
|
1353
1668
|
if (!child.pid) {
|
|
1354
1669
|
throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
|
|
1355
1670
|
}
|
|
1356
|
-
|
|
1671
|
+
fs15.writeFileSync(PID_FILE, String(child.pid));
|
|
1357
1672
|
child.unref();
|
|
1358
1673
|
logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
|
|
1359
|
-
const earlyExit = new Promise((
|
|
1360
|
-
child.once("exit", (code) =>
|
|
1361
|
-
child.once("error", () =>
|
|
1674
|
+
const earlyExit = new Promise((resolve4) => {
|
|
1675
|
+
child.once("exit", (code) => resolve4(code));
|
|
1676
|
+
child.once("error", () => resolve4(-1));
|
|
1362
1677
|
});
|
|
1363
|
-
const liveness = new Promise((
|
|
1678
|
+
const liveness = new Promise((resolve4) => setTimeout(() => resolve4("alive"), 2e3));
|
|
1364
1679
|
const outcome = await Promise.race([earlyExit, liveness]);
|
|
1365
1680
|
if (outcome !== "alive") {
|
|
1366
1681
|
const tail = readTail(logPath, 4e3);
|
|
@@ -1400,24 +1715,24 @@ ${tail}` : ""}`);
|
|
|
1400
1715
|
}
|
|
1401
1716
|
function readTail(filePath, bytes) {
|
|
1402
1717
|
try {
|
|
1403
|
-
const stat =
|
|
1718
|
+
const stat = fs15.statSync(filePath);
|
|
1404
1719
|
const start = Math.max(0, stat.size - bytes);
|
|
1405
|
-
const fd =
|
|
1720
|
+
const fd = fs15.openSync(filePath, "r");
|
|
1406
1721
|
try {
|
|
1407
1722
|
const buf = Buffer.alloc(stat.size - start);
|
|
1408
|
-
|
|
1723
|
+
fs15.readSync(fd, buf, 0, buf.length, start);
|
|
1409
1724
|
return buf.toString("utf8");
|
|
1410
1725
|
} finally {
|
|
1411
|
-
|
|
1726
|
+
fs15.closeSync(fd);
|
|
1412
1727
|
}
|
|
1413
1728
|
} catch {
|
|
1414
1729
|
return "";
|
|
1415
1730
|
}
|
|
1416
1731
|
}
|
|
1417
1732
|
async function stopEmulator(logger = noopLogger) {
|
|
1418
|
-
if (
|
|
1733
|
+
if (fs15.existsSync(PID_FILE)) {
|
|
1419
1734
|
try {
|
|
1420
|
-
const pid = Number(
|
|
1735
|
+
const pid = Number(fs15.readFileSync(PID_FILE, "utf8").trim());
|
|
1421
1736
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1422
1737
|
try {
|
|
1423
1738
|
process.kill(pid, "SIGTERM");
|
|
@@ -1430,13 +1745,13 @@ async function stopEmulator(logger = noopLogger) {
|
|
|
1430
1745
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1431
1746
|
}
|
|
1432
1747
|
}
|
|
1433
|
-
const [killCmd, ...killArgs] =
|
|
1748
|
+
const [killCmd, ...killArgs] = os7.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
|
|
1434
1749
|
try {
|
|
1435
1750
|
await runProcess({ command: killCmd, args: killArgs, logger });
|
|
1436
1751
|
} catch (err) {
|
|
1437
1752
|
logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
|
|
1438
1753
|
}
|
|
1439
|
-
|
|
1754
|
+
fs15.rm(PID_FILE, { force: true }, (err) => {
|
|
1440
1755
|
if (err) logger.warn(`Could not remove PID file: ${err.message}`);
|
|
1441
1756
|
});
|
|
1442
1757
|
}
|
|
@@ -1559,7 +1874,7 @@ async function getDisplaySize(config, opts = {}) {
|
|
|
1559
1874
|
|
|
1560
1875
|
// src/hdc/wait.ts
|
|
1561
1876
|
function delay(ms, signal) {
|
|
1562
|
-
return new Promise((
|
|
1877
|
+
return new Promise((resolve4, reject) => {
|
|
1563
1878
|
if (signal?.aborted) {
|
|
1564
1879
|
reject(new CancelledError("Wait cancelled."));
|
|
1565
1880
|
return;
|
|
@@ -1574,7 +1889,7 @@ function delay(ms, signal) {
|
|
|
1574
1889
|
};
|
|
1575
1890
|
const timer = setTimeout(() => {
|
|
1576
1891
|
cleanup();
|
|
1577
|
-
|
|
1892
|
+
resolve4();
|
|
1578
1893
|
}, ms);
|
|
1579
1894
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1580
1895
|
});
|
|
@@ -1603,21 +1918,20 @@ async function waitForCondition(opts) {
|
|
|
1603
1918
|
}
|
|
1604
1919
|
|
|
1605
1920
|
// src/hdc/app.ts
|
|
1606
|
-
import * as
|
|
1607
|
-
import * as
|
|
1608
|
-
import { spawn as spawn3 } from "child_process";
|
|
1921
|
+
import * as fs17 from "fs";
|
|
1922
|
+
import * as path17 from "path";
|
|
1609
1923
|
|
|
1610
1924
|
// src/hdc/project.ts
|
|
1611
|
-
import * as
|
|
1612
|
-
import * as
|
|
1925
|
+
import * as fs16 from "fs";
|
|
1926
|
+
import * as path16 from "path";
|
|
1613
1927
|
import JSON53 from "json5";
|
|
1614
1928
|
function readJson5(filePath) {
|
|
1615
|
-
const content =
|
|
1929
|
+
const content = fs16.readFileSync(filePath, "utf-8");
|
|
1616
1930
|
return JSON53.parse(content);
|
|
1617
1931
|
}
|
|
1618
1932
|
function getBundleName(projectDir) {
|
|
1619
|
-
const appJsonPath =
|
|
1620
|
-
if (!
|
|
1933
|
+
const appJsonPath = path16.join(projectDir, "AppScope", "app.json5");
|
|
1934
|
+
if (!fs16.existsSync(appJsonPath)) {
|
|
1621
1935
|
throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
|
|
1622
1936
|
}
|
|
1623
1937
|
const appJson = readJson5(appJsonPath);
|
|
@@ -1627,8 +1941,8 @@ function getBundleName(projectDir) {
|
|
|
1627
1941
|
return appJson.app.bundleName;
|
|
1628
1942
|
}
|
|
1629
1943
|
function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
1630
|
-
const moduleJsonPath =
|
|
1631
|
-
if (!
|
|
1944
|
+
const moduleJsonPath = path16.join(projectDir, moduleName, "src", "main", "module.json5");
|
|
1945
|
+
if (!fs16.existsSync(moduleJsonPath)) {
|
|
1632
1946
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
1633
1947
|
}
|
|
1634
1948
|
const moduleJson = readJson5(moduleJsonPath);
|
|
@@ -1651,8 +1965,8 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
|
1651
1965
|
async function installApp(opts) {
|
|
1652
1966
|
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1653
1967
|
const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
|
|
1654
|
-
const hapPath =
|
|
1655
|
-
if (!
|
|
1968
|
+
const hapPath = path17.isAbsolute(relativeHapPath) ? relativeHapPath : path17.join(opts.projectDir, relativeHapPath);
|
|
1969
|
+
if (!fs17.existsSync(hapPath)) {
|
|
1656
1970
|
throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
|
|
1657
1971
|
}
|
|
1658
1972
|
const result = await hdcExec({ config: opts.config, args: ["install", hapPath], timeoutMs: 3e5 });
|
|
@@ -1669,13 +1983,59 @@ async function installApp(opts) {
|
|
|
1669
1983
|
function detectAaStartFailure(output) {
|
|
1670
1984
|
return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
|
|
1671
1985
|
}
|
|
1986
|
+
function parseMainAbilityFromBmDump(stdout) {
|
|
1987
|
+
const start = stdout.indexOf("{");
|
|
1988
|
+
if (start < 0) return null;
|
|
1989
|
+
let info;
|
|
1990
|
+
try {
|
|
1991
|
+
info = JSON.parse(stdout.slice(start));
|
|
1992
|
+
} catch {
|
|
1993
|
+
return null;
|
|
1994
|
+
}
|
|
1995
|
+
const mods = Array.isArray(info?.hapModuleInfos) ? info.hapModuleInfos : [];
|
|
1996
|
+
const abilityOf = (m) => m?.mainElementName || m?.mainAbility || void 0;
|
|
1997
|
+
const isEntry = (m) => m?.moduleType === 1 || m?.moduleType === "1" || m?.moduleType === "entry";
|
|
1998
|
+
const entry = mods.find((m) => isEntry(m) && abilityOf(m));
|
|
1999
|
+
const any = mods.find((m) => abilityOf(m));
|
|
2000
|
+
return entry && abilityOf(entry) || any && abilityOf(any) || null;
|
|
2001
|
+
}
|
|
2002
|
+
async function resolveInstalledMainAbility(opts) {
|
|
2003
|
+
const res = await shell({
|
|
2004
|
+
config: opts.config,
|
|
2005
|
+
command: `bm dump -n ${opts.bundle}`,
|
|
2006
|
+
deviceSerial: opts.deviceSerial,
|
|
2007
|
+
timeoutMs: 15e3,
|
|
2008
|
+
logger: opts.logger
|
|
2009
|
+
});
|
|
2010
|
+
const ability = parseMainAbilityFromBmDump(res.stdout);
|
|
2011
|
+
if (!ability) {
|
|
2012
|
+
throw new OniroError(
|
|
2013
|
+
`Could not determine the main ability for installed bundle ${opts.bundle} (is it installed?). Pass an explicit ability instead.`
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
2016
|
+
return ability;
|
|
2017
|
+
}
|
|
1672
2018
|
async function launchApp(opts) {
|
|
1673
2019
|
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1674
|
-
|
|
1675
|
-
|
|
2020
|
+
let bundleName;
|
|
2021
|
+
let mainAbility;
|
|
2022
|
+
if (opts.bundleName) {
|
|
2023
|
+
bundleName = opts.bundleName;
|
|
2024
|
+
mainAbility = opts.abilityName ?? await resolveInstalledMainAbility({
|
|
2025
|
+
config: opts.config,
|
|
2026
|
+
bundle: bundleName,
|
|
2027
|
+
deviceSerial: opts.deviceSerial,
|
|
2028
|
+
logger: opts.logger
|
|
2029
|
+
});
|
|
2030
|
+
} else {
|
|
2031
|
+
if (!opts.projectDir) throw new OniroError("launchApp requires either bundleName or projectDir.");
|
|
2032
|
+
bundleName = getBundleName(opts.projectDir);
|
|
2033
|
+
mainAbility = getMainAbility(opts.projectDir, opts.moduleName, opts.abilityName);
|
|
2034
|
+
}
|
|
1676
2035
|
const result = await hdcExec({
|
|
1677
2036
|
config: opts.config,
|
|
1678
|
-
args: ["shell", "aa", "start", "-a", mainAbility, "-b", bundleName]
|
|
2037
|
+
args: ["shell", "aa", "start", "-a", mainAbility, "-b", bundleName],
|
|
2038
|
+
deviceSerial: opts.deviceSerial
|
|
1679
2039
|
});
|
|
1680
2040
|
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
1681
2041
|
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
@@ -1690,11 +2050,17 @@ function listRunningProcesses(config, options = {}) {
|
|
|
1690
2050
|
const logger = options.logger ?? noopLogger;
|
|
1691
2051
|
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
1692
2052
|
const hdc = getHdcPath(config);
|
|
1693
|
-
return new Promise((
|
|
1694
|
-
|
|
2053
|
+
return new Promise((resolve4, reject) => {
|
|
2054
|
+
let proc;
|
|
2055
|
+
try {
|
|
2056
|
+
proc = spawnCompat(hdc, ["track-jpid"]);
|
|
2057
|
+
} catch (err) {
|
|
2058
|
+
reject(new OniroError(`Failed to spawn ${hdc}: ${err instanceof Error ? err.message : String(err)}`, err));
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
1695
2061
|
const processes = [];
|
|
1696
2062
|
let resolved = false;
|
|
1697
|
-
proc.stdout
|
|
2063
|
+
proc.stdout?.on("data", (data) => {
|
|
1698
2064
|
for (const line of data.toString().split("\n").filter(Boolean)) {
|
|
1699
2065
|
const match = line.match(/^(\d+)\s+(.+)$/);
|
|
1700
2066
|
if (!match) continue;
|
|
@@ -1703,13 +2069,13 @@ function listRunningProcesses(config, options = {}) {
|
|
|
1703
2069
|
processes.push({ pid, name });
|
|
1704
2070
|
if (options.targetProcessName && name === options.targetProcessName && !resolved) {
|
|
1705
2071
|
resolved = true;
|
|
1706
|
-
proc
|
|
1707
|
-
|
|
2072
|
+
killProcessTree(proc);
|
|
2073
|
+
resolve4(pid);
|
|
1708
2074
|
return;
|
|
1709
2075
|
}
|
|
1710
2076
|
}
|
|
1711
2077
|
});
|
|
1712
|
-
proc.stderr
|
|
2078
|
+
proc.stderr?.on("data", (data) => {
|
|
1713
2079
|
logger.warn(`[hdc track-jpid] ${data.toString()}`);
|
|
1714
2080
|
});
|
|
1715
2081
|
proc.on("error", (err) => {
|
|
@@ -1721,11 +2087,11 @@ function listRunningProcesses(config, options = {}) {
|
|
|
1721
2087
|
const timeout = setTimeout(() => {
|
|
1722
2088
|
if (resolved) return;
|
|
1723
2089
|
resolved = true;
|
|
1724
|
-
proc
|
|
2090
|
+
killProcessTree(proc);
|
|
1725
2091
|
if (options.targetProcessName) {
|
|
1726
2092
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
1727
2093
|
} else {
|
|
1728
|
-
|
|
2094
|
+
resolve4(processes);
|
|
1729
2095
|
}
|
|
1730
2096
|
}, timeoutMs);
|
|
1731
2097
|
proc.on("close", () => {
|
|
@@ -1735,7 +2101,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
1735
2101
|
if (options.targetProcessName) {
|
|
1736
2102
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
1737
2103
|
} else {
|
|
1738
|
-
|
|
2104
|
+
resolve4(processes);
|
|
1739
2105
|
}
|
|
1740
2106
|
});
|
|
1741
2107
|
});
|
|
@@ -1755,8 +2121,17 @@ async function findRunningProcess(opts) {
|
|
|
1755
2121
|
logger: opts.logger
|
|
1756
2122
|
});
|
|
1757
2123
|
const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
|
|
1758
|
-
if (res.code
|
|
1759
|
-
|
|
2124
|
+
if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
|
|
2125
|
+
try {
|
|
2126
|
+
const list = await listRunningProcesses(opts.config, { timeoutMs: 1200, logger: opts.logger });
|
|
2127
|
+
if (Array.isArray(list)) {
|
|
2128
|
+
const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
|
|
2129
|
+
const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
|
|
2130
|
+
if (match) return match;
|
|
2131
|
+
}
|
|
2132
|
+
} catch {
|
|
2133
|
+
}
|
|
2134
|
+
return null;
|
|
1760
2135
|
}
|
|
1761
2136
|
async function uninstallApp(opts) {
|
|
1762
2137
|
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
@@ -1870,12 +2245,12 @@ async function reboot(opts) {
|
|
|
1870
2245
|
}
|
|
1871
2246
|
|
|
1872
2247
|
// src/hdc/screen.ts
|
|
1873
|
-
import * as
|
|
1874
|
-
import * as
|
|
1875
|
-
import * as
|
|
2248
|
+
import * as fs18 from "fs";
|
|
2249
|
+
import * as os8 from "os";
|
|
2250
|
+
import * as path18 from "path";
|
|
1876
2251
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1877
2252
|
function tmpName(ext) {
|
|
1878
|
-
return
|
|
2253
|
+
return path18.join(os8.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
|
|
1879
2254
|
}
|
|
1880
2255
|
async function takeScreenshot(opts) {
|
|
1881
2256
|
const remote = "/data/local/tmp/oniro_screenshot.jpeg";
|
|
@@ -1885,11 +2260,11 @@ async function takeScreenshot(opts) {
|
|
|
1885
2260
|
const local = tmpName("jpeg");
|
|
1886
2261
|
try {
|
|
1887
2262
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
1888
|
-
const pixels = await
|
|
2263
|
+
const pixels = await fs18.promises.readFile(local);
|
|
1889
2264
|
const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
|
|
1890
2265
|
return { pixels, width: size.width, height: size.height };
|
|
1891
2266
|
} finally {
|
|
1892
|
-
|
|
2267
|
+
fs18.promises.unlink(local).catch(() => {
|
|
1893
2268
|
});
|
|
1894
2269
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
1895
2270
|
});
|
|
@@ -1908,9 +2283,9 @@ async function captureBurst(opts) {
|
|
|
1908
2283
|
const local = tmpName(`burst_${i}.jpeg`);
|
|
1909
2284
|
try {
|
|
1910
2285
|
await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
|
|
1911
|
-
frames.push(await
|
|
2286
|
+
frames.push(await fs18.promises.readFile(local));
|
|
1912
2287
|
} finally {
|
|
1913
|
-
|
|
2288
|
+
fs18.promises.unlink(local).catch(() => {
|
|
1914
2289
|
});
|
|
1915
2290
|
}
|
|
1916
2291
|
}
|
|
@@ -1954,8 +2329,8 @@ function buildInputCommand(opts) {
|
|
|
1954
2329
|
}
|
|
1955
2330
|
async function sendInput(opts) {
|
|
1956
2331
|
const cmd = buildInputCommand(opts);
|
|
1957
|
-
const res = await
|
|
1958
|
-
|
|
2332
|
+
const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2333
|
+
ensureRemoteOk(res, `hdc shell ${cmd}`);
|
|
1959
2334
|
}
|
|
1960
2335
|
function buildGestureCommands(waypoints) {
|
|
1961
2336
|
if (waypoints.length < 2) {
|
|
@@ -1998,32 +2373,47 @@ function waypointsToRawEvents(waypoints, holdStartMs = 0, holdEndMs = 0) {
|
|
|
1998
2373
|
events.push({ type: "up", x: last.x, y: last.y, t: last.t + holdStartMs + holdEndMs });
|
|
1999
2374
|
return events;
|
|
2000
2375
|
}
|
|
2376
|
+
var UITEST_VELOCITY_MIN = 200;
|
|
2377
|
+
var UITEST_VELOCITY_MAX = 4e4;
|
|
2378
|
+
function buildGestureUitestCommand(waypoints, opts = {}) {
|
|
2379
|
+
if (waypoints.length < 2) throw new OniroError("sendGesture requires at least two waypoints.");
|
|
2380
|
+
const a = waypoints[0];
|
|
2381
|
+
const b = waypoints[waypoints.length - 1];
|
|
2382
|
+
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
2383
|
+
const dtMs = b.t - a.t;
|
|
2384
|
+
const op = (opts.holdStartMs ?? 0) > 0 ? "drag" : "swipe";
|
|
2385
|
+
let cmd = `uitest uiInput ${op} ${a.x} ${a.y} ${b.x} ${b.y}`;
|
|
2386
|
+
if (dtMs > 0 && dist > 0) {
|
|
2387
|
+
const v = Math.round(dist / (dtMs / 1e3));
|
|
2388
|
+
cmd += ` ${Math.max(UITEST_VELOCITY_MIN, Math.min(UITEST_VELOCITY_MAX, v))}`;
|
|
2389
|
+
}
|
|
2390
|
+
return cmd;
|
|
2391
|
+
}
|
|
2001
2392
|
async function sendGesture(opts) {
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
events: waypointsToRawEvents(opts.waypoints, opts.holdStartMs, opts.holdEndMs),
|
|
2007
|
-
deviceSerial: opts.deviceSerial,
|
|
2008
|
-
logger: opts.logger
|
|
2009
|
-
});
|
|
2010
|
-
return;
|
|
2393
|
+
if ((opts.holdEndMs ?? 0) > 0) {
|
|
2394
|
+
opts.logger?.warn(
|
|
2395
|
+
"sendGesture: hold-end has no uitest equivalent and is ignored; use sendRawTouch for a trailing hold."
|
|
2396
|
+
);
|
|
2011
2397
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2398
|
+
if (opts.waypoints.length > 2) {
|
|
2399
|
+
opts.logger?.debug(
|
|
2400
|
+
`sendGesture: ${opts.waypoints.length} waypoints collapsed to first\u2192last (uitest has no polyline path).`
|
|
2401
|
+
);
|
|
2015
2402
|
}
|
|
2403
|
+
const cmd = buildGestureUitestCommand(opts.waypoints, { holdStartMs: opts.holdStartMs });
|
|
2404
|
+
const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2405
|
+
ensureRemoteOk(res, `hdc shell ${cmd}`);
|
|
2016
2406
|
}
|
|
2017
2407
|
async function sendRawTouch(opts) {
|
|
2018
2408
|
const cmd = buildRawTouchCommand(opts.events);
|
|
2019
|
-
const res = await
|
|
2020
|
-
|
|
2409
|
+
const res = await shellChecked({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2410
|
+
ensureRemoteOk(res, `hdc shell ${cmd}`);
|
|
2021
2411
|
}
|
|
2022
2412
|
|
|
2023
2413
|
// src/hdc/dump.ts
|
|
2024
|
-
import * as
|
|
2025
|
-
import * as
|
|
2026
|
-
import * as
|
|
2414
|
+
import * as fs19 from "fs";
|
|
2415
|
+
import * as os9 from "os";
|
|
2416
|
+
import * as path19 from "path";
|
|
2027
2417
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
2028
2418
|
function parseBounds(s) {
|
|
2029
2419
|
const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
|
|
@@ -2108,10 +2498,10 @@ async function dumpLayout(opts) {
|
|
|
2108
2498
|
if (!m) throw new OniroError(`Could not parse dumpLayout output:
|
|
2109
2499
|
${dump.stdout}`);
|
|
2110
2500
|
const remote = m[1];
|
|
2111
|
-
const local =
|
|
2501
|
+
const local = path19.join(os9.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
|
|
2112
2502
|
try {
|
|
2113
2503
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2114
|
-
const parsed = JSON.parse(await
|
|
2504
|
+
const parsed = JSON.parse(await fs19.promises.readFile(local, "utf8"));
|
|
2115
2505
|
const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
|
|
2116
2506
|
if (opts.prune === false) {
|
|
2117
2507
|
return { display, tree: parsed };
|
|
@@ -2120,7 +2510,7 @@ ${dump.stdout}`);
|
|
|
2120
2510
|
const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
|
|
2121
2511
|
return { display, tree };
|
|
2122
2512
|
} finally {
|
|
2123
|
-
|
|
2513
|
+
fs19.promises.unlink(local).catch(() => {
|
|
2124
2514
|
});
|
|
2125
2515
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2126
2516
|
});
|
|
@@ -2128,14 +2518,21 @@ ${dump.stdout}`);
|
|
|
2128
2518
|
}
|
|
2129
2519
|
|
|
2130
2520
|
// src/hdc/hilog.ts
|
|
2131
|
-
import { spawn as spawn4 } from "child_process";
|
|
2132
2521
|
function setHilogLevel(opts) {
|
|
2133
2522
|
const logger = opts.logger ?? noopLogger;
|
|
2134
2523
|
const hdc = getHdcPath(opts.config);
|
|
2135
|
-
return new Promise((
|
|
2136
|
-
|
|
2524
|
+
return new Promise((resolve4, reject) => {
|
|
2525
|
+
let child;
|
|
2526
|
+
try {
|
|
2527
|
+
child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
|
|
2528
|
+
} catch (err) {
|
|
2529
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2530
|
+
logger.error(`[hilog] failed to spawn hdc: ${msg}`);
|
|
2531
|
+
reject(err instanceof Error ? err : new Error(msg));
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2137
2534
|
let stderr = "";
|
|
2138
|
-
child.stderr
|
|
2535
|
+
child.stderr?.on("data", (chunk) => {
|
|
2139
2536
|
stderr += chunk.toString();
|
|
2140
2537
|
});
|
|
2141
2538
|
child.once("error", (err) => {
|
|
@@ -2144,7 +2541,7 @@ function setHilogLevel(opts) {
|
|
|
2144
2541
|
});
|
|
2145
2542
|
child.once("close", (code) => {
|
|
2146
2543
|
if (code === 0) {
|
|
2147
|
-
|
|
2544
|
+
resolve4();
|
|
2148
2545
|
} else {
|
|
2149
2546
|
const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
|
|
2150
2547
|
logger.error(`[hilog] ${msg}`);
|
|
@@ -2164,7 +2561,7 @@ function streamHilog(opts) {
|
|
|
2164
2561
|
if (opts.domain !== void 0 && `${opts.domain}` !== "") {
|
|
2165
2562
|
args.push("-D", `${opts.domain}`);
|
|
2166
2563
|
}
|
|
2167
|
-
return
|
|
2564
|
+
return spawnCompat(hdc, args);
|
|
2168
2565
|
}
|
|
2169
2566
|
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+(.*)$/;
|
|
2170
2567
|
function parseHilogLine(line) {
|
|
@@ -2199,7 +2596,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
|
|
|
2199
2596
|
return proc?.pid;
|
|
2200
2597
|
}
|
|
2201
2598
|
function waitForLog(opts) {
|
|
2202
|
-
return new Promise((
|
|
2599
|
+
return new Promise((resolve4, reject) => {
|
|
2203
2600
|
const deadline = Date.now() + opts.timeoutMs;
|
|
2204
2601
|
let settled = false;
|
|
2205
2602
|
let child = null;
|
|
@@ -2210,10 +2607,7 @@ function waitForLog(opts) {
|
|
|
2210
2607
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
2211
2608
|
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2212
2609
|
if (child) {
|
|
2213
|
-
|
|
2214
|
-
child.kill("SIGKILL");
|
|
2215
|
-
} catch {
|
|
2216
|
-
}
|
|
2610
|
+
killProcessTree(child);
|
|
2217
2611
|
child = null;
|
|
2218
2612
|
}
|
|
2219
2613
|
};
|
|
@@ -2221,7 +2615,7 @@ function waitForLog(opts) {
|
|
|
2221
2615
|
if (settled) return;
|
|
2222
2616
|
settled = true;
|
|
2223
2617
|
cleanup();
|
|
2224
|
-
|
|
2618
|
+
resolve4(entry);
|
|
2225
2619
|
};
|
|
2226
2620
|
const fail = (err) => {
|
|
2227
2621
|
if (settled) return;
|
|
@@ -2229,6 +2623,7 @@ function waitForLog(opts) {
|
|
|
2229
2623
|
cleanup();
|
|
2230
2624
|
reject(err);
|
|
2231
2625
|
};
|
|
2626
|
+
const failWith = (err) => fail(err instanceof Error ? err : new Error(String(err)));
|
|
2232
2627
|
const onAbort = () => fail(new CancelledError("waitForLog cancelled."));
|
|
2233
2628
|
const timer = setTimeout(
|
|
2234
2629
|
() => fail(new OniroError(`No log line matched ${String(opts.pattern)} within ${opts.timeoutMs}ms.`)),
|
|
@@ -2258,17 +2653,17 @@ function waitForLog(opts) {
|
|
|
2258
2653
|
if (settled || child !== stream) return;
|
|
2259
2654
|
child = null;
|
|
2260
2655
|
if (Date.now() < deadline) {
|
|
2261
|
-
reconnectTimer = setTimeout(() => void spawnStream(), 1e3);
|
|
2656
|
+
reconnectTimer = setTimeout(() => void spawnStream().catch(failWith), 1e3);
|
|
2262
2657
|
}
|
|
2263
2658
|
};
|
|
2264
2659
|
stream.once("close", onEnd);
|
|
2265
2660
|
stream.once("error", onEnd);
|
|
2266
2661
|
};
|
|
2267
|
-
void spawnStream();
|
|
2662
|
+
void spawnStream().catch(failWith);
|
|
2268
2663
|
});
|
|
2269
2664
|
}
|
|
2270
2665
|
function watchLog(opts) {
|
|
2271
|
-
return new Promise((
|
|
2666
|
+
return new Promise((resolve4, reject) => {
|
|
2272
2667
|
const matches = [];
|
|
2273
2668
|
let settled = false;
|
|
2274
2669
|
let child = null;
|
|
@@ -2277,10 +2672,7 @@ function watchLog(opts) {
|
|
|
2277
2672
|
clearTimeout(timer);
|
|
2278
2673
|
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2279
2674
|
if (child) {
|
|
2280
|
-
|
|
2281
|
-
child.kill("SIGKILL");
|
|
2282
|
-
} catch {
|
|
2283
|
-
}
|
|
2675
|
+
killProcessTree(child);
|
|
2284
2676
|
child = null;
|
|
2285
2677
|
}
|
|
2286
2678
|
};
|
|
@@ -2288,7 +2680,7 @@ function watchLog(opts) {
|
|
|
2288
2680
|
if (settled) return;
|
|
2289
2681
|
settled = true;
|
|
2290
2682
|
cleanup();
|
|
2291
|
-
|
|
2683
|
+
resolve4(opts.dedup === false ? matches : dedupEntries(matches));
|
|
2292
2684
|
};
|
|
2293
2685
|
const onAbort = () => {
|
|
2294
2686
|
if (settled) return;
|
|
@@ -2313,7 +2705,12 @@ function watchLog(opts) {
|
|
|
2313
2705
|
if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
|
|
2314
2706
|
}
|
|
2315
2707
|
});
|
|
2316
|
-
})()
|
|
2708
|
+
})().catch((err) => {
|
|
2709
|
+
if (settled) return;
|
|
2710
|
+
settled = true;
|
|
2711
|
+
cleanup();
|
|
2712
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
2713
|
+
});
|
|
2317
2714
|
});
|
|
2318
2715
|
}
|
|
2319
2716
|
async function dumpLog(opts) {
|
|
@@ -2340,9 +2737,8 @@ async function dumpLog(opts) {
|
|
|
2340
2737
|
}
|
|
2341
2738
|
|
|
2342
2739
|
// src/build/runHvigorw.ts
|
|
2343
|
-
import * as
|
|
2344
|
-
import * as
|
|
2345
|
-
import { spawn as spawn5 } from "child_process";
|
|
2740
|
+
import * as fs20 from "fs";
|
|
2741
|
+
import * as path20 from "path";
|
|
2346
2742
|
function buildHvigorwArgs(opts) {
|
|
2347
2743
|
const args = [opts.task ?? "assembleHap", "--mode", "module"];
|
|
2348
2744
|
args.push("-p", `product=${opts.product ?? "default"}`);
|
|
@@ -2354,19 +2750,23 @@ function buildHvigorwArgs(opts) {
|
|
|
2354
2750
|
return args;
|
|
2355
2751
|
}
|
|
2356
2752
|
function runHvigorw(opts) {
|
|
2357
|
-
const { config
|
|
2753
|
+
const { config } = opts;
|
|
2754
|
+
const projectDir = toLongPath(opts.projectDir);
|
|
2358
2755
|
const logger = opts.logger ?? noopLogger;
|
|
2359
|
-
if (!
|
|
2756
|
+
if (!fs20.existsSync(path20.join(projectDir, "build-profile.json5"))) {
|
|
2360
2757
|
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
2361
2758
|
}
|
|
2362
|
-
if (!
|
|
2759
|
+
if (!fs20.existsSync(getCmdToolsPath(config))) {
|
|
2363
2760
|
throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
|
|
2364
2761
|
}
|
|
2365
2762
|
const hvigorw = getHvigorwPath(config, projectDir);
|
|
2763
|
+
if (!fs20.existsSync(hvigorw)) {
|
|
2764
|
+
throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
|
|
2765
|
+
}
|
|
2366
2766
|
const args = buildHvigorwArgs(opts);
|
|
2367
2767
|
if (process.platform !== "win32") {
|
|
2368
2768
|
try {
|
|
2369
|
-
|
|
2769
|
+
fs20.chmodSync(hvigorw, 493);
|
|
2370
2770
|
} catch {
|
|
2371
2771
|
}
|
|
2372
2772
|
}
|
|
@@ -2374,32 +2774,35 @@ function runHvigorw(opts) {
|
|
|
2374
2774
|
...process.env,
|
|
2375
2775
|
OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
|
|
2376
2776
|
};
|
|
2377
|
-
return new Promise((
|
|
2777
|
+
return new Promise((resolve4, reject) => {
|
|
2378
2778
|
if (opts.abortSignal?.aborted) {
|
|
2379
2779
|
reject(new CancelledError("hvigorw build cancelled."));
|
|
2380
2780
|
return;
|
|
2381
2781
|
}
|
|
2382
2782
|
logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
|
|
2383
|
-
|
|
2783
|
+
let child;
|
|
2784
|
+
try {
|
|
2785
|
+
child = spawnCompat(hvigorw, args, { cwd: projectDir, env });
|
|
2786
|
+
} catch (err) {
|
|
2787
|
+
reject(new OniroError(`Failed to start hvigorw: ${err instanceof Error ? err.message : String(err)}`, err));
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2384
2790
|
let settled = false;
|
|
2385
2791
|
const cleanup = () => opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2386
2792
|
const onAbort = () => {
|
|
2387
2793
|
if (settled) return;
|
|
2388
2794
|
settled = true;
|
|
2389
2795
|
cleanup();
|
|
2390
|
-
|
|
2391
|
-
child.kill("SIGKILL");
|
|
2392
|
-
} catch {
|
|
2393
|
-
}
|
|
2796
|
+
killProcessTree(child);
|
|
2394
2797
|
reject(new CancelledError("hvigorw build cancelled."));
|
|
2395
2798
|
};
|
|
2396
2799
|
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2397
|
-
child.stdout
|
|
2800
|
+
child.stdout?.on("data", (chunk) => {
|
|
2398
2801
|
const text = chunk.toString();
|
|
2399
2802
|
opts.onOutput?.(text, "stdout");
|
|
2400
2803
|
logger.info(text.trimEnd());
|
|
2401
2804
|
});
|
|
2402
|
-
child.stderr
|
|
2805
|
+
child.stderr?.on("data", (chunk) => {
|
|
2403
2806
|
const text = chunk.toString();
|
|
2404
2807
|
opts.onOutput?.(text, "stderr");
|
|
2405
2808
|
logger.warn(text.trimEnd());
|
|
@@ -2415,19 +2818,18 @@ function runHvigorw(opts) {
|
|
|
2415
2818
|
settled = true;
|
|
2416
2819
|
cleanup();
|
|
2417
2820
|
const exitCode = code ?? 1;
|
|
2418
|
-
if (exitCode === 0)
|
|
2821
|
+
if (exitCode === 0) resolve4({ exitCode });
|
|
2419
2822
|
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
2420
2823
|
});
|
|
2421
2824
|
});
|
|
2422
2825
|
}
|
|
2423
2826
|
|
|
2424
2827
|
// src/build/discoverHaps.ts
|
|
2425
|
-
import * as
|
|
2426
|
-
import * as
|
|
2427
|
-
function
|
|
2428
|
-
const
|
|
2429
|
-
|
|
2430
|
-
return first && first !== ".." ? first : "(root)";
|
|
2828
|
+
import * as fs21 from "fs";
|
|
2829
|
+
import * as path21 from "path";
|
|
2830
|
+
function moduleOfHap(hapFileName) {
|
|
2831
|
+
const dash = hapFileName.indexOf("-");
|
|
2832
|
+
return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
|
|
2431
2833
|
}
|
|
2432
2834
|
async function discoverHaps(opts) {
|
|
2433
2835
|
const { projectDir } = opts;
|
|
@@ -2435,14 +2837,14 @@ async function discoverHaps(opts) {
|
|
|
2435
2837
|
const result = {};
|
|
2436
2838
|
const walk = async (dir, depth) => {
|
|
2437
2839
|
if (depth > maxDepth) return;
|
|
2438
|
-
const entries = await
|
|
2840
|
+
const entries = await fs21.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2439
2841
|
for (const e of entries) {
|
|
2440
|
-
const full =
|
|
2842
|
+
const full = path21.join(dir, e.name);
|
|
2441
2843
|
if (e.isDirectory()) {
|
|
2442
2844
|
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
2443
2845
|
await walk(full, depth + 1);
|
|
2444
2846
|
} else if (e.isFile() && e.name.endsWith(".hap")) {
|
|
2445
|
-
const moduleName =
|
|
2847
|
+
const moduleName = moduleOfHap(e.name);
|
|
2446
2848
|
const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
|
|
2447
2849
|
if (e.name.includes("unsigned")) bucket.unsigned.push(full);
|
|
2448
2850
|
else if (e.name.includes("signed")) bucket.signed.push(full);
|
|
@@ -2454,8 +2856,8 @@ async function discoverHaps(opts) {
|
|
|
2454
2856
|
}
|
|
2455
2857
|
|
|
2456
2858
|
// src/build/ohpm.ts
|
|
2457
|
-
import * as
|
|
2458
|
-
import * as
|
|
2859
|
+
import * as fs22 from "fs";
|
|
2860
|
+
import * as path22 from "path";
|
|
2459
2861
|
function runOhpm(opts) {
|
|
2460
2862
|
return runProcess({
|
|
2461
2863
|
command: getOhpmPath(opts.config),
|
|
@@ -2468,7 +2870,7 @@ function runOhpm(opts) {
|
|
|
2468
2870
|
});
|
|
2469
2871
|
}
|
|
2470
2872
|
async function ensureOhModules(opts) {
|
|
2471
|
-
if (
|
|
2873
|
+
if (fs22.existsSync(path22.join(opts.projectDir, "oh_modules"))) {
|
|
2472
2874
|
return { installed: false };
|
|
2473
2875
|
}
|
|
2474
2876
|
const res = await runOhpm({
|
|
@@ -2520,10 +2922,11 @@ async function buildHap(opts) {
|
|
|
2520
2922
|
const logger = scopedLogger(opts.logger ?? noopLogger, "build");
|
|
2521
2923
|
const warnings = [];
|
|
2522
2924
|
const start = Date.now();
|
|
2925
|
+
const projectDir = toLongPath(opts.projectDir);
|
|
2523
2926
|
if (opts.autoInstallDeps !== false) {
|
|
2524
2927
|
const r = await ensureOhModules({
|
|
2525
2928
|
config: opts.config,
|
|
2526
|
-
projectDir
|
|
2929
|
+
projectDir,
|
|
2527
2930
|
abortSignal: opts.abortSignal,
|
|
2528
2931
|
onOutput: opts.onOutput,
|
|
2529
2932
|
logger: opts.logger
|
|
@@ -2532,7 +2935,7 @@ async function buildHap(opts) {
|
|
|
2532
2935
|
}
|
|
2533
2936
|
const { exitCode } = await runHvigorw({
|
|
2534
2937
|
config: opts.config,
|
|
2535
|
-
projectDir
|
|
2938
|
+
projectDir,
|
|
2536
2939
|
product: opts.product,
|
|
2537
2940
|
module: opts.module,
|
|
2538
2941
|
buildMode: opts.mode,
|
|
@@ -2542,7 +2945,7 @@ async function buildHap(opts) {
|
|
|
2542
2945
|
onOutput: opts.onOutput,
|
|
2543
2946
|
logger: opts.logger
|
|
2544
2947
|
});
|
|
2545
|
-
const discoveredHaps = await discoverHaps({ projectDir
|
|
2948
|
+
const discoveredHaps = await discoverHaps({ projectDir });
|
|
2546
2949
|
const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
|
|
2547
2950
|
if (!anySigned) {
|
|
2548
2951
|
warnings.push(
|
|
@@ -2563,21 +2966,21 @@ function isValidBundleName(bundleName) {
|
|
|
2563
2966
|
}
|
|
2564
2967
|
|
|
2565
2968
|
// src/project/templates.ts
|
|
2566
|
-
import * as
|
|
2567
|
-
import * as
|
|
2969
|
+
import * as fs23 from "fs";
|
|
2970
|
+
import * as path23 from "path";
|
|
2568
2971
|
function toHumanTemplateName(folderName) {
|
|
2569
2972
|
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
2570
2973
|
}
|
|
2571
2974
|
function listTemplates(templateRoot) {
|
|
2572
|
-
if (!
|
|
2573
|
-
const entries =
|
|
2975
|
+
if (!fs23.existsSync(templateRoot)) return [];
|
|
2976
|
+
const entries = fs23.readdirSync(templateRoot, { withFileTypes: true });
|
|
2574
2977
|
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
2575
|
-
const dir =
|
|
2576
|
-
const metaPath =
|
|
2978
|
+
const dir = path23.join(templateRoot, e.name);
|
|
2979
|
+
const metaPath = path23.join(dir, "template.json");
|
|
2577
2980
|
let meta = {};
|
|
2578
|
-
if (
|
|
2981
|
+
if (fs23.existsSync(metaPath)) {
|
|
2579
2982
|
try {
|
|
2580
|
-
meta = JSON.parse(
|
|
2983
|
+
meta = JSON.parse(fs23.readFileSync(metaPath, "utf8"));
|
|
2581
2984
|
} catch {
|
|
2582
2985
|
}
|
|
2583
2986
|
}
|
|
@@ -2592,57 +2995,57 @@ function listTemplates(templateRoot) {
|
|
|
2592
2995
|
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
2593
2996
|
const required = [
|
|
2594
2997
|
"build-profile.json5",
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2998
|
+
path23.join("AppScope", "app.json5"),
|
|
2999
|
+
path23.join(defaultModuleName, "src", "main", "module.json5"),
|
|
3000
|
+
path23.join(defaultModuleName, "oh-package.json5"),
|
|
2598
3001
|
"hvigorfile.ts"
|
|
2599
3002
|
];
|
|
2600
|
-
return required.filter((rel) => !
|
|
3003
|
+
return required.filter((rel) => !fs23.existsSync(path23.join(templateDir, rel)));
|
|
2601
3004
|
}
|
|
2602
3005
|
|
|
2603
3006
|
// src/project/createScaffold.ts
|
|
2604
|
-
import * as
|
|
2605
|
-
import * as
|
|
3007
|
+
import * as fs25 from "fs";
|
|
3008
|
+
import * as path24 from "path";
|
|
2606
3009
|
|
|
2607
3010
|
// src/project/jsonHelpers.ts
|
|
2608
|
-
import * as
|
|
3011
|
+
import * as fs24 from "fs";
|
|
2609
3012
|
import JSON54 from "json5";
|
|
2610
3013
|
function readJson5File(filePath) {
|
|
2611
|
-
return JSON54.parse(
|
|
3014
|
+
return JSON54.parse(fs24.readFileSync(filePath, "utf8"));
|
|
2612
3015
|
}
|
|
2613
3016
|
function writeJson5File(filePath, value) {
|
|
2614
|
-
|
|
3017
|
+
fs24.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
2615
3018
|
}
|
|
2616
3019
|
function readJsonFile(filePath) {
|
|
2617
|
-
return JSON.parse(
|
|
3020
|
+
return JSON.parse(fs24.readFileSync(filePath, "utf8"));
|
|
2618
3021
|
}
|
|
2619
3022
|
function writeJsonFile(filePath, value) {
|
|
2620
|
-
|
|
3023
|
+
fs24.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
2621
3024
|
}
|
|
2622
3025
|
|
|
2623
3026
|
// src/project/createScaffold.ts
|
|
2624
3027
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
2625
3028
|
async function pathExists(p) {
|
|
2626
3029
|
try {
|
|
2627
|
-
await
|
|
3030
|
+
await fs25.promises.access(p);
|
|
2628
3031
|
return true;
|
|
2629
3032
|
} catch {
|
|
2630
3033
|
return false;
|
|
2631
3034
|
}
|
|
2632
3035
|
}
|
|
2633
3036
|
async function copyDirRecursive(srcDir, destDir) {
|
|
2634
|
-
await
|
|
2635
|
-
const entries = await
|
|
3037
|
+
await fs25.promises.mkdir(destDir, { recursive: true });
|
|
3038
|
+
const entries = await fs25.promises.readdir(srcDir, { withFileTypes: true });
|
|
2636
3039
|
for (const entry of entries) {
|
|
2637
|
-
const src =
|
|
2638
|
-
const dest =
|
|
3040
|
+
const src = path24.join(srcDir, entry.name);
|
|
3041
|
+
const dest = path24.join(destDir, entry.name);
|
|
2639
3042
|
if (entry.isDirectory()) {
|
|
2640
3043
|
await copyDirRecursive(src, dest);
|
|
2641
3044
|
} else if (entry.isSymbolicLink()) {
|
|
2642
3045
|
continue;
|
|
2643
3046
|
} else if (entry.isFile()) {
|
|
2644
3047
|
if (entry.name === "template.json") continue;
|
|
2645
|
-
await
|
|
3048
|
+
await fs25.promises.copyFile(src, dest);
|
|
2646
3049
|
}
|
|
2647
3050
|
}
|
|
2648
3051
|
}
|
|
@@ -2650,9 +3053,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
|
|
|
2650
3053
|
const stack = [projectDir];
|
|
2651
3054
|
while (stack.length > 0) {
|
|
2652
3055
|
const currentDir = stack.pop();
|
|
2653
|
-
const entries = await
|
|
3056
|
+
const entries = await fs25.promises.readdir(currentDir, { withFileTypes: true });
|
|
2654
3057
|
for (const entry of entries) {
|
|
2655
|
-
const fullPath =
|
|
3058
|
+
const fullPath = path24.join(currentDir, entry.name);
|
|
2656
3059
|
if (entry.isDirectory()) {
|
|
2657
3060
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
2658
3061
|
stack.push(fullPath);
|
|
@@ -2674,23 +3077,23 @@ function toJavaPropertiesPath(p) {
|
|
|
2674
3077
|
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
2675
3078
|
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
2676
3079
|
`;
|
|
2677
|
-
|
|
3080
|
+
fs25.writeFileSync(path24.join(projectDir, "local.properties"), content, "utf8");
|
|
2678
3081
|
}
|
|
2679
3082
|
function renameIfExists(fromPath, toPath) {
|
|
2680
|
-
if (
|
|
2681
|
-
|
|
3083
|
+
if (fs25.existsSync(fromPath) && fromPath !== toPath) {
|
|
3084
|
+
fs25.renameSync(fromPath, toPath);
|
|
2682
3085
|
}
|
|
2683
3086
|
}
|
|
2684
3087
|
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
2685
|
-
const appJsonPath =
|
|
2686
|
-
if (
|
|
3088
|
+
const appJsonPath = path24.join(projectDir, "AppScope", "app.json5");
|
|
3089
|
+
if (fs25.existsSync(appJsonPath)) {
|
|
2687
3090
|
const appJson = readJson5File(appJsonPath);
|
|
2688
3091
|
appJson.app = appJson.app ?? {};
|
|
2689
3092
|
appJson.app.bundleName = args.bundleName;
|
|
2690
3093
|
writeJson5File(appJsonPath, appJson);
|
|
2691
3094
|
}
|
|
2692
|
-
const stringsPath =
|
|
2693
|
-
if (
|
|
3095
|
+
const stringsPath = path24.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
3096
|
+
if (fs25.existsSync(stringsPath)) {
|
|
2694
3097
|
const strings = readJsonFile(stringsPath);
|
|
2695
3098
|
if (Array.isArray(strings.string)) {
|
|
2696
3099
|
const appName = strings.string.find((s) => s.name === "app_name");
|
|
@@ -2698,8 +3101,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
2698
3101
|
}
|
|
2699
3102
|
writeJsonFile(stringsPath, strings);
|
|
2700
3103
|
}
|
|
2701
|
-
const buildProfilePath =
|
|
2702
|
-
if (
|
|
3104
|
+
const buildProfilePath = path24.join(projectDir, "build-profile.json5");
|
|
3105
|
+
if (fs25.existsSync(buildProfilePath)) {
|
|
2703
3106
|
const buildProfile = readJson5File(buildProfilePath);
|
|
2704
3107
|
buildProfile.app = buildProfile.app ?? {};
|
|
2705
3108
|
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
@@ -2712,23 +3115,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
2712
3115
|
}
|
|
2713
3116
|
writeJson5File(buildProfilePath, buildProfile);
|
|
2714
3117
|
}
|
|
2715
|
-
const moduleJsonPath =
|
|
2716
|
-
if (
|
|
3118
|
+
const moduleJsonPath = path24.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
3119
|
+
if (fs25.existsSync(moduleJsonPath)) {
|
|
2717
3120
|
const moduleJson = readJson5File(moduleJsonPath);
|
|
2718
3121
|
moduleJson.module = moduleJson.module ?? {};
|
|
2719
3122
|
moduleJson.module.name = args.moduleName;
|
|
2720
3123
|
writeJson5File(moduleJsonPath, moduleJson);
|
|
2721
3124
|
}
|
|
2722
|
-
const moduleOhPackagePath =
|
|
2723
|
-
if (
|
|
3125
|
+
const moduleOhPackagePath = path24.join(projectDir, args.moduleName, "oh-package.json5");
|
|
3126
|
+
if (fs25.existsSync(moduleOhPackagePath)) {
|
|
2724
3127
|
const pkg = readJson5File(moduleOhPackagePath);
|
|
2725
3128
|
pkg.name = args.moduleName;
|
|
2726
3129
|
writeJson5File(moduleOhPackagePath, pkg);
|
|
2727
3130
|
}
|
|
2728
|
-
const vscodeDir =
|
|
2729
|
-
|
|
3131
|
+
const vscodeDir = path24.join(projectDir, ".vscode");
|
|
3132
|
+
fs25.mkdirSync(vscodeDir, { recursive: true });
|
|
2730
3133
|
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
2731
|
-
writeJsonFile(
|
|
3134
|
+
writeJsonFile(path24.join(vscodeDir, "settings.json"), {
|
|
2732
3135
|
"oniro.hapPath": hapPath,
|
|
2733
3136
|
"files.associations": { "*.json5": "jsonc" }
|
|
2734
3137
|
});
|
|
@@ -2745,7 +3148,7 @@ async function createScaffold(opts) {
|
|
|
2745
3148
|
if (!opts.location || !await pathExists(opts.location)) {
|
|
2746
3149
|
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
2747
3150
|
}
|
|
2748
|
-
const templateDir =
|
|
3151
|
+
const templateDir = path24.join(opts.templateRoot, opts.templateId);
|
|
2749
3152
|
if (!await pathExists(templateDir)) {
|
|
2750
3153
|
throw new OniroError(`Template not found: ${templateDir}`);
|
|
2751
3154
|
}
|
|
@@ -2758,17 +3161,17 @@ async function createScaffold(opts) {
|
|
|
2758
3161
|
throw new OniroError(`Template '${opts.templateId}' is missing required files:
|
|
2759
3162
|
- ${missing.join("\n- ")}`);
|
|
2760
3163
|
}
|
|
2761
|
-
const projectDir =
|
|
3164
|
+
const projectDir = path24.join(opts.location, opts.projectName);
|
|
2762
3165
|
if (await pathExists(projectDir)) {
|
|
2763
3166
|
if (!opts.overwrite) {
|
|
2764
3167
|
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
2765
3168
|
}
|
|
2766
|
-
await
|
|
3169
|
+
await fs25.promises.rm(projectDir, { recursive: true, force: true });
|
|
2767
3170
|
}
|
|
2768
3171
|
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
2769
3172
|
await copyDirRecursive(templateDir, projectDir);
|
|
2770
3173
|
if (moduleName !== defaultModuleName) {
|
|
2771
|
-
renameIfExists(
|
|
3174
|
+
renameIfExists(path24.join(projectDir, defaultModuleName), path24.join(projectDir, moduleName));
|
|
2772
3175
|
}
|
|
2773
3176
|
updateTemplateConfigs(
|
|
2774
3177
|
projectDir,
|
|
@@ -2785,11 +3188,11 @@ async function createScaffold(opts) {
|
|
|
2785
3188
|
}
|
|
2786
3189
|
|
|
2787
3190
|
// src/project/listModules.ts
|
|
2788
|
-
import * as
|
|
2789
|
-
import * as
|
|
3191
|
+
import * as fs26 from "fs";
|
|
3192
|
+
import * as path25 from "path";
|
|
2790
3193
|
function listModules(opts) {
|
|
2791
|
-
const buildProfile =
|
|
2792
|
-
if (!
|
|
3194
|
+
const buildProfile = path25.join(opts.projectDir, "build-profile.json5");
|
|
3195
|
+
if (!fs26.existsSync(buildProfile)) {
|
|
2793
3196
|
throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
|
|
2794
3197
|
}
|
|
2795
3198
|
const parsed = readJson5File(buildProfile);
|
|
@@ -2801,12 +3204,12 @@ function listModules(opts) {
|
|
|
2801
3204
|
}
|
|
2802
3205
|
|
|
2803
3206
|
// src/project/abilities.ts
|
|
2804
|
-
import * as
|
|
2805
|
-
import * as
|
|
3207
|
+
import * as fs27 from "fs";
|
|
3208
|
+
import * as path26 from "path";
|
|
2806
3209
|
function listAbilities(opts) {
|
|
2807
3210
|
const moduleName = opts.moduleName ?? "entry";
|
|
2808
|
-
const moduleJsonPath =
|
|
2809
|
-
if (!
|
|
3211
|
+
const moduleJsonPath = path26.join(opts.projectDir, moduleName, "src", "main", "module.json5");
|
|
3212
|
+
if (!fs27.existsSync(moduleJsonPath)) {
|
|
2810
3213
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
2811
3214
|
}
|
|
2812
3215
|
const parsed = readJson5File(moduleJsonPath);
|
|
@@ -2814,12 +3217,12 @@ function listAbilities(opts) {
|
|
|
2814
3217
|
}
|
|
2815
3218
|
|
|
2816
3219
|
// src/project/prepareSigning.ts
|
|
2817
|
-
import * as
|
|
2818
|
-
import * as
|
|
3220
|
+
import * as fs28 from "fs";
|
|
3221
|
+
import * as path27 from "path";
|
|
2819
3222
|
function hasSigningArtifacts(dir) {
|
|
2820
|
-
if (!
|
|
2821
|
-
const buildProfile =
|
|
2822
|
-
if (!
|
|
3223
|
+
if (!fs28.existsSync(path27.join(dir, "signatures"))) return false;
|
|
3224
|
+
const buildProfile = path27.join(dir, "build-profile.json5");
|
|
3225
|
+
if (!fs28.existsSync(buildProfile)) return false;
|
|
2823
3226
|
try {
|
|
2824
3227
|
const parsed = readJson5File(buildProfile);
|
|
2825
3228
|
return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
|
|
@@ -2909,8 +3312,8 @@ async function diffHapAssets(opts) {
|
|
|
2909
3312
|
}
|
|
2910
3313
|
|
|
2911
3314
|
// src/install/apply.ts
|
|
2912
|
-
import * as
|
|
2913
|
-
import * as
|
|
3315
|
+
import * as fs29 from "fs";
|
|
3316
|
+
import * as path28 from "path";
|
|
2914
3317
|
function decideInstallMethod(s) {
|
|
2915
3318
|
if (!s.signInfoInconsistent) return "replace";
|
|
2916
3319
|
if (s.isSystemBundle && !s.allowUninstall) return "refuse";
|
|
@@ -2931,7 +3334,7 @@ async function pidOf(config, bundle, deviceSerial) {
|
|
|
2931
3334
|
async function resolveHapPath(opts) {
|
|
2932
3335
|
let hapPath = opts.hapPath;
|
|
2933
3336
|
if (hapPath) {
|
|
2934
|
-
if (!
|
|
3337
|
+
if (!path28.isAbsolute(hapPath) && opts.projectDir) hapPath = path28.join(opts.projectDir, hapPath);
|
|
2935
3338
|
} else {
|
|
2936
3339
|
if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
|
|
2937
3340
|
const haps = await discoverHaps({ projectDir: opts.projectDir });
|
|
@@ -2950,7 +3353,7 @@ async function resolveHapPath(opts) {
|
|
|
2950
3353
|
hapPath = withSigned[0][1].signed[0];
|
|
2951
3354
|
}
|
|
2952
3355
|
}
|
|
2953
|
-
if (!
|
|
3356
|
+
if (!fs29.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
|
|
2954
3357
|
return hapPath;
|
|
2955
3358
|
}
|
|
2956
3359
|
async function applyChanges(opts) {
|
|
@@ -2960,7 +3363,7 @@ async function applyChanges(opts) {
|
|
|
2960
3363
|
const allowUninstall = opts.allowUninstall ?? false;
|
|
2961
3364
|
const hapPath = await resolveHapPath(opts);
|
|
2962
3365
|
let assetsChanged = false;
|
|
2963
|
-
if (opts.installedHapPath &&
|
|
3366
|
+
if (opts.installedHapPath && fs29.existsSync(opts.installedHapPath)) {
|
|
2964
3367
|
try {
|
|
2965
3368
|
const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
|
|
2966
3369
|
assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
|
|
@@ -3002,7 +3405,7 @@ async function applyChanges(opts) {
|
|
|
3002
3405
|
}
|
|
3003
3406
|
|
|
3004
3407
|
// src/index.ts
|
|
3005
|
-
var VERSION = "0.
|
|
3408
|
+
var VERSION = "0.9.0";
|
|
3006
3409
|
export {
|
|
3007
3410
|
ALL_SDKS,
|
|
3008
3411
|
APL_VALUES,
|
|
@@ -3011,22 +3414,29 @@ export {
|
|
|
3011
3414
|
ChecksumMismatchError,
|
|
3012
3415
|
CmdToolsNotInstalledError,
|
|
3013
3416
|
CommandFailedError,
|
|
3417
|
+
INSTALL_TMP_DIRNAME,
|
|
3418
|
+
InsufficientSpaceError,
|
|
3014
3419
|
OHOS_URL_BASE,
|
|
3015
3420
|
OniroError,
|
|
3016
3421
|
SdkNotInstalledError,
|
|
3422
|
+
TMP_DIR_HINT,
|
|
3017
3423
|
UnsupportedPlatformError,
|
|
3018
3424
|
VERSION,
|
|
3019
3425
|
applyChanges,
|
|
3020
3426
|
attemptHdcConnection,
|
|
3427
|
+
buildCmdWrapper,
|
|
3021
3428
|
buildGestureCommands,
|
|
3429
|
+
buildGestureUitestCommand,
|
|
3022
3430
|
buildHap,
|
|
3023
3431
|
buildHvigorwArgs,
|
|
3024
3432
|
buildInputCommand,
|
|
3025
3433
|
buildRawTouchCommand,
|
|
3026
3434
|
captureBurst,
|
|
3027
3435
|
consoleLogger,
|
|
3436
|
+
createInstallTempDir,
|
|
3028
3437
|
createMaterial,
|
|
3029
3438
|
createScaffold,
|
|
3439
|
+
createTempWorkDir,
|
|
3030
3440
|
decideInstallMethod,
|
|
3031
3441
|
decideReboot,
|
|
3032
3442
|
decryptPwd,
|
|
@@ -3047,14 +3457,19 @@ export {
|
|
|
3047
3457
|
dumpScreen,
|
|
3048
3458
|
dumpWindow,
|
|
3049
3459
|
encryptPwd,
|
|
3460
|
+
ensureFreeSpace,
|
|
3050
3461
|
ensureOhModules,
|
|
3051
3462
|
ensureOk,
|
|
3463
|
+
ensureRemoteOk,
|
|
3464
|
+
escapeCmdArgument,
|
|
3465
|
+
escapeCmdCommand,
|
|
3052
3466
|
extractTarball,
|
|
3053
3467
|
extractZipWithProgress,
|
|
3054
3468
|
findAppProcessId,
|
|
3055
3469
|
findCmdToolsSourceDir,
|
|
3056
3470
|
findRunningProcess,
|
|
3057
3471
|
forceStop,
|
|
3472
|
+
formatBytes,
|
|
3058
3473
|
generateSigningConfigs,
|
|
3059
3474
|
getBundleName,
|
|
3060
3475
|
getCmdToolsBin,
|
|
@@ -3064,6 +3479,7 @@ export {
|
|
|
3064
3479
|
getDeviceInfo,
|
|
3065
3480
|
getDisplaySize,
|
|
3066
3481
|
getEmulatorDir,
|
|
3482
|
+
getFreeSpaceBytes,
|
|
3067
3483
|
getHdcPath,
|
|
3068
3484
|
getHvigorwPath,
|
|
3069
3485
|
getInstalledSdks,
|
|
@@ -3081,14 +3497,18 @@ export {
|
|
|
3081
3497
|
installEmulator,
|
|
3082
3498
|
isCmdToolsInstalled,
|
|
3083
3499
|
isEmulatorInstalled,
|
|
3500
|
+
isOutOfSpaceError,
|
|
3501
|
+
isRamBackedPath,
|
|
3084
3502
|
isValidBundleName,
|
|
3085
3503
|
isValidProjectName,
|
|
3504
|
+
killProcessTree,
|
|
3086
3505
|
launchApp,
|
|
3087
3506
|
listAbilities,
|
|
3088
3507
|
listDevices,
|
|
3089
3508
|
listModules,
|
|
3090
3509
|
listRunningProcesses,
|
|
3091
3510
|
listTemplates,
|
|
3511
|
+
needsCmdWrapper,
|
|
3092
3512
|
nonInteractivePrompter,
|
|
3093
3513
|
noopLogger,
|
|
3094
3514
|
noopProgress,
|
|
@@ -3098,6 +3518,8 @@ export {
|
|
|
3098
3518
|
parseCodelinterFindings,
|
|
3099
3519
|
parseDeviceList,
|
|
3100
3520
|
parseHilogLine,
|
|
3521
|
+
parseMainAbilityFromBmDump,
|
|
3522
|
+
parseRemoteExit,
|
|
3101
3523
|
pickSigningKind,
|
|
3102
3524
|
prepareSigning,
|
|
3103
3525
|
pruneLayout,
|
|
@@ -3108,6 +3530,8 @@ export {
|
|
|
3108
3530
|
removeCmdTools,
|
|
3109
3531
|
removeEmulator,
|
|
3110
3532
|
removeSdk,
|
|
3533
|
+
removeTempWorkDir,
|
|
3534
|
+
resolveTmpRoot,
|
|
3111
3535
|
runCodelinter,
|
|
3112
3536
|
runHvigorw,
|
|
3113
3537
|
runOhpm,
|
|
@@ -3120,11 +3544,15 @@ export {
|
|
|
3120
3544
|
sendRawTouch,
|
|
3121
3545
|
setHilogLevel,
|
|
3122
3546
|
shell,
|
|
3547
|
+
shellChecked,
|
|
3548
|
+
spawnCompat,
|
|
3123
3549
|
startEmulator,
|
|
3124
3550
|
staticConfig,
|
|
3125
3551
|
stopEmulator,
|
|
3126
3552
|
streamHilog,
|
|
3127
3553
|
takeScreenshot,
|
|
3554
|
+
toLongPath,
|
|
3555
|
+
toSpaceError,
|
|
3128
3556
|
uninstallApp,
|
|
3129
3557
|
validateTemplateLayout,
|
|
3130
3558
|
verifySha256,
|
|
@@ -3133,6 +3561,7 @@ export {
|
|
|
3133
3561
|
waitForCondition,
|
|
3134
3562
|
waitForLog,
|
|
3135
3563
|
watchLog,
|
|
3564
|
+
waypointsToRawEvents,
|
|
3136
3565
|
writeJson5File,
|
|
3137
3566
|
writeJsonFile
|
|
3138
3567
|
};
|