diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 94263cf4..2c65e2ad 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,6 +25,7 @@ "country-flag-icons": "^1.5.19", "date-fns": "^4.1.0", "file-replace-loader": "^1.4.2", + "fs-extra": "^11.3.1", "i18next": "^25.2.1", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", @@ -65,6 +66,7 @@ "@eslint/compat": "^1.3.1", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.30.1", + "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.14", "@types/latlon-geohash": "^2.0.4", "@types/leaflet": "^1.9.18", @@ -4419,6 +4421,17 @@ "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", "license": "MIT" }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", @@ -4494,6 +4507,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/latlon-geohash": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/latlon-geohash/-/latlon-geohash-2.0.4.tgz", @@ -8626,6 +8649,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8889,7 +8926,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -10562,6 +10598,18 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -16304,6 +16352,15 @@ "node": ">=4" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unrs-resolver": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index c564659a..565e7b22 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "@eslint/compat": "^1.3.1", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.30.1", + "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.14", "@types/latlon-geohash": "^2.0.4", "@types/leaflet": "^1.9.18", @@ -73,6 +74,7 @@ "country-flag-icons": "^1.5.19", "date-fns": "^4.1.0", "file-replace-loader": "^1.4.2", + "fs-extra": "^11.3.1", "i18next": "^25.2.1", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index 42c98374..9685126b 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -1,13 +1,21 @@ import path from 'path'; -import { Configuration } from 'webpack'; +import { Compiler, Configuration } from 'webpack'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import { version } from './package.json'; import { Buffer } from 'buffer'; +import fs from 'fs-extra'; // Declare __dirname for TypeScript declare const __dirname: string; +const sourceBuild = 'static/frontend'; +const outputPaths: string[] = [ + path.resolve(__dirname, '../nodeapp/static'), + path.resolve(__dirname, '../desktopApp/static'), + path.resolve(__dirname, '../web/static'), +]; + const config: Configuration = { entry: './src/index.js', module: { @@ -38,11 +46,11 @@ const configNode = (env: any, argv: { mode: string }): Configuration => { return { ...config, output: { - path: path.resolve(__dirname, 'static/frontend'), + path: path.resolve(__dirname, sourceBuild), filename: argv.mode === 'production' ? `main.v${version}.[contenthash].js` : `main.v${version}.js`, clean: true, - publicPath: '/static/frontend/', + publicPath: '/' + sourceBuild, }, plugins: [ // Django @@ -129,25 +137,29 @@ const configNode = (env: any, argv: { mode: string }): Configuration => { basePath: '/', }), - new CopyWebpackPlugin({ - patterns: [ - // Copy to nodeapp - { - from: path.resolve(__dirname, 'static'), - to: path.resolve(__dirname, '../nodeapp/static'), - }, - // Copy to desktopApp - { - from: path.resolve(__dirname, 'static'), - to: path.resolve(__dirname, '../desktopApp/static'), - }, - // Copy to web - { - from: path.resolve(__dirname, 'static'), - to: path.resolve(__dirname, '../web/static'), - }, - ], - }), + { + apply: (compiler: Compiler) => { + compiler.hooks.emit.tapAsync('CopyFilesPlugin', (compilation, callback) => { + Promise.all( + outputPaths.map((outputPath) => { + const sourceDir = path.resolve(__dirname, sourceBuild); + return fs + .copy(sourceDir, outputPath) + .then(() => { + console.log(`Files copied to ${outputPath}`); + }) + .catch((err) => { + console.error(`Error copying files to ${outputPath}:`, err); + }); + }), + ) + .then(() => { + callback(); + }) + .catch(callback); + }); + }, + }, ], }; };