140 lines
5.0 KiB
JavaScript
140 lines
5.0 KiB
JavaScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { ClassicLevel } from "../../barebone/fvtt/node_modules/classic-level/index.js";
|
|
import { analyzeStorage } from "../scripts/core/analyzer.js";
|
|
import { isMediaPath, normalizePath } from "../scripts/core/path-utils.js";
|
|
|
|
const [, , dataRootArg, worldIdArg, publicRootArg] = process.argv;
|
|
const dataRoot = path.resolve(dataRootArg ?? "./barebone/fvttdata/Data");
|
|
const worldId = worldIdArg ?? "dsa5";
|
|
const publicRoot = path.resolve(publicRootArg ?? "./barebone/fvtt/public");
|
|
const worldJsonPath = path.join(dataRoot, "worlds", worldId, "world.json");
|
|
const worldManifest = JSON.parse(await fs.readFile(worldJsonPath, "utf8"));
|
|
const worldSystemId = worldManifest.system ?? null;
|
|
|
|
async function* walkDirectory(storage, baseDir, relativeDir = "") {
|
|
const absoluteDir = path.join(baseDir, relativeDir);
|
|
let entries;
|
|
try {
|
|
entries = await fs.readdir(absoluteDir, { withFileTypes: true });
|
|
} catch (error) {
|
|
if (error?.code === "ENOENT") return;
|
|
throw error;
|
|
}
|
|
for (const entry of entries) {
|
|
const relativePath = normalizePath(path.posix.join(relativeDir, entry.name));
|
|
if (entry.isDirectory()) {
|
|
yield* walkDirectory(storage, baseDir, relativePath);
|
|
continue;
|
|
}
|
|
if (!isMediaPath(relativePath)) continue;
|
|
let stat;
|
|
try {
|
|
stat = await fs.stat(path.join(baseDir, relativePath));
|
|
} catch (error) {
|
|
if (error?.code === "ENOENT") continue;
|
|
throw error;
|
|
}
|
|
yield { storage, path: relativePath, size: stat.size };
|
|
}
|
|
}
|
|
|
|
async function* listOfflineFiles() {
|
|
yield* walkDirectory("data", dataRoot);
|
|
yield* walkDirectory("public", publicRoot);
|
|
}
|
|
|
|
async function* listWorldSources() {
|
|
const collectionsDir = path.join(dataRoot, "worlds", worldId, "data");
|
|
const collectionNames = await fs.readdir(collectionsDir);
|
|
for (const collectionName of collectionNames.sort()) {
|
|
const dbPath = path.join(collectionsDir, collectionName);
|
|
const db = new ClassicLevel(dbPath, { keyEncoding: "utf8", valueEncoding: "json" });
|
|
await db.open();
|
|
try {
|
|
for await (const [key, value] of db.iterator()) {
|
|
yield {
|
|
sourceType: "world-document",
|
|
sourceScope: { ownerType: "world", ownerId: worldId, systemId: worldSystemId, subtype: collectionName },
|
|
sourceLabel: `${collectionName} ${key}`,
|
|
value
|
|
};
|
|
}
|
|
} finally {
|
|
await db.close();
|
|
}
|
|
}
|
|
yield {
|
|
sourceType: "world-manifest",
|
|
sourceScope: { ownerType: "world", ownerId: worldId, systemId: worldSystemId, subtype: "manifest" },
|
|
sourceLabel: `world.json ${worldId}`,
|
|
value: worldManifest
|
|
};
|
|
|
|
for (const packageType of ["modules", "systems"]) {
|
|
const packagesDir = path.join(dataRoot, packageType);
|
|
const packageIds = await fs.readdir(packagesDir);
|
|
for (const packageId of packageIds.sort()) {
|
|
const manifestName = packageType === "modules" ? "module.json" : "system.json";
|
|
const manifestPath = path.join(packagesDir, packageId, manifestName);
|
|
try {
|
|
const value = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
yield {
|
|
sourceType: `${packageType.slice(0, -1)}-manifest`,
|
|
sourceScope: { ownerType: packageType.slice(0, -1), ownerId: packageId, subtype: "manifest" },
|
|
sourceLabel: `${manifestName} ${packageId}`,
|
|
value
|
|
};
|
|
for (const pack of value.packs ?? []) {
|
|
const packPath = resolvePackDirectory(path.join(packagesDir, packageId), pack.path ?? "");
|
|
if (!packPath) continue;
|
|
const db = new ClassicLevel(packPath, { keyEncoding: "utf8", valueEncoding: "json" });
|
|
await db.open();
|
|
try {
|
|
for await (const [key, packValue] of db.iterator()) {
|
|
yield {
|
|
sourceType: "package-pack-document",
|
|
sourceScope: {
|
|
ownerType: packageType.slice(0, -1),
|
|
ownerId: packageId,
|
|
subtype: `pack:${pack.name ?? path.basename(packPath)}`
|
|
},
|
|
sourceLabel: `${packageId}:${pack.name ?? path.basename(packPath)} ${key}`,
|
|
value: packValue
|
|
};
|
|
}
|
|
} finally {
|
|
await db.close();
|
|
}
|
|
}
|
|
} catch {
|
|
// Ignore directories without a manifest.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function resolvePackDirectory(packageRoot, packPath) {
|
|
if (!packPath) return null;
|
|
const normalized = packPath.replace(/\\/g, "/");
|
|
const withoutExtension = normalized.endsWith(".db") ? normalized.slice(0, -3) : normalized;
|
|
return path.join(packageRoot, withoutExtension);
|
|
}
|
|
|
|
const result = await analyzeStorage({
|
|
listFiles: listOfflineFiles,
|
|
listSources: listWorldSources
|
|
});
|
|
|
|
const summary = {
|
|
files: result.files.length,
|
|
references: result.references.length,
|
|
findings: result.findings.length,
|
|
byKind: result.findings.reduce((acc, finding) => {
|
|
acc[finding.kind] = (acc[finding.kind] ?? 0) + 1;
|
|
return acc;
|
|
}, {})
|
|
};
|
|
|
|
console.log(JSON.stringify(summary, null, 2));
|