@arkts/sdk-downloader
Version:
Download and extract the ArkTS SDK.
439 lines (433 loc) • 17.4 kB
JavaScript
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import crypto from "node:crypto";
import { computed } from "alien-signals";
import fg from "fast-glob";
import mitt from "mitt";
import progress from "progress-stream";
import * as tar from "tar";
import * as unzipper from "unzipper";
import http from "node:http";
import https from "node:https";
import { Buffer } from "node:buffer";
//#region src/errors/download.ts
var DownloadError = class extends Error {
constructor(code, options) {
super(options?.message);
this.code = code;
this.cause = options?.cause;
}
};
(function(_DownloadError) {
let Code = /* @__PURE__ */ function(Code$1) {
Code$1["DownloadFailed"] = "DOWNLOAD_FAILED";
Code$1["ZipExtractionFailed"] = "ZIP_EXTRACTION_FAILED";
Code$1["InvalidUrl"] = "INVALID_URL";
Code$1["Sha256Mismatch"] = "SHA256_MISMATCH";
Code$1["FileNotFound"] = "FILE_NOT_FOUND";
Code$1["InvalidSha256"] = "INVALID_SHA256";
Code$1["FileReadError"] = "FILE_READ_ERROR";
return Code$1;
}({});
_DownloadError.Code = Code;
})(DownloadError || (DownloadError = {}));
//#endregion
//#region src/enums/sdk.ts
let SdkVersion = /* @__PURE__ */ function(SdkVersion$1) {
SdkVersion$1["API10"] = "4.0.0";
SdkVersion$1["API11"] = "4.1.0";
SdkVersion$1["API12"] = "5.0.0";
SdkVersion$1["API13"] = "5.0.1";
SdkVersion$1["API14"] = "5.0.2";
SdkVersion$1["API15"] = "5.0.3";
SdkVersion$1["API18"] = "5.1.0";
SdkVersion$1["API20"] = "6.0.0-Beta1";
return SdkVersion$1;
}({});
let SdkArch = /* @__PURE__ */ function(SdkArch$1) {
SdkArch$1[SdkArch$1["X86"] = 0] = "X86";
SdkArch$1[SdkArch$1["ARM"] = 1] = "ARM";
return SdkArch$1;
}({});
let SdkOS = /* @__PURE__ */ function(SdkOS$1) {
SdkOS$1[SdkOS$1["MacOS"] = 0] = "MacOS";
SdkOS$1[SdkOS$1["Windows"] = 1] = "Windows";
SdkOS$1[SdkOS$1["Linux"] = 2] = "Linux";
return SdkOS$1;
}({});
const urls = {
[SdkVersion.API10]: {
[SdkArch.X86]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/4.0-Release/ohos-sdk-mac-public.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/4.0-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: "https://mirrors.huaweicloud.com/harmonyos/os/4.0-Release/ohos-sdk-windows_linux-public.tar.gz"
},
[SdkArch.ARM]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/4.0-Release/L2-SDK-MAC-M1-PUBLIC.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/4.0-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: null
}
},
[SdkVersion.API11]: {
[SdkArch.X86]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/4.1-Release/ohos-sdk-mac-public-signed.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/4.1-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: "https://mirrors.huaweicloud.com/harmonyos/os/4.1-Release/ohos-sdk-windows_linux-public.tar.gz"
},
[SdkArch.ARM]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/4.1-Release/L2-SDK-MAC-M1-PUBLIC.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/4.1-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: null
}
},
[SdkVersion.API12]: {
[SdkArch.X86]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.0-Release/ohos-sdk-mac-public.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.0-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.0-Release/ohos-sdk-windows_linux-public.tar.gz"
},
[SdkArch.ARM]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.0-Release/L2-SDK-MAC-M1-PUBLIC.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.0-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: null
}
},
[SdkVersion.API13]: {
[SdkArch.X86]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.1-Release/ohos-sdk-mac-public.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.1-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.1-Release/ohos-sdk-windows_linux-public.tar.gz"
},
[SdkArch.ARM]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.1-Release/L2-SDK-MAC-M1-PUBLIC.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.1-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: null
}
},
[SdkVersion.API14]: {
[SdkArch.X86]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.2-Release/ohos-sdk-mac-public.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.2-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.2-Release/ohos-sdk-windows_linux-public.tar.gz"
},
[SdkArch.ARM]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.2-Release/L2-SDK-MAC-M1-PUBLIC.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.2-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: null
}
},
[SdkVersion.API15]: {
[SdkArch.X86]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.3-Release/ohos-sdk-mac-public.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.3-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.3-Release/ohos-sdk-windows_linux-public.tar.gz"
},
[SdkArch.ARM]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.3-Release/L2-SDK-MAC-M1-PUBLIC.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.0.3-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: null
}
},
[SdkVersion.API18]: {
[SdkArch.X86]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.1.0-Release/ohos-sdk-mac-public.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.1.0-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: "https://mirrors.huaweicloud.com/harmonyos/os/5.1.0-Release/ohos-sdk-windows_linux-public.tar.gz"
},
[SdkArch.ARM]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/5.1.0-Release/L2-SDK-MAC-M1-PUBLIC.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/5.1.0-Release/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: null
}
},
[SdkVersion.API20]: {
[SdkArch.X86]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/6.0-Beta1/ohos-sdk-mac-public.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/6.0-Beta1/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: "https://mirrors.huaweicloud.com/harmonyos/os/6.0-Beta1/ohos-sdk-windows_linux-public.tar.gz"
},
[SdkArch.ARM]: {
[SdkOS.MacOS]: "https://mirrors.huaweicloud.com/harmonyos/os/6.0-Beta1/L2-SDK-MAC-M1-PUBLIC.tar.gz",
[SdkOS.Windows]: "https://mirrors.huaweicloud.com/harmonyos/os/6.0-Beta1/ohos-sdk-windows_linux-public.tar.gz",
[SdkOS.Linux]: null
}
}
};
/**
* Get the SDK URL for a given version, architecture, and operating system.
* @param version - The version of the SDK.
* @param arch - The architecture of the SDK.
* @param os - The operating system of the SDK.
* @returns The URL of the SDK.
*/
function getSdkUrl(version, arch, os) {
return urls?.[version]?.[arch]?.[os] ?? null;
}
/**
* Get all the SDK URLs.
* @returns The SDK URLs.
*/
function getSdkUrls() {
return urls;
}
//#endregion
//#region src/options.ts
function resolveDownloadOptions(options) {
const url = typeof options.url === "string" ? options.url : getSdkUrl(options.url.version, options.url.arch, options.url.os);
if (!url) throw new DownloadError(DownloadError.Code.InvalidUrl);
if (!fs.existsSync(options.cacheDir)) fs.mkdirSync(options.cacheDir, { recursive: true });
options.tempFilePath = options.tempFilePath ? path.resolve(options.tempFilePath) : path.resolve(path.join(options.cacheDir, "download.tmp.tar.gz"));
if (!fs.existsSync(path.dirname(options.tempFilePath))) fs.mkdirSync(path.dirname(options.tempFilePath), { recursive: true });
return {
...options,
url,
requester: options.requestType === "http" ? http : https,
tempFilePath: options.tempFilePath,
targetDir: path.resolve(options.targetDir),
resumeDownload: typeof options.resumeDownload === "boolean" ? options.resumeDownload : true
};
}
//#endregion
//#region src/request.ts
function makeRequest(resolvedOptions, startByte, extraOptions) {
return new Promise((resolve, reject) => {
resolvedOptions.requester.get(resolvedOptions.url, {
method: "GET",
headers: startByte ? { Range: `bytes=${startByte}-` } : void 0,
...extraOptions
}, (res) => {
if (res.statusCode === 416 || res.statusCode === 206) resolve(res);
else if (res.statusCode !== 200) reject(new DownloadError(DownloadError.Code.DownloadFailed, { cause: res }));
else resolve(res);
}).on("error", (err) => reject(err));
});
}
function makeSha256Request(resolvedOptions) {
return new Promise((resolve, reject) => {
resolvedOptions.requester.get(`${resolvedOptions.url}.sha256`, (res) => {
if (res.statusCode !== 200 && res.statusCode !== 201) reject(new DownloadError(DownloadError.Code.DownloadFailed, { cause: res.statusCode }));
const chunks = [];
res.on("data", (chunk) => {
chunks.push(chunk);
});
res.on("end", () => {
resolve(Buffer.concat(chunks).toString("utf-8"));
});
});
});
}
//#endregion
//#region src/download.ts
function typeAssert(_value) {}
function useDownloadProgress() {
let lastTime = Date.now();
let lastTransferred = 0;
let lastPercentage = 0;
const speedHistory = [];
const maxHistorySize = 5;
const minTimeInterval = 100;
return (progress$1) => {
const currentTime = Date.now();
const timeDifference = currentTime - lastTime;
const transferredDifference = progress$1.transferred - lastTransferred;
const increment = progress$1.percentage - lastPercentage;
let network;
let unit;
if (timeDifference >= minTimeInterval && transferredDifference > 0) {
const currentSpeed = transferredDifference / timeDifference * 1e3;
speedHistory.push(currentSpeed);
if (speedHistory.length > maxHistorySize) speedHistory.shift();
lastTime = currentTime;
lastTransferred = progress$1.transferred;
}
const avgSpeedBytesPerSecond = speedHistory.length > 0 ? speedHistory.reduce((sum, speed) => sum + speed, 0) / speedHistory.length : 0;
if (avgSpeedBytesPerSecond >= 1024 * 1024) {
network = Math.round(avgSpeedBytesPerSecond / (1024 * 1024) * 100) / 100;
unit = "MB";
} else {
network = Math.round(avgSpeedBytesPerSecond / 1024 * 100) / 100;
unit = "KB";
}
lastPercentage = progress$1.percentage;
return {
network,
unit,
increment
};
};
}
async function onDownloaded(writeStream, res) {
return new Promise((resolve, reject) => {
let isResolved = false;
function cleanup() {
isResolved = true;
if (!res.destroyed) res.destroy();
if (!writeStream.destroyed) writeStream.destroy();
}
function handleError(error) {
if (!isResolved) {
cleanup();
const errorMessage = error instanceof Error ? error.message : String(error);
reject(new DownloadError(DownloadError.Code.DownloadFailed, {
message: `Download failed: ${errorMessage}`,
cause: error
}));
}
}
writeStream.on("error", handleError);
writeStream.on("finish", () => {
if (!isResolved) {
isResolved = true;
if (!res.destroyed) res.destroy();
resolve();
}
});
res.on("error", handleError);
});
}
async function _checkSha256(filePath, sha256) {
if (!fs.existsSync(filePath)) throw new DownloadError(DownloadError.Code.FileNotFound, { message: `File not found: ${filePath}` });
if (!sha256 || typeof sha256 !== "string" || !/^[a-f0-9]{64}$/i.test(sha256.trim())) throw new DownloadError(DownloadError.Code.InvalidSha256, { message: "Invalid SHA256 format" });
return new Promise((resolve, reject) => {
const hash = crypto.createHash("sha256");
const stream = fs.createReadStream(filePath);
stream.on("error", (error) => {
reject(new DownloadError(DownloadError.Code.FileReadError, { message: `Failed to read file: ${error.message}` }));
});
stream.on("data", (chunk) => {
hash.update(chunk);
});
stream.on("end", () => {
const calculatedHash = hash.digest("hex").trim();
const expectedHash = sha256.trim();
if (calculatedHash !== expectedHash) {
const error = new DownloadError(DownloadError.Code.Sha256Mismatch, { message: `SHA256 checksum mismatch, expected: ${expectedHash}, actual: ${calculatedHash}` });
reject(error);
} else resolve();
});
});
}
async function _extractTar(resolvedOptions, extractedDir, emitter) {
await tar.extract({
file: resolvedOptions.tempFilePath,
cwd: extractedDir,
onReadEntry: (entry) => {
resolvedOptions.onTarExtracted?.(entry);
emitter.emit("tar-extracted", entry);
}
});
}
async function _extractZip(resolvedOptions, extractedDir, emitter) {
const currentOS = process.platform === "win32" ? "windows" : process.platform === "linux" ? "linux" : void 0;
let files = fg.sync(path.join(extractedDir, "**", "*.zip"), {
onlyFiles: true,
absolute: true
}).filter((file) => file.endsWith(".zip"));
if (currentOS === "linux" || currentOS === "windows") files = files.filter((filePath) => filePath.includes(currentOS));
async function extractSingleZip(filePath) {
const fileReadStream = fs.createReadStream(filePath).pipe(unzipper.Parse({ forceStream: true }));
const writePromises = [];
for await (const entry of fileReadStream) {
typeAssert(entry);
const currentFilePath = path.join(resolvedOptions.targetDir, entry.path);
if (entry.type === "File") {
if (!fs.existsSync(path.dirname(currentFilePath))) fs.mkdirSync(path.dirname(currentFilePath), { recursive: true });
const writePromise = new Promise((resolve, reject) => {
const writeStream = fs.createWriteStream(currentFilePath);
writeStream.on("error", reject);
writeStream.on("finish", resolve);
entry.pipe(writeStream);
});
writePromises.push(writePromise);
await resolvedOptions.onZipExtracted?.(entry);
emitter.emit("zip-extracted", entry);
} else if (entry.type === "Directory") {
if (!fs.existsSync(currentFilePath)) fs.mkdirSync(currentFilePath, { recursive: true });
}
}
await Promise.all(writePromises);
}
if (!fs.existsSync(resolvedOptions.targetDir)) fs.mkdirSync(resolvedOptions.targetDir, { recursive: true });
await Promise.all(files.map(extractSingleZip));
}
/**
* Download the ArkTS SDK.
*
* @param options - The options for the download.
*/
async function createDownloader(options) {
const resolvedOptions = resolveDownloadOptions(options);
const emitter = mitt();
async function startDownload(requestOptions) {
const startByte = computed(() => fs.existsSync(resolvedOptions.tempFilePath) ? fs.statSync(resolvedOptions.tempFilePath).size : 0);
const res = await makeRequest(resolvedOptions, startByte(), {
signal: resolvedOptions.signal,
...resolvedOptions.requestOptions,
...requestOptions
});
const totalLength = computed(() => (Number(res.headers["content-length"]) || 0) + startByte());
const downloadProgressStream = progress({
length: totalLength(),
transferred: startByte()
});
const progressHandler = useDownloadProgress();
downloadProgressStream.on("progress", (progress$1) => {
const progressEvent = {
...progress$1,
...progressHandler(progress$1)
};
options.onDownloadProgress?.(progressEvent);
emitter.emit("download-progress", progressEvent);
});
if (res.statusCode === 200 || res.statusCode === 206) {
const writeStream = fs.createWriteStream(resolvedOptions.tempFilePath, {
flags: startByte() > 0 ? "a" : "w",
start: startByte()
});
res.pipe(downloadProgressStream).pipe(writeStream);
await onDownloaded(writeStream, res);
}
}
async function checkSha256() {
const sha256 = await makeSha256Request(resolvedOptions);
await _checkSha256(resolvedOptions.tempFilePath, sha256);
}
const extractedDir = path.join(resolvedOptions.cacheDir, ".tar-extracted");
async function extractTar() {
if (!fs.existsSync(extractedDir)) fs.mkdirSync(extractedDir, { recursive: true });
await _extractTar(resolvedOptions, extractedDir, emitter);
}
async function extractZip() {
await _extractZip(resolvedOptions, extractedDir, emitter);
}
async function clean() {
if (resolvedOptions.clean !== false) {
fs.rmSync(resolvedOptions.tempFilePath, { recursive: true });
fs.rmSync(resolvedOptions.cacheDir, { recursive: true });
}
}
return {
startDownload,
checkSha256,
extractTar,
extractZip,
clean,
emit: emitter.emit,
on: emitter.on,
off: emitter.off,
all: emitter.all
};
}
/**
* Download the ArkTS SDK.
*
* @deprecated Deprecate this entry in newer version.
* @param options - The options for the download.
*/
async function download(options) {
return await createDownloader(options);
}
//#endregion
export { DownloadError, SdkArch, SdkOS, SdkVersion, createDownloader, download, getSdkUrl, getSdkUrls };
//# sourceMappingURL=download-Dayz2MQC.js.map