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; -}