feat: add a function to download natives and its test
This commit is contained in:
parent
dd548c5f26
commit
b6b5da3981
13 changed files with 215 additions and 43 deletions
|
|
@ -21,17 +21,16 @@
|
|||
"packageManager": "pnpm@10.17.1",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/node": "^24.5.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"globals": "^16.4.0",
|
||||
"p-queue": "^8.1.1",
|
||||
"prettier": "3.6.2",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript-eslint": "^8.44.1",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"p-limit": "^7.1.1",
|
||||
"p-queue": "^8.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
43
pnpm-lock.yaml
generated
43
pnpm-lock.yaml
generated
|
|
@ -7,20 +7,19 @@ settings:
|
|||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
p-limit:
|
||||
specifier: ^7.1.1
|
||||
version: 7.1.1
|
||||
p-queue:
|
||||
specifier: ^8.1.1
|
||||
version: 8.1.1
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.36.0
|
||||
version: 9.36.0
|
||||
'@types/adm-zip':
|
||||
specifier: ^0.5.7
|
||||
version: 0.5.7
|
||||
'@types/node':
|
||||
specifier: ^24.5.2
|
||||
version: 24.5.2
|
||||
adm-zip:
|
||||
specifier: ^0.5.16
|
||||
version: 0.5.16
|
||||
eslint:
|
||||
specifier: ^9.36.0
|
||||
version: 9.36.0
|
||||
|
|
@ -30,6 +29,9 @@ importers:
|
|||
globals:
|
||||
specifier: ^16.4.0
|
||||
version: 16.4.0
|
||||
p-queue:
|
||||
specifier: ^8.1.1
|
||||
version: 8.1.1
|
||||
prettier:
|
||||
specifier: 3.6.2
|
||||
version: 3.6.2
|
||||
|
|
@ -398,6 +400,9 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/adm-zip@0.5.7':
|
||||
resolution: {integrity: sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==}
|
||||
|
||||
'@types/chai@5.2.2':
|
||||
resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
|
||||
|
||||
|
|
@ -511,6 +516,10 @@ packages:
|
|||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
adm-zip@0.5.16:
|
||||
resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==}
|
||||
engines: {node: '>=12.0'}
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
||||
|
|
@ -929,10 +938,6 @@ packages:
|
|||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-limit@7.1.1:
|
||||
resolution: {integrity: sha512-i8PyM2JnsNChVSYWLr2BAjNoLi0BAYC+wecOnZnVV+YSNJkzP7cWmvI34dk0WArWfH9KwBHNoZI3P3MppImlIA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
p-locate@5.0.0:
|
||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -1315,10 +1320,6 @@ packages:
|
|||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yocto-queue@1.2.1:
|
||||
resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.10':
|
||||
|
|
@ -1558,6 +1559,10 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc@4.52.2':
|
||||
optional: true
|
||||
|
||||
'@types/adm-zip@0.5.7':
|
||||
dependencies:
|
||||
'@types/node': 24.5.2
|
||||
|
||||
'@types/chai@5.2.2':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
|
|
@ -1713,6 +1718,8 @@ snapshots:
|
|||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
adm-zip@0.5.16: {}
|
||||
|
||||
ajv@6.12.6:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
|
|
@ -2131,10 +2138,6 @@ snapshots:
|
|||
dependencies:
|
||||
yocto-queue: 0.1.0
|
||||
|
||||
p-limit@7.1.1:
|
||||
dependencies:
|
||||
yocto-queue: 1.2.1
|
||||
|
||||
p-locate@5.0.0:
|
||||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
|
|
@ -2499,5 +2502,3 @@ snapshots:
|
|||
strip-ansi: 7.1.2
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
yocto-queue@1.2.1: {}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * as java from "./java"
|
||||
export * as version from "./version"
|
||||
export * as java from "./java";
|
||||
export * as version from "./version";
|
||||
export * as natives from "./natives";
|
||||
|
|
|
|||
61
src/core/natives.ts
Normal file
61
src/core/natives.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { Library, Native, PoolFile, Version } from "../utils/types";
|
||||
import path from "path";
|
||||
import { ensureDir } from "../utils/fs";
|
||||
import { parseRule } from "../utils/rules";
|
||||
import { os } from "../utils/systemInfo";
|
||||
import { DownloadPool } from "../utils/fetch";
|
||||
import { getTempFolder } from "../utils/temp";
|
||||
import { unzipAll } from "../utils/unzip";
|
||||
|
||||
export async function download(destination: string, versionManifest: Version) {
|
||||
const nativesPath = path.join(destination);
|
||||
await ensureDir(nativesPath);
|
||||
const tempNativesPath = await getTempFolder("natives");
|
||||
|
||||
// Download archives to a temp folder
|
||||
const files = versionManifest.libraries.map<PoolFile>((library: Library) => {
|
||||
if (library.downloads && library.downloads.classifiers) {
|
||||
if (parseRule(library)) return null;
|
||||
|
||||
const native: Native =
|
||||
os() === "osx"
|
||||
? library.downloads.classifiers["natives-osx"] ||
|
||||
library.downloads.classifiers["natives-macos"]
|
||||
: library.downloads.classifiers[`natives-${os()}`];
|
||||
|
||||
const name = native.path.split("/").pop();
|
||||
|
||||
return { url: native.url, path: path.join(tempNativesPath, name) };
|
||||
}
|
||||
});
|
||||
|
||||
const dPool = new DownloadPool(files, 5);
|
||||
|
||||
await dPool.run();
|
||||
|
||||
// Extract every archives into the natives folder
|
||||
for (const key in files) {
|
||||
if (Object.prototype.hasOwnProperty.call(files, key)) {
|
||||
const file = files[key];
|
||||
if (file) unzipAll(file.path, nativesPath);
|
||||
}
|
||||
}
|
||||
|
||||
// for (const key in versionManifest.libraries) {
|
||||
// if (Object.prototype.hasOwnProperty.call(versionManifest.libraries, key)) {
|
||||
// const library = versionManifest.libraries[key];
|
||||
|
||||
// if (library.downloads && library.downloads.classifiers) {
|
||||
// if (parseRule(library)) continue;
|
||||
|
||||
// const native: Native =
|
||||
// this.getOS() === "osx"
|
||||
// ? library.downloads.classifiers["natives-osx"] ||
|
||||
// library.downloads.classifiers["natives-macos"]
|
||||
// : library.downloads.classifiers[`natives-${os()}`];
|
||||
|
||||
// const name = native.path.split("/").pop();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ export async function downloadFile(
|
|||
signal: AbortSignal,
|
||||
) {
|
||||
try {
|
||||
if (!file) return;
|
||||
const res = await fetch(file.url, { signal });
|
||||
|
||||
if (!res.ok)
|
||||
|
|
|
|||
17
src/utils/fs.ts
Normal file
17
src/utils/fs.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import fs from "fs/promises";
|
||||
|
||||
export async function exists(path: string) {
|
||||
try {
|
||||
await fs.access(path, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureDir(path: string, recursive = false) {
|
||||
if (!(await exists(path))) {
|
||||
await fs.mkdir(path, { recursive });
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
23
src/utils/rules.ts
Normal file
23
src/utils/rules.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { os } from "./systemInfo";
|
||||
import { Version } from "./types";
|
||||
|
||||
// Copied from https://github.com/Pierce01/MinecraftLauncher-core/blob/f4ce947658e82218011d92c36d4d8a1b8c0c2429/components/handler.js#L237
|
||||
export function parseRule(library: Version["libraries"][number]): boolean {
|
||||
if (library.rules) {
|
||||
if (library.rules.length > 1) {
|
||||
if (
|
||||
library.rules[0].action === "allow" &&
|
||||
library.rules[1].action === "disallow" &&
|
||||
library.rules[1].os.name === "osx"
|
||||
) {
|
||||
return os() === "osx";
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (library.rules[0].action === "allow" && library.rules[0].os)
|
||||
return library.rules[0].os.name !== os();
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
10
src/utils/systemInfo.ts
Normal file
10
src/utils/systemInfo.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export function os() {
|
||||
switch (process.platform) {
|
||||
case "win32":
|
||||
return "windows";
|
||||
case "darwin":
|
||||
return "osx";
|
||||
default:
|
||||
return "linux";
|
||||
}
|
||||
}
|
||||
15
src/utils/temp.ts
Normal file
15
src/utils/temp.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { tmpdir } from "os";
|
||||
import path from "path";
|
||||
import { ensureDir } from "./fs";
|
||||
import fs from "fs/promises";
|
||||
|
||||
/**
|
||||
* Returns an empty temporary folder.
|
||||
* The folder gets wiped before every call.
|
||||
*/
|
||||
export async function getTempFolder(name: string) {
|
||||
const tempPath = path.join(tmpdir(), "nlk", name);
|
||||
await fs.rm(tempPath, { force: true, recursive: true });
|
||||
await ensureDir(tempPath, true);
|
||||
return tempPath;
|
||||
}
|
||||
|
|
@ -74,15 +74,45 @@ export type Versions = {
|
|||
}[];
|
||||
};
|
||||
|
||||
export type Native = {
|
||||
path: string;
|
||||
sha1: string;
|
||||
size: number;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type NativeOS = "natives-linux" | "natives-windows" | "natives-macos";
|
||||
|
||||
type JVMRule = {
|
||||
action: string;
|
||||
os: { name?: string; version?: string; arch?: string };
|
||||
};
|
||||
|
||||
type LibraryRule = { action: string; os?: { name: string } };
|
||||
|
||||
export type Library = {
|
||||
downloads: {
|
||||
artifacts: {
|
||||
path: string;
|
||||
sha1: string;
|
||||
size: number;
|
||||
url: string;
|
||||
};
|
||||
classifiers?: {
|
||||
[key in NativeOS]: Native;
|
||||
};
|
||||
};
|
||||
name: string;
|
||||
rules?: LibraryRule[];
|
||||
natives?: { [key: string]: string };
|
||||
};
|
||||
|
||||
export type Version = {
|
||||
arguments: {
|
||||
game: string[];
|
||||
jvm:
|
||||
| {
|
||||
rules: {
|
||||
action: string;
|
||||
os: { name?: string; version?: string; arch?: string };
|
||||
}[];
|
||||
rules: JVMRule[];
|
||||
value: string[] | string;
|
||||
}
|
||||
| string[];
|
||||
|
|
@ -107,18 +137,7 @@ export type Version = {
|
|||
component: RuntimeComponent;
|
||||
majorVersion: number;
|
||||
};
|
||||
libraries: {
|
||||
downloads: {
|
||||
artifacts: {
|
||||
path: string;
|
||||
sha1: string;
|
||||
size: number;
|
||||
url: string;
|
||||
};
|
||||
name: string;
|
||||
rules: { action: string; os: { name: string } }[];
|
||||
};
|
||||
}[];
|
||||
libraries: Library[];
|
||||
logging: {
|
||||
client: {
|
||||
argument: string;
|
||||
|
|
|
|||
6
src/utils/unzip.ts
Normal file
6
src/utils/unzip.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import AdmZip from "adm-zip";
|
||||
|
||||
export function unzipAll(from: string, to: string) {
|
||||
const zip = new AdmZip(from);
|
||||
zip.extractAllTo(to, true);
|
||||
}
|
||||
18
tests/natives.test.ts
Normal file
18
tests/natives.test.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { expect, test } from "vitest";
|
||||
import * as nlk from "../dist/index.js";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
|
||||
const nativesPath = path.join(import.meta.dirname, "temp/natives");
|
||||
console.log(nativesPath)
|
||||
await fs.rm(nativesPath, { recursive: true, force: true });
|
||||
await fs.mkdir(nativesPath, { recursive: true });
|
||||
|
||||
test(
|
||||
"download natives for 1.15 correctly",
|
||||
{ timeout: 10000 },
|
||||
async () => {
|
||||
const versionManifest = await nlk.core.version.getVersionManifest("1.15");
|
||||
await nlk.core.natives.download(nativesPath, versionManifest);
|
||||
},
|
||||
);
|
||||
|
|
@ -5,4 +5,5 @@ export default defineConfig({
|
|||
target: "node22",
|
||||
format: ["cjs", "esm"],
|
||||
dts: true,
|
||||
sourcemap: true,
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue