Merge pull request #1154 from bbassie/fix/deleted-exports

Handle deleted export files
This commit is contained in:
Bastiaan authored and GitHub committed 2026-11-01 23:10:27 +00:00
1 parent d7d1859aa0
commit 1b87497e15
7 files changed
+178 -25

No files matched your search

+34
View File
@@ -226,6 +226,8 @@ export default class Controller {
await fs.mkdir(modsDirectory, { recursive: true });
const modStore = await lib.ModStore.fromDirectory(modsDirectory);
await this.validateExports(modPacks, "static");
// Add default mod packs
if (modPacks.size === 0) {
modPacks.setMany(lib.ModPack.defaultModPacks);
@@ -876,6 +878,37 @@ export default class Controller {
});
}
/** Remove export manifests that reference files missing from staticDirectory */
static async validateExports(modPacks: lib.SubscribableDatastore<lib.ModPack>, staticDirectory: string) {
const invalid: lib.ModPack[] = [];
for (const modPack of modPacks.valuesMutable()) {
if (!modPack.exportManifest) {
continue;
}
const missing: string[] = [];
for (const file of Object.values(modPack.exportManifest.assets)) {
const valid = path.basename(file) === file
&& await fs.stat(path.join(staticDirectory, file)).then(s => s.isFile(), () => false);
if (!valid) {
missing.push(file);
}
}
if (missing.length) {
Controller.dropExport(modPack, `missing ${missing.join(", ")}`);
invalid.push(modPack);
}
}
if (invalid.length) {
modPacks.setMany(invalid);
}
}
/** Clear the export manifest of a mutable mod pack, caller must save it */
static dropExport(modPack: lib.ModPack, reason: string) {
logger.warn(`Removing export data of mod pack ${modPack.name} (id ${modPack.id}): ${reason}`);
modPack.exportManifest = undefined;
}
static migrateRoles(rawJson: unknown[]): Static<typeof lib.Role.jsonSchema>[] {
const serialized = rawJson as Static<typeof lib.Role.jsonSchema>[];
// migrate: host token permissions were removed with host pairing in 2.1
@@ -984,6 +1017,7 @@ export default class Controller {
express.static(path.join(import.meta.dirname, "..", "..", "web", "static"), staticOptions)
);
app.use("/static", express.static("static", staticOptions)); // Used for data export files
app.use("/static", routes.missingExportAsset);
// Add API routes
routes.addRouteHandlers(app);
+24
View File
@@ -394,6 +394,30 @@ async function uploadExport(req: Request, res: Response) {
res.sendStatus(200);
}
// Reached when no static file matched, drops exports whose files were removed
export function missingExportAsset(req: Request, res: Response, next: any) {
const file = req.path.slice(1);
if (!["GET", "HEAD"].includes(req.method) || !file) {
next();
return;
}
const modPacks = res.app.locals.controller.modPacks;
const affected = [...modPacks.valuesMutable()].filter(
modPack => modPack.exportManifest && Object.values(modPack.exportManifest.assets).includes(file)
);
if (!affected.length) {
next();
return;
}
for (const modPack of affected) {
Controller.dropExport(modPack, `${file} was removed from disk`);
}
modPacks.setMany(affected);
res.status(404).type("text/plain").send(
`Export data file ${file} is missing, run export-data on an instance to regenerate it`
);
}
type ProxyStreamEvents = {
"error": [ err: any ],
"close": [],