diff --git a/.github/workflows/jest.yml b/.github/workflows/vitest.yml
similarity index 62%
rename from .github/workflows/jest.yml
rename to .github/workflows/vitest.yml
index 142cdcde87..d8e4142318 100644
--- a/.github/workflows/jest.yml
+++ b/.github/workflows/vitest.yml
@@ -1,25 +1,28 @@
-name: Run test and report code coverage on /react and /backend.ai-ui
+name: Run Vitest on /react, /packages/backend.ai-ui, and /scripts
on:
pull_request:
paths:
- react/src/**
- react/package.json
+ - react/vitest.config.ts
- packages/backend.ai-ui/src/**
- packages/backend.ai-ui/package.json
+ - packages/backend.ai-ui/vitest.config.ts
+ - scripts/**
+ - src/**
+ - vitest.config.ts
permissions:
contents: read
-defaults:
- run:
- working-directory: ./react
jobs:
check-changes:
runs-on: ubuntu-latest
outputs:
react-changed: ${{ steps.changes.outputs.react }}
backend-ai-ui-changed: ${{ steps.changes.outputs.backend-ai-ui }}
+ root-changed: ${{ steps.changes.outputs.root }}
steps:
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
@@ -29,17 +32,23 @@ jobs:
react:
- 'react/src/**'
- 'react/package.json'
+ - 'react/vitest.config.ts'
backend-ai-ui:
- 'packages/backend.ai-ui/src/**'
- 'packages/backend.ai-ui/package.json'
- react-coverage:
+ - 'packages/backend.ai-ui/vitest.config.ts'
+ root:
+ - 'scripts/**'
+ - 'src/**'
+ - 'vitest.config.ts'
+
+ react-vitest:
needs: check-changes
if: needs.check-changes.outputs.react-changed == 'true'
- permissions:
- checks: write
- pull-requests: write
- contents: write
runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./react
steps:
- uses: actions/checkout@v5
- uses: jwalton/gh-find-current-pr@v1
@@ -65,38 +74,24 @@ jobs:
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
+ working-directory: .
run: pnpm install --merge-git-branch-lockfiles --no-frozen-lockfile
- name: Run ESLint on React
run: pnpm run lint
- - name: run relay-compiler
+ - name: Run relay-compiler
run: pnpm run relay
- - name: Clear Jest cache
- run: pnpm exec jest --clearCache
- - name: Jest report
- uses: ArtiomTr/jest-coverage-report-action@v2
- with:
- skip-step: install
- working-directory: ./react
- package-manager: pnpm
- test-script: pnpm run test
- prnumber: ${{ steps.findPr.outputs.number }}
- annotations: failed-tests
+ - name: Run Vitest
+ run: pnpm run vitest
- backend-ai-ui-coverage:
+ backend-ai-ui-vitest:
needs: check-changes
if: needs.check-changes.outputs.backend-ai-ui-changed == 'true'
- permissions:
- checks: write
- pull-requests: write
- contents: write
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./packages/backend.ai-ui
steps:
- uses: actions/checkout@v5
- - uses: jwalton/gh-find-current-pr@v1
- id: findPr
- uses: pnpm/action-setup@v5
name: Install pnpm
with:
@@ -118,19 +113,42 @@ jobs:
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
+ working-directory: .
run: pnpm install --merge-git-branch-lockfiles --no-frozen-lockfile
- name: Run ESLint on backend.ai-ui
run: pnpm run lint
- - name: run relay-compiler
+ - name: Run relay-compiler
run: cd ../.. && pnpm run relay
- - name: Clear Jest cache
- run: pnpm exec jest --clearCache
- - name: Jest report for backend.ai-ui
- uses: ArtiomTr/jest-coverage-report-action@v2
+ - name: Run Vitest
+ run: pnpm run vitest
+
+ root-vitest:
+ needs: check-changes
+ if: needs.check-changes.outputs.root-changed == 'true'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v4
+ name: Install pnpm
+ with:
+ version: latest
+ run_install: false
+ - uses: actions/setup-node@v5
with:
- skip-step: install
- working-directory: ./packages/backend.ai-ui
- package-manager: pnpm
- test-script: pnpm run test
- prnumber: ${{ steps.findPr.outputs.number }}
- annotations: failed-tests
+ node-version-file: ".nvmrc"
+ cache: "pnpm"
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+ - uses: actions/cache@v5
+ name: Setup pnpm cache
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
+ restore-keys: |
+ ${{ runner.os }}-pnpm-store-
+ - name: Install dependencies
+ run: pnpm install --merge-git-branch-lockfiles --no-frozen-lockfile
+ - name: Run Vitest
+ run: pnpm run vitest
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 20137a3ca9..ef8261d27a 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -100,4 +100,3 @@ patchedDependencies:
'@lobehub/fluent-emoji@2.0.0': react/patches/lobehub__fluent-emoji@2.0.0.patch
'@rc-component/form': react/patches/@rc-component__form.patch
ansi_up@6.0.6: react/patches/ansi_up@6.0.6.patch
- react-scripts@5.0.1: react/patches/react-scripts@5.0.1.patch
diff --git a/react/babel.config.cjs b/react/babel.config.cjs
deleted file mode 100644
index bbccd1ea63..0000000000
--- a/react/babel.config.cjs
+++ /dev/null
@@ -1,52 +0,0 @@
-const path = require('path');
-
-module.exports = {
- presets: [
- [
- '@babel/preset-env',
- {
- targets: {
- node: 'current',
- },
- },
- ],
- [
- '@babel/preset-react',
- {
- runtime: 'automatic',
- },
- ],
- '@babel/preset-typescript',
- ],
- plugins: ['@babel/plugin-syntax-import-attributes'],
- overrides: [
- {
- include: ['./src/**/*', path.resolve(__dirname, 'src/**/*')], // include only react/src folder
- plugins: [
- [
- 'relay',
- {
- artifactDirectory: path.resolve(__dirname, 'src/__generated__'),
- },
- ],
- ],
- },
- {
- include: [
- '../packages/backend.ai-ui/src/**/*',
- path.resolve(__dirname, '../packages/backend.ai-ui/src/**/*'),
- ], // include only backend.ai-ui/src folder
- plugins: [
- [
- 'relay',
- {
- artifactDirectory: path.resolve(
- __dirname,
- '../packages/backend.ai-ui/src/__generated__',
- ),
- },
- ],
- ],
- },
- ],
-};
diff --git a/react/craco.config.cjs b/react/craco.config.cjs
deleted file mode 100644
index 3d7bb69d33..0000000000
--- a/react/craco.config.cjs
+++ /dev/null
@@ -1,467 +0,0 @@
-const HtmlWebpackPlugin = require('html-webpack-plugin');
-const { GenerateSW } = require('workbox-webpack-plugin');
-const path = require('path');
-const fs = require('fs');
-
-const {
- getLoader,
- loaderByName,
- addBeforeLoader,
- addBeforeAssetModule,
- getAssetModule,
- assetModuleByName,
- whenDev,
-} = require('@craco/craco');
-
-const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
-
-module.exports = {
- eslint: {
- enable: false, // Disable ESLint webpack plugin for ESLint 9 compatibility. Use CLI lint instead.
- },
- devServer: (devServerConfig, { env, paths }) => {
- // Serve static files from the root project directory so that
- // /resources/*, /config.toml, /manifest/*, /dist/* are available
- // without a separate static file server.
- const projectRoot = path.resolve(__dirname, '..');
- const existingStatic = devServerConfig.static
- ? Array.isArray(devServerConfig.static)
- ? devServerConfig.static
- : [devServerConfig.static]
- : [];
- // Project root comes first so that config.toml, resources/, manifest/,
- // etc. are resolved from the canonical location even if react/public/
- // happens to contain a stale copy.
- devServerConfig.static = [
- {
- directory: projectRoot,
- publicPath: '/',
- // Disable file watching on the static directory to prevent full page
- // reloads when static assets (resources/, dist/, manifest/) change.
- // HMR handles React component updates; static files are served as-is.
- watch: false,
- },
- // Serve the self-hosted Monaco AMD runtime at /resources/monaco/vs
- // from react/node_modules/monaco-editor/min/vs. In production, the
- // same tree is copied to build/web/resources/monaco/vs by the root
- // `copymonaco` script. Without this, @monaco-editor/react falls
- // back to loading Monaco from the jsDelivr CDN and fails in
- // offline / air-gapped setups. The /resources prefix keeps Monaco
- // inside the existing static-asset namespace alongside theme and
- // model-definition schemas, so no new top-level route is added.
- {
- directory: path.resolve(
- __dirname,
- 'node_modules/monaco-editor/min/vs',
- ),
- publicPath: '/resources/monaco/vs',
- watch: false,
- },
- ...existingStatic,
- ];
-
- // Enable HMR explicitly and disable liveReload to prevent full page reloads
- // when HMR updates can handle the change.
- devServerConfig.hot = true;
- devServerConfig.liveReload = false;
-
- // Override deprecated middleware options with setupMiddlewares
- const originalOnBefore = devServerConfig.onBeforeSetupMiddleware;
- const originalOnAfter = devServerConfig.onAfterSetupMiddleware;
-
- if (originalOnBefore || originalOnAfter) {
- delete devServerConfig.onBeforeSetupMiddleware;
- delete devServerConfig.onAfterSetupMiddleware;
-
- devServerConfig.setupMiddlewares = (middlewares, devServer) => {
- if (originalOnBefore) {
- originalOnBefore(devServer);
- }
- if (originalOnAfter) {
- originalOnAfter(devServer);
- }
- return middlewares;
- };
- }
-
- // Watch config.toml, index.html, and i18n translation files for changes
- // and trigger a full page reload.
- // We cannot rely on devServerConfig.watchFiles for this because liveReload is
- // set to false (to prevent HMR fallback reloads on React source changes). In
- // webpack-dev-server v4, the watchFiles mechanism checks liveReload before
- // sending the browser reload signal, so with liveReload:false, file changes
- // are detected but the reload signal is never sent. We work around this by
- // setting up fs.watch watchers in setupMiddlewares that explicitly send the
- // 'static-changed' WebSocket message to trigger a full page reload.
- const existingSetupMiddlewares = devServerConfig.setupMiddlewares;
- devServerConfig.setupMiddlewares = (middlewares, devServer) => {
- if (existingSetupMiddlewares) {
- middlewares = existingSetupMiddlewares(middlewares, devServer);
- }
-
- const filesToWatch = [
- path.resolve(__dirname, '../config.toml'),
- path.resolve(__dirname, '../index.html'),
- // Watch the i18n directory so that changes to translation JSON files
- // trigger a full page reload. i18next-http-backend fetches these files
- // at runtime (they are not bundled by webpack), so a page reload is
- // needed to re-fetch the updated translations.
- path.resolve(__dirname, '../resources/i18n'),
- // Watch theme.json so that theme customization changes during development
- // trigger a full page reload.
- path.resolve(__dirname, '../resources/theme.json'),
- ];
-
- // Use a single shared debounce timer across all watchers so that
- // simultaneous events (e.g. on macOS where FSEvents can fire watchers
- // for sibling files in the same directory) coalesce into a single
- // reload signal. This also handles editors that write files in
- // multiple steps (e.g. write + rename).
- // Store the debounce timer on devServer so it can be cleared during
- // shutdown (see onListening below).
- const sendReload = () => {
- clearTimeout(devServer._reloadDebounceTimer);
- devServer._reloadDebounceTimer = setTimeout(() => {
- devServer.sendMessage(
- devServer.webSocketServer.clients,
- 'static-changed',
- );
- }, 300);
- };
-
- const watchers = filesToWatch
- .filter((file) => fs.existsSync(file))
- .map((file) => {
- const isDir = fs.statSync(file).isDirectory();
- if (isDir) {
- // Directories: use fs.watch (fs.watchFile doesn't work on dirs)
- return fs.watch(file, () => sendReload());
- }
- // Files: use fs.watchFile (polling) so that watchers survive
- // file replacements from editors that use atomic save
- // (write temp → rename), which causes fs.watch to stop working
- // because the original inode is replaced.
- fs.watchFile(file, { interval: 500 }, (curr, prev) => {
- if (curr.mtimeMs !== prev.mtimeMs) {
- sendReload();
- }
- });
- // Return a close handle compatible with fs.watch watchers
- return { close: () => fs.unwatchFile(file) };
- });
-
- // Store watchers on the devServer instance so onListening can patch
- // server.close to clean them up on shutdown. We cannot patch
- // devServer.server.close here because devServer.server is not yet
- // created at setupMiddlewares time (createServer() runs after
- // setupMiddlewares() in webpack-dev-server v4's initialize() flow).
- devServer._fileWatchers = (devServer._fileWatchers || []).concat(
- watchers,
- );
-
- return middlewares;
- };
-
- // Patch devServer.server.close to close file watchers on shutdown.
- // This runs after server.listen(), so devServer.server is guaranteed
- // to exist here (unlike in setupMiddlewares which runs before createServer()).
- const existingOnListening = devServerConfig.onListening;
- devServerConfig.onListening = (devServer) => {
- if (existingOnListening) {
- existingOnListening(devServer);
- }
- const originalClose = devServer.server.close.bind(devServer.server);
- devServer.server.close = (callback) => {
- // Clear any pending debounce timer to prevent sendReload from
- // firing after the server has been shut down.
- clearTimeout(devServer._reloadDebounceTimer);
- (devServer._fileWatchers || []).forEach((w) => w.close());
- originalClose(callback);
- };
- };
-
- return devServerConfig;
- },
- babel: {
- plugins: ['@babel/plugin-syntax-import-attributes'],
- },
- webpack: {
- // When you change the this value, you might need to clear cache restart the dev server.
- // you can use `rm -rf node_modules/.cache` to clear cache.
- configure: (webpackConfig, { env, paths }) => {
- // `some.file?raw` will be treated as `asset/source`
- const { isFound, match } = getAssetModule(webpackConfig, (rule) => {
- return rule.oneOf;
- });
-
- const babelLoader = webpackConfig.module.rules
- .find((rule) => rule.oneOf)
- .oneOf.find(
- (rule) => rule.loader && rule.loader.includes('babel-loader'),
- );
-
- if (babelLoader && babelLoader.options) {
- babelLoader.options.plugins = babelLoader.options.plugins || [];
- babelLoader.options.plugins.push([
- 'babel-plugin-react-compiler',
- {
- compilationMode: 'annotation',
- },
- ]);
- babelLoader.options.overrides = [
- {
- include: [
- (filePath) => filePath.includes(path.resolve(__dirname, 'src')),
- ], // include only react/src folder
- plugins: [
- [
- 'relay',
- {
- artifactDirectory: path.resolve(
- __dirname,
- 'src/__generated__',
- ),
- },
- ],
- ],
- },
- {
- include: [
- (filePath) => {
- const targetDir = path.resolve(
- __dirname,
- '../packages/backend.ai-ui/src',
- );
- return filePath.includes(targetDir);
- },
- ], // include only backend.ai-ui/src folder
- plugins: [
- [
- 'relay',
- {
- artifactDirectory: path.resolve(
- __dirname,
- '../packages/backend.ai-ui/src/__generated__',
- ),
- },
- ],
- ],
- },
- ];
- }
-
- if (isFound) {
- match.rule.oneOf = [
- {
- test: /\.svg$/i,
- resourceQuery: /react/,
-
- issuer: /\.[jt]sx?$/,
- use: ['@svgr/webpack'],
- },
- {
- resourceQuery: /raw/,
- type: 'asset/source',
- },
- ...match.rule.oneOf,
- ];
- } else {
- throw new Error('Cannot find asset module');
- }
-
- // Configure TypeScript loader for files in alias directories
- if (env === 'development') {
- const { isFound: tsLoaderFound, match: tsMatch } = getLoader(
- webpackConfig,
- loaderByName('babel-loader'),
- );
-
- if (tsLoaderFound) {
- // Extend the include path to handle aliased directories
- const backendAiUiPath = path.resolve(
- __dirname,
- '../packages/backend.ai-ui/src',
- );
- if (tsMatch.loader.include) {
- if (Array.isArray(tsMatch.loader.include)) {
- tsMatch.loader.include.push(backendAiUiPath);
- } else {
- tsMatch.loader.include = [
- tsMatch.loader.include,
- backendAiUiPath,
- ];
- }
- } else {
- tsMatch.loader.include = [paths.appSrc, backendAiUiPath];
- }
- }
- }
-
- // For development when loading react bundle on other host, you need to set the public path to the dev server address.
- if (process.env.BUILD_TARGET === 'electron') {
- webpackConfig.output.publicPath = 'es6://';
- }
-
- // use `index.html` of original webUI` instead of using react specific one.
- const webuiIndexHtml = path.resolve(__dirname, '../index.html');
- webpackConfig.plugins = webpackConfig.plugins.map((plugin) => {
- if (plugin.constructor.name === 'HtmlWebpackPlugin') {
- if (env === 'development') {
- const content = fs.readFileSync(webuiIndexHtml, {
- encoding: 'utf-8',
- });
-
- // Use templateContent for the initial template content injection.
- // The `template` path is also provided so HtmlWebpackPlugin can
- // track the file for changes. Note: templateContent takes precedence
- // over template when both are specified; the template path here is
- // used only as a reference for webpack's dependency tracking to
- // enable proper HMR behavior when the HTML file changes.
- plugin = new HtmlWebpackPlugin({
- inject: true,
- template: webuiIndexHtml,
- templateContent: content.replace(
- '// DEV_JS_INJECTING',
- 'globalThis.process = {env: {NODE_ENV: "development"}};',
- ),
- });
- } else {
- plugin = new HtmlWebpackPlugin({
- inject: true,
- template: webuiIndexHtml,
- minify: {
- removeComments: true,
- collapseWhitespace: true,
- removeRedundantAttributes: true,
- useShortDoctype: true,
- removeEmptyAttributes: true,
- removeStyleLinkTypeAttributes: true,
- keepClosingSlash: true,
- minifyJS: true,
- minifyCSS: true,
- minifyURLs: true,
- },
- });
- }
- }
-
- return plugin;
- });
- paths.appHtml = webuiIndexHtml;
-
- // Configure webpack's own file watcher to ignore files that are not
- // part of the webpack module graph. When webpack resolves aliases
- // outside react/ (e.g. backend.ai-client-esm → ../dist/lib/...),
- // enhanced-resolve walks up the directory tree and adds the project
- // root as a context dependency. This causes webpack to watch ALL files
- // in the project root, triggering unnecessary rebuilds when config
- // files or editor temp files change (which leads to HMR "Cannot find
- // update" → full page reload).
- //
- // We use a single RegExp instead of a string array because webpack
- // validates that ignored arrays contain only strings (no RegExp),
- // and we need pattern matching to catch editor temp files.
- //
- // The RegExp ignores:
- // 1. node_modules everywhere
- // 2. resources/ and manifest/ directories (static assets)
- // 3. All files directly in the project root (config.toml, index.html,
- // editor temp files like .config.toml.XXXXX, etc.)
- //
- // NOT ignored (must remain watched):
- // - dist/lib/backend.ai-client-esm.js (webpack alias, needs HMR)
- // - packages/backend.ai-ui/src/** (workspace package, dev alias)
- if (env === 'development') {
- const escapedRoot = path
- .resolve(__dirname, '..')
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- webpackConfig.watchOptions = {
- ...webpackConfig.watchOptions,
- ignored: new RegExp(
- [
- 'node_modules',
- escapedRoot + '[\\\\/]resources[\\\\/]',
- escapedRoot + '[\\\\/]manifest[\\\\/]',
- // Match files directly in the project root (no deeper path
- // separators). This catches config.toml, index.html, and any
- // temp files created by editors during atomic save operations.
- escapedRoot + '[\\\\/][^\\\\/]+$',
- ].join('|'),
- ),
- };
- }
-
- // Remove ModuleScopePlugin to allow imports outside react/src.
- // Needed for: backend.ai-ui package, backend.ai-client-esm (via alias to dist/lib/)
- webpackConfig.resolve.plugins = webpackConfig.resolve.plugins.filter(
- (plugin) =>
- !(
- plugin instanceof ModuleScopePlugin ||
- plugin.constructor.name === 'ModuleScopePlugin'
- ),
- );
-
- // Generate service worker for production builds using Workbox.
- // This replaces the previous Rollup-based service worker generation.
- if (env === 'production') {
- webpackConfig.plugins.push(
- new GenerateSW({
- swDest: 'sw.js',
- skipWaiting: true,
- clientsClaim: true,
- exclude: [/\.map$/, /asset-manifest\.json$/],
- maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5 MB
- }),
- );
- }
-
- return {
- ...webpackConfig,
- resolve: {
- ...webpackConfig.resolve,
- alias: {
- ...webpackConfig.resolve.alias,
- // Backend.AI client ESM library (used by global-stores.ts to set globalThis classes)
- 'backend.ai-client-esm': path.resolve(
- __dirname,
- '../dist/lib/backend.ai-client-esm.js',
- ),
- ...whenDev(
- () => ({
- 'backend.ai-ui/dist': path.resolve(
- __dirname,
- '../packages/backend.ai-ui/src',
- ),
- 'backend.ai-ui': path.resolve(
- __dirname,
- '../packages/backend.ai-ui/src',
- ),
- }),
- {},
- ),
- },
- fallback: {
- ...webpackConfig.resolve.fallback,
- buffer: require.resolve('buffer'),
- stream: require.resolve('stream-browserify'),
- child_process: false,
- },
- },
- ignoreWarnings: [
- {
- module: /@melloware\/react-logviewer/,
- message: /Failed to parse source map/,
- },
- {
- module: /@antv\//,
- message: /Failed to parse source map/,
- },
- {
- module: /@microsoft\/fetch-event-source/,
- message: /Failed to parse source map/,
- },
- ],
- };
- },
- },
-};
diff --git a/react/package.json b/react/package.json
index 0a3af017ca..977276df62 100644
--- a/react/package.json
+++ b/react/package.json
@@ -87,15 +87,14 @@
"zod": "^4.3.6"
},
"scripts": {
- "start": "NODE_OPTIONS='--max-old-space-size=4096' craco start",
+ "start": "NODE_OPTIONS='--max-old-space-size=4096' vite",
"vite:dev": "vite",
"vite:build": "vite build",
"vitest": "vitest run",
"vitest:watch": "vitest",
"build": "pnpm run build:only && cp -r ./build/* ../build/web/",
- "build:only": "NODE_OPTIONS='--max-old-space-size=4096' pnpm run relay && NODE_OPTIONS='--max-old-space-size=4096' craco build",
- "test": "NODE_OPTIONS='$NODE_OPTIONS --no-deprecation --experimental-vm-modules' jest",
- "eject": "react-scripts eject",
+ "build:only": "pnpm run relay && vite build",
+ "test": "vitest run",
"relay": "relay-compiler",
"relay:watch": "nodemon --watch schema.graphql --watch client-directives.graphql --exec 'pnpm run relay --watch'",
"lint": "eslint ./src --max-warnings=0",
@@ -121,7 +120,6 @@
"@babel/preset-env": "^7.29.2",
"@babel/preset-react": "^7.28.5",
"@babel/preset-typescript": "catalog:",
- "@craco/craco": "^7.1.0",
"@eslint/js": "catalog:",
"@tanstack/eslint-plugin-query": "^5.99.0",
"@testing-library/jest-dom": "catalog:",
@@ -139,7 +137,6 @@
"@types/relay-runtime": "catalog:",
"@types/relay-test-utils": "catalog:",
"@vitejs/plugin-react": "^4.7.0",
- "babel-jest": "catalog:",
"babel-plugin-react-compiler": "catalog:",
"babel-plugin-relay": "^20.1.1",
"babel-preset-react-app": "^10.1.0",
@@ -152,7 +149,6 @@
"eslint-plugin-react-hooks": "catalog:",
"eslint-plugin-relay": "^2.0.0",
"globals": "catalog:",
- "html-webpack-plugin": "5.6.3",
"jsdom": "^29.0.2",
"nodemon": "^3.1.14",
"prop-types": "^15.8.1",
@@ -162,14 +158,11 @@
"react-test-renderer": "^19.2.5",
"relay-compiler": "catalog:",
"relay-test-utils": "catalog:",
- "stream-browserify": "^3.0.0",
"typescript-eslint": "catalog:",
"vite": "^6.4.1",
"vite-plugin-node-polyfills": "^0.24.0",
"vite-plugin-pwa": "^1.2.0",
"vite-plugin-svgr": "^4.5.0",
- "vitest": "^4.1.4",
- "webpack": "catalog:",
- "workbox-webpack-plugin": "^7.4.0"
+ "vitest": "^4.1.4"
}
}
diff --git a/react/patches/react-scripts@5.0.1.patch b/react/patches/react-scripts@5.0.1.patch
deleted file mode 100644
index 5db18274d0..0000000000
--- a/react/patches/react-scripts@5.0.1.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-diff --git a/scripts/start.js b/scripts/start.js
-index 8b9a2c26b4df349bc81548ace0354900cf904693..0e6cb9ad0c3fcd3b4b738318d4fb27755ab52cc2 100644
---- a/scripts/start.js
-+++ b/scripts/start.js
-@@ -54,6 +54,7 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
- // Tools like Cloud9 rely on this.
- const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
- const HOST = process.env.HOST || '0.0.0.0';
-+const PROXY = process.env.BAI_WEBUI_DEV_PROXY || process.env.PROXY || '';
-
- if (process.env.HOST) {
- console.log(
-@@ -108,7 +109,7 @@ checkBrowsers(paths.appPath, isInteractive)
- webpack,
- });
- // Load proxy config
-- const proxySetting = require(paths.appPackageJson).proxy;
-+ const proxySetting = require(paths.appPackageJson).proxy || PROXY;
- const proxyConfig = prepareProxy(
- proxySetting,
- paths.appPublic,
diff --git a/react/public/.gitkeep b/react/public/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/react/src/react-app-env.d.ts b/react/src/react-app-env.d.ts
deleted file mode 100644
index a159d65e8a..0000000000
--- a/react/src/react-app-env.d.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- @license
- Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
- */
-///
-
-// declare module 'babel-plugin-relay/macro' {
-// export { graphql as default } from 'react-relay';
-// }
-
-declare module 'backend.ai-client-esm' {
- const ai: { backend: { Client: any; ClientConfig: any } };
- export = ai;
-}
-
-type ArrayElement =
- ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
-type ArgumentTypes = F extends (...args: infer A) => any
- ? A
- : never;
-
-interface BackendAIOptions {
- get(key: string, defaultValue?: T, namespace?: string): T;
- set(key: string, value: any, namespace?: string): void;
- exists(key: string, namespace?: string): boolean;
-}
-
-type BackendAIClient = import('./hooks').BackendAIClient;
-
-declare module globalThis {
- // eslint-disable-next-line no-var
- var isDarkMode: boolean;
- // eslint-disable-next-line no-var
- var isElectron: boolean;
- // eslint-disable-next-line no-var
- var electronInitialHref: string;
- // eslint-disable-next-line no-var
- var packageEdition: string;
- // eslint-disable-next-line no-var
- var packageVersion: string;
- // eslint-disable-next-line no-var
- var packageValidUntil: string;
- // eslint-disable-next-line no-var
- var buildVersion: string;
- // eslint-disable-next-line no-var
- var appLauncher: {
- showLauncher?: (sessionId: {
- 'session-name'?: string;
- 'session-uuid'?: string;
- 'access-key'?: string;
- mode?: SessionMode;
- 'app-services'?: Array;
- runtime?: string;
- filename?: string;
- }) => void;
- forceUseV1Proxy?: {
- checked: boolean;
- };
- forceUseV2Proxy?: {
- checked: boolean;
- };
- };
- // eslint-disable-next-line no-var
- var backendaiclient: BackendAIClient | null | undefined;
- // eslint-disable-next-line no-var
- var backendaioptions: BackendAIOptions | undefined;
-}
-
-type DeepPartial = {
- [P in keyof T]?: T[P] extends Array
- ? Array>
- : T[P] extends ReadonlyArray
- ? ReadonlyArray>
- : T[P] extends object
- ? DeepPartial
- : T[P];
-};
-
-type SelectivePartial = Partial> & Omit;
-
-type OptionalFieldsOnly = {
- [K in keyof T as {} extends Pick ? K : never]?: T[K];
-};
-
-type NonNullableItem = NonNullable['items']>>[0];
-
-type NonNullableNodeOnEdges = NonNullable<
- NonNullable['edges'][0]>>['node']
->;
-
-interface Window {
- switchLanguage: (lang: string) => void;
-}