add more providers and refactor path to support urls

This commit is contained in:
cswimr 2025-02-08 08:32:58 -06:00
parent fff43a8d8f
commit eb836474ab
Signed by: cswimr
GPG key ID: 0EC431A8DA8F8087
5 changed files with 175 additions and 77 deletions

View file

@ -4,6 +4,7 @@
"": {
"name": "packwizjs",
"dependencies": {
"curseforge-api": "^1.3.0",
"toml": "^3.0.0",
},
"devDependencies": {
@ -109,6 +110,8 @@
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"curseforge-api": ["curseforge-api@1.3.0", "", {}, "sha512-MxIFAtmzCBmZaDUF7bDs8wPa8mAr1qneT9YKw1pVTjW0+cWGYop05Qm+ZEoAVToVQPYcNFHA2GoBPNX48zo+aQ=="],
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],

View file

@ -11,6 +11,7 @@
"typescript": "^5.0.0"
},
"dependencies": {
"curseforge-api": "^1.3.0",
"toml": "^3.0.0"
}
}

View file

@ -1,55 +1,109 @@
import * as fs from "fs";
import * as toml from "toml";
import FilePath from "./path";
import { CurseForgeClient } from "curseforge-api";
import Resource from "./resource";
// const CURSE_CLIENT = new CurseForgeClient();
export interface Metafile {
name: string;
filename: string;
side: "server" | "client" | "both";
provider: ModrinthProvider | CurseForgeProvider;
provider: Provider;
}
export interface ModrinthProvider {
url: string;
hashFormat: string;
hash: string;
modId: string;
versionId: string;
export abstract class Provider {
constructor(
public hash: string,
public hashFormat: string,
) {}
abstract getDownloadUrl(): Promise<Resource>;
}
export interface CurseForgeProvider {
hashFormat: string;
hash: string;
mode: string;
fileId: number;
projectId: number;
export class UrlProvider extends Provider {
constructor(
public hash: string,
public hashFormat: string,
public url: string,
) {
super(hash, hashFormat);
}
async getDownloadUrl(): Promise<Resource> {
return new Resource(this.url);
}
}
export class ModrinthProvider extends UrlProvider {
constructor(
hash: string,
hashFormat: string,
url: string,
public modId: string,
public versionId: string,
) {
super(hash, hashFormat, url);
}
}
export class GitHubProvider extends UrlProvider {
constructor(
hash: string,
hashFormat: string,
url: string,
public branch: string,
public regex: string,
public slug: string,
public tag: string,
) {
super(hash, hashFormat, url);
}
}
export class CurseForgeProvider extends Provider {
constructor(
public hash: string,
public hashFormat: string,
public mode: string,
public fileId: number,
public projectId: number,
) {
super(hash, hashFormat);
}
async getDownloadUrl(): Promise<Resource> {
// const mod = await CURSE_CLIENT.getMod(this.projectId);
// const file = await mod.getFile(this.fileId);
// return new Resource(await file.getDownloadURL());
return new Resource("https://google.com/search?q=curseforge+sucks"); // TODO: figure this out, i hate curseforge
}
}
/**
* Represents a file entry in the Packwiz index.
*/
export class IndexFileEntry {
readonly file: FilePath;
readonly file: Resource;
readonly hash: string;
readonly metafile: boolean;
constructor(
file: string | FilePath,
file: string | Resource,
hash: string,
metafile: boolean = false,
) {
this.file = file instanceof FilePath ? file : new FilePath(file);
this.file = file instanceof Resource ? file : new Resource(file);
this.hash = hash;
this.metafile = metafile;
}
parse(): Metafile {
async parse(): Promise<Metafile> {
if (this.metafile === false) {
throw new Error(
"Cannot parse non-metafiles! Use `file.readText()` instead to get the file contents.",
);
}
const fileContent = this.file.readText();
const fileContent = await this.file.fetchContents();
const parsed = toml.parse(fileContent);
const metafile: Metafile = {
@ -62,23 +116,29 @@ export class IndexFileEntry {
return metafile;
}
private parseProvider(parsed: any): ModrinthProvider | CurseForgeProvider {
private parseProvider(parsed: any): Provider {
if (parsed.update?.modrinth) {
return {
url: parsed.download.url,
hashFormat: parsed.download["hash-format"],
hash: parsed.download.hash,
modId: parsed.update.modrinth["mod-id"],
versionId: parsed.update.modrinth["version"],
};
return new ModrinthProvider(
parsed.download.url,
parsed.download["hash-format"],
parsed.download.hash,
parsed.update.modrinth["mod-id"],
parsed.update.modrinth["version"],
);
} else if (parsed.update?.curseforge) {
return {
hashFormat: parsed.download["hash-format"],
hash: parsed.download.hash,
mode: parsed.download.mode,
fileId: parsed.update.curseforge["file-id"],
projectId: parsed.update.curseforge["project-id"],
};
return new CurseForgeProvider(
parsed.download["hash-format"],
parsed.download.hash,
parsed.download.mode,
parsed.update.curseforge["file-id"],
parsed.update.curseforge["project-id"],
);
} else if (parsed.download) {
return new UrlProvider(
parsed.download.hash,
parsed.download["hash-format"],
parsed.download.url,
);
} else {
throw new Error("Unknown provider in TOML.");
}
@ -89,7 +149,7 @@ export class IndexFileEntry {
* Represents the structure of a Packwiz index file, as well as providing its location.
*/
export interface PackwizIndex {
location: FilePath;
location: Resource;
hashFormat: string;
files: IndexFileEntry[];
}
@ -103,9 +163,11 @@ export interface PackwizIndex {
* @param indexFilePath - The path of the TOML file.
* @returns The parsed index as a structured object.
*/
export function parsePackwizIndex(indexFilePath: string): PackwizIndex {
const indexFile = new FilePath(indexFilePath);
const parsed = toml.parse(indexFile.readText());
export async function parsePackwizIndex(
indexFilePath: string,
): Promise<PackwizIndex> {
const indexFile = new Resource(indexFilePath);
const parsed = toml.parse(await indexFile.fetchContents());
return {
location: indexFile,
hashFormat: parsed["hash-format"],

View file

@ -1,38 +0,0 @@
import * as fs from "fs";
import * as path from "path";
export default class FilePath {
constructor(public readonly path: string) {}
toString() {
return this.path;
}
get name() {
return path.basename(this.path);
}
get parent() {
return new FilePath(path.dirname(this.path));
}
get ext() {
return path.extname(this.path);
}
exists() {
return fs.existsSync(this.path);
}
readText(): string {
return fs.readFileSync(this.path, "utf-8");
}
// writeText(content: string) {
// fs.writeFileSync(this.path, content, "utf-8");
// }
join(...segments: string[]) {
return new FilePath(path.join(this.path, ...segments));
}
}

70
src/resource.ts Normal file
View file

@ -0,0 +1,70 @@
import * as fs from "fs/promises";
import { URL } from "url";
import * as path from "path";
export default class Resource {
constructor(public readonly path: string) {}
toString() {
return this.path;
}
get isUrl() {
try {
new URL(this.path);
return true;
} catch {
return false;
}
}
get name() {
return this.isUrl
? new URL(this.path).pathname.split("/").pop() || ""
: path.basename(this.path);
}
get parent() {
if (this.isUrl) {
const url = new URL(this.path);
url.pathname = path.dirname(url.pathname);
return new Resource(url.toString());
}
return new Resource(path.dirname(this.path));
}
get ext() {
return this.isUrl
? path.extname(new URL(this.path).pathname)
: path.extname(this.path);
}
async exists(): Promise<boolean> {
if (this.isUrl) {
const response = await fetch(this.path);
if (!response.ok) return false;
return true;
} else {
return fs.exists(this.path);
}
}
async fetchContents(): Promise<string> {
if (this.isUrl) {
const response = await fetch(this.path);
if (!response.ok)
throw new Error(`Failed to fetch ${this.path}: ${response.statusText}`);
return await response.text();
}
return fs.readFile(this.path, { encoding: "utf8" });
}
join(...segments: string[]) {
if (this.isUrl) {
const url = new URL(this.path);
url.pathname = path.join(url.pathname, ...segments);
return new Resource(url.toString());
}
return new Resource(path.join(this.path, ...segments));
}
}