mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
feat: local vfs
This commit is contained in:
1 parent
b9878a05a1
commit
34ee954502
6 files changed
+89
-42
No files matched your search
@@ -1,10 +1,12 @@
|
||||
import { assertSecret } from "@blitzkit/core";
|
||||
import { AbstractVFS } from "../core/vfs/abstract";
|
||||
import { LocalVFS } from "../core/vfs/local";
|
||||
import { SteamVFS } from "../core/vfs/steam";
|
||||
import { ZipVFS } from "../core/vfs/zip";
|
||||
|
||||
const steamPattern = /steam:(\d+)\/(\d+)/;
|
||||
const zipPattern = /zip:(.+)/;
|
||||
const localePattern = /local:"?(.+)"?/;
|
||||
|
||||
const source = Bun.argv
|
||||
.find((arg) => arg.startsWith("--source="))
|
||||
@@ -21,11 +23,14 @@ if (!source) {
|
||||
assertSecret(import.meta.env.STEAM_USERNAME),
|
||||
assertSecret(import.meta.env.STEAM_PASSWORD),
|
||||
app,
|
||||
depot
|
||||
depot,
|
||||
).init();
|
||||
} else if (zipPattern.test(source)) {
|
||||
const [, url] = source.match(zipPattern)!;
|
||||
resolved = await new ZipVFS(url).init();
|
||||
} else if (localePattern.test(source)) {
|
||||
const [, path] = source.match(localePattern)!;
|
||||
resolved = await new LocalVFS(path).init();
|
||||
} else {
|
||||
throw new Error("No valid source specified");
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export class AssetUploader {
|
||||
|
||||
constructor(public message: string) {
|
||||
[this.owner, this.repo] = assertSecret(
|
||||
import.meta.env.PUBLIC_ASSET_REPO
|
||||
import.meta.env.PUBLIC_ASSET_REPO,
|
||||
).split("/");
|
||||
this.branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export class AssetUploader {
|
||||
const blobPath = `${this.owner}/${this.repo}/${this.branch}/${change.path}`;
|
||||
const response = await fetch(
|
||||
`https://raw.githubusercontent.com/${blobPath}`,
|
||||
{ headers: { "Accept-Encoding": "identity" } }
|
||||
{ headers: { "Accept-Encoding": "identity" } },
|
||||
);
|
||||
let diff: { change: number; status: DiffStatus };
|
||||
|
||||
@@ -71,7 +71,7 @@ export class AssetUploader {
|
||||
} else if (response.status === 200) {
|
||||
if (
|
||||
this.heuristicFormats.some((format) =>
|
||||
change.path.endsWith(`.${format}`)
|
||||
change.path.endsWith(`.${format}`),
|
||||
)
|
||||
) {
|
||||
/**
|
||||
@@ -94,7 +94,7 @@ export class AssetUploader {
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected status code ${response.status} for ${change.path}`
|
||||
`Unexpected status code ${response.status} for ${change.path}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export class AssetUploader {
|
||||
console.log(
|
||||
`${diff.status === DiffStatus.New ? "🟢" : "🟡"} (${
|
||||
diff.change > 0 ? "+" : ""
|
||||
}${diff.change.toLocaleString()}B) ${change.path}`
|
||||
}${diff.change.toLocaleString()}B) ${change.path}`,
|
||||
);
|
||||
|
||||
// flush BEFORE going over limit
|
||||
@@ -160,8 +160,8 @@ export class AssetUploader {
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(
|
||||
resolve,
|
||||
Math.max(0, this.time_between_blobs + (start - end))
|
||||
)
|
||||
Math.max(0, this.time_between_blobs + (start - end)),
|
||||
),
|
||||
);
|
||||
|
||||
blobs.push({ sha, path: change.path, mode: "100644", type: "blob" });
|
||||
@@ -170,11 +170,11 @@ export class AssetUploader {
|
||||
break;
|
||||
} catch {
|
||||
console.warn(
|
||||
` failed ${change.path}; retrying in ${this.time_per_blob}ms...`
|
||||
` failed ${change.path}; retrying in ${this.time_per_blob}ms...`,
|
||||
);
|
||||
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, this.time_per_blob)
|
||||
setTimeout(resolve, this.time_per_blob),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ export class AssetUploader {
|
||||
[Symbol.dispose]() {
|
||||
if (this.changes.length > 0) {
|
||||
throw new Error(
|
||||
`${this.changes.length} uncommitted changes. Did you forget to flush?`
|
||||
`${this.changes.length} uncommitted changes. Did you forget to flush?`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,24 +11,22 @@ export abstract class AbstractVFS {
|
||||
|
||||
abstract dispose(): void;
|
||||
|
||||
abstract has(path: string): boolean;
|
||||
abstract has(path: string): Promise<boolean>;
|
||||
|
||||
abstract raw(path: string): Promise<Uint8Array>;
|
||||
|
||||
abstract paths(): string[];
|
||||
|
||||
resolve(path: string) {
|
||||
async resolve(path: string) {
|
||||
const normalized = normalize(path);
|
||||
if (this.has(normalized)) return normalized;
|
||||
if (await this.has(normalized)) return normalized;
|
||||
|
||||
const dvplPath = `${normalized}.dvpl`;
|
||||
if (this.has(dvplPath)) return dvplPath;
|
||||
if (await this.has(dvplPath)) return dvplPath;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
assert(path: string) {
|
||||
const resolved = this.resolve(path);
|
||||
async assert(path: string) {
|
||||
const resolved = await this.resolve(path);
|
||||
|
||||
if (resolved === null) throw new Error(`File not found: ${path}`);
|
||||
|
||||
@@ -36,7 +34,7 @@ export abstract class AbstractVFS {
|
||||
}
|
||||
|
||||
async file(file: string) {
|
||||
const resolved = this.assert(file);
|
||||
const resolved = await this.assert(file);
|
||||
const raw = await this.raw(resolved);
|
||||
let buffer = raw;
|
||||
|
||||
@@ -47,21 +45,7 @@ export abstract class AbstractVFS {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
dir(path: string) {
|
||||
const parentSegments = path.split("/").length;
|
||||
const children = new Set<string>();
|
||||
|
||||
for (const child of this.paths()) {
|
||||
if (!child.startsWith(path) || child === path) continue;
|
||||
|
||||
const childSegments = child.split("/");
|
||||
const nextSegment = childSegments[parentSegments];
|
||||
|
||||
children.add(nextSegment);
|
||||
}
|
||||
|
||||
return Array.from(children);
|
||||
}
|
||||
abstract dir(path: string): Promise<string[]>;
|
||||
|
||||
async text(path: string) {
|
||||
const file = await this.file(path);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { AbstractVFS } from "./abstract";
|
||||
|
||||
export class LocalVFS extends AbstractVFS {
|
||||
constructor(private base: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
async init() {
|
||||
return this;
|
||||
}
|
||||
|
||||
async has(path: string) {
|
||||
const file = Bun.file(`${this.base}/${path}`);
|
||||
return await file.exists();
|
||||
}
|
||||
|
||||
async raw(path: string) {
|
||||
const file = Bun.file(`${this.base}/${path}`);
|
||||
const buffer = await file.arrayBuffer();
|
||||
const array = new Uint8Array(buffer);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
async dir(path: string) {
|
||||
return await readdir(`${this.base}/${path}`);
|
||||
}
|
||||
|
||||
dispose() {}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export class SteamVFS extends AbstractVFS {
|
||||
return this;
|
||||
}
|
||||
|
||||
has(path: string) {
|
||||
async has(path: string) {
|
||||
return this.manifest.has(path);
|
||||
}
|
||||
|
||||
@@ -89,8 +89,20 @@ export class SteamVFS extends AbstractVFS {
|
||||
return new Uint8Array(downloaded.file);
|
||||
}
|
||||
|
||||
paths() {
|
||||
return Array.from(this.manifest.keys());
|
||||
async dir(path: string) {
|
||||
const parentSegments = path.split("/").length;
|
||||
const children = new Set<string>();
|
||||
|
||||
for (const child of this.manifest.keys()) {
|
||||
if (!child.startsWith(path) || child === path) continue;
|
||||
|
||||
const childSegments = child.split("/");
|
||||
const nextSegment = childSegments[parentSegments];
|
||||
|
||||
children.add(nextSegment);
|
||||
}
|
||||
|
||||
return Array.from(children);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
@@ -5,7 +5,10 @@ import { AbstractVFS } from "./abstract";
|
||||
export class ZipVFS extends AbstractVFS {
|
||||
entries = new Map<string, ZipEntry>();
|
||||
|
||||
constructor(private url: string, private root = "app/") {
|
||||
constructor(
|
||||
private url: string,
|
||||
private root = "app/",
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -21,7 +24,7 @@ export class ZipVFS extends AbstractVFS {
|
||||
return this;
|
||||
}
|
||||
|
||||
has(path: string) {
|
||||
async has(path: string) {
|
||||
return this.entries.has(path);
|
||||
}
|
||||
|
||||
@@ -30,8 +33,20 @@ export class ZipVFS extends AbstractVFS {
|
||||
return new Uint8Array(arrayBuffer);
|
||||
}
|
||||
|
||||
paths() {
|
||||
return Array.from(this.entries.keys());
|
||||
async dir(path: string) {
|
||||
const parentSegments = path.split("/").length;
|
||||
const children = new Set<string>();
|
||||
|
||||
for (const child of this.entries.keys()) {
|
||||
if (!child.startsWith(path) || child === path) continue;
|
||||
|
||||
const childSegments = child.split("/");
|
||||
const nextSegment = childSegments[parentSegments];
|
||||
|
||||
children.add(nextSegment);
|
||||
}
|
||||
|
||||
return Array.from(children);
|
||||
}
|
||||
|
||||
dispose() {}
|
||||
|
||||
Reference in new issue
Block a user