Merged PR 95: Base path fix after Vite migration
parent
984ee08a95
commit
1a131a903b
|
@ -4,4 +4,3 @@ build
|
||||||
__mocks__
|
__mocks__
|
||||||
.vscode
|
.vscode
|
||||||
helm
|
helm
|
||||||
private
|
|
|
@ -6,3 +6,6 @@ VITE_APP_MACHINE_PING_INTERVAL=600000
|
||||||
|
|
||||||
#300000 milliseconds = 5 minutes
|
#300000 milliseconds = 5 minutes
|
||||||
VITE_APP_MACHINE_STARTING_TIME=300000
|
VITE_APP_MACHINE_STARTING_TIME=300000
|
||||||
|
|
||||||
|
# VITE_APP_BASE_URL=/network-resurrector/
|
||||||
|
VITE_APP_BASE_URL=
|
|
@ -1,6 +1,6 @@
|
||||||
PUBLIC_URL=
|
VITE_APP_BASE_URL=/@RUNTIME_BASE_URL@/
|
||||||
VITE_APP_TUITIO_URL=https://#######
|
VITE_APP_TUITIO_URL=https://<VITE_APP_TUITIO_URL>
|
||||||
VITE_APP_NETWORK_RESURRECTOR_API_URL=https://#######
|
VITE_APP_NETWORK_RESURRECTOR_API_URL=https://<VITE_APP_NETWORK_RESURRECTOR_API_URL>
|
||||||
|
|
||||||
#900000 milliseconds = 15 minutes
|
#900000 milliseconds = 15 minutes
|
||||||
VITE_APP_MACHINE_PING_INTERVAL=900000
|
VITE_APP_MACHINE_PING_INTERVAL=900000
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
FROM node:23-slim AS builder
|
FROM node:23-slim AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ARG APP_SUBFOLDER=""
|
|
||||||
|
|
||||||
COPY .npmrc .npmrc
|
COPY .npmrc .npmrc
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
@ -12,24 +10,22 @@ RUN rm -f .npmrc
|
||||||
COPY . ./
|
COPY . ./
|
||||||
|
|
||||||
# build the react app
|
# build the react app
|
||||||
RUN if [ -z "$APP_SUBFOLDER" ]; then npm run build; else PUBLIC_URL=/${APP_SUBFOLDER}/ npm run build; fi
|
RUN npm run build
|
||||||
|
|
||||||
# PRODUCTION ENVIRONMENT
|
# PRODUCTION ENVIRONMENT
|
||||||
FROM node:23-slim
|
FROM node:23-slim
|
||||||
|
|
||||||
ARG APP_SUBFOLDER=""
|
|
||||||
|
|
||||||
RUN printf '\n\n- Copy application files\n'
|
RUN printf '\n\n- Copy application files\n'
|
||||||
COPY --from=builder /app/build ./application/${APP_SUBFOLDER}
|
COPY --from=builder /app/build ./application
|
||||||
COPY --from=builder /app/build/index.html ./application/
|
COPY --from=builder /app/runtimeSetup.js ./application/runtimeSetup.js
|
||||||
COPY --from=builder /app/setenv.js ./application/setenv.js
|
|
||||||
|
|
||||||
#install static server
|
#install static server
|
||||||
RUN npm install -g serve
|
RUN npm install -g serve
|
||||||
|
|
||||||
# environment variables
|
# environment variables
|
||||||
ENV AUTHOR="Tudor Stanciu"
|
ENV AUTHOR="Tudor Stanciu"
|
||||||
ENV PUBLIC_URL=/${APP_SUBFOLDER}/
|
ENV APP_NAME="Network resurrector UI"
|
||||||
|
|
||||||
ARG APP_VERSION=0.0.0
|
ARG APP_VERSION=0.0.0
|
||||||
ENV APP_VERSION=${APP_VERSION}
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
|
|
||||||
|
@ -41,4 +37,4 @@ WORKDIR /
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["sh", "-c", "node application/setenv.js && serve -s application -p 80"]
|
CMD ["sh", "-c", "node application/runtimeSetup.js && serve -s application -p 80"]
|
|
@ -7,7 +7,7 @@ import tseslint from "typescript-eslint";
|
||||||
import prettier from "eslint-plugin-prettier";
|
import prettier from "eslint-plugin-prettier";
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{ ignores: ["node_modules", "dist", "build", "**/public", "setenv.js"] },
|
{ ignores: ["node_modules", "dist", "build", "**/public", "runtimeSetup.js"] },
|
||||||
{
|
{
|
||||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
files: ["**/*.{js,jsx,ts,tsx}"],
|
files: ["**/*.{js,jsx,ts,tsx}"],
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@eslint/js": "^9.25.1",
|
"@eslint/js": "^9.25.1",
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.16",
|
||||||
|
"@types/node": "^22.15.2",
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^19.1.2",
|
||||||
"@types/react-dom": "^19.1.2",
|
"@types/react-dom": "^19.1.2",
|
||||||
"@types/react-world-flags": "^1.6.0",
|
"@types/react-world-flags": "^1.6.0",
|
||||||
|
@ -2024,6 +2025,16 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "22.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz",
|
||||||
|
"integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||||
|
@ -6586,6 +6597,13 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/unicorn-magic": {
|
"node_modules/unicorn-magic": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@eslint/js": "^9.25.1",
|
"@eslint/js": "^9.25.1",
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.16",
|
||||||
|
"@types/node": "^22.15.2",
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^19.1.2",
|
||||||
"@types/react-dom": "^19.1.2",
|
"@types/react-dom": "^19.1.2",
|
||||||
"@types/react-world-flags": "^1.6.0",
|
"@types/react-world-flags": "^1.6.0",
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
"use strict";
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
const prefix = "VITE_APP_";
|
||||||
|
const RUNTIME_BASE_URL_PLACEHOLDER = "/@RUNTIME_BASE_URL@";
|
||||||
|
const APP_DIR = "./application";
|
||||||
|
const ENV_JS_DIR = APP_DIR;
|
||||||
|
|
||||||
|
function generateScriptContent() {
|
||||||
|
const prefixRegex = new RegExp(`^${prefix}`);
|
||||||
|
const env = process.env;
|
||||||
|
const config = Object.keys(env)
|
||||||
|
.filter(key => prefixRegex.test(key))
|
||||||
|
.reduce((c, key) => Object.assign({}, c, { [key]: env[key] }), {});
|
||||||
|
|
||||||
|
return `window.env=${JSON.stringify(config)};`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSha256Hash(input) {
|
||||||
|
const hash = crypto.createHash("sha256");
|
||||||
|
hash.update(input);
|
||||||
|
return hash.digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateIndexHtml(envFileName, basePath) {
|
||||||
|
const indexPath = path.join(APP_DIR, "index.html");
|
||||||
|
|
||||||
|
if (!fs.existsSync(indexPath)) {
|
||||||
|
console.error(`Error: ${indexPath} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let indexContent = fs.readFileSync(indexPath, "utf8");
|
||||||
|
|
||||||
|
// Replace base path placeholder with actual value
|
||||||
|
indexContent = indexContent.replace(new RegExp(RUNTIME_BASE_URL_PLACEHOLDER, "g"), basePath || "/");
|
||||||
|
|
||||||
|
// Replace any existing env script with the new one
|
||||||
|
const envScriptRegex = /<script src="[^"]*env(\.\w+)?\.js"[^>]*><\/script>/;
|
||||||
|
const newEnvScript = `<script src="${envFileName}"></script>`;
|
||||||
|
|
||||||
|
if (envScriptRegex.test(indexContent)) {
|
||||||
|
indexContent = indexContent.replace(envScriptRegex, newEnvScript);
|
||||||
|
} else {
|
||||||
|
// If no existing env script, add it before the first script tag
|
||||||
|
const insertPoint = indexContent.indexOf("<script");
|
||||||
|
if (insertPoint !== -1) {
|
||||||
|
indexContent =
|
||||||
|
indexContent.substring(0, insertPoint) + newEnvScript + "\n " + indexContent.substring(insertPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(indexPath, indexContent, "utf8");
|
||||||
|
console.log(`Updated ${indexPath} with base path: ${basePath || "/"} and env script: ${envFileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupOldEnvFiles(newEnvFileName) {
|
||||||
|
// Find all env*.js files and delete them except the new one
|
||||||
|
const files = fs.readdirSync(ENV_JS_DIR);
|
||||||
|
const envFiles = files.filter(file => /^env(\.\w+)?\.js$/.test(file) && file !== newEnvFileName);
|
||||||
|
|
||||||
|
for (const file of envFiles) {
|
||||||
|
const filePath = path.join(ENV_JS_DIR, file);
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
console.log(`Removed old env file: ${filePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
console.log("Setting environment variables...");
|
||||||
|
|
||||||
|
// Generate env script content
|
||||||
|
const scriptContent = generateScriptContent();
|
||||||
|
|
||||||
|
// Compute hash for cache busting
|
||||||
|
const hash = getSha256Hash(scriptContent);
|
||||||
|
const fragment = hash.substring(0, 8);
|
||||||
|
const envFileName = `env.${fragment}.js`;
|
||||||
|
const envFilePath = path.join(ENV_JS_DIR, envFileName);
|
||||||
|
|
||||||
|
// Ensure build directory exists
|
||||||
|
if (!fs.existsSync(APP_DIR)) {
|
||||||
|
console.log(`Creating build directory: ${APP_DIR}`);
|
||||||
|
fs.mkdirSync(APP_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write new env.js file
|
||||||
|
fs.writeFileSync(envFilePath, scriptContent, "utf8");
|
||||||
|
console.log(`Updated ${envFilePath} with ${prefix}* environment variables`);
|
||||||
|
|
||||||
|
// Clean up old env.js files
|
||||||
|
cleanupOldEnvFiles(envFileName);
|
||||||
|
|
||||||
|
// Get base path from environment and update index.html
|
||||||
|
const basePath = process.env.VITE_APP_BASE_URL || "/";
|
||||||
|
updateIndexHtml(envFileName, basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
|
@ -1,27 +0,0 @@
|
||||||
"use strict";
|
|
||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
const prefix = "VITE_APP_";
|
|
||||||
const publicUrl = import.meta.env.PUBLIC_URL || "";
|
|
||||||
const scriptPath = path.join("./application", publicUrl, "env.js");
|
|
||||||
|
|
||||||
function generateScriptContent() {
|
|
||||||
const prefixRegex = new RegExp(`^${prefix}`);
|
|
||||||
const env = import.meta.env;
|
|
||||||
const config = Object.keys(env)
|
|
||||||
.filter(key => prefixRegex.test(key))
|
|
||||||
.reduce((c, key) => Object.assign({}, c, { [key]: env[key] }), {});
|
|
||||||
return `window.env=${JSON.stringify(config)};`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveScriptContent(scriptContents) {
|
|
||||||
fs.writeFile(scriptPath, scriptContents, "utf8", function (err) {
|
|
||||||
if (err) throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Setting environment variables...");
|
|
||||||
const scriptContent = generateScriptContent();
|
|
||||||
saveScriptContent(scriptContent);
|
|
||||||
console.log(`Updated ${scriptPath} with ${prefix}* environment variables: ${scriptContent}.`);
|
|
|
@ -3,6 +3,7 @@ import App from "./App";
|
||||||
import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom";
|
import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom";
|
||||||
import { useTuitioToken } from "@flare/tuitio-client-react";
|
import { useTuitioToken } from "@flare/tuitio-client-react";
|
||||||
import LoginContainer from "../features/login/components/LoginContainer";
|
import LoginContainer from "../features/login/components/LoginContainer";
|
||||||
|
import env from "utils/env";
|
||||||
|
|
||||||
const PrivateRoute = ({ children }: { children: React.ReactElement }): React.ReactElement => {
|
const PrivateRoute = ({ children }: { children: React.ReactElement }): React.ReactElement => {
|
||||||
const { valid } = useTuitioToken();
|
const { valid } = useTuitioToken();
|
||||||
|
@ -43,7 +44,7 @@ const PublicRoute = ({ children }: { children: React.ReactElement }): React.Reac
|
||||||
|
|
||||||
const AppRouter: React.FC = () => {
|
const AppRouter: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter basename={import.meta.env.PUBLIC_URL || ""}>
|
<BrowserRouter basename={env.VITE_APP_BASE_URL || ""}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Navigate to="/dashboard" />} />
|
<Route path="/" element={<Navigate to="/dashboard" />} />
|
||||||
<Route
|
<Route
|
||||||
|
|
|
@ -5,6 +5,8 @@ import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import "moment/locale/ro.js";
|
import "moment/locale/ro.js";
|
||||||
import "moment/locale/de.js";
|
import "moment/locale/de.js";
|
||||||
|
import { ensureTrailingSlash } from "utils/url";
|
||||||
|
import env from "utils/env";
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(Backend)
|
.use(Backend)
|
||||||
|
@ -77,7 +79,7 @@ i18n
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
backend: {
|
backend: {
|
||||||
loadPath: `${import.meta.env.PUBLIC_URL || ""}/locales/{{lng}}/{{ns}}.json`
|
loadPath: `${ensureTrailingSlash(env.VITE_APP_BASE_URL || "")}locales/{{lng}}/{{ns}}.json`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
const ensureTrailingSlash = (url: string) => {
|
||||||
|
if (url === null || url === undefined) return url;
|
||||||
|
if (url.endsWith("/")) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
return `${url}/`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ensureTrailingSlash };
|
|
@ -19,7 +19,7 @@
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"allowUnreachableCode": false,
|
"allowUnreachableCode": false,
|
||||||
"types": ["vite/client"]
|
"types": ["node", "vite/client"]
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
import { defineConfig } from "vite";
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
import { defineConfig, loadEnv } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import viteTsconfigPaths from "vite-tsconfig-paths";
|
import viteTsconfigPaths from "vite-tsconfig-paths";
|
||||||
import eslintPlugin from "vite-plugin-eslint";
|
import eslintPlugin from "vite-plugin-eslint";
|
||||||
import checker from "vite-plugin-checker";
|
import checker from "vite-plugin-checker";
|
||||||
|
import { ensureTrailingSlash } from "./src/utils/url";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, process.cwd());
|
||||||
|
const baseUrl = ensureTrailingSlash(env.VITE_APP_BASE_URL || "/");
|
||||||
|
|
||||||
|
return {
|
||||||
// depending on your application, base can also be "/"
|
// depending on your application, base can also be "/"
|
||||||
base: "/",
|
base: baseUrl,
|
||||||
plugins: [
|
plugins: [
|
||||||
react({
|
react({
|
||||||
jsxImportSource: "@emotion/react",
|
jsxImportSource: "@emotion/react",
|
||||||
|
@ -27,4 +34,5 @@ export default defineConfig({
|
||||||
build: {
|
build: {
|
||||||
outDir: "build"
|
outDir: "build"
|
||||||
}
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue