mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:32:21 +00:00
eca5794ebb
## Description:
Only mentioning removals/major updates/notable changes below, not all
minor upgrades.
### Removed:
- "@aws-sdk/client-s3": not used anywhere (was used in Archive.ts
previously)
- chai, "@types/chai", sinon-chai: not used anywhere, probably leftover.
Vitest uses a bundled version of Chai for its expect asserations under
the hood too.
- protobufjs, "@types/google-protobuf": not used anywhere, probably left
from evan's experiment with it? Removed from vite.config.ts too.
- "@types/jquery": not used anywhere, probably leftover
- sinon, "@types/sinon": not used anywhere just like chai, probably
leftover. And Vitest provides us with the same functionality.
- "@types/systeminformation": dependency systeminformation was removed
last year, this is an unneeded, deprecated and unmaintained remainder.
- vite-tsconfig-paths: removed, and removed the import and usage in
vite.config.ts and replaced it by adding `tsconfigPaths: true` to the
`resolve` block. Because of this message displayed on running the tests:
"The plugin "vite-tsconfig-paths" is detected. Vite now supports
tsconfig paths resolution natively via the resolve.tsconfigPaths option.
You can remove the plugin and set resolve.tsconfigPaths: true in your
Vite config instead."
- vite-plugin-static-copy: removed, we don't use it anymore (was used in
our vite.config.ts once,, probably before Vite natively supported
copying static assets via its publicDir configuration)
### Updated:
- color.js: v0.5 > v0.6, no breaking change affecting us
- cross-env: v7 > v10. It's a publicly archived repo since Nov 2025. But
before that he got it up-to-date from June 2025, porting to TS, dropping
old Node versions, dependencies etc. Seems still good to use for some
amount of time to come.
- dotenv: v16 > v17, now logs an informational message by default when
it loads an environment file. Can be disabled by using
dotenv.config({quite: true}) if needed.
- ejs: v3 > v5: security patches mostly. Vite still uses v3 btw.
- eslint: v9 > v10. Newly enabled rules by default:
'no-unassigned-vars', 'no-useless-assignment' and
'preserve-caught-error'. Mostly faster and minimum support moved to
higher node versions, which shouldn't be a problem.
- "@eslint/compat": v1 > v2. Minimum supported Node versions, which
should not be a problem.
- intl-messageformat: v10 > v11 no breaking changes that affect us
- jdom: v27 > v29. Faster. Most notably minimum support moved to higher
node v22 version, which should not be a problem. Also, see types/node,
kind of expecting v24 to be installed now.
- nanoid: from v3 to v5, no breaking changes that affect us
- "@opentelemetry/sdk-logs": now that addLogRecordProcessor is removed,
changed Logger.ts to pass an (empty) provider array directly to the
LoggerProvider constructor. Follows the changes in
https://github.com/open-telemetry/opentelemetry-js/pull/5588
- "@tailwindcss/vite": supports vite v8 from 4.2.2, and a fix for it in
4.2.4
- tailwindcss: supports vite v8 from 4.2.2
-- in 4.1.15 (we were already above this version) break-words was
deprecated in favor of wrap-break-word. But break-words, which we use in
15 places, will still work as expected
(https://github.com/tailwindlabs/tailwindcss/pull/19157). Same goes for
also deprecated "order-none".
- "@types/node": from v22 to v24, assuming most now use node 24
- vite v7 > v8:
-- is now on 8.0.10 so first bugs are out of it, while v8 itself also
fixed a big number of bugs.
-- in vite.config.ts, fixed Ts error/compilation issue by changing the
manualChunks option in build.rollupOptions.output to use the function
syntax, which is required by the updated types instead of the object
syntax.
- zod: no changes that affect us
### Prettier:
Updated only because of (new because of update?) Prettier errors for
files untouched in this PR originally:
- PathFinder.Parabola.ts
- WorkerMessages.ts
- ClanModal.handlers.test.ts
- ClanModal.rendering.test.ts
- CONTRIBUTING.md
- README.md
### ESLint:
Fixes needed to silence errors coming from newly enabled recommended
rules 'no-useless-assignment' and 'preserve-caught-error':
For 'no-useless-assignment' (default assignment never used because of
unreachable code or they are guaranteed to get a value, so they can be
undefinedat the start. Exception was AttackExecution, so made the
default value of 0 the default case in the switch statement):
- ClientGameRunner
- GameModeSelector
- NameBoxCalculator
- StructureDrawingUtils
- TerritoryLayer
- Diagnostics
- GameRunner
- ColorAllocator
- DefaultConfig
- AttackExecution
- AiAttackBehavior
- Worker.worker
- GamePreviewBuilder
For 'preserve-caught-error', disabled the rule here because the possible
fix `{cause: error}` was introduced in ES2022 while we're still on
target ES2020 currently:
- GameServer
- Privilege
_Error: The value assigned to 'gameMap' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'timeDisplay' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'scalingFactor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'radius' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'teamColor' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'gl' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'power' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'tickExecutionDuration' is not used in
subsequent statements. (no-useless-assignment)
Error: The value assigned to 'selectedIndex' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'mag' is not used in subsequent statements.
(no-useless-assignment)
Error: The value assigned to 'speed' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'matchesCriteria' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'shouldContinue' is not used in subsequent
statements. (no-useless-assignment)
Error: The value assigned to 'description' is not used in subsequent
statements. (no-useless-assignment)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)
Error: There is no `cause` attached to the symptom error being thrown.
(preserve-caught-error)_
All tests pass. TypeScript and ESLint errors resolved.
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
tryout33
---------
Co-authored-by: Copilot <copilot@github.com>
254 lines
8.1 KiB
TypeScript
254 lines
8.1 KiB
TypeScript
import tailwindcss from "@tailwindcss/vite";
|
|
import fs from "fs";
|
|
import { lookup as lookupMime } from "mrmime";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { defineConfig, loadEnv, type Plugin } from "vite";
|
|
import { createHtmlPlugin } from "vite-plugin-html";
|
|
import {
|
|
type AssetManifest,
|
|
buildAssetUrl,
|
|
rewriteAssetsForCdn,
|
|
} from "./src/core/AssetUrls";
|
|
import {
|
|
buildPublicAssetManifest,
|
|
copyRootPublicFiles,
|
|
createHashedPublicAssetFiles,
|
|
getProprietaryDir,
|
|
getResourcesDir,
|
|
writePublicAssetManifest,
|
|
} from "./src/server/PublicAssetManifest";
|
|
|
|
// Vite already handles these, but its good practice to define them explicitly
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
function serveProprietaryDir(
|
|
proprietaryDir: string,
|
|
resourcesDir: string,
|
|
): Plugin {
|
|
return {
|
|
name: "serve-proprietary-dir",
|
|
configureServer(server) {
|
|
// Must run before Vite's htmlFallback; skip when resources/ has the file
|
|
// so publicDir keeps precedence.
|
|
server.middlewares.use((req, res, next) => {
|
|
if (!req.url) return next();
|
|
const rel = decodeURIComponent(
|
|
new URL(req.url, "http://x").pathname,
|
|
).replace(/^\//, "");
|
|
if (rel.includes("..")) return next();
|
|
if (fs.existsSync(path.join(resourcesDir, rel))) return next();
|
|
const filePath = path.join(proprietaryDir, rel);
|
|
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile())
|
|
return next();
|
|
const mime = lookupMime(filePath);
|
|
if (mime) res.setHeader("Content-Type", mime);
|
|
res.setHeader("Cache-Control", "no-store");
|
|
fs.createReadStream(filePath).pipe(res);
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig(({ mode }) => {
|
|
const env = loadEnv(mode, process.cwd(), "");
|
|
const isProduction = mode === "production";
|
|
const resourcesDir = getResourcesDir(__dirname);
|
|
const proprietaryDir = getProprietaryDir(__dirname);
|
|
const sourceDirs = [resourcesDir, proprietaryDir];
|
|
const assetManifest: AssetManifest = isProduction
|
|
? buildPublicAssetManifest(sourceDirs)
|
|
: {};
|
|
const cdnBase = env.CDN_BASE ?? "";
|
|
const htmlAssetData = {
|
|
assetManifest: JSON.stringify(assetManifest),
|
|
cdnBase: JSON.stringify(cdnBase),
|
|
gameEnv: JSON.stringify(env.GAME_ENV ?? "dev"),
|
|
manifestHref: buildAssetUrl("manifest.json", assetManifest, cdnBase),
|
|
faviconHref: buildAssetUrl("images/Favicon.svg", assetManifest, cdnBase),
|
|
gameplayScreenshotUrl: buildAssetUrl(
|
|
"images/GameplayScreenshot.png",
|
|
assetManifest,
|
|
cdnBase,
|
|
),
|
|
backgroundImageUrl: buildAssetUrl(
|
|
"images/background.webp",
|
|
assetManifest,
|
|
cdnBase,
|
|
),
|
|
desktopLogoImageUrl: buildAssetUrl(
|
|
"images/OpenFront.png",
|
|
assetManifest,
|
|
cdnBase,
|
|
),
|
|
mobileLogoImageUrl: buildAssetUrl("images/OF.png", assetManifest, cdnBase),
|
|
};
|
|
|
|
// Vite's HTML transform replaces the source <script src="/src/client/Main.ts">
|
|
// with the hashed bundle URL and injects <link rel="modulepreload"> /
|
|
// <link rel="stylesheet"> tags. rewriteAssetsForCdn rewrites those refs to
|
|
// an EJS placeholder so RenderHtml.ts can prefix them with CDN_BASE at
|
|
// request time.
|
|
const injectCdnBaseTemplate = (): Plugin => ({
|
|
name: "inject-cdn-base-template",
|
|
apply: "build" as const,
|
|
enforce: "post",
|
|
transformIndexHtml: rewriteAssetsForCdn,
|
|
});
|
|
|
|
let viteBundleFiles: string[] = [];
|
|
const syncHashedPublicAssets = (): Plugin => ({
|
|
name: "sync-hashed-public-assets",
|
|
apply: "build" as const,
|
|
writeBundle(_options, bundle) {
|
|
viteBundleFiles = Object.keys(bundle);
|
|
},
|
|
closeBundle() {
|
|
const outDir = path.join(__dirname, "static");
|
|
copyRootPublicFiles(resourcesDir, outDir);
|
|
// Run the source→hashed copy first; createHashedPublicAssetFiles iterates
|
|
// assetManifest and expects every key to resolve to a file in resources/
|
|
// or proprietary/. Vite's bundle output (assets/...) doesn't, so it's
|
|
// merged in after.
|
|
createHashedPublicAssetFiles(sourceDirs, outDir, assetManifest);
|
|
// Track Vite's own bundle output (vendor chunks, JS, CSS, workers under
|
|
// static/assets/) in the manifest so the deploy-time R2 upload covers
|
|
// them alongside the hashed source assets. Skip non-assets/ emits like
|
|
// index.html — those are served by the app, not from R2.
|
|
for (const fileName of viteBundleFiles) {
|
|
if (!fileName.startsWith("assets/")) continue;
|
|
assetManifest[fileName] = `/${fileName}`;
|
|
}
|
|
writePublicAssetManifest(outDir, assetManifest);
|
|
},
|
|
});
|
|
|
|
// In dev, redirect visits to /w*/game/* to "/" so Vite serves the index.html.
|
|
const devGameHtmlBypass = (req?: {
|
|
url?: string;
|
|
method?: string;
|
|
headers?: { accept?: string | string[] };
|
|
}) => {
|
|
if (req?.method !== "GET") return undefined;
|
|
const accept = req.headers?.accept;
|
|
const acceptValue = Array.isArray(accept)
|
|
? accept.join(",")
|
|
: (accept ?? "");
|
|
if (!acceptValue.includes("text/html")) return undefined;
|
|
if (!req.url) return undefined;
|
|
if (/^\/w\d+\/game\/[^/]+/.test(req.url)) {
|
|
return "/";
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
return {
|
|
test: {
|
|
globals: true,
|
|
environment: "jsdom",
|
|
setupFiles: "./tests/setup.ts",
|
|
},
|
|
root: "./",
|
|
base: "/",
|
|
publicDir: isProduction ? false : "resources",
|
|
|
|
resolve: {
|
|
tsconfigPaths: true,
|
|
alias: {
|
|
resources: path.resolve(__dirname, "resources"),
|
|
},
|
|
},
|
|
|
|
plugins: [
|
|
...(!isProduction
|
|
? [serveProprietaryDir(proprietaryDir, resourcesDir)]
|
|
: []),
|
|
...(isProduction
|
|
? []
|
|
: [
|
|
createHtmlPlugin({
|
|
minify: false,
|
|
entry: "/src/client/Main.ts",
|
|
template: "index.html",
|
|
inject: {
|
|
data: {
|
|
gitCommit: JSON.stringify("DEV"),
|
|
...htmlAssetData,
|
|
},
|
|
},
|
|
}),
|
|
]),
|
|
...(isProduction
|
|
? [injectCdnBaseTemplate(), syncHashedPublicAssets()]
|
|
: []),
|
|
tailwindcss(),
|
|
],
|
|
|
|
define: {
|
|
__ASSET_MANIFEST__: JSON.stringify(assetManifest),
|
|
"process.env.WEBSOCKET_URL": JSON.stringify(
|
|
isProduction ? "" : "localhost:3000",
|
|
),
|
|
"process.env.GAME_ENV": JSON.stringify(isProduction ? "prod" : "dev"),
|
|
"process.env.STRIPE_PUBLISHABLE_KEY": JSON.stringify(
|
|
env.STRIPE_PUBLISHABLE_KEY,
|
|
),
|
|
"process.env.API_DOMAIN": JSON.stringify(env.API_DOMAIN),
|
|
// Add other process.env variables if needed, OR migrate code to import.meta.env
|
|
},
|
|
|
|
build: {
|
|
outDir: "static", // Webpack outputs to 'static', assuming we want to keep this.
|
|
emptyOutDir: true,
|
|
assetsDir: "assets", // Sub-directory for assets
|
|
rollupOptions: {
|
|
output: {
|
|
manualChunks: (id) => {
|
|
const vendorModules = ["pixi.js", "howler", "zod"];
|
|
if (vendorModules.some((module) => id.includes(module))) {
|
|
return "vendor";
|
|
}
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
server: {
|
|
port: 9000,
|
|
// Automatically open the browser when the server starts
|
|
open: process.env.SKIP_BROWSER_OPEN !== "true",
|
|
proxy: {
|
|
"/lobbies": {
|
|
target: "ws://localhost:3000",
|
|
ws: true,
|
|
changeOrigin: true,
|
|
},
|
|
// Worker proxies
|
|
"/w0": {
|
|
target: "ws://localhost:3001",
|
|
ws: true,
|
|
secure: false,
|
|
changeOrigin: true,
|
|
bypass: (req) => devGameHtmlBypass(req),
|
|
rewrite: (path) => path.replace(/^\/w0/, ""),
|
|
},
|
|
"/w1": {
|
|
target: "ws://localhost:3002",
|
|
ws: true,
|
|
secure: false,
|
|
changeOrigin: true,
|
|
bypass: (req) => devGameHtmlBypass(req),
|
|
rewrite: (path) => path.replace(/^\/w1/, ""),
|
|
},
|
|
// API proxies
|
|
"/api": {
|
|
target: "http://localhost:3000",
|
|
changeOrigin: true,
|
|
secure: false,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
});
|