110 lines
4 KiB
JavaScript
110 lines
4 KiB
JavaScript
import { defineConfig } from 'vite';
|
|
import vue from '@vitejs/plugin-vue';
|
|
import path from 'node:path';
|
|
import fs from 'node:fs/promises';
|
|
|
|
const RENDER_ROOT = path.resolve(__dirname, '..', '.output-render');
|
|
const CACHE_ROOT = path.resolve(__dirname, '..', '.cache');
|
|
|
|
// Tiny dev plugin that exposes the .output-render directory at /render and
|
|
// serves a /api/index endpoint enumerating maps and variants. Keeps the app
|
|
// dependency-free of any external server.
|
|
function renderRootPlugin() {
|
|
return {
|
|
name: 'psx-render-root',
|
|
configureServer(server) {
|
|
server.middlewares.use('/render', async (req, res, next) => {
|
|
try {
|
|
const url = decodeURIComponent(req.url.split('?')[0]);
|
|
const filePath = path.join(RENDER_ROOT, url);
|
|
if (!filePath.startsWith(RENDER_ROOT)) {
|
|
res.statusCode = 403;
|
|
res.end('Forbidden');
|
|
return;
|
|
}
|
|
const stat = await fs.stat(filePath);
|
|
if (stat.isDirectory()) {
|
|
const entries = await fs.readdir(filePath, { withFileTypes: true });
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.end(JSON.stringify(entries.map((e) => ({ name: e.name, isDir: e.isDirectory() }))));
|
|
return;
|
|
}
|
|
const ext = path.extname(filePath).toLowerCase();
|
|
const mime = ext === '.png' ? 'image/png'
|
|
: ext === '.json' ? 'application/json'
|
|
: ext === '.log' ? 'text/plain'
|
|
: 'application/octet-stream';
|
|
res.setHeader('Content-Type', mime);
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
const data = await fs.readFile(filePath);
|
|
res.end(data);
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
res.statusCode = 404;
|
|
res.end('Not found');
|
|
return;
|
|
}
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
server.middlewares.use('/api/index', async (req, res) => {
|
|
try {
|
|
const indexPath = path.join(RENDER_ROOT, 'index.json');
|
|
const data = await fs.readFile(indexPath, 'utf8');
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.end(data);
|
|
} catch (error) {
|
|
res.statusCode = 500;
|
|
res.end(JSON.stringify({ error: error.message }));
|
|
}
|
|
});
|
|
|
|
// Serve per-map sprite cache: /sprites/<map>/bundle_<hex>/frame_NNN.png
|
|
// The exporter writes individual decoded sprite PNGs into
|
|
// <projectRoot>/.cache/<mapStem>/sprites/, sharing the cache between
|
|
// both the auto and region01 variants of the same map (bundle decoding
|
|
// is invariant to the record-set choice).
|
|
server.middlewares.use('/sprites', async (req, res, next) => {
|
|
try {
|
|
const url = decodeURIComponent(req.url.split('?')[0]);
|
|
// url like "/L2/bundle_00085c40/frame_000.png"
|
|
// map to "<CACHE_ROOT>/L2/sprites/bundle_00085c40/frame_000.png"
|
|
const segments = url.split('/').filter(Boolean);
|
|
if (segments.length < 2) {
|
|
res.statusCode = 404;
|
|
res.end('Not found');
|
|
return;
|
|
}
|
|
const [mapStem, ...rest] = segments;
|
|
const filePath = path.join(CACHE_ROOT, mapStem, 'sprites', ...rest);
|
|
if (!filePath.startsWith(CACHE_ROOT)) {
|
|
res.statusCode = 403;
|
|
res.end('Forbidden');
|
|
return;
|
|
}
|
|
const data = await fs.readFile(filePath);
|
|
res.setHeader('Content-Type', 'image/png');
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.end(data);
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
res.statusCode = 404;
|
|
res.end('Not found');
|
|
return;
|
|
}
|
|
next(error);
|
|
}
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig({
|
|
plugins: [vue(), renderRootPlugin()],
|
|
server: {
|
|
port: 5180,
|
|
strictPort: false,
|
|
},
|
|
});
|