The subdomain proxies to the same folder, but the build had the path /feuerwerk/ baked in. On feuerwerk.nockal.com the page loaded its assets, the API and the data URLs from /feuerwerk/... and got 404. The session cookie had Path=/feuerwerk/, so the browser would never have sent it to /api/ there either. - site.ts takes the base path from the folder of the open page: /feuerwerk/ on nockal.com, / on the subdomain. Release links point at the address you are working on. - vite.config.ts builds with a relative base. The copy plugin rewrites the pages to ./dist/assets/ and stops the build if it finds nothing to rewrite. The dev server now runs at http://localhost:5175/. - Auth.php sets the cookie without a Path, so it belongs to the API folder the browser sees. Sign-in and sign-out also clear the old /feuerwerk/ cookie, otherwise it would keep signing in after a logout. Existing sessions stay valid. - README: the new dev server address. Tests: - curl on both addresses: pages, JS, CSS and API 200 - sign-in, session, sign-out on both addresses - e2e 70 against both addresses: 13/13 each, no CSP violation - tsc, lint, prettier clean, site and releases tests 14/14 - PHP tests 7 of 8 files green, videos.test.php fails only because the web-php-fpm container has no ffmpeg Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { basename, dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { defineConfig, type Plugin } from 'vite';
|
|
import { publishPlugins } from './tools/lib/plugins.mjs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
/**
|
|
* The workspace folder itself is what nginx serves under https://nockal.com/feuerwerk/, and the
|
|
* same folder answers at https://feuerwerk.nockal.com/. So the build knows no path: its base is
|
|
* relative, and the pages find the API and the data from where they are (src/core/site.ts).
|
|
* The Vite source pages live in `web/`: `index.html` is the editor, `play.html` a released game
|
|
* (#266). The build writes hashed assets into `dist/` and this plugin copies the built pages to the
|
|
* folder root, so that the site URLs load the production bundles while `api/` and `data/` sit next
|
|
* to them. One folder up, their `./assets/` becomes `./dist/assets/`.
|
|
*/
|
|
const PAGES = ['index.html', 'play.html'];
|
|
|
|
function publishPages(): Plugin {
|
|
let outDir = '';
|
|
return {
|
|
name: 'feuerwerk:publish-pages',
|
|
apply: 'build',
|
|
configResolved(config) {
|
|
outDir = resolve(config.root, config.build.outDir);
|
|
},
|
|
closeBundle() {
|
|
const folder = basename(outDir);
|
|
for (const page of PAGES) {
|
|
const html = readFileSync(resolve(outDir, page), 'utf8');
|
|
const published = html.replaceAll('"./assets/', `"./${folder}/assets/`);
|
|
if (published === html) throw new Error(`${page}: no ./assets/ link to move`);
|
|
writeFileSync(resolve(outDir, '..', page), published);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* What the built pages may load (#427): their own files, Rapier's WebAssembly, images, sounds and
|
|
* fonts from data and blob URLs, and Pixi's workers from blob URLs. No inline script or style, and
|
|
* no `eval`: Pixi gets by without it through `pixi.js/unsafe-eval` (see src/page.ts). nginx sends
|
|
* the same policy as a header; this copy is the one CI runs the whole suite under. Only the build
|
|
* gets it, the dev server injects its CSS as inline styles.
|
|
*/
|
|
const CONTENT_SECURITY_POLICY = [
|
|
"default-src 'self'",
|
|
"script-src 'self' 'wasm-unsafe-eval'",
|
|
"style-src 'self'",
|
|
"img-src 'self' data: blob:",
|
|
"media-src 'self' data: blob:",
|
|
"font-src 'self' data: blob:",
|
|
"connect-src 'self' data: blob:",
|
|
"worker-src 'self' blob:",
|
|
"object-src 'none'",
|
|
"base-uri 'none'",
|
|
"form-action 'none'",
|
|
].join('; ');
|
|
|
|
function contentSecurityPolicy(): Plugin {
|
|
return {
|
|
name: 'feuerwerk:content-security-policy',
|
|
apply: 'build',
|
|
transformIndexHtml: () => [
|
|
{
|
|
tag: 'meta',
|
|
attrs: { 'http-equiv': 'Content-Security-Policy', content: CONTENT_SECURITY_POLICY },
|
|
injectTo: 'head-prepend',
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Editor plugins are loaded from `data/plugins/`, which is not in the repository; the ones that
|
|
* ship with it live in `plugins/` and are put there on every build. A file of the same name is
|
|
* replaced, anything else in the folder is left alone. Whether one runs is the open project's
|
|
* business, not the build's (#119).
|
|
*/
|
|
function shipPlugins(): Plugin {
|
|
return {
|
|
name: 'feuerwerk:publish-plugins',
|
|
apply: 'build',
|
|
closeBundle() {
|
|
publishPlugins(__dirname);
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig({
|
|
root: 'web',
|
|
base: './',
|
|
publicDir: false,
|
|
plugins: [contentSecurityPolicy(), publishPages(), shipPlugins()],
|
|
build: {
|
|
outDir: '../dist',
|
|
emptyOutDir: true,
|
|
target: 'es2022',
|
|
sourcemap: true,
|
|
rollupOptions: {
|
|
input: Object.fromEntries(
|
|
PAGES.map((page) => [page.replace('.html', ''), resolve(__dirname, 'web', page)]),
|
|
),
|
|
},
|
|
},
|
|
server: {
|
|
port: 5175,
|
|
// During development the PHP API and the data are served by the production nginx; proxy them.
|
|
proxy: {
|
|
'^/(api|data)/': {
|
|
target: 'https://nockal.com',
|
|
changeOrigin: true,
|
|
rewrite: (path) => `/feuerwerk${path}`,
|
|
},
|
|
},
|
|
},
|
|
test: {
|
|
root: '.',
|
|
include: ['tests/**/*.test.ts'],
|
|
environment: 'node',
|
|
// Unset locally, so vitest keeps its own guess (one worker per core). CI sets it while the
|
|
// gate runs next to the editor suite: the two jobs share four cores, and the suite measures
|
|
// text widths and frame counts, which a machine under full load reports differently.
|
|
maxWorkers: Number(process.env.GM_TEST_WORKERS) || undefined,
|
|
},
|
|
});
|