gameyfin/app/vite.generated.ts
grimsi ecd369cd30 Migrate to Spring Boot 4 (#868)
* Switched from TomCat to Jetty
* Hibernate migrations
* Removed dependency on Spring-Boot-Content-FS
* Migrate to Jackson 3
* Migrate LegacyExtensionFinder -> IndexedExtensionFinder
* Fix code inspection issues
* Exclude Config classes from Sonar coverage calcualtion
* Add FileStorageServiceTest
* Add tests for (De-)serializers
* Exclude H2 package from Sonar coverage reporting
* Add Sonar scan
* Update JVM in CI
* Update dependency versions
2026-02-05 13:07:41 +01:00

675 lines
26 KiB
TypeScript

/**
* 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 './build/plugins/application-theme-plugin/theme-handle.js';
import { rewriteCssUrls } from './build/plugins/theme-loader/theme-loader-utils.js';
import { addFunctionComponentSourceLocationBabel } from './build/plugins/react-function-location-plugin/react-function-location-plugin.js';
import settings from './build/vaadin-dev-server-settings.json';
import {
AssetInfo,
ChunkInfo,
defineConfig,
mergeConfig,
OutputOptions,
PluginOption,
UserConfigFn
} from 'vite';
import * as rollup from 'rollup';
import brotli from 'rollup-plugin-brotli';
import checker from 'vite-plugin-checker';
import postcssLit from './build/plugins/rollup-plugin-postcss-lit-custom/rollup-plugin-postcss-lit.js';
import vaadinI18n from './build/plugins/rollup-plugin-vaadin-i18n/rollup-plugin-vaadin-i18n.js';
import serviceWorkerPlugin from './build/plugins/vite-plugin-service-worker';
import vaadinBundlesPlugin from './build/plugins/vite-plugin-vaadin-bundles';
import { visualizer } from 'rollup-plugin-visualizer';
import reactPlugin from '@vitejs/plugin-react';
import vitePluginFileSystemRouter from '@vaadin/hilla-file-router/vite-plugin.js';
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);
},
handleHotUpdate(context) {
const contextPath = path.resolve(context.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',
handleHotUpdate(context) {
console.log('Recompiling because', context.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
}
},
esbuild: {
legalComments: 'inline',
},
build: {
minify: productionMode,
outDir: buildOutputFolder,
emptyOutDir: devBundle,
assetsDir: 'VAADIN/build',
target,
rollupOptions: {
input: {
indexhtml: projectIndexHtml,
...(hasExportedWebComponents ? { webcomponenthtml: path.resolve(frontendFolder, 'web-component.html') } : {})
},
output: {
// Workaround to enable dynamic imports with top-level await for
// commonjs modules, such as "atmosphere.js" in Hilla. Extracting
// Rollup's commonjs helpers into separate manual chunk avoids
// circular dependencies in this case. Caused
// - https://github.com/vitejs/vite/issues/10995
// - https://github.com/rollup/rollup/issues/5884
// - https://github.com/vitejs/vite/issues/19695
// - https://github.com/vitejs/vite/issues/12209
manualChunks: (id: string) => id.startsWith('\0commonjsHelpers.js') ? 'commonjsHelpers' : null
},
onwarn: (warning: rollup.RollupLog, defaultHandler: rollup.LoggingFunction) => {
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) => warning.id?.endsWith(id))) {
return;
}
defaultHandler(warning);
}
}
},
optimizeDeps: {
esbuildOptions: {
target,
},
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 && vaadinBundlesPlugin({
nodeModulesFolder
}),
devMode && showRecompileReason(),
settings.offlineEnabled && serviceWorkerPlugin({
srcPath: settings.clientServiceWorkerSource,
}),
!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 and debug source info
reactPlugin({
include: '**/*.tsx',
babel: {
// We need to use babel to provide the source information for it to be correct
// (otherwise Babel will slightly rewrite the source file and esbuild generate source info for the modified file)
presets: [
[
'@babel/preset-react',
{
runtime: 'automatic',
importSource: productionMode ? 'react' : 'Frontend/generated/jsx-dev-transform',
development: !productionMode
}
]
],
// React writes the source location for where components are used, this writes for where they are defined
plugins: [
!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;
}
}
},
vitePluginFileSystemRouter({isDevMode: devMode}),
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;
}