diff --git a/BRANDING.md b/BRANDING.md new file mode 100644 index 000000000..b2043dd9a --- /dev/null +++ b/BRANDING.md @@ -0,0 +1,151 @@ +# Branding + +The UI supports static branding at build time. Dynamically switching brands is not +possible with the current implementation. + +## Summary + +Each of the project modules need to do some branding enablement. + +- `@konveyor-ui/common` pulls in the branding assets and packages the configuration, + strings and assets within the common package. The other modules pull branding + from the common module. + +- `@konveyor-ui/client` uses branding from the common package: + + - The location of `favicon.ico`, `manifest.json` and any other branding + assets that may be referenced in the `brandingStrings` are sourced from the + common package. + + - The `brandingStrings` are used by the dev-server runtime, to fill out the + `index.html` template. + + - The about modal and application masthead components use the branding strings + provided by the common module to display brand appropriate logos, titles and + about information. Since the common module provides all the information, it + is packaged directly into the app at build time. + +- `@konveyor-ui/server` uses the `brandingStrings` from the common package to fill + out the `index.html` template. + +## Providing alternate branding + +To provide an alternate branding to the build, specify the path to the branding assets +with the `BRANDING` environment variable. Relative paths in `BRANDING` are computed +from the project source root. + +Each brand requires the presence of at least the following files: + +- `strings.json` +- `favicon.ico` +- `manifest.json` + +With a file path of `/alt/custom-branding`, a build that uses an alternate branding +is run as: + +```sh +> BRANDING=/alt/custom-branding npm run build +``` + +The dev server can also be run this way. Since file watching of the branding assets +is not implemented in the common module's build watch mode, it may be necessary to +manually build the common module before running the dev server. When working on a +brand, it is useful to run the dev server like this: + +```sh +> export BRANDING=/alt/custom-branding +> npm run build -w common +> npm run start:dev +> unset BRANDING # when you don't want to use the custom branding path anymore +``` + +### File details + +#### strings.json + +The expected shape of `strings.json` is defined in [branding.ts](./common/src/branding.ts). + +The default version of the file is [branding/strings.json](./branding/strings.json). + +A minimal viable example of the file is: + +```json +{ + "application": { + "title": "Konveyor" + }, + "about": { + "displayName": "Konveyor" + }, + "masthead": {} +} +``` + +At build time, the json file is processed as an [ejs](https://ejs.co/) template. The +variable `brandingRoot` is provided as the relative root of the branding +assets within the build of the common module. Consider the location of `strings.json` +in your branding directory as the base `brandingRoot` when creating a new brand. + +For example, to properly reference a logo within this branding structure: + +``` + special-brand/ + images/ + masthead-logo.svg + about-logo.svg + strings.json +``` + +Use a url string like this: + +```json +{ + "about": { + "imageSrc": "<%= brandingRoot %>/images/about-logo.svg" + } +} +``` + +and in the output of `BRANDING=special-brand npm run build -w common`, the `imageSrc` +will be `branding/images/about-logo.svg` with all of the files in `special-branding/*` +copied to and available to the client and server modules from +`@konveyor-ui/common/branding/*`. + +#### favicon.ico + +A standard favorite icon file `favicon.ico` is required to be in the same directory +as `strings.json` + +#### manifest.json + +A standard [web app manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest) +file `manifest.json` is required to be in the same directory as `strings.json`. + +## Technical details + +All branding strings and assets are pulled in to the common module. The client and +server modules access the branding from the common module build. + +The `common` module relies on rollup packaging to embed all of the brand for easy +use. The use of branding strings in `client` and `server` modules is straight forward. +Pulling in `strings.json` and providing the base path to the brand assets is a +more complicated. + +The `common` module provides the `brandingAssetPath()` function to let the build time +code find the root path to all brand assets. Webpack configuration files use this +function to source the favicon.ico, manifest.json and other brand assets to be copied +to the application bundle. + +The `brandingStrings` is typed and sourced from a json file. To pass typescript builds, +a stub json file needs to be available at transpile time. By using a typescript paths +of `@branding/strings.json`, the stub json is found at transpile time. The generated +javascript will still import the path alias. The +[virtual rollup plugin](https://github.com/rollup/plugins/tree/master/packages/virtual) +further transform the javascript output by replacing the `@branding/strings.json` import +with a dynamically built module containing the contents of the brand's `strings.json`. +The brand json becomes a virtual module embedded in the common module. + +A build for a custom brand will fail (1) if the expected files cannot be read, or (2) +if `strings.json` is not a valid JSON file. **Note:** The context of `stings.json` is +not currently validated. If something is missing or a url is malformed, it will only +be visible as a runtime error. diff --git a/client/public/konveyor-favicon.ico b/branding/favicon.ico similarity index 100% rename from client/public/konveyor-favicon.ico rename to branding/favicon.ico diff --git a/client/public/logo.png b/branding/images/logo.png similarity index 100% rename from client/public/logo.png rename to branding/images/logo.png diff --git a/client/public/logo192.png b/branding/images/logo192.png similarity index 100% rename from client/public/logo192.png rename to branding/images/logo192.png diff --git a/client/public/logo512.png b/branding/images/logo512.png similarity index 100% rename from client/public/logo512.png rename to branding/images/logo512.png diff --git a/client/src/app/images/Konveyor-white-logo.svg b/branding/images/masthead-logo.svg similarity index 100% rename from client/src/app/images/Konveyor-white-logo.svg rename to branding/images/masthead-logo.svg diff --git a/client/public/manifest.json b/branding/manifest.json similarity index 81% rename from client/public/manifest.json rename to branding/manifest.json index 2a98b57f0..eb8ffec95 100644 --- a/client/public/manifest.json +++ b/branding/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "tackle-ui", - "name": "Tackle UI", + "short_name": "konveyor-ui", + "name": "Konveyor UI", "icons": [ { "src": "favicon.ico", diff --git a/branding/strings.json b/branding/strings.json new file mode 100644 index 000000000..116aa41ce --- /dev/null +++ b/branding/strings.json @@ -0,0 +1,21 @@ +{ + "application": { + "title": "Konveyor", + "name": "Konveyor Tackle UI", + "description": "Konveyor/Tackle UI" + }, + "about": { + "displayName": "Konveyor", + "imageSrc": "<%= brandingRoot %>/images/masthead-logo.svg", + "documentationUrl": "https://konveyor.github.io/konveyor/" + }, + "masthead": { + "leftBrand": { + "src": "<%= brandingRoot %>/images/masthead-logo.svg", + "alt": "brand", + "height": "60px" + }, + "leftTitle": null, + "rightBrand": null + } +} diff --git a/client/__mocks__/react-i18next.js b/client/__mocks__/react-i18next.js new file mode 100644 index 000000000..60f67f05e --- /dev/null +++ b/client/__mocks__/react-i18next.js @@ -0,0 +1,68 @@ +/* eslint-env node */ + +// Adapted from https://github.com/i18next/react-i18next/blob/master/example/test-jest/src/__mocks__/react-i18next.js +import React from "react"; +import * as reactI18next from "react-i18next"; + +const hasChildren = (node) => + node && (node.children || (node.props && node.props.children)); + +const getChildren = (node) => + node && node.children ? node.children : node.props && node.props.children; + +const renderNodes = (reactNodes) => { + if (typeof reactNodes === "string") { + return reactNodes; + } + + return Object.keys(reactNodes).map((key, i) => { + const child = reactNodes[key]; + const isElement = React.isValidElement(child); + + if (typeof child === "string") { + return child; + } + if (hasChildren(child)) { + const inner = renderNodes(getChildren(child)); + return React.cloneElement(child, { ...child.props, key: i }, inner); + } + if (typeof child === "object" && !isElement) { + return Object.keys(child).reduce( + (str, childKey) => `${str}${child[childKey]}`, + "" + ); + } + + return child; + }); +}; + +const useMock = [(k) => k, { changeLanguage: () => new Promise(() => {}) }]; +useMock.t = (k) => k; +useMock.i18n = { changeLanguage: () => new Promise(() => {}) }; + +module.exports = { + Trans: ({ children, i18nKey }) => + !children + ? i18nKey + : Array.isArray(children) + ? renderNodes(children) + : renderNodes([children]), + + Translation: ({ children }) => children((k) => k, { i18n: {} }), + + useTranslation: () => useMock, + + initReactI18next: { + type: "3rdParty", + init: () => {}, + }, + + // mock if needed + withTranslation: reactI18next.withTranslation, + I18nextProvider: reactI18next.I18nextProvider, + setDefaults: reactI18next.setDefaults, + getDefaults: reactI18next.getDefaults, + setI18n: reactI18next.setI18n, + getI18n: reactI18next.getI18n, +}; diff --git a/client/config/jest.config.ts b/client/config/jest.config.ts index 202a284ba..6d683a602 100644 --- a/client/config/jest.config.ts +++ b/client/config/jest.config.ts @@ -23,6 +23,9 @@ const config: JestConfigWithTsJest = { "@patternfly/react-icons/dist/esm/icons/": "/__mocks__/fileMock.js", + // other mocks + "react-i18next": "/__mocks__/react-i18next.js", + // match the paths in tsconfig.json "@app/(.*)": "/src/app/$1", "@assets/(.*)": @@ -44,7 +47,7 @@ const config: JestConfigWithTsJest = { }, // Code to set up the testing framework before each test file in the suite is executed - setupFilesAfterEnv: ["/src/app/setupTests.ts"], + setupFilesAfterEnv: ["/src/app/test-config/setupTests.ts"], }; export default config; diff --git a/client/config/webpack.common.ts b/client/config/webpack.common.ts index ffaa95c5c..637b94365 100644 --- a/client/config/webpack.common.ts +++ b/client/config/webpack.common.ts @@ -1,15 +1,18 @@ import path from "path"; import { Configuration } from "webpack"; -// import CaseSensitivePathsWebpackPlugin from "case-sensitive-paths-webpack-plugin"; import CopyPlugin from "copy-webpack-plugin"; import Dotenv from "dotenv-webpack"; import { TsconfigPathsPlugin } from "tsconfig-paths-webpack-plugin"; import MonacoWebpackPlugin from "monaco-editor-webpack-plugin"; +import { brandingAssetPath } from "@konveyor-ui/common"; import { LANGUAGES_BY_FILE_EXTENSION } from "./monacoConstants"; -const BG_IMAGES_DIRNAME = "images"; const pathTo = (relativePath: string) => path.resolve(__dirname, relativePath); +const brandingPath = brandingAssetPath(); +const manifestPath = path.resolve(brandingPath, "manifest.json"); + +const BG_IMAGES_DIRNAME = "images"; const config: Configuration = { entry: { @@ -150,22 +153,26 @@ const config: Configuration = { exports: "xmllint", }, }, - // For monaco-editor-webpack-plugin + { + test: /\.yaml$/, + use: "raw-loader", + }, + + // For monaco-editor-webpack-plugin ---> { test: /\.css$/, include: [pathTo("../../node_modules/monaco-editor")], use: ["style-loader", "css-loader"], }, - // For monaco-editor-webpack-plugin { test: /\.ttf$/, type: "asset/resource", }, + // <--- For monaco-editor-webpack-plugin ], }, plugins: [ - // new CaseSensitivePathsWebpackPlugin(), new Dotenv({ systemvars: true, silent: true, @@ -174,15 +181,19 @@ const config: Configuration = { patterns: [ { from: pathTo("../public/locales"), - to: pathTo("../dist/locales"), + to: "./locales/", }, { - from: pathTo("../public/manifest.json"), - to: pathTo("../dist/manifest.json"), + from: pathTo("../public/templates"), + to: "./templates/", }, { - from: pathTo("../public/templates"), - to: pathTo("../dist/templates"), + from: manifestPath, + to: ".", + }, + { + from: brandingPath, + to: "./branding/", }, ], }), diff --git a/client/config/webpack.dev.ts b/client/config/webpack.dev.ts index d935f8d63..9c0c367e3 100644 --- a/client/config/webpack.dev.ts +++ b/client/config/webpack.dev.ts @@ -2,7 +2,6 @@ import path from "path"; import { mergeWithRules } from "webpack-merge"; import type { Configuration as WebpackConfiguration } from "webpack"; import type { Configuration as DevServerConfiguration } from "webpack-dev-server"; - import CopyPlugin from "copy-webpack-plugin"; import HtmlWebpackPlugin from "html-webpack-plugin"; import ReactRefreshTypeScript from "react-refresh-typescript"; @@ -14,12 +13,14 @@ import { KONVEYOR_ENV, SERVER_ENV_KEYS, proxyMap, + brandingStrings, + brandingAssetPath, } from "@konveyor-ui/common"; import { stylePaths } from "./stylePaths"; import commonWebpackConfiguration from "./webpack.common"; -const brandType = KONVEYOR_ENV.PROFILE; const pathTo = (relativePath: string) => path.resolve(__dirname, relativePath); +const faviconPath = path.resolve(brandingAssetPath(), "favicon.ico"); interface Configuration extends WebpackConfiguration { devServer?: DevServerConfiguration; @@ -75,10 +76,6 @@ const config: Configuration = mergeWithRules({ include: [...stylePaths], use: ["style-loader", "css-loader"], }, - { - test: /\.yaml$/, - use: "raw-loader", - }, ], }, @@ -96,15 +93,16 @@ const config: Configuration = mergeWithRules({ }, ], }), + // index.html generated at compile time to inject `_env` new HtmlWebpackPlugin({ filename: "index.html", template: pathTo("../public/index.html.ejs"), templateParameters: { _env: encodeEnv(KONVEYOR_ENV, SERVER_ENV_KEYS), - brandType, + branding: brandingStrings, }, - favicon: pathTo(`../public/${brandType}-favicon.ico`), + favicon: faviconPath, minify: { collapseWhitespace: false, keepClosingSlash: true, diff --git a/client/config/webpack.prod.ts b/client/config/webpack.prod.ts index 5b1f667e3..db3978e74 100644 --- a/client/config/webpack.prod.ts +++ b/client/config/webpack.prod.ts @@ -5,12 +5,12 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin"; import CssMinimizerPlugin from "css-minimizer-webpack-plugin"; import HtmlWebpackPlugin from "html-webpack-plugin"; -import { KONVEYOR_ENV } from "@konveyor-ui/common"; +import { brandingAssetPath } from "@konveyor-ui/common"; import { stylePaths } from "./stylePaths"; import commonWebpackConfiguration from "./webpack.common"; -const brandType = KONVEYOR_ENV.PROFILE; const pathTo = (relativePath: string) => path.resolve(__dirname, relativePath); +const faviconPath = path.resolve(brandingAssetPath(), "favicon.ico"); const config = merge(commonWebpackConfiguration, { mode: "production", @@ -36,10 +36,6 @@ const config = merge(commonWebpackConfiguration, { include: [...stylePaths], use: [MiniCssExtractPlugin.loader, "css-loader"], }, - { - test: /\.yaml$/, - use: "raw-loader", - }, ], }, @@ -56,11 +52,12 @@ const config = merge(commonWebpackConfiguration, { new webpack.EnvironmentPlugin({ NODE_ENV: "production", }), + // index.html generated at runtime via the express server to inject `_env` new HtmlWebpackPlugin({ filename: "index.html.ejs", template: `!!raw-loader!${pathTo("../public/index.html.ejs")}`, - favicon: pathTo(`../public/${brandType}-favicon.ico`), + favicon: faviconPath, minify: { collapseWhitespace: false, keepClosingSlash: true, diff --git a/client/public/index.html.ejs b/client/public/index.html.ejs index 632b99ed8..0db08e342 100644 --- a/client/public/index.html.ejs +++ b/client/public/index.html.ejs @@ -1,15 +1,9 @@ - <% if (brandType == 'mta') { %> - Migration Toolkit for Applications - - - <% } else { %> - Konveyor - - - <% } %> + <%= branding.application.title %> + + diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index 29f0802aa..161c2e055 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -1,8 +1,8 @@ { "about": { "about": "About", - "bottom1": "{{brandType}} is a project within the", - "bottom2": "For more information, refer to", + "bottom1": "{{brandType}} is a project within the <2>Konveyor community.", + "bottom2": "For more information, refer to <1>{{brandType}} documentation.", "description": "{{brandType}} allows users to maintain their portfolio of applications with a full set of metadata and to assess their suitability for modernization leveraging a questionnaire based approach.", "iconLibrary": "The Icon Library used in this project is a derivative of the <2>Standard Icons library by <5>Red Hat, used under <8>CC BY 4.0", "introduction": "{{brandType}} is a collection of tools that supports large-scale application modernization and migration projects to Kubernetes." diff --git a/client/public/mta-favicon.ico b/client/public/mta-favicon.ico deleted file mode 100644 index 7fe9dd26e..000000000 Binary files a/client/public/mta-favicon.ico and /dev/null differ diff --git a/client/src/app/Constants.ts b/client/src/app/Constants.ts index 81954b6c0..2633f1e29 100644 --- a/client/src/app/Constants.ts +++ b/client/src/app/Constants.ts @@ -12,14 +12,8 @@ import { import { EffortEstimate, ProposedAction, Risk } from "@app/api/models"; import { ENV } from "./env"; -export enum BrandType { - Konveyor = "konveyor", - MTA = "mta", -} - export const isAuthRequired = ENV.AUTH_REQUIRED !== "false"; export const uploadLimit = ENV.UI_INGRESS_PROXY_BODY_SIZE || "500m"; -export const APP_BRAND = ENV.PROFILE as BrandType; export const isRWXSupported = ENV.RWX_SUPPORTED === "true"; export const DEFAULT_SELECT_MAX_HEIGHT = 200; diff --git a/client/src/app/hooks/useBranding.ts b/client/src/app/hooks/useBranding.ts new file mode 100644 index 000000000..cb90e3511 --- /dev/null +++ b/client/src/app/hooks/useBranding.ts @@ -0,0 +1,12 @@ +import { BrandingStrings, brandingStrings } from "@konveyor-ui/common"; + +/** + * Wrap the branding strings in a hook so components access it in a standard + * React way instead of a direct import. This allows the branding implementation + * to change in future with a minimal amount of refactoring in existing components. + */ +export const useBranding = (): BrandingStrings => { + return brandingStrings; +}; + +export default useBranding; diff --git a/client/src/app/images/avatar.svg b/client/src/app/images/avatar.svg deleted file mode 100644 index 11c80b85f..000000000 --- a/client/src/app/images/avatar.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/client/src/app/images/konveyor-logo-white-text.png b/client/src/app/images/konveyor-logo-white-text.png deleted file mode 100644 index 7fb562747..000000000 Binary files a/client/src/app/images/konveyor-logo-white-text.png and /dev/null differ diff --git a/client/src/app/images/logo-navbar-patternfly.svg b/client/src/app/images/logo-navbar-patternfly.svg deleted file mode 100644 index 232a493d1..000000000 --- a/client/src/app/images/logo-navbar-patternfly.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - PatternFly Logo - Created with Sketch. - - - - - - - - - - - - \ No newline at end of file diff --git a/client/src/app/images/logo-navbar.svg b/client/src/app/images/logo-navbar.svg deleted file mode 100644 index 61bf32f4f..000000000 --- a/client/src/app/images/logo-navbar.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/app/images/logo.svg b/client/src/app/images/logo.svg deleted file mode 100644 index 099049afb..000000000 --- a/client/src/app/images/logo.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/client/src/app/images/logoRedHat.svg b/client/src/app/images/logoRedHat.svg deleted file mode 100644 index 679f69799..000000000 --- a/client/src/app/images/logoRedHat.svg +++ /dev/null @@ -1 +0,0 @@ -RedHat-Logo-A-Reverse \ No newline at end of file diff --git a/client/src/app/images/tackle.png b/client/src/app/images/tackle.png deleted file mode 100644 index 6e61e7a5a..000000000 Binary files a/client/src/app/images/tackle.png and /dev/null differ diff --git a/client/src/app/layout/AppAboutModal/AppAboutModal.tsx b/client/src/app/layout/AppAboutModal/AppAboutModal.tsx index d1ff97743..9e466a16e 100644 --- a/client/src/app/layout/AppAboutModal/AppAboutModal.tsx +++ b/client/src/app/layout/AppAboutModal/AppAboutModal.tsx @@ -9,71 +9,74 @@ import { TextList, TextListItem, } from "@patternfly/react-core"; - -import konveyorBrandImage from "@app/images/Konveyor-white-logo.svg"; -import mtaBrandImage from "@app/images/logoRedHat.svg"; -import { APP_BRAND, BrandType } from "@app/Constants"; import { ENV } from "@app/env"; +import useBranding from "@app/hooks/useBranding"; export interface AppAboutModalProps { isOpen: boolean; onClose: () => void; } +const TRANSPARENT_1x1_GIF = + "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw== "; + export const AppAboutModal: React.FC = ({ isOpen, onClose, }) => { const { t } = useTranslation(); - const brandName = - APP_BRAND === BrandType.Konveyor - ? "Konveyor" - : "Migration Toolkit for Applications"; + const { about } = useBranding(); + return ( {t("about.about")} + - {t("about.introduction", { brandType: brandName })} + {t("about.introduction", { brandType: about.displayName })} + - {t("about.description", { brandType: brandName })} + {t("about.description", { brandType: about.displayName })} + - {t("about.bottom1", { brandType: brandName })}{" "} - - Konveyor community - - . + + {{ brandType: about.displayName }} is a project within the + + Konveyor community + + . + - - {t("about.bottom2")}{" "} - - {brandName} documentation + + {about.documentationUrl ? ( + + + For more information, refer to + + {{ brandType: about.displayName }} documentation + + . + - . - + ) : null} + The Icon Library used in this project is a derivative of the{" "} diff --git a/client/src/app/layout/AppAboutModal/tests/AppAboutModal.test.tsx b/client/src/app/layout/AppAboutModal/tests/AppAboutModal.test.tsx index db7d749b3..6b173e120 100644 --- a/client/src/app/layout/AppAboutModal/tests/AppAboutModal.test.tsx +++ b/client/src/app/layout/AppAboutModal/tests/AppAboutModal.test.tsx @@ -1,4 +1,4 @@ -import { render } from "@testing-library/react"; +import { render } from "@app/test-config/test-utils"; import React from "react"; import { AppAboutModal } from "../AppAboutModal"; diff --git a/client/src/app/layout/AppAboutModal/tests/__snapshots__/AppAboutModal.test.tsx.snap b/client/src/app/layout/AppAboutModal/tests/__snapshots__/AppAboutModal.test.tsx.snap index d876d1c24..d78b2a383 100644 --- a/client/src/app/layout/AppAboutModal/tests/__snapshots__/AppAboutModal.test.tsx.snap +++ b/client/src/app/layout/AppAboutModal/tests/__snapshots__/AppAboutModal.test.tsx.snap @@ -36,7 +36,7 @@ exports[`AppAboutModal 1`] = ` Logo
- about.bottom1 - + Konveyor + is a project within the - about.bottom2 - + For more information, refer to { + const { + masthead: { leftBrand, leftTitle, rightBrand }, + } = useBranding(); + const toolbar = ( @@ -69,16 +72,21 @@ export const HeaderApp: React.FC = () => { - {APP_BRAND === BrandType.MTA && ( + {rightBrand ? ( - Logo + - )} + ) : null} ); + return ( @@ -88,17 +96,22 @@ export const HeaderApp: React.FC = () => { - {APP_BRAND === BrandType.Konveyor ? ( + {leftBrand ? ( - ) : ( - - Migration Toolkit for Applications + ) : null} + {leftTitle ? ( + <Title + className="logo-pointer" + headingLevel={leftTitle?.heading ?? "h1"} + size={leftTitle?.size ?? "2xl"} + > + {leftTitle.text} - )} + ) : null} {toolbar} diff --git a/client/src/app/layout/HeaderApp/header.css b/client/src/app/layout/HeaderApp/header.css index 1585cdb0c..4bda787c4 100644 --- a/client/src/app/layout/HeaderApp/header.css +++ b/client/src/app/layout/HeaderApp/header.css @@ -1,10 +1,3 @@ .logo-pointer { cursor: default !important; } -.pf-v5-c-page__header-brand-link { - all: unset !important; -} -.redhat-logo-style { - height: 30px; - vertical-align: middle; -} diff --git a/client/src/app/layout/HeaderApp/tests/__snapshots__/HeaderApp.test.tsx.snap b/client/src/app/layout/HeaderApp/tests/__snapshots__/HeaderApp.test.tsx.snap index 880956053..8517bcbd8 100644 --- a/client/src/app/layout/HeaderApp/tests/__snapshots__/HeaderApp.test.tsx.snap +++ b/client/src/app/layout/HeaderApp/tests/__snapshots__/HeaderApp.test.tsx.snap @@ -46,7 +46,7 @@ exports[`Test snapshot 1`] = ` brand @@ -170,7 +170,7 @@ exports[`Test snapshot 1`] = ` brand diff --git a/client/src/app/logo.svg b/client/src/app/logo.svg deleted file mode 100644 index 9dfc1c058..000000000 --- a/client/src/app/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/app/setupTests.ts b/client/src/app/setupTests.ts deleted file mode 100644 index 99dfb8bca..000000000 --- a/client/src/app/setupTests.ts +++ /dev/null @@ -1,50 +0,0 @@ -import "@testing-library/jest-dom"; - -let mockInitialized = false; - -jest.mock("@react-keycloak/web", () => { - const originalModule = jest.requireActual("@react-keycloak/web"); - return { - ...originalModule, - useKeycloak: () => [mockInitialized], - }; -}); - -jest.mock("react-i18next", () => ({ - Trans: ({ children }: { children: any }) => { - return children; - }, - useTranslation: () => { - return { - t: (str: any) => str, - i18n: { - changeLanguage: () => new Promise(() => {}), - }, - }; - }, -})); - -jest.mock("react-router-dom", () => ({ - ...jest.requireActual("react-router-dom"), - useLocation: () => ({ - pathname: "localhost:3000/example/path", - }), -})); - -jest.mock("react-i18next", () => ({ - Trans: ({ children }: { children: any }) => { - return children; - }, - useTranslation: () => { - return { - t: (str: any) => str, - i18n: { - changeLanguage: () => new Promise(() => {}), - }, - }; - }, - initReactI18next: { - type: "3rdParty", - init: jest.fn(), - }, -})); diff --git a/client/src/app/test-config/setupTests.ts b/client/src/app/test-config/setupTests.ts new file mode 100644 index 000000000..0618b987c --- /dev/null +++ b/client/src/app/test-config/setupTests.ts @@ -0,0 +1,18 @@ +import "@testing-library/jest-dom"; + +const mockInitialized = false; + +jest.mock("@react-keycloak/web", () => { + const originalModule = jest.requireActual("@react-keycloak/web"); + return { + ...originalModule, + useKeycloak: () => [mockInitialized], + }; +}); + +jest.mock("react-router-dom", () => ({ + ...jest.requireActual("react-router-dom"), + useLocation: () => ({ + pathname: "localhost:3000/example/path", + }), +})); diff --git a/common/package.json b/common/package.json index fd763c768..7292b1091 100644 --- a/common/package.json +++ b/common/package.json @@ -10,7 +10,8 @@ "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.cjs" - } + }, + "./package.json": "./package.json" }, "types": "./dist", "main": "./dist/index.cjs", diff --git a/common/rollup.config.js b/common/rollup.config.js index 8ca573c28..d6fbad60b 100644 --- a/common/rollup.config.js +++ b/common/rollup.config.js @@ -1,5 +1,35 @@ +/* eslint-env node */ + +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { readFileSync } from "node:fs"; +import util from "node:util"; + +import copy from "rollup-plugin-copy"; import nodeResolve from "@rollup/plugin-node-resolve"; import typescript from "@rollup/plugin-typescript"; +import virtual from "@rollup/plugin-virtual"; +import ejs from "ejs"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); +const pathTo = (...relativePath) => path.resolve(__dirname, ...relativePath); + +const baseBrandingPath = process.env.BRANDING ?? "./branding"; +const brandingPath = pathTo("../", baseBrandingPath); +const jsonStrings = JSON.parse( + readFileSync(path.resolve(brandingPath, "./strings.json"), "utf8") +); +const stringModule = ejs.render( + ` + export const strings = ${util.inspect(jsonStrings)}; + export default strings; +`, + { + brandingRoot: "branding", + } +); + +console.log("Using branding assets from:", brandingPath); const config = { input: "src/index.ts", @@ -21,7 +51,16 @@ const config = { clearScreen: false, }, - plugins: [nodeResolve(), typescript()], + plugins: [ + copy({ + targets: [{ src: `${brandingPath}/**/*`, dest: "dist/branding" }], + }), + nodeResolve(), + typescript(), + virtual({ + "@branding/strings.json": stringModule, + }), + ], }; export default config; diff --git a/common/src/branding-strings-stub.json b/common/src/branding-strings-stub.json new file mode 100644 index 000000000..8477bd903 --- /dev/null +++ b/common/src/branding-strings-stub.json @@ -0,0 +1,21 @@ +{ + "application": { + "title": "Stub to allow package build to work", + "name": "", + "description": "" + }, + "about": { + "displayName": "", + "image": "", + "documentationUrl": "" + }, + "masthead": { + "leftBrand": { + "src": "", + "alt": "", + "height": "" + }, + "leftTitle": null, + "rightBrand": null + } +} diff --git a/common/src/branding.ts b/common/src/branding.ts new file mode 100644 index 000000000..059051f76 --- /dev/null +++ b/common/src/branding.ts @@ -0,0 +1,48 @@ +export interface MastheadBrand { + src: string; + alt: string; + height: string; +} + +export interface MastheadTitle { + text: string; + heading?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + size?: "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl"; +} + +export interface BrandingStrings { + application: { + title: string; + name?: string; + description?: string; + }; + + about: { + displayName: string; + imageSrc?: string; + documentationUrl?: string; + }; + + masthead: { + leftBrand?: MastheadBrand; + leftTitle?: MastheadTitle; + rightBrand?: MastheadBrand; + }; +} + +// Note: Typescript will look at the `paths` definition to resolve this import +// to a stub JSON file. In the next rollup build step, that import will +// be replaced by the rollup virtual plugin with a dynamically generated +// JSON import with the actual branding information. +import * as stringsJson from "@branding/strings.json"; + +export const brandingStrings = + stringsJson.default as unknown as BrandingStrings; + +/** + * Return the `node_modules/` resolved path for the branding assets. + */ +export const brandingAssetPath = () => + require + .resolve("@konveyor-ui/common/package.json") + .replace(/(.)\/package.json$/, "$1") + "/dist/branding"; diff --git a/common/src/environment.ts b/common/src/environment.ts index 06309a33b..060f2b3e9 100644 --- a/common/src/environment.ts +++ b/common/src/environment.ts @@ -25,9 +25,6 @@ export type KonveyorEnvType = { /** SSO / Keycloak client id */ KEYCLOAK_CLIENT_ID: string; - /** Branding to apply to the UI */ - PROFILE: "konveyor" | "mta"; - /** UI upload file size limit in megabytes (MB), suffixed with "m" */ UI_INGRESS_PROXY_BODY_SIZE: string; @@ -42,6 +39,9 @@ export type KonveyorEnvType = { /** Target URL for the UI server's `/hub` proxy */ TACKLE_HUB_URL?: string; + + /** Location of branding files (relative paths computed from the project source root) */ + BRANDING?: string; }; /** @@ -52,6 +52,7 @@ export const SERVER_ENV_KEYS = [ "PORT", "KEYCLOAK_SERVER_URL", "TACKLE_HUB_URL", + "BRANDING", ]; /** @@ -68,10 +69,10 @@ export const buildKonveyorEnv = ({ KEYCLOAK_REALM = "tackle", KEYCLOAK_CLIENT_ID = "tackle-ui", - PROFILE = "konveyor", UI_INGRESS_PROXY_BODY_SIZE = "500m", RWX_SUPPORTED = "true", TACKLE_HUB_URL, + BRANDING, }: Partial = {}): KonveyorEnvType => ({ NODE_ENV, PORT, @@ -83,10 +84,10 @@ export const buildKonveyorEnv = ({ KEYCLOAK_REALM, KEYCLOAK_CLIENT_ID, - PROFILE, UI_INGRESS_PROXY_BODY_SIZE, RWX_SUPPORTED, TACKLE_HUB_URL, + BRANDING, }); /** diff --git a/common/src/index.ts b/common/src/index.ts index 12f839c15..fd048162a 100644 --- a/common/src/index.ts +++ b/common/src/index.ts @@ -1,5 +1,6 @@ export * from "./environment.js"; export * from "./proxies.js"; +export * from "./branding.js"; /** * Return a base64 encoded JSON string containing the given `env` object. diff --git a/common/tsconfig.json b/common/tsconfig.json index e6785502e..c65dfdab8 100644 --- a/common/tsconfig.json +++ b/common/tsconfig.json @@ -6,13 +6,18 @@ "compilerOptions": { "strict": true, "target": "es2020", - "module": "Node16", - "moduleResolution": "Node16", + "module": "node16", + "moduleResolution": "node16", "outDir": "./dist", "declaration": true, "declarationMap": true, "sourceMap": true, - "inlineSources": true + "inlineSources": true, + "resolveJsonModule": true, + + "paths": { + "@branding/strings.json": ["./src/branding-strings-stub.json"] + } } } diff --git a/package-lock.json b/package-lock.json index 6ed9531d8..e6b4957ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,12 @@ "client" ], "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.3", - "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.1.0", - "@rollup/plugin-run": "^3.0.1", - "@rollup/plugin-typescript": "^11.1.2", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-run": "^3.0.2", + "@rollup/plugin-typescript": "^11.1.6", + "@rollup/plugin-virtual": "^3.0.2", "@tanstack/eslint-plugin-query": "^4.34.1", "@types/jest": "^29.5.4", "@types/node": "^18.14.2", @@ -41,7 +42,8 @@ "lint-staged": "^14.0.1", "prettier": "^3.0.2", "rimraf": "^4.4.1", - "rollup": "^3.27.2", + "rollup": "^4.9.5", + "rollup-plugin-copy": "^3.5.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "type-fest": "^3.13.0", @@ -1905,9 +1907,9 @@ } }, "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz", - "integrity": "sha512-L92Vz9WUZXDnlQQl3EwbypJR4+DM2EbsO+/KOcEkP4Mc6Ct453EeDB2uH9lgRwj4w5yflgNpq9pHOiY8aoUXBQ==", + "version": "25.0.7", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz", + "integrity": "sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -1915,13 +1917,13 @@ "estree-walker": "^2.0.2", "glob": "^8.0.3", "is-reference": "1.2.1", - "magic-string": "^0.27.0" + "magic-string": "^0.30.3" }, "engines": { "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.68.0||^3.0.0" + "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -1930,18 +1932,18 @@ } }, "node_modules/@rollup/plugin-json": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz", - "integrity": "sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", "dev": true, "dependencies": { - "@rollup/pluginutils": "^5.0.1" + "@rollup/pluginutils": "^5.1.0" }, "engines": { "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -1950,9 +1952,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz", - "integrity": "sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w==", + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -1966,7 +1968,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.78.0||^3.0.0" + "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -1975,9 +1977,9 @@ } }, "node_modules/@rollup/plugin-run": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-run/-/plugin-run-3.0.1.tgz", - "integrity": "sha512-wbYR1Ahz8ohYnlyXzpBTwhGWfs+OO/uZMjgpDGr8AgnL/XfoTbO7BuNYY8ncR/j/1dhCJo+NDuTRkIeCxE434Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-run/-/plugin-run-3.0.2.tgz", + "integrity": "sha512-pgQcDA34u6C+8Yr0FSd2T7iN2UTQe8zJ+1pbceNJ9MkvGrN0sz4D6Mzg3eYkGrbV4zC7H5QvwlzC1GqH4PrSrg==", "dev": true, "dependencies": { "@types/node": "14.18.30" @@ -1986,7 +1988,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.0.0||^3.0.0" + "rollup": "^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -2001,19 +2003,19 @@ "dev": true }, "node_modules/@rollup/plugin-typescript": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.3.tgz", - "integrity": "sha512-8o6cNgN44kQBcpsUJTbTXMTtb87oR1O0zgP3Dxm71hrNgparap3VujgofEilTYJo+ivf2ke6uy3/E5QEaiRlDA==", + "version": "11.1.6", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz", + "integrity": "sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==", "dev": true, "dependencies": { - "@rollup/pluginutils": "^5.0.1", + "@rollup/pluginutils": "^5.1.0", "resolve": "^1.22.1" }, "engines": { "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.14.0||^3.0.0", + "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" }, @@ -2026,10 +2028,27 @@ } } }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", + "dev": true, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", - "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", "dev": true, "dependencies": { "@types/estree": "^1.0.0", @@ -2040,7 +2059,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -2048,6 +2067,175 @@ } } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz", + "integrity": "sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz", + "integrity": "sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz", + "integrity": "sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz", + "integrity": "sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz", + "integrity": "sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz", + "integrity": "sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz", + "integrity": "sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz", + "integrity": "sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", + "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz", + "integrity": "sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz", + "integrity": "sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz", + "integrity": "sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz", + "integrity": "sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2546,9 +2734,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/express": { @@ -2581,6 +2769,25 @@ "integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==", "dev": true }, + "node_modules/@types/fs-extra": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", + "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", @@ -2706,6 +2913,12 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", "devOptional": true }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", @@ -11040,12 +11253,12 @@ } }, "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -14128,21 +14341,139 @@ } }, "node_modules/rollup": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.1.tgz", - "integrity": "sha512-c+ebvQz0VIH4KhhCpDsI+Bik0eT8ZFEVZEYw0cGMVqIP8zc+gnwl7iXCamTw7vzv2MeuZFZfdx5JJIq+ehzDlg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.5.tgz", + "integrity": "sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.9.5", + "@rollup/rollup-android-arm64": "4.9.5", + "@rollup/rollup-darwin-arm64": "4.9.5", + "@rollup/rollup-darwin-x64": "4.9.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.5", + "@rollup/rollup-linux-arm64-gnu": "4.9.5", + "@rollup/rollup-linux-arm64-musl": "4.9.5", + "@rollup/rollup-linux-riscv64-gnu": "4.9.5", + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@rollup/rollup-linux-x64-musl": "4.9.5", + "@rollup/rollup-win32-arm64-msvc": "4.9.5", + "@rollup/rollup-win32-ia32-msvc": "4.9.5", + "@rollup/rollup-win32-x64-msvc": "4.9.5", "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-copy": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz", + "integrity": "sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==", + "dev": true, + "dependencies": { + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" + }, + "engines": { + "node": ">=8.3" + } + }, + "node_modules/rollup-plugin-copy/node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, + "node_modules/rollup-plugin-copy/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/rollup-plugin-copy/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup-plugin-copy/node_modules/globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-copy/node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup-plugin-copy/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/rollup-plugin-copy/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/run-applescript": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", diff --git a/package.json b/package.json index 574a135ae..e65f3f6fa 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,12 @@ "npm": ">=9.5.0" }, "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.3", - "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.1.0", - "@rollup/plugin-run": "^3.0.1", - "@rollup/plugin-typescript": "^11.1.2", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-run": "^3.0.2", + "@rollup/plugin-typescript": "^11.1.6", + "@rollup/plugin-virtual": "^3.0.2", "@tanstack/eslint-plugin-query": "^4.34.1", "@types/jest": "^29.5.4", "@types/node": "^18.14.2", @@ -65,7 +66,8 @@ "lint-staged": "^14.0.1", "prettier": "^3.0.2", "rimraf": "^4.4.1", - "rollup": "^3.27.2", + "rollup": "^4.9.5", + "rollup-plugin-copy": "^3.5.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "type-fest": "^3.13.0", diff --git a/server/src/index.js b/server/src/index.js index 44081efc1..161a59231 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -1,7 +1,7 @@ /* eslint-env node */ -import path from "path"; -import { fileURLToPath } from "url"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; import express from "express"; import ejs from "ejs"; @@ -14,12 +14,12 @@ import { KONVEYOR_ENV, SERVER_ENV_KEYS, proxyMap, + brandingStrings, } from "@konveyor-ui/common"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); const pathToClientDist = path.join(__dirname, "../../client/dist"); -const brandType = KONVEYOR_ENV.PROFILE; const port = parseInt(KONVEYOR_ENV.PORT, 10) || 8080; const app = express(); @@ -48,7 +48,7 @@ app.get("*", (_, res) => { } else { res.render("index.html.ejs", { _env: encodeEnv(KONVEYOR_ENV, SERVER_ENV_KEYS), - brandType, + branding: brandingStrings, }); } });