From a79cd0acf900a0c6aaf2b8caefaf230aadc0376e Mon Sep 17 00:00:00 2001 From: Jamie Benstead Date: Fri, 18 Sep 2026 14:36:59 +0100 Subject: [PATCH] Fix local Scratch asset fixtures so the teapot project loads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scratch requests assets at /internalapi/asset/./get/ with a trailing slash. Vite treats that as a directory request and 404s, so the local fixtures never loaded and sprites rendered as broken images. Add a dev/preview middleware that serves the fixture file at the same path without the trailing slash, and set the content type from the md5ext — the fixture file is named `get` with no extension, so Vite sent it empty. Also add the Stage backdrop fixture (cd21514d...svg), which was never committed. Copied from scratch-gui's own default-project. No production impact: the middleware only defines configureServer and configurePreviewServer, so none of it is emitted into the build. The new fixture does ship, since public/ is copied to build/, but that matches the teapot fixture already tracked there. Nothing reads these paths in production — Scratch takes its asset host from REACT_APP_API_ENDPOINT, which points at editor-api on a different origin, and nginx serves no /api route. Co-Authored-By: Claude Opus 5 (1M context) --- .../cd21514d0531fdffb22204e0ec5ed84a.svg/get | 3 ++ vite.config.js | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 public/api/scratch/assets/internalapi/asset/cd21514d0531fdffb22204e0ec5ed84a.svg/get diff --git a/public/api/scratch/assets/internalapi/asset/cd21514d0531fdffb22204e0ec5ed84a.svg/get b/public/api/scratch/assets/internalapi/asset/cd21514d0531fdffb22204e0ec5ed84a.svg/get new file mode 100644 index 000000000..15f73119b --- /dev/null +++ b/public/api/scratch/assets/internalapi/asset/cd21514d0531fdffb22204e0ec5ed84a.svg/get @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 96dcce2f5..386c9e725 100644 --- a/vite.config.js +++ b/vite.config.js @@ -7,6 +7,7 @@ import { nodePolyfills } from "vite-plugin-node-polyfills"; const path = require("path"); const fs = require("fs"); +const mime = require("mime-types"); const { browserTargets, processEnvBuildDefine, @@ -76,6 +77,45 @@ const serveStandalonePyodideWorkerInDev = (replacements) => ({ }, }); +const scratchAssetFixtureRoot = path.resolve( + __dirname, + "public/api/scratch/assets/internalapi/asset", +); +const scratchAssetUrlPrefix = "/api/scratch/assets/internalapi/asset/"; + +const serveScratchAssetFixtures = () => ({ + name: "serve-scratch-asset-fixtures", + configureServer(server) { + server.middlewares.use(scratchAssetFixtureMiddleware); + }, + configurePreviewServer(server) { + server.middlewares.use(scratchAssetFixtureMiddleware); + }, +}); + +const scratchAssetFixtureMiddleware = (req, res, next) => { + const url = decodeURIComponent((req.url || "").split("?")[0]); + if (!url.startsWith(scratchAssetUrlPrefix) || !url.endsWith("/get/")) { + return next(); + } + + const assetName = url.slice(scratchAssetUrlPrefix.length, -"/get/".length); + const filePath = path.resolve(scratchAssetFixtureRoot, assetName, "get"); + if ( + !filePath.startsWith(`${scratchAssetFixtureRoot}${path.sep}`) || + !fs.existsSync(filePath) + ) { + return next(); + } + + res.setHeader( + "Content-Type", + mime.lookup(assetName) || "application/octet-stream", + ); + res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); + res.end(fs.readFileSync(filePath)); +}; + const serveIndexAtRootForCypress = () => ({ name: "serve-index-at-root-for-cypress", apply: "serve", @@ -127,6 +167,7 @@ export default defineConfig(async ({ mode }) => { ], }), serveCrossOriginResources(), + serveScratchAssetFixtures(), serveIndexAtRootForCypress(), injectProcessEnvIntoDevHtml(mode, env), serveStandalonePyodideWorkerInDev({