feat: add environment configuration files for frontend (#536)
Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com>
This commit is contained in:
parent
42ffd9b0c5
commit
e666e2ebfb
|
|
@ -0,0 +1,4 @@
|
||||||
|
LOGO=logo-light-theme.svg
|
||||||
|
LOGO_DARK=logo-dark-theme.svg
|
||||||
|
FAVICON=favicon.ico
|
||||||
|
PRODUCT_NAME="Notebooks"
|
||||||
|
|
@ -1,2 +1,7 @@
|
||||||
# Test against prod build hosted by lightweight http server
|
# Test against prod build hosted by lightweight http server
|
||||||
BASE_URL=http://localhost:9001
|
BASE_URL=http://localhost:9001
|
||||||
|
DEPLOYMENT_MODE=standalone
|
||||||
|
POLL_INTERVAL=9999999
|
||||||
|
DIST_DIR=./dist
|
||||||
|
URL_PREFIX=/
|
||||||
|
PUBLIC_PATH=/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
APP_ENV=development
|
||||||
|
DEPLOYMENT_MODE=standalone
|
||||||
|
MOCK_API_ENABLED=true
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
APP_ENV=production
|
||||||
|
|
@ -5,6 +5,5 @@ yarn.lock
|
||||||
stats.json
|
stats.json
|
||||||
coverage
|
coverage
|
||||||
.idea
|
.idea
|
||||||
.env
|
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
|
|
@ -50,11 +50,7 @@ This is the default setup for running the UI locally. Make sure you build the pr
|
||||||
npm run start:dev
|
npm run start:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
The command above requires the backend to be active in order to serve data. To run the UI independently, without establishing a connection to the backend, use the following command to start the application with a mocked API:
|
The command above starts the UI with mocked data by default, so you can run the application without requiring a connection to the backend. This behavior can be customized in the `.env.development` file by setting the `MOCK_API_ENABLED` environment variable to `false`.
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run start:dev:mock
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
targets: {
|
||||||
|
chrome: 110,
|
||||||
|
},
|
||||||
|
useBuiltIns: 'usage',
|
||||||
|
corejs: '3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@babel/preset-react',
|
||||||
|
'@babel/preset-typescript',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -6,3 +6,5 @@ millicores
|
||||||
workspacekind
|
workspacekind
|
||||||
workspacekinds
|
workspacekinds
|
||||||
healthcheck
|
healthcheck
|
||||||
|
pficon
|
||||||
|
svgs
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
const dotenvExpand = require('dotenv-expand');
|
||||||
|
const Dotenv = require('dotenv-webpack');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the project is standalone or nested.
|
||||||
|
*
|
||||||
|
* @param {string} directory
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
const getProjectIsRootDir = (directory) => {
|
||||||
|
const dotenvLocalFile = path.resolve(directory, '.env.local');
|
||||||
|
const dotenvFile = path.resolve(directory, '.env');
|
||||||
|
let localIsRoot;
|
||||||
|
let isRoot;
|
||||||
|
|
||||||
|
if (fs.existsSync(dotenvLocalFile)) {
|
||||||
|
const { IS_PROJECT_ROOT_DIR: DOTENV_LOCAL_ROOT } = dotenv.parse(
|
||||||
|
fs.readFileSync(dotenvLocalFile),
|
||||||
|
);
|
||||||
|
localIsRoot = DOTENV_LOCAL_ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(dotenvFile)) {
|
||||||
|
const { IS_PROJECT_ROOT_DIR: DOTENV_ROOT } = dotenv.parse(fs.readFileSync(dotenvFile));
|
||||||
|
isRoot = DOTENV_ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return localIsRoot !== undefined ? localIsRoot !== 'false' : isRoot !== 'false';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return tsconfig compilerOptions.
|
||||||
|
*
|
||||||
|
* @param {string} directory
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
const getTsCompilerOptions = (directory) => {
|
||||||
|
const tsconfigFile = path.resolve(directory, './tsconfig.json');
|
||||||
|
let tsCompilerOptions = {};
|
||||||
|
|
||||||
|
if (fs.existsSync(tsconfigFile)) {
|
||||||
|
const { compilerOptions = { outDir: './dist', baseUrl: './src' } } = require(tsconfigFile);
|
||||||
|
tsCompilerOptions = compilerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tsCompilerOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup a webpack dotenv plugin config.
|
||||||
|
*
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
const setupWebpackDotenvFile = (path) => {
|
||||||
|
const settings = {
|
||||||
|
systemvars: true,
|
||||||
|
silent: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
settings.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Dotenv(settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup multiple webpack dotenv file parameters.
|
||||||
|
*
|
||||||
|
* @param {string} directory
|
||||||
|
* @param {string} env
|
||||||
|
* @param {boolean} isRoot
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
const setupWebpackDotenvFilesForEnv = ({ directory, env, isRoot = true }) => {
|
||||||
|
const dotenvWebpackSettings = [];
|
||||||
|
|
||||||
|
if (process.env.CY_MOCK) {
|
||||||
|
dotenvWebpackSettings.push(
|
||||||
|
setupWebpackDotenvFile(path.resolve(directory, `.env.cypress.mock`)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env) {
|
||||||
|
dotenvWebpackSettings.push(
|
||||||
|
setupWebpackDotenvFile(path.resolve(directory, `.env.${env}.local`)),
|
||||||
|
);
|
||||||
|
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, `.env.${env}`)));
|
||||||
|
}
|
||||||
|
|
||||||
|
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '.env.local')));
|
||||||
|
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '.env')));
|
||||||
|
|
||||||
|
if (!isRoot) {
|
||||||
|
if (env) {
|
||||||
|
dotenvWebpackSettings.push(
|
||||||
|
setupWebpackDotenvFile(path.resolve(directory, '..', `.env.${env}.local`)),
|
||||||
|
);
|
||||||
|
dotenvWebpackSettings.push(
|
||||||
|
setupWebpackDotenvFile(path.resolve(directory, '..', `.env.${env}`)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '..', '.env.local')));
|
||||||
|
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '..', '.env')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotenvWebpackSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup, and access, a dotenv file and the related set of parameters.
|
||||||
|
*
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
const setupDotenvFile = (path) => {
|
||||||
|
const dotenvInitial = dotenv.config({ path });
|
||||||
|
dotenvExpand(dotenvInitial);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup and access local and specific dotenv file parameters.
|
||||||
|
*
|
||||||
|
* @param {string} env
|
||||||
|
*/
|
||||||
|
const setupDotenvFilesForEnv = ({ env }) => {
|
||||||
|
const RELATIVE_DIRNAME = path.resolve(__dirname, '..');
|
||||||
|
const IS_ROOT = getProjectIsRootDir(RELATIVE_DIRNAME);
|
||||||
|
const { baseUrl: TS_BASE_URL, outDir: TS_OUT_DIR } = getTsCompilerOptions(RELATIVE_DIRNAME);
|
||||||
|
|
||||||
|
if (process.env.CY_MOCK) {
|
||||||
|
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, `.env.cypress.mock`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IS_ROOT) {
|
||||||
|
if (env) {
|
||||||
|
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', `.env.${env}.local`));
|
||||||
|
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', `.env.${env}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', '.env.local'));
|
||||||
|
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', '.env'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env) {
|
||||||
|
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, `.env.${env}.local`));
|
||||||
|
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, `.env.${env}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '.env.local'));
|
||||||
|
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '.env'));
|
||||||
|
|
||||||
|
const DEPLOYMENT_MODE = process.env.DEPLOYMENT_MODE || 'kubeflow';
|
||||||
|
const AUTH_METHOD = process.env.AUTH_METHOD || 'internal';
|
||||||
|
const IMAGES_DIRNAME = process.env.IMAGES_DIRNAME || 'images';
|
||||||
|
const PUBLIC_PATH = process.env.PUBLIC_PATH || '/workspaces';
|
||||||
|
const SRC_DIR = path.resolve(RELATIVE_DIRNAME, process.env.SRC_DIR || TS_BASE_URL || 'src');
|
||||||
|
const COMMON_DIR = path.resolve(RELATIVE_DIRNAME, process.env.COMMON_DIR || '../common');
|
||||||
|
const DIST_DIR = path.resolve(RELATIVE_DIRNAME, process.env.DIST_DIR || TS_OUT_DIR || 'dist');
|
||||||
|
const HOST = process.env.HOST || DEPLOYMENT_MODE === 'kubeflow' ? '0.0.0.0' : 'localhost';
|
||||||
|
const PORT = process.env.PORT || '9000';
|
||||||
|
const PROXY_PROTOCOL = process.env.PROXY_PROTOCOL || 'http';
|
||||||
|
const PROXY_HOST = process.env.PROXY_HOST || 'localhost';
|
||||||
|
const PROXY_PORT = process.env.PROXY_PORT || process.env.PORT || 4000;
|
||||||
|
const DEV_MODE = process.env.DEV_MODE || undefined;
|
||||||
|
const OUTPUT_ONLY = process.env._OUTPUT_ONLY === 'true';
|
||||||
|
|
||||||
|
process.env._RELATIVE_DIRNAME = RELATIVE_DIRNAME;
|
||||||
|
process.env._IS_PROJECT_ROOT_DIR = IS_ROOT;
|
||||||
|
process.env._IMAGES_DIRNAME = IMAGES_DIRNAME;
|
||||||
|
process.env._PUBLIC_PATH = PUBLIC_PATH;
|
||||||
|
process.env._SRC_DIR = SRC_DIR;
|
||||||
|
process.env._COMMON_DIR = COMMON_DIR;
|
||||||
|
process.env._DIST_DIR = DIST_DIR;
|
||||||
|
process.env._HOST = HOST;
|
||||||
|
process.env._PORT = PORT;
|
||||||
|
process.env._PROXY_PROTOCOL = PROXY_PROTOCOL;
|
||||||
|
process.env._PROXY_HOST = PROXY_HOST;
|
||||||
|
process.env._PROXY_PORT = PROXY_PORT;
|
||||||
|
process.env._OUTPUT_ONLY = OUTPUT_ONLY;
|
||||||
|
process.env._DEV_MODE = DEV_MODE;
|
||||||
|
process.env._DEPLOYMENT_MODE = DEPLOYMENT_MODE;
|
||||||
|
process.env._AUTH_METHOD = AUTH_METHOD;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { setupWebpackDotenvFilesForEnv, setupDotenvFilesForEnv };
|
||||||
|
|
@ -23,5 +23,6 @@ module.exports = {
|
||||||
relativeDir,
|
relativeDir,
|
||||||
'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css',
|
'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css',
|
||||||
),
|
),
|
||||||
|
path.resolve(relativeDir, 'node_modules/@patternfly/react-catalog-view-extension/dist/css'),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,74 @@
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const CopyPlugin = require('copy-webpack-plugin');
|
const CopyPlugin = require('copy-webpack-plugin');
|
||||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
const { setupWebpackDotenvFilesForEnv } = require('./dotenv');
|
||||||
const Dotenv = require('dotenv-webpack');
|
const { name } = require('../package.json');
|
||||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
|
||||||
const { EnvironmentPlugin } = require('webpack');
|
const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME;
|
||||||
const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
|
const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR;
|
||||||
const IMAGES_DIRNAME = 'images';
|
const IMAGES_DIRNAME = process.env._IMAGES_DIRNAME;
|
||||||
const relativeDir = path.resolve(__dirname, '..');
|
const PUBLIC_PATH = process.env._PUBLIC_PATH;
|
||||||
module.exports = (env) => {
|
const SRC_DIR = process.env._SRC_DIR;
|
||||||
return {
|
const COMMON_DIR = process.env._COMMON_DIR;
|
||||||
|
const DIST_DIR = process.env._DIST_DIR;
|
||||||
|
const OUTPUT_ONLY = process.env._OUTPUT_ONLY;
|
||||||
|
const FAVICON = process.env.FAVICON;
|
||||||
|
const PRODUCT_NAME = process.env.PRODUCT_NAME;
|
||||||
|
const COVERAGE = process.env.COVERAGE;
|
||||||
|
const DEPLOYMENT_MODE = process.env._DEPLOYMENT_MODE;
|
||||||
|
const BASE_PATH = DEPLOYMENT_MODE === 'kubeflow' ? '/workspaces' : PUBLIC_PATH;
|
||||||
|
|
||||||
|
if (OUTPUT_ONLY !== 'true') {
|
||||||
|
console.info(
|
||||||
|
`\nPrepping files...` +
|
||||||
|
`\n\tSRC DIR: ${SRC_DIR}` +
|
||||||
|
`\n\tOUTPUT DIR: ${DIST_DIR}` +
|
||||||
|
`\n\tPUBLIC PATH: ${PUBLIC_PATH}` +
|
||||||
|
`\n\tBASE_PATH: ${BASE_PATH}\n`,
|
||||||
|
);
|
||||||
|
if (COVERAGE === 'true') {
|
||||||
|
console.info('\nAdding code coverage instrumentation.\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (env) => ({
|
||||||
|
entry: {
|
||||||
|
app: path.join(SRC_DIR, 'index.tsx'),
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.(tsx|ts|jsx)?$/,
|
test: /\.(tsx|ts|jsx|js)?$/,
|
||||||
|
exclude: [/node_modules/, /__tests__/, /__mocks__/],
|
||||||
|
include: [SRC_DIR, COMMON_DIR],
|
||||||
use: [
|
use: [
|
||||||
{
|
COVERAGE === 'true' && '@jsdevtools/coverage-istanbul-loader',
|
||||||
|
env === 'development'
|
||||||
|
? { loader: 'swc-loader' }
|
||||||
|
: {
|
||||||
loader: 'ts-loader',
|
loader: 'ts-loader',
|
||||||
options: {
|
options: {
|
||||||
experimentalWatchApi: true,
|
transpileOnly: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(svg|ttf|eot|woff|woff2)$/,
|
test: /\.(svg|ttf|eot|woff|woff2)$/,
|
||||||
type: 'asset/resource',
|
|
||||||
// only process modules with this loader
|
// only process modules with this loader
|
||||||
// if they live under a 'fonts' or 'pficon' directory
|
// if they live under a 'fonts' or 'pficon' directory
|
||||||
include: [
|
include: [
|
||||||
path.resolve(relativeDir, 'node_modules/patternfly/dist/fonts'),
|
path.resolve(RELATIVE_DIRNAME, 'node_modules/patternfly/dist/fonts'),
|
||||||
path.resolve(
|
path.resolve(
|
||||||
relativeDir,
|
RELATIVE_DIRNAME,
|
||||||
'node_modules/@patternfly/react-core/dist/styles/assets/fonts',
|
'node_modules/@patternfly/react-core/dist/styles/assets/fonts',
|
||||||
),
|
),
|
||||||
path.resolve(
|
path.resolve(
|
||||||
relativeDir,
|
RELATIVE_DIRNAME,
|
||||||
'node_modules/@patternfly/react-core/dist/styles/assets/pficon',
|
'node_modules/@patternfly/react-core/dist/styles/assets/pficon',
|
||||||
),
|
),
|
||||||
path.resolve(relativeDir, 'node_modules/@patternfly/patternfly/assets/fonts'),
|
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/patternfly/assets/fonts'),
|
||||||
path.resolve(relativeDir, 'node_modules/@patternfly/patternfly/assets/pficon'),
|
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/patternfly/assets/pficon'),
|
||||||
],
|
],
|
||||||
use: {
|
use: {
|
||||||
loader: 'file-loader',
|
loader: 'file-loader',
|
||||||
|
|
@ -96,30 +123,31 @@ module.exports = (env) => {
|
||||||
{
|
{
|
||||||
test: /\.(jpg|jpeg|png|gif)$/i,
|
test: /\.(jpg|jpeg|png|gif)$/i,
|
||||||
include: [
|
include: [
|
||||||
path.resolve(relativeDir, 'src'),
|
SRC_DIR,
|
||||||
path.resolve(relativeDir, 'node_modules/patternfly'),
|
COMMON_DIR,
|
||||||
path.resolve(relativeDir, 'node_modules/@patternfly/patternfly/assets/images'),
|
path.resolve(RELATIVE_DIRNAME, 'node_modules/patternfly'),
|
||||||
path.resolve(relativeDir, 'node_modules/@patternfly/react-styles/css/assets/images'),
|
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/patternfly/assets/images'),
|
||||||
|
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/react-styles/css/assets/images'),
|
||||||
path.resolve(
|
path.resolve(
|
||||||
relativeDir,
|
RELATIVE_DIRNAME,
|
||||||
'node_modules/@patternfly/react-core/dist/styles/assets/images',
|
'node_modules/@patternfly/react-core/dist/styles/assets/images',
|
||||||
),
|
),
|
||||||
path.resolve(
|
path.resolve(
|
||||||
relativeDir,
|
RELATIVE_DIRNAME,
|
||||||
'node_modules/@patternfly/react-core/node_modules/@patternfly/react-styles/css/assets/images',
|
'node_modules/@patternfly/react-core/node_modules/@patternfly/react-styles/css/assets/images',
|
||||||
),
|
),
|
||||||
path.resolve(
|
path.resolve(
|
||||||
relativeDir,
|
RELATIVE_DIRNAME,
|
||||||
'node_modules/@patternfly/react-table/node_modules/@patternfly/react-styles/css/assets/images',
|
'node_modules/@patternfly/react-table/node_modules/@patternfly/react-styles/css/assets/images',
|
||||||
),
|
),
|
||||||
path.resolve(
|
path.resolve(
|
||||||
relativeDir,
|
RELATIVE_DIRNAME,
|
||||||
'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css/assets/images',
|
'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css/assets/images',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
type: 'asset/inline',
|
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
|
loader: 'url-loader',
|
||||||
options: {
|
options: {
|
||||||
limit: 5000,
|
limit: 5000,
|
||||||
outputPath: 'images',
|
outputPath: 'images',
|
||||||
|
|
@ -140,47 +168,78 @@ module.exports = (env) => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/i,
|
test: /\.ya?ml$/,
|
||||||
use: ['style-loader', 'css-loader'],
|
use: 'js-yaml-loader',
|
||||||
include: [
|
|
||||||
path.resolve(
|
|
||||||
relativeDir,
|
|
||||||
'node_modules/@patternfly/react-catalog-view-extension/dist/css/react-catalog-view-extension.css',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: '[name].bundle.js',
|
filename: '[name].bundle.js',
|
||||||
path: path.resolve(relativeDir, 'dist'),
|
path: DIST_DIR,
|
||||||
publicPath: APP_PREFIX,
|
publicPath: BASE_PATH,
|
||||||
|
uniqueName: name,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({
|
...setupWebpackDotenvFilesForEnv({
|
||||||
template: path.resolve(relativeDir, 'src', 'index.html'),
|
directory: RELATIVE_DIRNAME,
|
||||||
|
isRoot: IS_PROJECT_ROOT_DIR,
|
||||||
}),
|
}),
|
||||||
new Dotenv({
|
new HtmlWebpackPlugin({
|
||||||
systemvars: true,
|
template: path.join(SRC_DIR, 'index.html'),
|
||||||
silent: true,
|
title: PRODUCT_NAME,
|
||||||
|
favicon: path.join(SRC_DIR, 'images', FAVICON),
|
||||||
|
publicPath: BASE_PATH,
|
||||||
|
base: {
|
||||||
|
href: BASE_PATH,
|
||||||
|
},
|
||||||
|
chunks: ['app'],
|
||||||
}),
|
}),
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [{ from: './src/images', to: 'images' }],
|
patterns: [
|
||||||
}),
|
{
|
||||||
new ForkTsCheckerWebpackPlugin(),
|
from: path.join(SRC_DIR, 'locales'),
|
||||||
new EnvironmentPlugin({
|
to: path.join(DIST_DIR, 'locales'),
|
||||||
APP_PREFIX: process.env.APP_PREFIX || '/workspaces',
|
noErrorOnMissing: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(SRC_DIR, 'favicons'),
|
||||||
|
to: path.join(DIST_DIR, 'favicons'),
|
||||||
|
noErrorOnMissing: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(SRC_DIR, 'images'),
|
||||||
|
to: path.join(DIST_DIR, 'images'),
|
||||||
|
noErrorOnMissing: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(SRC_DIR, 'favicon.ico'),
|
||||||
|
to: path.join(DIST_DIR),
|
||||||
|
noErrorOnMissing: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(SRC_DIR, 'favicon.png'),
|
||||||
|
to: path.join(DIST_DIR),
|
||||||
|
noErrorOnMissing: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(SRC_DIR, 'manifest.json'),
|
||||||
|
to: path.join(DIST_DIR),
|
||||||
|
noErrorOnMissing: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(SRC_DIR, 'robots.txt'),
|
||||||
|
to: path.join(DIST_DIR),
|
||||||
|
noErrorOnMissing: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.ts', '.tsx', '.jsx'],
|
extensions: ['.js', '.ts', '.tsx', '.jsx'],
|
||||||
plugins: [
|
alias: {
|
||||||
new TsconfigPathsPlugin({
|
'~': path.resolve(SRC_DIR),
|
||||||
configFile: path.resolve(relativeDir, './tsconfig.json'),
|
},
|
||||||
}),
|
|
||||||
],
|
|
||||||
symlinks: false,
|
symlinks: false,
|
||||||
cacheWithContext: false,
|
cacheWithContext: false,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,141 @@
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
const { execSync } = require('child_process');
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { EnvironmentPlugin } = require('webpack');
|
|
||||||
const { merge } = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
const common = require('./webpack.common.js');
|
const { setupWebpackDotenvFilesForEnv, setupDotenvFilesForEnv } = require('./dotenv');
|
||||||
const { stylePaths } = require('./stylePaths');
|
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
const HOST = process.env.HOST || 'localhost';
|
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||||
const PORT = process.env.PORT || '9000';
|
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
|
||||||
const PROXY_HOST = process.env.PROXY_HOST || 'localhost';
|
|
||||||
const PROXY_PORT = process.env.PROXY_PORT || '4000';
|
|
||||||
const PROXY_PROTOCOL = process.env.PROXY_PROTOCOL || 'http:';
|
|
||||||
const MOCK_API_ENABLED = process.env.MOCK_API_ENABLED || 'false';
|
|
||||||
const relativeDir = path.resolve(__dirname, '..');
|
|
||||||
const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
|
|
||||||
|
|
||||||
module.exports = merge(common('development'), {
|
const smp = new SpeedMeasurePlugin({ disable: !process.env.MEASURE });
|
||||||
|
|
||||||
|
setupDotenvFilesForEnv({ env: 'development' });
|
||||||
|
const webpackCommon = require('./webpack.common.js');
|
||||||
|
|
||||||
|
const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME;
|
||||||
|
const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR;
|
||||||
|
const SRC_DIR = process.env._SRC_DIR;
|
||||||
|
const COMMON_DIR = process.env._COMMON_DIR;
|
||||||
|
const PUBLIC_PATH = process.env._PUBLIC_PATH;
|
||||||
|
const DIST_DIR = process.env._DIST_DIR;
|
||||||
|
const HOST = process.env._HOST;
|
||||||
|
const PORT = process.env._PORT;
|
||||||
|
const PROXY_PROTOCOL = process.env._PROXY_PROTOCOL;
|
||||||
|
const PROXY_HOST = process.env._PROXY_HOST;
|
||||||
|
const PROXY_PORT = process.env._PROXY_PORT;
|
||||||
|
const DEPLOYMENT_MODE = process.env._DEPLOYMENT_MODE;
|
||||||
|
const AUTH_METHOD = process.env._AUTH_METHOD;
|
||||||
|
const BASE_PATH = DEPLOYMENT_MODE === 'kubeflow' ? '/workspaces' : PUBLIC_PATH;
|
||||||
|
|
||||||
|
// Function to generate headers based on deployment mode
|
||||||
|
const getProxyHeaders = () => {
|
||||||
|
if (AUTH_METHOD === 'internal') {
|
||||||
|
return {
|
||||||
|
'kubeflow-userid': 'user@example.com',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (AUTH_METHOD === 'user_token') {
|
||||||
|
try {
|
||||||
|
const token = execSync(
|
||||||
|
"kubectl config view --raw --minify --flatten -o jsonpath='{.users[].user.token}'",
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
const username = execSync("kubectl auth whoami -o jsonpath='{.status.userInfo.username}'")
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.info('Logged in as user:', username);
|
||||||
|
return {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'x-forwarded-access-token': token,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Failed to get Kubernetes token:', error.message);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = smp.wrap(
|
||||||
|
merge(
|
||||||
|
{
|
||||||
|
plugins: [
|
||||||
|
...setupWebpackDotenvFilesForEnv({
|
||||||
|
directory: RELATIVE_DIRNAME,
|
||||||
|
env: 'development',
|
||||||
|
isRoot: IS_PROJECT_ROOT_DIR,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
webpackCommon('development'),
|
||||||
|
{
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
devtool: 'eval-source-map',
|
devtool: 'eval-source-map',
|
||||||
|
optimization: {
|
||||||
|
runtimeChunk: 'single',
|
||||||
|
removeEmptyChunks: true,
|
||||||
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
host: HOST,
|
host: HOST,
|
||||||
port: PORT,
|
port: PORT,
|
||||||
|
compress: true,
|
||||||
historyApiFallback: {
|
historyApiFallback: {
|
||||||
index: APP_PREFIX + '/index.html',
|
index: `${BASE_PATH}/index.html`,
|
||||||
},
|
|
||||||
open: [APP_PREFIX],
|
|
||||||
static: {
|
|
||||||
directory: path.resolve(relativeDir, 'dist'),
|
|
||||||
publicPath: APP_PREFIX,
|
|
||||||
},
|
|
||||||
client: {
|
|
||||||
overlay: true,
|
|
||||||
},
|
},
|
||||||
|
hot: true,
|
||||||
|
open: [BASE_PATH],
|
||||||
proxy: [
|
proxy: [
|
||||||
{
|
{
|
||||||
context: ['/api'],
|
context: ['/api', '/workspaces/api'],
|
||||||
target: {
|
target: {
|
||||||
host: PROXY_HOST,
|
host: PROXY_HOST,
|
||||||
protocol: PROXY_PROTOCOL,
|
protocol: PROXY_PROTOCOL,
|
||||||
port: PROXY_PORT,
|
port: PROXY_PORT,
|
||||||
},
|
},
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
headers: getProxyHeaders(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
devMiddleware: {
|
||||||
|
stats: 'errors-only',
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
overlay: false,
|
||||||
|
},
|
||||||
|
static: {
|
||||||
|
directory: DIST_DIR,
|
||||||
|
publicPath: BASE_PATH,
|
||||||
|
},
|
||||||
|
onListening: (devServer) => {
|
||||||
|
if (devServer) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`\x1b[32m✓ Dashboard available at: \x1b[4mhttp://localhost:${
|
||||||
|
devServer.server.address().port
|
||||||
|
}\x1b[0m`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
include: [...stylePaths],
|
include: [
|
||||||
|
SRC_DIR,
|
||||||
|
COMMON_DIR,
|
||||||
|
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly'),
|
||||||
|
],
|
||||||
use: ['style-loader', 'css-loader'],
|
use: ['style-loader', 'css-loader'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new EnvironmentPlugin({
|
new ForkTsCheckerWebpackPlugin(),
|
||||||
WEBPACK_REPLACE__mockApiEnabled: MOCK_API_ENABLED,
|
new ReactRefreshWebpackPlugin({ overlay: false }),
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
});
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,61 @@
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
const path = require('path');
|
||||||
|
|
||||||
const { merge } = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
const common = require('./webpack.common.js');
|
|
||||||
const { EnvironmentPlugin } = require('webpack');
|
|
||||||
const { stylePaths } = require('./stylePaths');
|
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||||
|
const { setupWebpackDotenvFilesForEnv, setupDotenvFilesForEnv } = require('./dotenv');
|
||||||
|
|
||||||
const PRODUCTION = process.env.PRODUCTION || 'false';
|
setupDotenvFilesForEnv({ env: 'production' });
|
||||||
|
const webpackCommon = require('./webpack.common.js');
|
||||||
|
|
||||||
module.exports = merge(common('production'), {
|
const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME;
|
||||||
|
const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR;
|
||||||
|
const SRC_DIR = process.env._SRC_DIR;
|
||||||
|
const COMMON_DIR = process.env._COMMON_DIR;
|
||||||
|
const DIST_DIR = process.env._DIST_DIR;
|
||||||
|
const OUTPUT_ONLY = process.env._OUTPUT_ONLY;
|
||||||
|
|
||||||
|
if (OUTPUT_ONLY !== 'true') {
|
||||||
|
console.info(`Cleaning OUTPUT DIR...\n ${DIST_DIR}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = merge(
|
||||||
|
{
|
||||||
|
plugins: [
|
||||||
|
...setupWebpackDotenvFilesForEnv({
|
||||||
|
directory: RELATIVE_DIRNAME,
|
||||||
|
env: 'production',
|
||||||
|
isRoot: IS_PROJECT_ROOT_DIR,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
webpackCommon('production'),
|
||||||
|
{
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
optimization: {
|
optimization: {
|
||||||
minimizer: [
|
minimize: true,
|
||||||
new TerserJSPlugin({}),
|
minimizer: [new TerserJSPlugin(), new CssMinimizerPlugin()],
|
||||||
new CssMinimizerPlugin({
|
|
||||||
minimizerOptions: {
|
|
||||||
preset: ['default', { mergeLonghand: false }],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: '[name].css',
|
filename: '[name].css',
|
||||||
chunkFilename: '[name].bundle.css',
|
chunkFilename: '[name].bundle.css',
|
||||||
}),
|
ignoreOrder: true,
|
||||||
new EnvironmentPlugin({
|
|
||||||
PRODUCTION,
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
include: [...stylePaths],
|
include: [
|
||||||
|
SRC_DIR,
|
||||||
|
COMMON_DIR,
|
||||||
|
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly'),
|
||||||
|
],
|
||||||
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,91 +18,88 @@
|
||||||
"build:bundle-profile": "webpack --config ./config/webpack.prod.js --profile --json > ./bundle.stats.json",
|
"build:bundle-profile": "webpack --config ./config/webpack.prod.js --profile --json > ./bundle.stats.json",
|
||||||
"build:bundle-analyze": "webpack-bundle-analyzer ./bundle.stats.json",
|
"build:bundle-analyze": "webpack-bundle-analyzer ./bundle.stats.json",
|
||||||
"build:clean": "rimraf ./dist",
|
"build:clean": "rimraf ./dist",
|
||||||
"build:prod": "cross-env PRODUCTION=true webpack --config ./config/webpack.prod.js",
|
"build:prod": "webpack --config ./config/webpack.prod.js",
|
||||||
"generate:api": "./scripts/generate-api.sh && npm run prettier",
|
"generate:api": "./scripts/generate-api.sh && npm run prettier",
|
||||||
"start:dev": "cross-env STYLE_THEME=$npm_config_theme webpack serve --hot --color --config ./config/webpack.dev.js",
|
"start:dev": "webpack serve --hot --color --config ./config/webpack.dev.js",
|
||||||
"start:dev:mock": "cross-env MOCK_API_ENABLED=true STYLE_THEME=$npm_config_theme npm run start:dev",
|
"test": "run-s prettier:check test:lint test:type-check test:unit test:cypress-ci",
|
||||||
"test": "run-s prettier:check test:lint test:unit test:cypress-ci",
|
"test:cypress-ci": "npx concurrently -P -k -s first \"CY_MOCK=1 npm run cypress:server:build && npm run cypress:server\" \"npx wait-on tcp:127.0.0.1:9001 && npm run cypress:run:mock -- {@}\" -- ",
|
||||||
"test:cypress-ci": "npx concurrently -P -k -s first \"npm run cypress:server:build && npm run cypress:server\" \"npx wait-on tcp:127.0.0.1:9001 && npm run cypress:run:mock -- {@}\" -- ",
|
|
||||||
"test:jest": "jest --passWithNoTests",
|
"test:jest": "jest --passWithNoTests",
|
||||||
"test:unit": "npm run test:jest -- --silent",
|
"test:unit": "npm run test:jest -- --silent",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:coverage": "jest --coverage",
|
"test:coverage": "jest --coverage",
|
||||||
"test:lint": "eslint --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src",
|
"test:lint": "eslint --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src",
|
||||||
"test:lint:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix",
|
"test:lint:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix",
|
||||||
|
"test:type-check": "tsc --noEmit",
|
||||||
"test:fix": "run-s prettier test:lint:fix",
|
"test:fix": "run-s prettier test:lint:fix",
|
||||||
"cypress:open": "cypress open --project src/__tests__/cypress",
|
"cypress:open": "cypress open --project src/__tests__/cypress",
|
||||||
"cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ",
|
"cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ",
|
||||||
"cypress:run": "cypress run -b chrome --project src/__tests__/cypress",
|
"cypress:run": "cypress run -b chrome --project src/__tests__/cypress",
|
||||||
"cypress:run:mock": "CY_MOCK=1 npm run cypress:run -- ",
|
"cypress:run:mock": "CY_MOCK=1 npm run cypress:run -- ",
|
||||||
"cypress:server:build": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 APP_PREFIX=/ webpack --config ./config/webpack.prod.js",
|
"cypress:server:build": "npm run build",
|
||||||
"cypress:server": "serve ./dist -p 9001 -s -L",
|
"cypress:server": "serve ./dist -p 9001 -s -L",
|
||||||
"prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"",
|
"prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"",
|
||||||
"prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"",
|
"prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"",
|
||||||
"prepare": "cd ../../ && husky workspaces/frontend/.husky"
|
"prepare": "cd ../../ && husky workspaces/frontend/.husky"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cspell/eslint-plugin": "^9.1.2",
|
"@mui/icons-material": "^6.4.8",
|
||||||
"@cypress/code-coverage": "^3.13.5",
|
|
||||||
"@mui/icons-material": "^6.3.1",
|
|
||||||
"@mui/material": "^6.3.1",
|
"@mui/material": "^6.3.1",
|
||||||
"@mui/types": "^7.2.21",
|
"@mui/types": "^7.2.21",
|
||||||
"@testing-library/cypress": "^10.0.1",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@swc/core": "^1.9.1",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
|
||||||
"@testing-library/react": "^14.0.0",
|
|
||||||
"@testing-library/user-event": "14.4.3",
|
|
||||||
"@types/chai-subset": "^1.3.5",
|
"@types/chai-subset": "^1.3.5",
|
||||||
"@types/jest": "^29.5.3",
|
"@types/classnames": "^2.3.1",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/dompurify": "^3.2.0",
|
||||||
"@types/victory": "^33.1.5",
|
"@types/jest": "^29.5.13",
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
|
"@types/lodash-es": "^4.17.8",
|
||||||
|
"@types/react-dom": "^18.3.1",
|
||||||
|
"@types/showdown": "^2.0.3",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.35.0",
|
||||||
"chai-subset": "^1.6.0",
|
"chai-subset": "^1.6.0",
|
||||||
"concurrently": "^9.1.0",
|
"concurrently": "^9.1.0",
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^13.0.0",
|
||||||
"core-js": "^3.39.0",
|
"core-js": "^3.40.0",
|
||||||
"cross-env": "^7.0.3",
|
"css-loader": "^7.1.2",
|
||||||
"css-loader": "^6.11.0",
|
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
"dotenv": "^16.5.0",
|
||||||
"cypress": "^13.16.1",
|
"dotenv-expand": "^5.1.0",
|
||||||
"cypress-axe": "^1.5.0",
|
"dotenv-webpack": "^6.0.0",
|
||||||
"cypress-high-resolution": "^1.0.0",
|
"expect": "^30.0.2",
|
||||||
"cypress-mochawesome-reporter": "^3.8.2",
|
"file-loader": "^6.2.0",
|
||||||
"cypress-multi-reporters": "^2.0.4",
|
"fork-ts-checker-webpack-plugin": "^9.0.2",
|
||||||
"dotenv": "^16.4.5",
|
"html-webpack-plugin": "^5.6.3",
|
||||||
"dotenv-webpack": "^8.1.0",
|
|
||||||
"expect": "^29.7.0",
|
|
||||||
"fork-ts-checker-webpack-plugin": "^9.0.3",
|
|
||||||
"html-webpack-plugin": "^5.6.0",
|
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"imagemin": "^8.0.1",
|
"imagemin": "^9.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"junit-report-merger": "^7.0.0",
|
"junit-report-merger": "^7.0.1",
|
||||||
"mini-css-extract-plugin": "^2.9.0",
|
"mini-css-extract-plugin": "^2.9.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "^3.3.0",
|
"prettier": "^3.3.3",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"react-router-dom": "^6.26.1",
|
"react-refresh": "^0.14.2",
|
||||||
"regenerator-runtime": "^0.13.11",
|
"regenerator-runtime": "^0.14.1",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"sass": "^1.83.4",
|
"sass": "^1.87.0",
|
||||||
"sass-loader": "^16.0.4",
|
"sass-loader": "^16.0.0",
|
||||||
"serve": "^14.2.1",
|
"speed-measure-webpack-plugin": "^1.5.0",
|
||||||
"style-loader": "^3.3.4",
|
"style-loader": "^4.0.0",
|
||||||
"svg-url-loader": "^8.0.0",
|
"svg-url-loader": "^8.0.0",
|
||||||
"terser-webpack-plugin": "^5.3.10",
|
"swagger-typescript-api": "13.2.7",
|
||||||
"ts-jest": "^29.1.4",
|
"swc-loader": "^0.2.6",
|
||||||
"ts-loader": "^9.5.1",
|
"terser-webpack-plugin": "^5.3.11",
|
||||||
|
"ts-loader": "^9.5.2",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||||
"tslib": "^2.6.0",
|
"tslib": "^2.7.0",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.8.2",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"webpack": "^5.91.0",
|
"webpack": "^5.97.1",
|
||||||
"webpack-bundle-analyzer": "^4.10.2",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^6.0.1",
|
||||||
"webpack-dev-server": "^5.2.2",
|
"webpack-dev-server": "^5.2.0",
|
||||||
"webpack-merge": "^5.10.0"
|
"webpack-merge": "^6.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@patternfly/patternfly": "^6.3.1",
|
"@patternfly/patternfly": "^6.3.1",
|
||||||
|
|
@ -112,35 +109,58 @@
|
||||||
"@patternfly/react-icons": "^6.3.1",
|
"@patternfly/react-icons": "^6.3.1",
|
||||||
"@patternfly/react-styles": "^6.3.1",
|
"@patternfly/react-styles": "^6.3.1",
|
||||||
"@patternfly/react-table": "^6.3.1",
|
"@patternfly/react-table": "^6.3.1",
|
||||||
|
"@patternfly/react-templates": "^6.3.1",
|
||||||
"@patternfly/react-tokens": "^6.3.1",
|
"@patternfly/react-tokens": "^6.3.1",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"axios": "^1.10.0",
|
|
||||||
"date-fns": "^4.1.0",
|
|
||||||
"eslint-plugin-local-rules": "^3.0.2",
|
|
||||||
"js-yaml": "^4.1.0",
|
|
||||||
"npm-run-all": "^4.1.5",
|
|
||||||
"react": "^18",
|
|
||||||
"react-dom": "^18",
|
|
||||||
"react-router": "^6.26.2",
|
|
||||||
"sirv-cli": "^2.0.2"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.8.1",
|
"axios": "^1.10.0",
|
||||||
"@typescript-eslint/parser": "^8.12.2",
|
"date-fns": "^4.1.0",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"dompurify": "^3.2.4",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"lodash-es": "^4.17.15",
|
||||||
|
"react": "^18",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"react-router": "^7.5.2",
|
||||||
|
"react-router-dom": "^7.6.1",
|
||||||
|
"sass": "^1.83.0",
|
||||||
|
"showdown": "^2.1.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@babel/preset-env": "^7.26.9",
|
||||||
|
"@babel/preset-react": "^7.18.6",
|
||||||
|
"@babel/preset-typescript": "^7.21.5",
|
||||||
|
"@cspell/eslint-plugin": "^9.1.2",
|
||||||
|
"@cypress/code-coverage": "^3.14.1",
|
||||||
|
"@cypress/webpack-preprocessor": "^6.0.4",
|
||||||
|
"@testing-library/cypress": "^10.0.3",
|
||||||
|
"@testing-library/dom": "^10.4.0",
|
||||||
|
"@testing-library/jest-dom": "^6.6.2",
|
||||||
|
"@testing-library/react": "^16.2.0",
|
||||||
|
"@testing-library/user-event": "14.6.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
||||||
|
"@typescript-eslint/parser": "^8.31.1",
|
||||||
|
"cypress": "^14.4.1",
|
||||||
|
"cypress-axe": "^1.6.0",
|
||||||
|
"cypress-high-resolution": "^1.0.0",
|
||||||
|
"cypress-mochawesome-reporter": "^3.8.2",
|
||||||
|
"cypress-multi-reporters": "^2.0.5",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-import-resolver-node": "^0.3.7",
|
"eslint-import-resolver-node": "^0.3.7",
|
||||||
"eslint-import-resolver-typescript": "^3.5.3",
|
"eslint-import-resolver-typescript": "^3.8.3",
|
||||||
"eslint-plugin-cypress": "^3.3.0",
|
"eslint-plugin-cypress": "^3.3.0",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
|
"eslint-plugin-local-rules": "^3.0.2",
|
||||||
"eslint-plugin-no-only-tests": "^3.1.0",
|
"eslint-plugin-no-only-tests": "^3.1.0",
|
||||||
"eslint-plugin-no-relative-import-paths": "^1.5.2",
|
"eslint-plugin-no-relative-import-paths": "^1.6.1",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.4.0",
|
||||||
"eslint-plugin-react": "^7.37.2",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"swagger-typescript-api": "^13.2.7"
|
"npm-run-all": "^4.1.5",
|
||||||
|
"serve": "^14.2.4",
|
||||||
|
"ts-jest": "^29.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,16 @@ import { defineConfig } from 'cypress';
|
||||||
import coverage from '@cypress/code-coverage/task';
|
import coverage from '@cypress/code-coverage/task';
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore no types available
|
// @ts-ignore no types available
|
||||||
|
import webpack from '@cypress/webpack-preprocessor';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore no types available
|
||||||
import cypressHighResolution from 'cypress-high-resolution';
|
import cypressHighResolution from 'cypress-high-resolution';
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore no types available
|
// @ts-ignore no types available
|
||||||
import { beforeRunHook, afterRunHook } from 'cypress-mochawesome-reporter/lib';
|
import { beforeRunHook, afterRunHook } from 'cypress-mochawesome-reporter/lib';
|
||||||
import { mergeFiles } from 'junit-report-merger';
|
import { mergeFiles } from 'junit-report-merger';
|
||||||
import { env, BASE_URL } from '~/__tests__/cypress/cypress/utils/testConfig';
|
import { env, BASE_URL } from '~/__tests__/cypress/cypress/utils/testConfig';
|
||||||
|
import webpackConfig from './cypress/webpack.config';
|
||||||
|
|
||||||
const resultsDir = `${env.CY_RESULTS_DIR || 'results'}/${env.CY_MOCK ? 'mocked' : 'e2e'}`;
|
const resultsDir = `${env.CY_RESULTS_DIR || 'results'}/${env.CY_MOCK ? 'mocked' : 'e2e'}`;
|
||||||
|
|
||||||
|
|
@ -41,7 +45,6 @@ export default defineConfig({
|
||||||
env: {
|
env: {
|
||||||
MOCK: !!env.CY_MOCK,
|
MOCK: !!env.CY_MOCK,
|
||||||
coverage: !!env.CY_COVERAGE,
|
coverage: !!env.CY_COVERAGE,
|
||||||
APP_PREFIX: env.APP_PREFIX || '/workspaces',
|
|
||||||
codeCoverage: {
|
codeCoverage: {
|
||||||
exclude: [path.resolve(__dirname, '../../third_party/**')],
|
exclude: [path.resolve(__dirname, '../../third_party/**')],
|
||||||
},
|
},
|
||||||
|
|
@ -49,12 +52,20 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
defaultCommandTimeout: 10000,
|
defaultCommandTimeout: 10000,
|
||||||
e2e: {
|
e2e: {
|
||||||
baseUrl: env.CY_MOCK ? BASE_URL || 'http://localhost:9001' : 'http://localhost:9000',
|
baseUrl: BASE_URL,
|
||||||
specPattern: env.CY_MOCK ? `cypress/tests/mocked/**/*.cy.ts` : `cypress/tests/e2e/**/*.cy.ts`,
|
specPattern: env.CY_MOCK ? `cypress/tests/mocked/**/*.cy.ts` : `cypress/tests/e2e/**/*.cy.ts`,
|
||||||
experimentalInteractiveRunEvents: true,
|
experimentalInteractiveRunEvents: true,
|
||||||
setupNodeEvents(on, config) {
|
setupNodeEvents(on, config) {
|
||||||
cypressHighResolution(on, config);
|
cypressHighResolution(on, config);
|
||||||
coverage(on, config);
|
coverage(on, config);
|
||||||
|
|
||||||
|
// Configure webpack preprocessor with custom webpack config
|
||||||
|
const options = {
|
||||||
|
webpackOptions: webpackConfig,
|
||||||
|
watchOptions: {},
|
||||||
|
};
|
||||||
|
on('file:preprocessor', webpack(options));
|
||||||
|
|
||||||
on('task', {
|
on('task', {
|
||||||
readJSON(filePath: string) {
|
readJSON(filePath: string) {
|
||||||
const absPath = path.resolve(__dirname, filePath);
|
const absPath = path.resolve(__dirname, filePath);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ describe('Application', () => {
|
||||||
cy.intercept('GET', `/api/v1/workspaces/${mockNamespaces[0].name}`, {
|
cy.intercept('GET', `/api/v1/workspaces/${mockNamespaces[0].name}`, {
|
||||||
body: mockBFFResponse({ mockWorkspace1 }),
|
body: mockBFFResponse({ mockWorkspace1 }),
|
||||||
}).as('getWorkspaces');
|
}).as('getWorkspaces');
|
||||||
cy.visit('/workspaces');
|
cy.visit('/');
|
||||||
cy.wait('@getNamespaces');
|
cy.wait('@getNamespaces');
|
||||||
cy.wait('@getWorkspaces');
|
cy.wait('@getWorkspaces');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ describe('WorkspaceDetailsActivity Component', () => {
|
||||||
cy.intercept('GET', 'api/v1/workspaces', {
|
cy.intercept('GET', 'api/v1/workspaces', {
|
||||||
body: mockBFFResponse(mockWorkspaces),
|
body: mockBFFResponse(mockWorkspaces),
|
||||||
}).as('getWorkspaces');
|
}).as('getWorkspaces');
|
||||||
cy.visit('/workspaces');
|
cy.visit('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
// This tests depends on the mocked workspaces data at home page, needs revisit once workspace data fetched from BE
|
// This tests depends on the mocked workspaces data at home page, needs revisit once workspace data fetched from BE
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const webpackConfig = {
|
||||||
|
mode: 'development' as const,
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||||
|
alias: {
|
||||||
|
'~': path.resolve(__dirname, '../../../'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(tsx|ts|jsx|js)?$/,
|
||||||
|
exclude: [/node_modules/],
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: {
|
||||||
|
transpileOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(svg|ttf|eot|woff|woff2)$/,
|
||||||
|
// Handle fonts from PatternFly and other sources
|
||||||
|
include: [
|
||||||
|
path.resolve(__dirname, '../../../node_modules/patternfly/dist/fonts'),
|
||||||
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../../node_modules/@patternfly/react-core/dist/styles/assets/fonts',
|
||||||
|
),
|
||||||
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../../node_modules/@patternfly/react-core/dist/styles/assets/pficon',
|
||||||
|
),
|
||||||
|
path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/fonts'),
|
||||||
|
path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/pficon'),
|
||||||
|
],
|
||||||
|
use: {
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
limit: 5000,
|
||||||
|
outputPath: 'fonts',
|
||||||
|
name: '[name].[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.svg$/,
|
||||||
|
include: (input: string): boolean => input.indexOf('background-filter.svg') > 1,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
limit: 5000,
|
||||||
|
outputPath: 'svgs',
|
||||||
|
name: '[name].[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.svg$/,
|
||||||
|
// Handle SVG files
|
||||||
|
include: (input: string): boolean =>
|
||||||
|
input.indexOf('images') > -1 &&
|
||||||
|
input.indexOf('fonts') === -1 &&
|
||||||
|
input.indexOf('background-filter') === -1 &&
|
||||||
|
input.indexOf('pficon') === -1,
|
||||||
|
use: {
|
||||||
|
loader: 'raw-loader',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(jpg|jpeg|png|gif)$/i,
|
||||||
|
include: [
|
||||||
|
path.resolve(__dirname, '../../'),
|
||||||
|
path.resolve(__dirname, '../../../node_modules/patternfly'),
|
||||||
|
path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/images'),
|
||||||
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../../node_modules/@patternfly/react-styles/css/assets/images',
|
||||||
|
),
|
||||||
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../../../node_modules/@patternfly/react-core/dist/styles/assets/images',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'url-loader',
|
||||||
|
options: {
|
||||||
|
limit: 5000,
|
||||||
|
outputPath: 'images',
|
||||||
|
name: '[name].[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.s[ac]ss$/i,
|
||||||
|
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: ['style-loader', 'css-loader'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default webpackConfig;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { TextEncoder } from 'util';
|
import { TextEncoder as UtilTextEncoder } from 'util';
|
||||||
import { JestAssertionError } from 'expect';
|
import { JestAssertionError } from 'expect';
|
||||||
import 'core-js/actual/array/to-sorted';
|
import 'core-js/actual/array/to-sorted';
|
||||||
import {
|
import {
|
||||||
|
|
@ -7,7 +7,10 @@ import {
|
||||||
createComparativeValue,
|
createComparativeValue,
|
||||||
} from '~/__tests__/unit/testUtils/hooks';
|
} from '~/__tests__/unit/testUtils/hooks';
|
||||||
|
|
||||||
global.TextEncoder = TextEncoder;
|
// Ensure TextEncoder is available in the JSDOM environment for tests.
|
||||||
|
// Node's util.TextEncoder has slightly different TS types than DOM's, so cast to any.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(globalThis as any).TextEncoder = UtilTextEncoder;
|
||||||
|
|
||||||
const tryExpect = (expectFn: () => void) => {
|
const tryExpect = (expectFn: () => void) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,15 @@ import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon';
|
||||||
import ErrorBoundary from '~/app/error/ErrorBoundary';
|
import ErrorBoundary from '~/app/error/ErrorBoundary';
|
||||||
import NamespaceSelector from '~/shared/components/NamespaceSelector';
|
import NamespaceSelector from '~/shared/components/NamespaceSelector';
|
||||||
import logoDarkTheme from '~/images/logo-dark-theme.svg';
|
import logoDarkTheme from '~/images/logo-dark-theme.svg';
|
||||||
|
import { DEPLOYMENT_MODE, isMUITheme } from '~/shared/utilities/const';
|
||||||
|
import { DeploymentMode, Theme } from '~/shared/utilities/types';
|
||||||
import { NamespaceContextProvider } from './context/NamespaceContextProvider';
|
import { NamespaceContextProvider } from './context/NamespaceContextProvider';
|
||||||
import AppRoutes from './AppRoutes';
|
import AppRoutes from './AppRoutes';
|
||||||
import NavSidebar from './NavSidebar';
|
import NavSidebar from './NavSidebar';
|
||||||
import { NotebookContextProvider } from './context/NotebookContext';
|
import { NotebookContextProvider } from './context/NotebookContext';
|
||||||
import { isMUITheme, Theme } from './const';
|
|
||||||
import { BrowserStorageContextProvider } from './context/BrowserStorageContext';
|
import { BrowserStorageContextProvider } from './context/BrowserStorageContext';
|
||||||
|
|
||||||
const isStandalone = process.env.PRODUCTION !== 'true';
|
const isStandalone = DEPLOYMENT_MODE === DeploymentMode.Standalone;
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,10 @@ const AppRoutes: React.FC = () => {
|
||||||
<Route path={AppRoutePaths.workspaceKinds} element={<WorkspaceKinds />} />
|
<Route path={AppRoutePaths.workspaceKinds} element={<WorkspaceKinds />} />
|
||||||
<Route path={AppRoutePaths.workspaceKindCreate} element={<WorkspaceKindForm />} />
|
<Route path={AppRoutePaths.workspaceKindCreate} element={<WorkspaceKindForm />} />
|
||||||
<Route path={AppRoutePaths.workspaceKindEdit} element={<WorkspaceKindForm />} />
|
<Route path={AppRoutePaths.workspaceKindEdit} element={<WorkspaceKindForm />} />
|
||||||
<Route path="/" element={<Navigate to={AppRoutePaths.workspaces} replace />} />
|
<Route
|
||||||
|
path={AppRoutePaths.root}
|
||||||
|
element={<Navigate to={AppRoutePaths.workspaces} replace />}
|
||||||
|
/>
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
{
|
{
|
||||||
// TODO: Remove the linter skip when we implement authentication
|
// TODO: Remove the linter skip when we implement authentication
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ import {
|
||||||
} from '@patternfly/react-core/dist/esm/components/Nav';
|
} from '@patternfly/react-core/dist/esm/components/Nav';
|
||||||
import { PageSidebar, PageSidebarBody } from '@patternfly/react-core/dist/esm/components/Page';
|
import { PageSidebar, PageSidebarBody } from '@patternfly/react-core/dist/esm/components/Page';
|
||||||
import { useTypedLocation } from '~/app/routerHelper';
|
import { useTypedLocation } from '~/app/routerHelper';
|
||||||
|
import { isMUITheme, LOGO_LIGHT, URL_PREFIX } from '~/shared/utilities/const';
|
||||||
import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes';
|
import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes';
|
||||||
import { APP_PREFIX, isMUITheme, LOGO_LIGHT } from './const';
|
|
||||||
|
|
||||||
const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => {
|
const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => {
|
||||||
const location = useTypedLocation();
|
const location = useTypedLocation();
|
||||||
|
|
@ -61,7 +61,7 @@ const NavSidebar: React.FC = () => {
|
||||||
<NavItem>
|
<NavItem>
|
||||||
<Brand
|
<Brand
|
||||||
className="kubeflow_brand"
|
className="kubeflow_brand"
|
||||||
src={`${window.location.origin}${APP_PREFIX}/images/${LOGO_LIGHT}`}
|
src={`${window.location.origin}${URL_PREFIX}/images/${LOGO_LIGHT}`}
|
||||||
alt="Kubeflow Logo"
|
alt="Kubeflow Logo"
|
||||||
/>
|
/>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import {
|
||||||
SearchInputProps,
|
SearchInputProps,
|
||||||
} from '@patternfly/react-core/dist/esm/components/SearchInput';
|
} from '@patternfly/react-core/dist/esm/components/SearchInput';
|
||||||
import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput';
|
import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput';
|
||||||
import FormFieldset from 'app/components/FormFieldset';
|
import FormFieldset from '~/app/components/FormFieldset';
|
||||||
import { isMUITheme } from 'app/const';
|
import { isMUITheme } from '~/shared/utilities/const';
|
||||||
|
|
||||||
type ThemeAwareSearchInputProps = Omit<SearchInputProps, 'onChange' | 'onClear'> & {
|
type ThemeAwareSearchInputProps = Omit<SearchInputProps, 'onChange' | 'onClear'> & {
|
||||||
onChange: (value: string) => void; // Simplified onChange signature
|
onChange: (value: string) => void; // Simplified onChange signature
|
||||||
|
|
|
||||||
|
|
@ -504,12 +504,7 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
|
||||||
})}
|
})}
|
||||||
{canCreateWorkspaces && (
|
{canCreateWorkspaces && (
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<Button
|
<Button variant="primary" ouiaId="Primary" onClick={createWorkspace}>
|
||||||
size="lg"
|
|
||||||
variant="primary"
|
|
||||||
ouiaId="Primary"
|
|
||||||
onClick={createWorkspace}
|
|
||||||
>
|
|
||||||
Create workspace
|
Create workspace
|
||||||
</Button>
|
</Button>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
export const BFF_API_VERSION = 'v1';
|
|
||||||
|
|
||||||
export enum Theme {
|
|
||||||
Default = 'default-theme',
|
|
||||||
MUI = 'mui-theme',
|
|
||||||
// Future themes can be added here
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isMUITheme = (): boolean => STYLE_THEME === Theme.MUI;
|
|
||||||
const STYLE_THEME = process.env.STYLE_THEME || Theme.MUI;
|
|
||||||
|
|
||||||
export const LOGO_LIGHT = process.env.LOGO || 'logo.svg';
|
|
||||||
|
|
||||||
export const DEFAULT_POLLING_RATE_MS = 10000;
|
|
||||||
|
|
||||||
export const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { ReactNode, useMemo } from 'react';
|
import React, { ReactNode, useMemo } from 'react';
|
||||||
import { APP_PREFIX, BFF_API_VERSION } from '~/app/const';
|
|
||||||
import EnsureAPIAvailability from '~/app/EnsureAPIAvailability';
|
import EnsureAPIAvailability from '~/app/EnsureAPIAvailability';
|
||||||
|
import { BFF_API_PREFIX, BFF_API_VERSION } from '~/shared/utilities/const';
|
||||||
import useNotebookAPIState, { NotebookAPIState } from './useNotebookAPIState';
|
import useNotebookAPIState, { NotebookAPIState } from './useNotebookAPIState';
|
||||||
|
|
||||||
export type NotebookContextType = {
|
export type NotebookContextType = {
|
||||||
|
|
@ -19,8 +19,8 @@ interface NotebookContextProviderProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotebookContextProvider: React.FC<NotebookContextProviderProps> = ({ children }) => {
|
export const NotebookContextProvider: React.FC<NotebookContextProviderProps> = ({ children }) => {
|
||||||
// Remove trailing slash from APP_PREFIX to avoid double slashes
|
// Remove trailing slash from BFF_API_PREFIX to avoid double slashes
|
||||||
const cleanPrefix = APP_PREFIX.replace(/\/$/, '');
|
const cleanPrefix = BFF_API_PREFIX.replace(/\/$/, '');
|
||||||
const hostPath = `${cleanPrefix}/api/${BFF_API_VERSION}`;
|
const hostPath = `${cleanPrefix}/api/${BFF_API_VERSION}`;
|
||||||
|
|
||||||
const [apiState, refreshAPIState] = useNotebookAPIState(hostPath);
|
const [apiState, refreshAPIState] = useNotebookAPIState(hostPath);
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@ import { NotebookApis, notebookApisImpl } from '~/shared/api/notebookApi';
|
||||||
import { APIState } from '~/shared/api/types';
|
import { APIState } from '~/shared/api/types';
|
||||||
import useAPIState from '~/shared/api/useAPIState';
|
import useAPIState from '~/shared/api/useAPIState';
|
||||||
import { mockNotebookApisImpl } from '~/shared/mock/mockNotebookApis';
|
import { mockNotebookApisImpl } from '~/shared/mock/mockNotebookApis';
|
||||||
|
import { MOCK_API_ENABLED } from '~/shared/utilities/const';
|
||||||
|
|
||||||
export type NotebookAPIState = APIState<NotebookApis>;
|
export type NotebookAPIState = APIState<NotebookApis>;
|
||||||
|
|
||||||
const MOCK_API_ENABLED = process.env.WEBPACK_REPLACE__mockApiEnabled === 'true';
|
|
||||||
|
|
||||||
const useNotebookAPIState = (
|
const useNotebookAPIState = (
|
||||||
hostPath: string | null,
|
hostPath: string | null,
|
||||||
): [apiState: NotebookAPIState, refreshAPIState: () => void] => {
|
): [apiState: NotebookAPIState, refreshAPIState: () => void] => {
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ import useGenericObjectState from '~/app/hooks/useGenericObjectState';
|
||||||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||||
import { WorkspaceKindFormData } from '~/app/types';
|
import { WorkspaceKindFormData } from '~/app/types';
|
||||||
import { safeApiCall } from '~/shared/api/apiUtils';
|
import { safeApiCall } from '~/shared/api/apiUtils';
|
||||||
import { CONTENT_TYPE_KEY, ContentType } from '~/shared/utilities/const';
|
import { CONTENT_TYPE_KEY } from '~/shared/utilities/const';
|
||||||
import { ApiValidationError, WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
import { ApiValidationError, WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||||
|
import { ContentType } from '~/shared/utilities/types';
|
||||||
import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload';
|
import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload';
|
||||||
import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties';
|
import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties';
|
||||||
import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage';
|
import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage';
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export const WorkspaceKindDetailsImages: React.FunctionComponent<WorkspaceDetail
|
||||||
workspaceCount:
|
workspaceCount:
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
workspaceCountPerKind[workspaceKind.name]
|
workspaceCountPerKind[workspaceKind.name]
|
||||||
? workspaceCountPerKind[workspaceKind.name].countByImage[image.id] ?? 0
|
? (workspaceCountPerKind[workspaceKind.name].countByImage[image.id] ?? 0)
|
||||||
: 0,
|
: 0,
|
||||||
}))}
|
}))}
|
||||||
tableKind="image"
|
tableKind="image"
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export const WorkspaceKindDetailsPodConfigs: React.FunctionComponent<
|
||||||
kindName: workspaceKind.name,
|
kindName: workspaceKind.name,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
workspaceCount: workspaceCountPerKind[workspaceKind.name]
|
workspaceCount: workspaceCountPerKind[workspaceKind.name]
|
||||||
? workspaceCountPerKind[workspaceKind.name].countByPodConfig[podConfig.id] ?? 0
|
? (workspaceCountPerKind[workspaceKind.name].countByPodConfig[podConfig.id] ?? 0)
|
||||||
: 0,
|
: 0,
|
||||||
workspaceCountRouteState: {
|
workspaceCountRouteState: {
|
||||||
podConfigId: podConfig.id,
|
podConfigId: podConfig.id,
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ import { useTypedLocation, useTypedNavigate, useTypedParams } from '~/app/router
|
||||||
import WorkspaceTable, { WorkspaceTableRef } from '~/app/components/WorkspaceTable';
|
import WorkspaceTable, { WorkspaceTableRef } from '~/app/components/WorkspaceTable';
|
||||||
import { useWorkspacesByKind } from '~/app/hooks/useWorkspaces';
|
import { useWorkspacesByKind } from '~/app/hooks/useWorkspaces';
|
||||||
import WorkspaceKindSummaryExpandableCard from '~/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard';
|
import WorkspaceKindSummaryExpandableCard from '~/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard';
|
||||||
import { DEFAULT_POLLING_RATE_MS } from '~/app/const';
|
|
||||||
import { LoadingSpinner } from '~/app/components/LoadingSpinner';
|
import { LoadingSpinner } from '~/app/components/LoadingSpinner';
|
||||||
import { LoadError } from '~/app/components/LoadError';
|
import { LoadError } from '~/app/components/LoadError';
|
||||||
import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions';
|
import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions';
|
||||||
import { usePolling } from '~/app/hooks/usePolling';
|
import { usePolling } from '~/app/hooks/usePolling';
|
||||||
|
import { POLL_INTERVAL } from '~/shared/utilities/const';
|
||||||
|
|
||||||
const WorkspaceKindSummary: React.FC = () => {
|
const WorkspaceKindSummary: React.FC = () => {
|
||||||
const [isSummaryExpanded, setIsSummaryExpanded] = useState(true);
|
const [isSummaryExpanded, setIsSummaryExpanded] = useState(true);
|
||||||
|
|
@ -30,7 +30,7 @@ const WorkspaceKindSummary: React.FC = () => {
|
||||||
podConfigId,
|
podConfigId,
|
||||||
});
|
});
|
||||||
|
|
||||||
usePolling(refreshWorkspaces, DEFAULT_POLLING_RATE_MS);
|
usePolling(refreshWorkspaces, POLL_INTERVAL);
|
||||||
|
|
||||||
const tableRowActions = useWorkspaceRowActions([{ id: 'viewDetails' }]);
|
const tableRowActions = useWorkspaceRowActions([{ id: 'viewDetails' }]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'
|
||||||
import WorkspaceTable from '~/app/components/WorkspaceTable';
|
import WorkspaceTable from '~/app/components/WorkspaceTable';
|
||||||
import { useNamespaceContext } from '~/app/context/NamespaceContextProvider';
|
import { useNamespaceContext } from '~/app/context/NamespaceContextProvider';
|
||||||
import { useWorkspacesByNamespace } from '~/app/hooks/useWorkspaces';
|
import { useWorkspacesByNamespace } from '~/app/hooks/useWorkspaces';
|
||||||
import { DEFAULT_POLLING_RATE_MS } from '~/app/const';
|
|
||||||
import { LoadingSpinner } from '~/app/components/LoadingSpinner';
|
import { LoadingSpinner } from '~/app/components/LoadingSpinner';
|
||||||
import { LoadError } from '~/app/components/LoadError';
|
import { LoadError } from '~/app/components/LoadError';
|
||||||
import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions';
|
import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions';
|
||||||
import { usePolling } from '~/app/hooks/usePolling';
|
import { usePolling } from '~/app/hooks/usePolling';
|
||||||
import { WorkspacesWorkspaceState } from '~/generated/data-contracts';
|
import { WorkspacesWorkspaceState } from '~/generated/data-contracts';
|
||||||
|
import { POLL_INTERVAL } from '~/shared/utilities/const';
|
||||||
|
|
||||||
export const Workspaces: React.FunctionComponent = () => {
|
export const Workspaces: React.FunctionComponent = () => {
|
||||||
const { selectedNamespace } = useNamespaceContext();
|
const { selectedNamespace } = useNamespaceContext();
|
||||||
|
|
@ -18,7 +18,7 @@ export const Workspaces: React.FunctionComponent = () => {
|
||||||
const [workspaces, workspacesLoaded, workspacesLoadError, refreshWorkspaces] =
|
const [workspaces, workspacesLoaded, workspacesLoadError, refreshWorkspaces] =
|
||||||
useWorkspacesByNamespace(selectedNamespace);
|
useWorkspacesByNamespace(selectedNamespace);
|
||||||
|
|
||||||
usePolling(refreshWorkspaces, DEFAULT_POLLING_RATE_MS);
|
usePolling(refreshWorkspaces, POLL_INTERVAL);
|
||||||
|
|
||||||
const tableRowActions = useWorkspaceRowActions([
|
const tableRowActions = useWorkspaceRowActions([
|
||||||
{ id: 'viewDetails' },
|
{ id: 'viewDetails' },
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ type NavigateOptions<T extends AppRouteKey> = CommonNavigateOptions &
|
||||||
* Go to my route
|
* Go to my route
|
||||||
* </Link>
|
* </Link>
|
||||||
*/
|
*/
|
||||||
export function buildPath<T extends AppRouteKey>(to: T, params: RouteParamsMap[T]): string {
|
export function buildPath<T extends AppRouteKey>(to: T, params?: RouteParamsMap[T]): string {
|
||||||
return generatePath(AppRoutePaths[to], params as RouteParamsMap[T]);
|
return generatePath(AppRoutePaths[to], params as RouteParamsMap[T]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
|
@ -2,15 +2,15 @@ import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||||
|
import { URL_PREFIX } from '~/shared/utilities/const';
|
||||||
import App from './app/App';
|
import App from './app/App';
|
||||||
import { APP_PREFIX } from './app/const';
|
|
||||||
|
|
||||||
const theme = createTheme({ cssVariables: true });
|
const theme = createTheme({ cssVariables: true });
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Router basename={APP_PREFIX}>
|
<Router basename={URL_PREFIX}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<App />
|
<App />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
|
||||||
|
|
@ -158,3 +158,21 @@ type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export type ExactlyOne<T> = AtMostOne<T> & AtLeastOne<T>;
|
export type ExactlyOne<T> = AtMostOne<T> & AtLeastOne<T>;
|
||||||
|
|
||||||
|
export const asEnumMember = <T extends object>(
|
||||||
|
member: T[keyof T] | string | number | undefined | null,
|
||||||
|
e: T,
|
||||||
|
): T[keyof T] | null => (isEnumMember(member, e) ? member : null);
|
||||||
|
|
||||||
|
export const isEnumMember = <T extends object>(
|
||||||
|
member: T[keyof T] | string | number | undefined | unknown | null,
|
||||||
|
e: T,
|
||||||
|
): member is T[keyof T] => {
|
||||||
|
if (member != null) {
|
||||||
|
return Object.entries(e)
|
||||||
|
.filter(([key]) => Number.isNaN(Number(key)))
|
||||||
|
.map(([, value]) => value)
|
||||||
|
.includes(member);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,19 @@
|
||||||
|
import { asEnumMember } from '~/shared/typeHelpers';
|
||||||
|
import { DeploymentMode, Theme } from '~/shared/utilities/types';
|
||||||
|
|
||||||
|
export const STYLE_THEME = asEnumMember(process.env.STYLE_THEME, Theme) || Theme.MUI;
|
||||||
|
export const DEPLOYMENT_MODE =
|
||||||
|
asEnumMember(process.env.DEPLOYMENT_MODE, DeploymentMode) || DeploymentMode.Kubeflow;
|
||||||
export const DEV_MODE = process.env.APP_ENV === 'development';
|
export const DEV_MODE = process.env.APP_ENV === 'development';
|
||||||
export const AUTH_HEADER = process.env.AUTH_HEADER || 'kubeflow-userid';
|
export const POLL_INTERVAL = process.env.POLL_INTERVAL
|
||||||
|
? parseInt(process.env.POLL_INTERVAL)
|
||||||
|
: 30000;
|
||||||
|
export const LOGO_LIGHT = process.env.LOGO || 'logo-light-theme.svg';
|
||||||
|
export const URL_PREFIX = process.env.URL_PREFIX ?? '/workspaces';
|
||||||
|
export const BFF_API_PREFIX = process.env.BFF_API_PREFIX ?? '/';
|
||||||
|
export const BFF_API_VERSION = 'v1';
|
||||||
|
export const MOCK_API_ENABLED = process.env.MOCK_API_ENABLED === 'true';
|
||||||
|
|
||||||
export const CONTENT_TYPE_KEY = 'Content-Type';
|
export const CONTENT_TYPE_KEY = 'Content-Type';
|
||||||
|
|
||||||
export enum ContentType {
|
export const isMUITheme = (): boolean => STYLE_THEME === Theme.MUI;
|
||||||
YAML = 'application/yaml',
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
export enum DeploymentMode {
|
||||||
|
Standalone = 'standalone',
|
||||||
|
Kubeflow = 'kubeflow',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Theme {
|
||||||
|
Default = 'default-theme',
|
||||||
|
MUI = 'mui-theme',
|
||||||
|
// Future themes can be added here
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ContentType {
|
||||||
|
YAML = 'application/yaml',
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./src",
|
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "ES6",
|
"target": "es2021",
|
||||||
"lib": ["es6", "dom"],
|
"lib": ["es2021", "dom", "ES2023.Array"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "bundler",
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
|
|
@ -18,12 +17,16 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"baseUrl": "./src",
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./*"]
|
"~/*": ["./*"]
|
||||||
},
|
},
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"noErrorTruncation": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"allowImportingTsExtensions": true
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx", "**/*.jsx", "**/*.js"],
|
"include": ["**/*.ts", "**/*.tsx", "**/*.jsx", "**/*.js"],
|
||||||
"exclude": ["node_modules", "src/__tests__/cypress"]
|
"exclude": ["node_modules", "dist", "public-cypress", "src/__tests__/cypress"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue