dev: add local upload handler for development environment

This commit is contained in:
Julien Maille 2026-03-08 19:51:16 +01:00
parent 1c1c0037c0
commit 10f4d6952c
4 changed files with 141 additions and 0 deletions

60
package-lock.json generated
View file

@ -26,6 +26,7 @@
"@neutralinojs/neu": "^11.7.0",
"eslint": "^9.39.3",
"eslint-config-prettier": "^10.1.8",
"formidable": "^3.5.4",
"globals": "^17.4.0",
"htmlhint": "^1.9.1",
"miniflare": "^4.20260301.1",
@ -3310,6 +3311,19 @@
"url": "https://www.patreon.com/shalithasuranga"
}
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -3348,6 +3362,16 @@
"node": ">= 8"
}
},
"node_modules/@paralleldrive/cuid2": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
"integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "^1.1.5"
}
},
"node_modules/@poppinss/colors": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
@ -4144,6 +4168,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"dev": true,
"license": "MIT"
},
"node_modules/astral-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
@ -4976,6 +5007,17 @@
"node": ">=8"
}
},
"node_modules/dezalgo": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"dev": true,
"license": "ISC",
"dependencies": {
"asap": "^2.0.0",
"wrappy": "1"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -5831,6 +5873,24 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/formidable": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@paralleldrive/cuid2": "^2.2.2",
"dezalgo": "^1.0.4",
"once": "^1.4.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
},
"node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",

View file

@ -32,6 +32,7 @@
"@neutralinojs/neu": "^11.7.0",
"eslint": "^9.39.3",
"eslint-config-prettier": "^10.1.8",
"formidable": "^3.5.4",
"globals": "^17.4.0",
"htmlhint": "^1.9.1",
"miniflare": "^4.20260301.1",

78
vite-plugin-upload.js Normal file
View file

@ -0,0 +1,78 @@
import { formidable } from 'formidable';
import fs from 'fs';
import { Blob } from 'buffer';
import { loadEnv } from 'vite';
export default function uploadPlugin() {
let env = {};
const handler = async (req, res, next) => {
if (req.url === '/upload' && req.method === 'POST') {
const form = formidable({});
try {
const [_fields, files] = await form.parse(req);
const uploadedFile = files.file?.[0];
if (!uploadedFile) {
res.statusCode = 400;
res.end(JSON.stringify({ success: false, error: 'No file provided' }));
return;
}
const fileData = fs.readFileSync(uploadedFile.filepath);
const useR2 = env.R2_ENABLED === 'true';
let url;
if (useR2) {
// We could implement R2 upload here too, but for simplicity in dev
// we'll stick to catbox unless specifically requested to match R2 perfectly.
// However, to be helpful, let's at least mention it.
console.log('R2 upload detected in env, but dev plugin is using catbox fallback for now.');
}
// Forward to catbox.moe (default production behavior when R2 is disabled)
const formData = new FormData();
formData.append('reqtype', 'fileupload');
formData.append('fileToUpload', new Blob([fileData], { type: uploadedFile.mimetype }), uploadedFile.originalFilename);
const response = await fetch('https://catbox.moe/user/api.php', {
method: 'POST',
body: formData,
});
url = await response.text();
if (!response.ok) {
throw new Error(`Upload failed: ${url}`);
}
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
success: true,
url: url.trim(),
}));
} catch (err) {
console.error('Local upload error:', err);
res.statusCode = 500;
res.end(JSON.stringify({ success: false, error: err.message }));
}
return;
}
next();
};
return {
name: 'upload-plugin',
config(_, { mode }) {
env = loadEnv(mode, process.cwd(), '');
},
configureServer(server) {
server.middlewares.use(handler);
},
configurePreviewServer(server) {
server.middlewares.use(handler);
},
};
}

View file

@ -2,6 +2,7 @@ import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';
import neutralino from 'vite-plugin-neutralino';
import authGatePlugin from './vite-plugin-auth-gate.js';
import uploadPlugin from './vite-plugin-upload.js';
export default defineConfig(({ mode }) => {
const IS_NEUTRALINO = mode === 'neutralino';
@ -34,6 +35,7 @@ export default defineConfig(({ mode }) => {
plugins: [
IS_NEUTRALINO && neutralino(),
authGatePlugin(),
uploadPlugin(),
VitePWA({
registerType: 'prompt',
workbox: {