/**
 * NOTICE: this is an auto-generated file
 *
 * This file has been generated by the `flow:prepare-frontend` maven goal.
 * This file will be overwritten on every run. Any custom changes should be made to vite.config.ts
 */
import path from 'path';
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, Stats } from 'fs';
import { createHash } from 'crypto';
import * as net from 'net';

import { processThemeResources } from './target/plugins/application-theme-plugin/theme-handle.js';
import { rewriteCssUrls } from './target/plugins/theme-loader/theme-loader-utils.js';
import { addFunctionComponentSourceLocationBabel } from './target/plugins/react-function-location-plugin/react-function-location-plugin.js';
import settings from './target/vaadin-dev-server-settings.json';
import {
  AssetInfo,
  ChunkInfo,
  defineConfig,
  mergeConfig,
  OutputOptions,
  PluginOption,
  UserConfigFn
} from 'vite';

import brotli from 'rollup-plugin-brotli';
import checker from 'vite-plugin-checker';
import postcssLit from './target/plugins/rollup-plugin-postcss-lit-custom/rollup-plugin-postcss-lit.js';
import vaadinI18n from './target/plugins/rollup-plugin-vaadin-i18n/rollup-plugin-vaadin-i18n.js';

export { default as useLocalWebComponents } from './target/plugins/vite-plugin-local-web-components';

import { visualizer } from 'rollup-plugin-visualizer';
import reactPlugin from '@vitejs/plugin-react';
import babel from '@rolldown/plugin-babel';




const frontendFolder = path.resolve(__dirname, settings.frontendFolder);
const themeFolder = path.resolve(frontendFolder, settings.themeFolder);
const frontendBundleFolder = path.resolve(__dirname, settings.frontendBundleOutput);
const devBundleFolder = path.resolve(__dirname, settings.devBundleOutput);
const devBundle = !!process.env.devBundle;
const jarResourcesFolder = path.resolve(__dirname, settings.jarResourcesFolder);
const themeResourceFolder = path.resolve(__dirname, settings.themeResourceFolder);
const projectPackageJsonFile = path.resolve(__dirname, 'package.json');

const buildOutputFolder = devBundle ? devBundleFolder : frontendBundleFolder;
const statsFolder = path.resolve(__dirname, devBundle ? settings.devBundleStatsOutput : settings.statsOutput);
const statsFile = path.resolve(statsFolder, 'stats.json');
const bundleSizeFile = path.resolve(statsFolder, 'bundle-size.html');
const i18nFolder = path.resolve(__dirname, settings.i18nOutput);
const nodeModulesFolder = path.resolve(__dirname, 'node_modules');
const webComponentTags = '';

const projectIndexHtml = path.resolve(frontendFolder, 'index.html');

const projectStaticAssetsFolders = [
  path.resolve(__dirname, 'src', 'main', 'resources', 'META-INF', 'resources'),
  path.resolve(__dirname, 'src', 'main', 'resources', 'static'),
  frontendFolder
];

// Folders in the project which can contain application themes
const themeProjectFolders = projectStaticAssetsFolders.map((folder) => path.resolve(folder, settings.themeFolder));

const themeOptions = {
  devMode: false,
  useDevBundle: devBundle,
  // The following matches folder 'frontend/generated/themes/'
  // (not 'frontend/themes') for theme in JAR that is copied there
  themeResourceFolder: path.resolve(themeResourceFolder, settings.themeFolder),
  themeProjectFolders: themeProjectFolders,
  projectStaticAssetsOutputFolder: devBundle
    ? path.resolve(devBundleFolder, '../assets')
    : path.resolve(__dirname, settings.staticOutput),
  frontendGeneratedFolder: path.resolve(frontendFolder, settings.generatedFolder),
  projectStaticOutput:  path.resolve(__dirname, settings.staticOutput),
  javaResourceFolder: settings.javaResourceFolder ? path.resolve(__dirname, settings.javaResourceFolder) : ''
};

const hasExportedWebComponents = existsSync(path.resolve(frontendFolder, 'web-component.html'));
const commercialBannerComponent = path.resolve(frontendFolder, settings.generatedFolder, 'commercial-banner.js');
const hasCommercialBanner = existsSync(commercialBannerComponent);

const target = ['es2023'];

// Block debug and trace logs.
console.trace = () => {};
console.debug = () => {};

