diff --git a/.env.sample b/.env.sample index 6004ab062..00e9e3ad2 100644 --- a/.env.sample +++ b/.env.sample @@ -10,6 +10,9 @@ REACT_APP_DISABLE_ERROR_BOUNDARY=1 # API Url REACT_APP_API_URL=example.com +# Define directory with overrides relative to app root +EXTENSION_DIR=./src/extensions/ + # CSRF_WHITELIST_HEADER_FOR_LOCAL_DEVELOPMENT=X-WHITELIST-HEADER diff --git a/.github/workflows/dockerImage.yml b/.github/workflows/dockerImage.yml index 959d6f867..947e7ff58 100644 --- a/.github/workflows/dockerImage.yml +++ b/.github/workflows/dockerImage.yml @@ -90,6 +90,7 @@ jobs: uses: actions/download-artifact@v2 with: name: buildfiles + path: build - name: Get current time id: time uses: nanzm/get-time-action@v1.1 diff --git a/Dockerfile b/Dockerfile index af652d372..be993a199 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,4 @@ ARG DOCKER_MATRIX=ghcr.io FROM $DOCKER_MATRIX/onlineberatung/onlineberatung-nginx/onlineberatung-nginx:dockerimage.v.001-main -COPY beratung-hilfe.html /usr/share/nginx/html/ -COPY error.401.html /usr/share/nginx/html/ -COPY error.404.html /usr/share/nginx/html/ -COPY error.500.html /usr/share/nginx/html/ -COPY favicon.ico /usr/share/nginx/html/ -COPY logo192.png /usr/share/nginx/html/ -COPY logo512.png /usr/share/nginx/html/ -COPY robots.txt /usr/share/nginx/html/ -COPY public /usr/share/nginx/html/public -COPY src /usr/share/nginx/html/src -COPY static /usr/share/nginx/html/static -COPY releases /usr/share/nginx/html/releases -COPY under-construction.html /usr/share/nginx/html/ +COPY build /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/config/paths.js b/config/paths.js index e265272c7..e57190c7d 100644 --- a/config/paths.js +++ b/config/paths.js @@ -63,6 +63,7 @@ module.exports = { }, appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), + appExtensions: resolveApp(process.env.EXTENSION_DIR ?? './src/extensions'), appTsConfig: resolveApp('tsconfig.json'), appJsConfig: resolveApp('jsconfig.json'), yarnLockFile: resolveApp('yarn.lock'), diff --git a/config/webpack.config.js b/config/webpack.config.js index adb17967a..be41715e3 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -86,6 +86,17 @@ const hasJsxRuntime = (() => { } })(); +const getTemplate = (templatePath) => { + const templateAbsPath = path.resolve(paths.appExtensions, templatePath); + if ( + fs.existsSync(templateAbsPath) && + !fs.lstatSync(templateAbsPath).isDirectory() + ) { + return templateAbsPath; + } + return path.resolve(paths.appSrc, templatePath); +}; + const localAliases = (paths) => paths // Remove paths which are not overridden @@ -204,18 +215,20 @@ module.exports = function (webpackEnv) { { loader: require.resolve(preProcessor), options: { - sassOptions: { - includePaths: [ - path.resolve( - process.cwd(), - './src/resources/styles/settings.scss' - ) - ] + additionalData: (content) => { + let newContent = `@import "${path.resolve( + paths.appSrc, + 'resources/styles/settings.scss' + )}"; `; + const settingsPathExtensions = path.resolve( + paths.appExtensions, + 'resources/styles/settings.scss' + ); + if (fs.existsSync(settingsPathExtensions)) { + newContent += `@import "${settingsPathExtensions}"; `; + } + return `${newContent} ${content}`; }, - additionalData: `@import "${path.resolve( - process.cwd(), - './src/resources/styles/settings.scss' - )}";`, sourceMap: true } } @@ -362,7 +375,9 @@ module.exports = function (webpackEnv) { 'react-dom$': 'react-dom/profiling', 'scheduler/tracing': 'scheduler/tracing-profiling' }), - ...(modules.webpackAliases || {}) + ...(modules.webpackAliases || {}), + 'src': paths.appSrc, + 'extensions': path.join(paths.appSrc, 'extensions') }, plugins: [ // Prevents users from importing files from outside of src/ (or node_modules/). @@ -459,6 +474,7 @@ module.exports = function (webpackEnv) { test: /\.(js|mjs|jsx|ts|tsx)$/, include: [ paths.appSrc, + paths.appExtensions, path.resolve( 'node_modules/@onlineberatung/onlineberatung-frontend' ) @@ -637,7 +653,7 @@ module.exports = function (webpackEnv) { type: 'app' }, inject: true, - template: 'src/pages/app.html', + template: getTemplate('pages/app.html'), chunks: ['app'], filename: 'beratung-hilfe.html' }), @@ -647,7 +663,7 @@ module.exports = function (webpackEnv) { type: 'error', errorType: '400' }, - template: 'src/pages/app.html', + template: getTemplate('pages/app.html'), chunks: ['error'], filename: 'error.400.html' }), @@ -657,7 +673,7 @@ module.exports = function (webpackEnv) { type: 'error', errorType: '401' }, - template: 'src/pages/app.html', + template: getTemplate('pages/app.html'), chunks: ['error'], filename: 'error.401.html' }), @@ -667,7 +683,7 @@ module.exports = function (webpackEnv) { type: 'error', errorType: '404' }, - template: 'src/pages/app.html', + template: getTemplate('pages/app.html'), chunks: ['error'], filename: 'error.404.html' }), @@ -677,12 +693,14 @@ module.exports = function (webpackEnv) { type: 'error', errorType: '500' }, - template: 'src/pages/app.html', + template: getTemplate('pages/app.html'), chunks: ['error'], filename: 'error.500.html' }), new CopyPlugin({ - patterns: [{ from: 'src/pages/under-construction.html' }] + patterns: [ + { from: getTemplate('pages/under-construction.html') } + ] }), // Inlines the webpack runtime script. This script is too small to warrant // a network request. @@ -851,42 +869,64 @@ module.exports = function (webpackEnv) { } }), new webpack.NormalModuleReplacementPlugin( - new RegExp( - `${process.cwd()}/(node_modules/@onlineberatung/onlineberatung-frontend/)?src/(?!extensions/).*` - ), + new RegExp(`${paths.appSrc}/.*`), async (result) => { let originalPath = path.join( result.context, result.request ); + + if (originalPath.indexOf(paths.appExtensions) >= 0) { + return; + } + // Check if absolute import if (result.request.indexOf('/') === 0) { originalPath = result.request; } const newPath = originalPath.replace( - new RegExp( - `${process.cwd()}/(node_modules/@onlineberatung/onlineberatung-frontend\/)?src/` - ), - `${process.cwd()}/src/extensions/` + `${paths.appSrc}/`, + `${paths.appExtensions}/` ); - ['', ...paths.moduleFileExtensions.map((ext) => `.${ext}`)] + let originalExt = null; + [ + '', + '.html', + ...paths.moduleFileExtensions.map((ext) => `.${ext}`) + ] .filter((ext) => useTypeScript || !ext.includes('ts')) .forEach((ext) => { - if (fs.existsSync(`${newPath}${ext}`)) { - console.log( - `Overwritten ${originalPath} -> ${`${newPath}${ext}`}` - ); - - if (result.createData) { - result.createData.resource = `${newPath}${ext}`; - result.createData.context = path.dirname( - `${newPath}${ext}` - ); - } + if ( + fs.existsSync(`${originalPath}${ext}`) && + !fs + .lstatSync(`${originalPath}${ext}`) + .isDirectory() + ) { + originalExt = ext; } }); + + if (originalExt === null) { + return; + } + + if ( + fs.existsSync(`${newPath}${originalExt}`) && + !fs.lstatSync(`${newPath}${originalExt}`).isDirectory() + ) { + console.log( + `Overwritten ${originalPath} -> ${newPath}${originalExt}` + ); + + if (result.createData) { + result.createData.resource = `${newPath}${originalExt}`; + result.createData.context = path.dirname( + `${newPath}.${originalExt}` + ); + } + } } ), ...localAliases([