function statsExtracterPlugin(): PluginOption {
  function collectThemeJsonsInFrontend(themeJsonContents: Record<string, string>, themeName: string) {
    const themeJson = path.resolve(frontendFolder, settings.themeFolder, themeName, 'theme.json');
    if (existsSync(themeJson)) {
      const themeJsonContent = readFileSync(themeJson, { encoding: 'utf-8' }).replace(/\r\n/g, '\n');
      themeJsonContents[themeName] = themeJsonContent;
      const themeJsonObject = JSON.parse(themeJsonContent);
      if (themeJsonObject.parent) {
        collectThemeJsonsInFrontend(themeJsonContents, themeJsonObject.parent);
      }
    }
  }

  return {
    name: 'vaadin:stats',
    enforce: 'post',
    async writeBundle(options: OutputOptions, bundle: { [fileName: string]: AssetInfo | ChunkInfo }) {
      const modules = Object.values(bundle).flatMap((b) => (b.modules ? Object.keys(b.modules) : []));
      const nodeModulesFolders = modules
        .map((id) => id.replace(/\\/g, '/'))
        .filter((id) => id.startsWith(nodeModulesFolder.replace(/\\/g, '/')))
        .map((id) => id.substring(nodeModulesFolder.length + 1));
      const npmModules = nodeModulesFolders
        .map((id) => id.replace(/\\/g, '/'))
        .map((id) => {
          const parts = id.split('/');
          if (id.startsWith('@')) {
            return parts[0] + '/' + parts[1];
          } else {
            return parts[0];
          }
        })
        .sort()
        .filter((value, index, self) => self.indexOf(value) === index);
      const npmModuleAndVersion = Object.fromEntries(npmModules.map((module) => [module, getVersion(module)]));
      const cvdls = Object.fromEntries(
        npmModules
          .filter((module) => getCvdlName(module) != null)
          .map((module) => [module, { name: getCvdlName(module), version: getVersion(module) }])
      );

      mkdirSync(path.dirname(statsFile), { recursive: true });
      const projectPackageJson = JSON.parse(readFileSync(projectPackageJsonFile, { encoding: 'utf-8' }));

      const entryScripts = Object.values(bundle)
        .filter((bundle) => bundle.isEntry)
        .map((bundle) => bundle.fileName);

      const generatedIndexHtml = path.resolve(buildOutputFolder, 'index.html');
      const customIndexData: string = readFileSync(projectIndexHtml, { encoding: 'utf-8' });
      const generatedIndexData: string = readFileSync(generatedIndexHtml, {
        encoding: 'utf-8'
      });

      const customIndexRows = new Set(customIndexData.split(/[\r\n]/).filter((row) => row.trim() !== ''));
      const generatedIndexRows = generatedIndexData.split(/[\r\n]/).filter((row) => row.trim() !== '');

      const rowsGenerated: string[] = [];
      generatedIndexRows.forEach((row) => {
        if (!customIndexRows.has(row)) {
          rowsGenerated.push(row);
        }
      });

      //After dev-bundle build add used Flow frontend imports JsModule/JavaScript/CssImport

      const parseImports = (filename: string, result: Set<string>): void => {
        const content: string = readFileSync(filename, { encoding: 'utf-8' });
        const lines = content.split('\n');
        const staticImports = lines
          .filter((line) => line.startsWith('import '))
          .map((line) => line.substring(line.indexOf("'") + 1, line.lastIndexOf("'")))
          .map((line) => (line.includes('?') ? line.substring(0, line.lastIndexOf('?')) : line));
        const dynamicImports = lines
          .filter((line) => line.includes('import('))
          .map((line) => line.replace(/.*import\(/, ''))
          .map((line) => line.split(/'/)[1])
          .map((line) => (line.includes('?') ? line.substring(0, line.lastIndexOf('?')) : line));

        staticImports.forEach((staticImport) => result.add(staticImport));

        dynamicImports.map((dynamicImport) => {
          const importedFile = path.resolve(path.dirname(filename), dynamicImport);
          parseImports(importedFile, result);
        });
      };

      const generatedImportsSet = new Set<string>();
      parseImports(
        path.resolve(themeOptions.frontendGeneratedFolder, 'flow', 'generated-flow-imports.js'),
        generatedImportsSet
      );
      parseImports(
        path.resolve(themeOptions.frontendGeneratedFolder, 'app-shell-imports.js'),
        generatedImportsSet
      );
      const generatedImports = Array.from(generatedImportsSet).sort();

      const frontendFiles: Record<string, string> = {};
      frontendFiles['index.html'] = createHash('sha256').update(customIndexData.replace(/\r\n/g, '\n'), 'utf8').digest('hex');

      const projectFileExtensions = ['.js', '.js.map', '.ts', '.ts.map', '.tsx', '.tsx.map', '.css', '.css.map'];

      const isThemeComponentsResource = (id: string) =>
          id.startsWith(themeOptions.frontendGeneratedFolder.replace(/\\/g, '/'))
              && id.match(/.*\/jar-resources\/themes\/[^\/]+\/components\//);

      const isGeneratedWebComponentResource = (id: string) =>
          id.startsWith(themeOptions.frontendGeneratedFolder.replace(/\\/g, '/'))
              && id.match(/.*\/flow\/web-components\//);

      const isFrontendResourceCollected = (id: string) =>
          !id.startsWith(themeOptions.frontendGeneratedFolder.replace(/\\/g, '/'))
          || isThemeComponentsResource(id)
          || isGeneratedWebComponentResource(id);

      // collects project's frontend resources in frontend folder, excluding
      // 'generated' sub-folder, except for legacy shadow DOM stylesheets
      // packaged in `theme/components/` folder
      // and generated web component resources in `flow/web-components` folder.
      modules
        .map((id) => id.replace(/\\/g, '/'))
        .filter((id) => id.startsWith(frontendFolder.replace(/\\/g, '/')))
        .filter(isFrontendResourceCollected)
        .map((id) => id.substring(frontendFolder.length + 1))
        .map((line: string) => (line.includes('?') ? line.substring(0, line.lastIndexOf('?')) : line))
        .forEach((line: string) => {
          // \r\n from windows made files may be used so change to \n
          const filePath = path.resolve(frontendFolder, line);
          if (projectFileExtensions.includes(path.extname(filePath))) {
            const fileBuffer = readFileSync(filePath, { encoding: 'utf-8' }).replace(/\r\n/g, '\n');
            frontendFiles[line] = createHash('sha256').update(fileBuffer, 'utf8').digest('hex');
          }
        });

      // collects frontend resources from the JARs
      generatedImports
        .filter((line: string) => line.includes('generated/jar-resources'))
        .forEach((line: string) => {
          let filename = line.substring(line.indexOf('generated'));
          // \r\n from windows made files may be used ro remove to be only \n
          const fileBuffer = readFileSync(path.resolve(frontendFolder, filename), { encoding: 'utf-8' }).replace(
            /\r\n/g,
            '\n'
          );
          const hash = createHash('sha256').update(fileBuffer, 'utf8').digest('hex');

          const fileKey = line.substring(line.indexOf('jar-resources/') + 14);
          frontendFiles[fileKey] = hash;
        });
      // collects and hash rest of the Frontend resources excluding files in /generated/ and /themes/
      // and files already in frontendFiles.
      let frontendFolderAlias = "Frontend";
      generatedImports
        .filter((line: string) => line.startsWith(frontendFolderAlias + '/'))
        .filter((line: string) => !line.startsWith(frontendFolderAlias + '/generated/'))
        .filter((line: string) => !line.startsWith(frontendFolderAlias + '/themes/'))
        .map((line) => line.substring(frontendFolderAlias.length + 1))
        .filter((line: string) => !frontendFiles[line])
        .forEach((line: string) => {
          const filePath = path.resolve(frontendFolder, line);
          if (projectFileExtensions.includes(path.extname(filePath)) && existsSync(filePath)) {
            const fileBuffer = readFileSync(filePath, { encoding: 'utf-8' }).replace(/\r\n/g, '\n');
            frontendFiles[line] = createHash('sha256').update(fileBuffer, 'utf8').digest('hex');
          }
        });
      // If a index.ts exists hash it to be able to see if it changes.
      if (existsSync(path.resolve(frontendFolder, 'index.ts'))) {
        const fileBuffer = readFileSync(path.resolve(frontendFolder, 'index.ts'), { encoding: 'utf-8' }).replace(
          /\r\n/g,
          '\n'
        );
        frontendFiles[`index.ts`] = createHash('sha256').update(fileBuffer, 'utf8').digest('hex');
      }
      if (hasCommercialBanner) {
        const fileBuffer = readFileSync(commercialBannerComponent, { encoding: 'utf-8' }).replace(/\r\n/g, '\n');
        frontendFiles[settings.generatedFolder + '/commercial-banner.js'] = createHash('sha256').update(fileBuffer, 'utf8').digest('hex');
      }

      const themeJsonContents: Record<string, string> = {};
      const themesFolder = path.resolve(jarResourcesFolder, 'themes');
      if (existsSync(themesFolder)) {
        readdirSync(themesFolder).forEach((themeFolder) => {
          const themeJson = path.resolve(themesFolder, themeFolder, 'theme.json');
          if (existsSync(themeJson)) {
            themeJsonContents[path.basename(themeFolder)] = readFileSync(themeJson, { encoding: 'utf-8' }).replace(
              /\r\n/g,
              '\n'
            );
          }
        });
      }

      collectThemeJsonsInFrontend(themeJsonContents, settings.themeName);

      let webComponents: string[] = [];
      if (webComponentTags) {
        webComponents = webComponentTags.split(';');
      }

      const stats = {
        packageJsonDependencies: projectPackageJson.dependencies,
        npmModules: npmModuleAndVersion,
        bundleImports: generatedImports,
        frontendHashes: frontendFiles,
        themeJsonContents: themeJsonContents,
        entryScripts,
        webComponents,
        cvdlModules: cvdls,
        packageJsonHash: projectPackageJson?.vaadin?.hash,
        indexHtmlGenerated: rowsGenerated
      };
      writeFileSync(statsFile, JSON.stringify(stats, null, 1));
    }
  };
}

function themePlugin(opts: { devMode: boolean }): PluginOption {
  const fullThemeOptions = { ...themeOptions, devMode: opts.devMode };
  return {
    name: 'vaadin:theme',
    config() {
      processThemeResources(fullThemeOptions, console);
    },
    configureServer(server) {
      function handleThemeFileCreateDelete(themeFile: string, stats?: Stats) {
        if (themeFile.startsWith(themeFolder)) {
          const changed = path.relative(themeFolder, themeFile);
          console.debug('Theme file ' + (!!stats ? 'created' : 'deleted'), changed);
          processThemeResources(fullThemeOptions, console);
        }
      }
      server.watcher.on('add', handleThemeFileCreateDelete);
      server.watcher.on('unlink', handleThemeFileCreateDelete);
    },
    hotUpdate({ file }) {
      const contextPath = path.resolve(file);
      const themePath = path.resolve(themeFolder);
      if (contextPath.startsWith(themePath)) {
        const changed = path.relative(themePath, contextPath);

        console.debug('Theme file changed', changed);

        if (changed.startsWith(settings.themeName)) {
          processThemeResources(fullThemeOptions, console);
        }
      }
    },
    async resolveId(id, importer) {
      // force theme generation if generated theme sources does not yet exist
      // this may happen for example during Java hot reload when updating
      // @Theme annotation value
      if (
        path.resolve(themeOptions.frontendGeneratedFolder, 'theme.js') === importer &&
        !existsSync(path.resolve(themeOptions.frontendGeneratedFolder, id))
      ) {
        console.debug('Generate theme file ' + id + ' not existing. Processing theme resource');
        processThemeResources(fullThemeOptions, console);
        return;
      }
      if (!id.startsWith(settings.themeFolder)) {
        return;
      }
      for (const location of [themeResourceFolder, frontendFolder]) {
        const result = await this.resolve(path.resolve(location, id));
        if (result) {
          return result;
        }
      }
    },
    async transform(raw, id, options) {
      // rewrite urls for the application theme css files
      const [bareId, query] = id.split('?');
      if (
        (!bareId?.startsWith(themeFolder) && !bareId?.startsWith(themeOptions.themeResourceFolder)) ||
        !bareId?.endsWith('.css')
      ) {
        return;
      }
      const resourceThemeFolder = bareId.startsWith(themeFolder) ? themeFolder : themeOptions.themeResourceFolder;
      const [themeName] =  bareId.substring(resourceThemeFolder.length + 1).split('/');
      return rewriteCssUrls(raw, path.dirname(bareId), path.resolve(resourceThemeFolder, themeName), console, opts);
    }
  };
}

function runWatchDog(watchDogPort: number, watchDogHost: string | undefined) {
  const client = new net.Socket();
  client.setEncoding('utf8');
  client.on('error', function (err) {
    console.log('Watchdog connection error. Terminating vite process...', err);
    client.destroy();
    process.exit(0);
  });
  client.on('close', function () {
    client.destroy();
    runWatchDog(watchDogPort, watchDogHost);
  });

  client.connect(watchDogPort, watchDogHost || 'localhost');
}

const allowedFrontendFolders = [frontendFolder, nodeModulesFolder];

function showRecompileReason(): PluginOption {
  return {
    name: 'vaadin:why-you-compile',
    hotUpdate({ file }) {
      console.log('Recompiling because', file, 'changed');
    }
  };
}

const DEV_MODE_START_REGEXP = /\/\*[\*!]\s+vaadin-dev-mode:start/;
const DEV_MODE_CODE_REGEXP = /\/\*[\*!]\s+vaadin-dev-mode:start([\s\S]*)vaadin-dev-mode:end\s+\*\*\//i;

function preserveUsageStats() {
  return {
    name: 'vaadin:preserve-usage-stats',

    transform(src: string, id: string) {
      if (id.includes('vaadin-usage-statistics')) {
        if (src.includes('vaadin-dev-mode:start')) {
          const expectedComment = '/*! vaadin-dev-mode:start';
          const newSrc = src.replace(DEV_MODE_START_REGEXP, expectedComment);
          if (newSrc === src) {
            if (!src.includes(expectedComment)) {
              console.error('vaadin-dev-mode:start tag not found');
            }
          } else if (!newSrc.match(DEV_MODE_CODE_REGEXP)) {
            console.error('New comment fails to match original regexp');
          } else {
            return { code: newSrc };
          }
        }
      }

      return { code: src };
    }
  };
}

export const vaadinConfig: UserConfigFn = (env) => {
  const devMode = env.mode === 'development';
  const productionMode = !devMode && !devBundle
  const commercialBanner = productionMode && hasCommercialBanner;

  if (devMode && process.env.watchDogPort) {
    // Open a connection with the Java dev-mode handler in order to finish
    // vite when it exits or crashes.
    runWatchDog(parseInt(process.env.watchDogPort), process.env.watchDogHost);
  }

  return {
    root: frontendFolder,
    base: '',
    publicDir: false,
    resolve: {
      alias: {
        '@vaadin/flow-frontend': jarResourcesFolder,
        Frontend: frontendFolder
      },
      preserveSymlinks: true
    },
    define: {
      OFFLINE_PATH: settings.offlinePath,
      VITE_ENABLED: 'true'
    },
    server: {
      host: '127.0.0.1',
      strictPort: true,
      fs: {
        allow: allowedFrontendFolders
      }
    },
    build: {
      minify: productionMode,
      outDir: buildOutputFolder,
      emptyOutDir: devBundle,
      assetsDir: 'VAADIN/build',
      target,
      rolldownOptions: {
        input: {
          indexhtml: projectIndexHtml,

          ...(hasExportedWebComponents ? { webcomponenthtml: path.resolve(frontendFolder, 'web-component.html') } : {})
        },
        output: {
          // Rolldown does not guarantee ESM-spec module execution order by
          // default. Vaadin components (via Polymer) depend on correct
          // initialization order, especially when top-level await is used.
          strictExecutionOrder: true,
        },
        onwarn: (warning: any, defaultHandler: (warning: any) => void) => {
          const ignoreEvalWarning = [
            'generated/jar-resources/FlowClient.js',
            'generated/jar-resources/vaadin-spreadsheet/spreadsheet-export.js',
            '@vaadin/charts/src/helpers.js'
          ];
          if (warning.code === 'EVAL' && warning.id && !!ignoreEvalWarning.find((id: string) => warning.id?.endsWith(id))) {
            return;
          }
          defaultHandler(warning);
        }
      }
    },
    optimizeDeps: {
      entries: [
        // Pre-scan entrypoints in Vite to avoid reloading on first open
        'generated/vaadin.ts'
      ],
      exclude: [
        '@vaadin/router',
        '@vaadin/vaadin-license-checker',
        '@vaadin/vaadin-usage-statistics',
        'workbox-core',
        'workbox-precaching',
        'workbox-routing',
        'workbox-strategies'
      ]
    },
    plugins: [
      productionMode && brotli(),
      devMode && showRecompileReason(),
      
      !devMode && statsExtracterPlugin(),
      !productionMode && preserveUsageStats(),
      themePlugin({ devMode }),
      postcssLit({
        include: ['**/*.css', /.*\/.*\.css\?.*/],
        exclude: [
          `${themeFolder}/**/*.css`,
          new RegExp(`${themeFolder}/.*/.*\\.css\\?.*`),
          `${themeResourceFolder}/**/*.css`,
          new RegExp(`${themeResourceFolder}/.*/.*\\.css\\?.*`),
          new RegExp('.*/.*\\?html-proxy.*')
        ]
      }),
      // The React plugin provides fast refresh. In dev mode Babel (below)
      // handles the JSX transform so OXC skips it — keeping all source
      // locations derived from Babel's AST, which refers to the original
      // source. In production OXC does the JSX transform.
      reactPlugin({
        include: '**/*.tsx',
      }),
      // Babel runs with enforce:'pre' (default), so it sees the original
      // source. All line/column values it embeds in the output come from
      // the original AST — not affected by any formatting differences in
      // Babel's printed output that follows.
      //
      // In dev mode Babel also does the JSX dev transform with the custom
      // jsxImportSource, which captures JSX element locations in
      // _debugInfo.source (React 19 no longer exposes _source on fibers).
      babel({
        include: '**/*.tsx',
        plugins: [
          !productionMode && [
            '@babel/plugin-transform-react-jsx-development',
            { importSource: 'Frontend/generated/jsx-dev-transform' }
          ],
          !productionMode && addFunctionComponentSourceLocationBabel(),
          [
            'module:@preact/signals-react-transform',
            {
              mode: 'all' // Needed to include translations which do not use something.value
            }
          ]
        ].filter(Boolean),
      }),
      
      productionMode && vaadinI18n({
        cwd: __dirname,
        meta: {
          output: {
            dir: i18nFolder,
          },
        },
      }),
      {
        name: 'vaadin:force-remove-html-middleware',
        configureServer(server) {
          return () => {
            server.middlewares.stack = server.middlewares.stack.filter((mw) => {
              const handleName = `${mw.handle}`;
              return !handleName.includes('viteHtmlFallbackMiddleware');
            });
          };
        },
      },
      hasExportedWebComponents && {
        name: 'vaadin:inject-entrypoints-to-web-component-html',
        transformIndexHtml: {
          order: 'pre',
          handler(_html, { path, server }) {
            if (path !== '/web-component.html') {
              return;
            }
            const scripts = [
              {
                tag: 'script',
                attrs: { type: 'module', src: `/generated/vaadin-web-component.ts` },
                injectTo: 'head'
              }
            ];
            if (commercialBanner) {
              scripts.push({
                tag: 'script',
                attrs: { type: 'module', src: '/generated/commercial-banner.js' },
                injectTo: 'head'
              });
            }
            return scripts;
          }
        }
      },
      {
        name: 'vaadin:inject-entrypoints-to-index-html',
        transformIndexHtml: {
          order: 'pre',
          handler(_html, { path, server }) {
            if (path !== '/index.html') {
              return;
            }

            const scripts = [];

            if (devMode) {
              scripts.push({
                tag: 'script',
                attrs: { type: 'module', src: `/generated/vite-devmode.ts`, onerror: "document.location.reload()" },
                injectTo: 'head'
              });
            }
            scripts.push({
              tag: 'script',
              attrs: { type: 'module', src: '/generated/vaadin.ts' },
              injectTo: 'head'
            });
            if (commercialBanner) {
              scripts.push({
                tag: 'script',
                attrs: { type: 'module', src: '/generated/commercial-banner.js' },
                injectTo: 'head'
              });
            }
            return scripts;
          }
        }
      },
      
      checker({
        typescript: true
      }),
      productionMode && visualizer({ brotliSize: true, filename: bundleSizeFile })
    ]
  };
};

export const overrideVaadinConfig = (customConfig: UserConfigFn) => {
  return defineConfig((env) => mergeConfig(vaadinConfig(env), customConfig(env)));
};
function getVersion(module: string): string {
  const packageJson = path.resolve(nodeModulesFolder, module, 'package.json');
  return JSON.parse(readFileSync(packageJson, { encoding: 'utf-8' })).version;
}
function getCvdlName(module: string): string {
  const packageJson = path.resolve(nodeModulesFolder, module, 'package.json');
  return JSON.parse(readFileSync(packageJson, { encoding: 'utf-8' })).cvdlName;
}
