Repository: jhipster/jhipster-lite
Pull request #10840 (2024-09-14 19:19:53)

Vue module now depends on typescript module
See #10421


Patch:
diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactory.java b/src/main/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactory.java
index 19ba80b45..39055c152 100644
--- a/src/main/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactory.java
+++ b/src/main/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactory.java
@@ -1,14 +1,18 @@
 package tech.jhipster.lite.generator.client.vue.core.domain;
 
 import static tech.jhipster.lite.module.domain.JHipsterModule.*;
-import static tech.jhipster.lite.module.domain.packagejson.NodeModuleFormat.MODULE;
 import static tech.jhipster.lite.module.domain.packagejson.VersionSource.COMMON;
 import static tech.jhipster.lite.module.domain.packagejson.VersionSource.VUE;
+import static tech.jhipster.lite.module.domain.replacement.ReplacementCondition.notContainingReplacement;
 
+import java.util.function.Consumer;
+import tech.jhipster.lite.module.domain.Indentation;
 import tech.jhipster.lite.module.domain.JHipsterModule;
 import tech.jhipster.lite.module.domain.file.JHipsterDestination;
 import tech.jhipster.lite.module.domain.file.JHipsterSource;
 import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
+import tech.jhipster.lite.module.domain.replacement.MandatoryReplacer;
+import tech.jhipster.lite.module.domain.replacement.TextReplacer;
 import tech.jhipster.lite.shared.error.domain.Assert;
 
 public class VueModulesFactory {
@@ -17,7 +21,6 @@ public class VueModulesFactory {
   private static final JHipsterSource APP_SOURCE = from("client/vue/webapp/app");
   private static final JHipsterSource DOCUMENTATION_SOURCE = SOURCE.append("documentation");
   private static final JHipsterSource IMAGE_SOURCE = SOURCE.append("webapp/content/images");
-  private static final JHipsterSource SOURCE_COMMON = from("client/common");
 
   private static final JHipsterDestination MAIN_DESTINATION = to("src/main/webapp/app");
   private static final JHipsterDestination TEST_DESTINATION = to("src/test/webapp");
@@ -42,55 +45,33 @@ public class VueModulesFactory {
       .preCommitActions(stagedFilesFilter("{src/**/,}*.{ts,vue}"), preCommitCommands("eslint --fix", "prettier --write"))
       .documentation(documentationTitle("Vue"), DOCUMENTATION_SOURCE.file("vue.md"))
       .packageJson()
-        .type(MODULE)
+        .removeDevDependency(packageName("@tsconfig/recommended"))
         .addDependency(packageName("vue"), VUE)
         .addDependency(packageName("axios"), VUE)
         .addDependency(packageName("vue-router"), VUE)
-        .addDevDependency(packageName("@typescript-eslint/parser"), COMMON)
+        .addDependency(packageName("piqure"), VUE)
         .addDevDependency(packageName("@vitejs/plugin-vue"), VUE)
-        .addDevDependency(packageName("typescript-eslint"), COMMON)
-        .addDevDependency(packageName("globals"), COMMON)
         .addDevDependency(packageName("@vue/test-utils"), VUE)
         .addDevDependency(packageName("@vue/tsconfig"), VUE)
-        .addDevDependency(packageName("@vitest/coverage-istanbul"), COMMON)
-        .addDevDependency(packageName("eslint"), COMMON)
-        .addDevDependency(packageName("eslint-config-prettier"), COMMON)
         .addDevDependency(packageName("eslint-plugin-vue"), VUE)
         .addDevDependency(packageName("jsdom"), COMMON)
-        .addDevDependency(packageName("typescript"), COMMON)
         .addDevDependency(packageName("vite"), COMMON)
-        .addDevDependency(packageName("vite-tsconfig-paths"), COMMON)
-        .addDevDependency(packageName("vitest"), COMMON)
-        .addDevDependency(packageName("vitest-sonar-reporter"), COMMON)
         .addDevDependency(packageName("vue-tsc"), VUE)
         .addDevDependency(packageName("@types/sinon"), VUE)
         .addDevDependency(packageName("sinon"), VUE)
-        .addDevDependency(packageName("npm-run-all2"), COMMON)
         .addScript(scriptKey("build"), scriptCommand("npm-run-all build:*"))
         .addScript(scriptKey("build:tsc"), scriptCommand("vue-tsc -p tsconfig.build.json --noEmit"))
         .addScript(scriptKey("build:vite"), scriptCommand("vite build --emptyOutDir"))
         .addScript(scriptKey("dev"), scriptCommand("npm-run-all --parallel dev:*"))
         .addScript(scriptKey("dev:vite"), scriptCommand("vite"))
-        .addScript(scriptKey("watch"), scriptCommand("npm-run-all --parallel watch:*"))
-        .addDevDependency(packageName("piqure"), VUE)
-        .addScript(scriptKey("lint"), scriptCommand("eslint ."))
         .addScript(scriptKey("preview"), scriptCommand("vite preview"))
         .addScript(scriptKey("start"), scriptCommand("vite"))
         .addScript(scriptKey("watch:tsc"), scriptCommand("npm run build:tsc -- --watch"))
-        .addScript(scriptKey("test"), scriptCommand("npm run watch:test"))
-        .addScript(scriptKey("watch:test"), scriptCommand("vitest --"))
-        .addScript(scriptKey("test:coverage"), scriptCommand("vitest run --coverage"))
         .and()
       .files()
-        .add(SOURCE.template("eslint.config.js.mustache"), to("eslint.config.js"))
-        .add(SOURCE.file("tsconfig.json"), to("tsconfig.json"))
         .add(SOURCE.file("tsconfig.build.json"), to("tsconfig.build.json"))
         .batch(SOURCE, to("."))
           .addTemplate("vite.config.ts")
-          .addTemplate("vitest.config.ts")
-          .and()
-        .batch(SOURCE_COMMON, to("."))
-          .addFile(".npmrc")
           .and()
         .add(SOURCE.template("webapp/index.html"), to("src/main/webapp/index.html"))
         .batch(APP_SOURCE, MAIN_DESTINATION)
@@ -115,10 +96,84 @@ public class VueModulesFactory {
         .add(APP_SOURCE.template("test/webapp/unit/shared/http/infrastructure/secondary/AxiosStub.ts.mustache"), TEST_DESTINATION.append("unit/shared/http/infrastructure/secondary/AxiosStub.ts"))
         .add(APP_SOURCE.template("test/webapp/unit/router/infrastructure/primary/HomeRouter.spec.ts.mustache"), TEST_DESTINATION.append("unit/router/infrastructure/primary/HomeRouter.spec.ts"))
         .and()
+      .apply(patchEslintConfig(properties))
+      .apply(patchTsConfig(properties))
+      .apply(patchVitestConfig(properties))
       .build();
     //@formatter:on
   }
 
+  private Consumer<JHipsterModuleBuilder> patchEslintConfig(JHipsterModuleProperties properties) {
+    String vuePluginConfig =
+      """
+      \t...vue.configs['flat/recommended'],
+      \t{
+      \t\tfiles: ['**/*.vue'],
+      \t\tlanguageOptions: {
+      \t\t\tparserOptions: { parser: '@typescript-eslint/parser' },
+      \t\t\tglobals: { ...globals.browser },
+      \t\t},
+      \t},\
+      """.replace("\t", properties.indentation().spaces());
+    //@formatter:off
+    return moduleBuilder -> moduleBuilder
+      .mandatoryReplacements()
+        .in(path("eslint.config.js"))
+          .add(lineAfterRegex("from 'typescript-eslint'"), "import vue from 'eslint-plugin-vue';")
+          .add(lineAfterRegex("...typescript.configs.recommended"), vuePluginConfig)
+          .add(text("files: ['src/*/webapp/**/*.ts']"), "files: ['src/*/webapp/**/*.vue', 'src/*/webapp/**/*.ts']")
+          .add(eslintTypescriptVueRule("'vue/html-self-closing': 'off',", properties.indentation()))
+          .add(eslintTypescriptVueRule("'@typescript-eslint/no-explicit-any': 'off',", properties.indentation()))
+          .add(eslintTypescriptVueRule("'@typescript-eslint/no-empty-object-type': 'off',", properties.indentation()))
+          .and()
+        .and();
+    //@formatter:on
+  }
+
+  private static MandatoryReplacer eslintTypescriptVueRule(String rule, Indentation indentation) {
+    return new MandatoryReplacer(lineAfterRegex("quotes: \\['error', 'single'"), indentation.times(3) + rule);
+  }
+
+  private Consumer<JHipsterModuleBuilder> patchTsConfig(JHipsterModuleProperties properties) {
+    //@formatter:off
+    return moduleBuilder -> moduleBuilder
+      .mandatoryReplacements()
+        .in(path("tsconfig.json"))
+          .add(text("@tsconfig/recommended/tsconfig.json"), "@vue/tsconfig/tsconfig.dom.json")
+          .add(tsConfigCompilerOption("sourceMap", true, properties.indentation()))
+          .add(tsConfigCompilerOption("allowJs", true, properties.indentation()))
+          .add(new TextReplacer(notContainingReplacement(), "\"types\": ["), "\"types\": [\"vite/client\", ")
+          .and()
+        .and();
+    //@formatter:on
+  }
+
+  private static MandatoryReplacer tsConfigCompilerOption(String optionName, boolean optionValue, Indentation indentation) {
+    String compilerOption = indentation.times(2) + "\"%s\": %s,".formatted(optionName, optionValue);
+    return new MandatoryReplacer(lineAfterRegex("\"compilerOptions\":"), compilerOption);
+  }
+
+  private Consumer<JHipsterModuleBuilder> patchVitestConfig(JHipsterModuleProperties properties) {
+    //@formatter:off
+    return moduleBuilder -> moduleBuilder
+      .mandatoryReplacements()
+        .in(path("vitest.config.ts"))
+          .add(lineAfterRegex("from 'vitest/config';"), "import vue from '@vitejs/plugin-vue';")
+          .add(new TextReplacer(notContainingReplacement(), "plugins: ["), "plugins: [vue(), ")
+          .add(text("environment: 'node',"), "environment: 'jsdom',")
+          .add(vitestCoverageExclusion(properties,"src/main/webapp/**/*.component.ts"))
+          .add(vitestCoverageExclusion(properties,"src/main/webapp/app/router.ts"))
+          .add(vitestCoverageExclusion(properties,"src/main/webapp/app/injections.ts"))
+          .add(vitestCoverageExclusion(properties,"src/main/webapp/app/main.ts"))
+        .and();
+    //@formatter:on
+  }
+
+  private static MandatoryReplacer vitestCoverageExclusion(JHipsterModuleProperties properties, String filePattern) {
+    Indentation indentation = properties.indentation();
+    return new MandatoryReplacer(lineAfterRegex("configDefaults.coverage.exclude"), indentation.times(4) + "'" + filePattern + "',");
+  }
+
   public JHipsterModule buildPiniaModule(JHipsterModuleProperties properties) {
     Assert.notNull("properties", properties);
 
diff --git a/src/main/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/VueModuleConfiguration.java b/src/main/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/VueModuleConfiguration.java
index 0b5baf0fa..412fe1607 100644
--- a/src/main/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/VueModuleConfiguration.java
+++ b/src/main/java/tech/jhipster/lite/generator/client/vue/core/infrastructure/primary/VueModuleConfiguration.java
@@ -19,7 +19,7 @@ class VueModuleConfiguration {
       .slug(VUE_CORE)
       .propertiesDefinition(JHipsterModulePropertiesDefinition.builder().addIndentation().build())
       .apiDoc("Frontend - Vue", "Add Vue+Vite")
-      .organization(JHipsterModuleOrganization.builder().feature(CLIENT_CORE).addDependency(INIT).addDependency(PRETTIER).build())
+      .organization(JHipsterModuleOrganization.builder().feature(CLIENT_CORE).addDependency(TYPESCRIPT).addDependency(PRETTIER).build())
       .tags("client", "init", "vue")
       .factory(vue::buildVueModule);
   }
diff --git a/src/main/java/tech/jhipster/lite/generator/typescript/core/domain/TypescriptModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/typescript/core/domain/TypescriptModuleFactory.java
index 100acd2d9..846154ab3 100644
--- a/src/main/java/tech/jhipster/lite/generator/typescript/core/domain/TypescriptModuleFactory.java
+++ b/src/main/java/tech/jhipster/lite/generator/typescript/core/domain/TypescriptModuleFactory.java
@@ -12,6 +12,7 @@ import tech.jhipster.lite.shared.error.domain.Assert;
 public class TypescriptModuleFactory {
 
   private static final JHipsterSource SOURCE = from("typescript");
+  private static final JHipsterSource SOURCE_COMMON = from("client/common");
 
   public JHipsterModule buildModule(JHipsterModuleProperties properties) {
     Assert.notNull("properties", properties);
@@ -27,7 +28,6 @@ public class TypescriptModuleFactory {
         .addDevDependency(packageName("@vitest/coverage-istanbul"), COMMON)
         .addDevDependency(packageName("eslint"), COMMON)
         .addDevDependency(packageName("eslint-config-prettier"), COMMON)
-        .addDevDependency(packageName("eslint-plugin-import-x"), COMMON)
         .addDevDependency(packageName("globals"), COMMON)
         .addDevDependency(packageName("npm-run-all2"), COMMON)
         .addDevDependency(packageName("typescript-eslint"), COMMON)
@@ -42,6 +42,9 @@ public class TypescriptModuleFactory {
         .addScript(scriptKey("watch:test"), scriptCommand("vitest --"))
         .and()
       .files()
+        .batch(SOURCE_COMMON, to("."))
+          .addFile(".npmrc")
+          .and()
         .batch(SOURCE, to("."))
           .addFile("tsconfig.json")
           .addTemplate("vitest.config.ts")
diff --git a/src/main/java/tech/jhipster/lite/module/domain/JHipsterModule.java b/src/main/java/tech/jhipster/lite/module/domain/JHipsterModule.java
index dbf6231e2..f48c78922 100644
--- a/src/main/java/tech/jhipster/lite/module/domain/JHipsterModule.java
+++ b/src/main/java/tech/jhipster/lite/module/domain/JHipsterModule.java
@@ -1,13 +1,12 @@
 package tech.jhipster.lite.module.domain;
 
-import static tech.jhipster.lite.module.domain.replacement.ReplacementCondition.*;
+import static tech.jhipster.lite.module.domain.replacement.ReplacementCondition.always;
+import static tech.jhipster.lite.module.domain.replacement.ReplacementCondition.notContainingReplacement;
 
 import java.nio.file.Paths;
-import java.util.Collection;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 import java.util.Map.Entry;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
@@ -22,41 +21,29 @@ import tech.jhipster.lite.module.domain.gitignore.JHipsterModuleGitIgnore;
 import tech.jhipster.lite.module.domain.gitignore.JHipsterModuleGitIgnore.JHipsterModuleGitIgnoreBuilder;
 import tech.jhipster.lite.module.domain.gradleconfiguration.JHipsterModuleGradleConfigurations;
 import tech.jhipster.lite.module.domain.gradleconfiguration.JHipsterModuleGradleConfigurations.JHipsterModuleGradleConfigurationBuilder;
-import tech.jhipster.lite.module.domain.gradleplugin.GradleCommunityPlugin;
+import tech.jhipster.lite.module.domain.gradleplugin.*;
 import tech.jhipster.lite.module.domain.gradleplugin.GradleCommunityPlugin.GradleCommunityPluginIdBuilder;
-import tech.jhipster.lite.module.domain.gradleplugin.GradleCommunityProfilePlugin;
 import tech.jhipster.lite.module.domain.gradleplugin.GradleCommunityProfilePlugin.GradleCommunityProfilePluginIdBuilder;
-import tech.jhipster.lite.module.domain.gradleplugin.GradleCorePlugin;
 import tech.jhipster.lite.module.domain.gradleplugin.GradleCorePlugin.GradleCorePluginIdBuilder;
-import tech.jhipster.lite.module.domain.gradleplugin.JHipsterModuleGradlePlugins;
 import tech.jhipster.lite.module.domain.gradleplugin.JHipsterModuleGradlePlugins.JHipsterModuleGradlePluginBuilder;
 import tech.jhipster.lite.module.domain.javabuild.*;
 import tech.jhipster.lite.module.domain.javabuild.JHipsterModuleMavenBuildExtensions.JHipsterModuleMavenBuildExtensionsBuilder;
 import tech.jhipster.lite.module.domain.javabuild.MavenBuildExtension.MavenBuildExtensionGroupIdBuilder;
-import tech.jhipster.lite.module.domain.javabuildprofile.BuildProfileActivation;
+import tech.jhipster.lite.module.domain.javabuildprofile.*;
 import tech.jhipster.lite.module.domain.javabuildprofile.BuildProfileActivation.BuildProfileActivationBuilder;
-import tech.jhipster.lite.module.domain.javabuildprofile.BuildProfileId;
-import tech.jhipster.lite.module.domain.javabuildprofile.JHipsterModuleJavaBuildProfiles;
 import tech.jhipster.lite.module.domain.javabuildprofile.JHipsterModuleJavaBuildProfiles.JHipsterModuleJavaBuildProfilesBuilder;
-import tech.jhipster.lite.module.domain.javadependency.DependencyId;
-import tech.jhipster.lite.module.domain.javadependency.JHipsterModuleJavaDependencies;
+import tech.jhipster.lite.module.domain.javadependency.*;
 import tech.jhipster.lite.module.domain.javadependency.JHipsterModuleJavaDependencies.JHipsterModuleJavaDependenciesBuilder;
-import tech.jhipster.lite.module.domain.javadependency.JavaDependency;
 import tech.jhipster.lite.module.domain.javadependency.JavaDependency.JavaDependencyGroupIdBuilder;
 import tech.jhipster.lite.module.domain.javaproperties.*;
 import tech.jhipster.lite.module.domain.javaproperties.JHipsterModuleSpringFactories.JHipsterModuleSpringFactoriesBuilder;
 import tech.jhipster.lite.module.domain.javaproperties.JHipsterModuleSpringProperties.JHipsterModuleSpringPropertiesBuilder;
-import tech.jhipster.lite.module.domain.mavenplugin.JHipsterModuleMavenPlugins;
+import tech.jhipster.lite.module.domain.mavenplugin.*;
 import tech.jhipster.lite.module.domain.mavenplugin.JHipsterModuleMavenPlugins.JHipsterModuleMavenPluginsBuilder;
-import tech.jhipster.lite.module.domain.mavenplugin.MavenPlugin;
 import tech.jhipster.lite.module.domain.mavenplugin.MavenPlugin.MavenPluginGroupIdBuilder;
-import tech.jhipster.lite.module.domain.mavenplugin.MavenPluginExecution;
 import tech.jhipster.lite.module.domain.mavenplugin.MavenPluginExecution.MavenPluginExecutionGoalsBuilder;
-import tech.jhipster.lite.module.domain.packagejson.JHipsterModulePackageJson;
+import tech.jhipster.lite.module.domain.packagejson.*;
 import tech.jhipster.lite.module.domain.packagejson.JHipsterModulePackageJson.JHipsterModulePackageJsonBuilder;
-import tech.jhipster.lite.module.domain.packagejson.PackageName;
-import tech.jhipster.lite.module.domain.packagejson.ScriptCommand;
-import tech.jhipster.lite.module.domain.packagejson.ScriptKey;
 import tech.jhipster.lite.module.domain.postaction.JHipsterModulePostActions;
 import tech.jhipster.lite.module.domain.postaction.JHipsterModulePostActions.JHipsterModulePostActionsBuilder;
 import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
@@ -526,6 +513,13 @@ public final class JHipsterModule {
       return properties;
     }
 
+    public JHipsterModuleBuilder apply(Consumer<JHipsterModuleBuilder> builderCustomizer) {
+      Assert.notNull("builderCustomizer", builderCustomizer);
+      builderCustomizer.accept(this);
+
+      return this;
+    }
+
     public JHipsterModuleBuilder documentation(DocumentationTitle title, JHipsterSource source) {
       shortcuts.documentation(title, source);
 
diff --git a/src/main/java/tech/jhipster/lite/module/domain/JHipsterModuleShortcuts.java b/src/main/java/tech/jhipster/lite/module/domain/JHipsterModuleShortcuts.java
index 8addcf7a9..6fc7f3416 100644
--- a/src/main/java/tech/jhipster/lite/module/domain/JHipsterModuleShortcuts.java
+++ b/src/main/java/tech/jhipster/lite/module/domain/JHipsterModuleShortcuts.java
@@ -68,14 +68,18 @@ final class JHipsterModuleShortcuts {
     Assert.notBlank("name", name);
     Assert.notNull("level", level);
 
-    builder.optionalReplacements().in(SPRING_TEST_LOG_FILE).add(JHIPSTER_LOGGER_NEEDLE, logger(name, level));
+    builder.optionalReplacements().in(SPRING_TEST_LOG_FILE).add(logConfigurationEntry(name, level));
   }
 
   void springMainLogger(String name, LogLevel level) {
     Assert.notBlank("name", name);
     Assert.notNull("level", level);
 
-    builder.optionalReplacements().in(SPRING_MAIN_LOG_FILE).add(JHIPSTER_LOGGER_NEEDLE, logger(name, level));
+    builder.optionalReplacements().in(SPRING_MAIN_LOG_FILE).add(logConfigurationEntry(name, level));
+  }
+
+  private OptionalReplacer logConfigurationEntry(String name, LogLevel level) {
+    return new OptionalReplacer(JHIPSTER_LOGGER_NEEDLE, logger(name, level));
   }
 
   private String logger(String name, LogLevel level) {
diff --git a/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleMandatoryReplacementsFactory.java b/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleMandatoryReplacementsFactory.java
index 8f7c3550e..35a31ffff 100644
--- a/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleMandatoryReplacementsFactory.java
+++ b/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleMandatoryReplacementsFactory.java
@@ -52,6 +52,12 @@ public final class JHipsterModuleMandatoryReplacementsFactory extends JHipsterMo
       super(replacements, file);
     }
 
+    public JHipsterModuleFileMandatoryReplacementsFactoryBuilder add(MandatoryReplacer mandatoryReplacer) {
+      replacements().add(buildReplacer(file(), mandatoryReplacer.replacer(), mandatoryReplacer.updatedValue()));
+
+      return this;
+    }
+
     @Override
     protected ContentReplacer buildReplacer(JHipsterProjectFilePath file, ElementReplacer toReplace, String replacement) {
       return new MandatoryFileReplacer(file, new MandatoryReplacer(toReplace, replacement));
diff --git a/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleOptionalReplacementsFactory.java b/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleOptionalReplacementsFactory.java
index e5d3e3a8e..66df8a99b 100644
--- a/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleOptionalReplacementsFactory.java
+++ b/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleOptionalReplacementsFactory.java
@@ -80,6 +80,12 @@ public final class JHipsterModuleOptionalReplacementsFactory extends JHipsterMod
       super(replacements, file);
     }
 
+    public JHipsterModuleFileOptionalReplacementsFactoryBuilder add(OptionalReplacer mandatoryReplacer) {
+      replacements().add(buildReplacer(file(), mandatoryReplacer.replacer(), mandatoryReplacer.updatedValue()));
+
+      return this;
+    }
+
     @Override
     protected ContentReplacer buildReplacer(JHipsterProjectFilePath file, ElementReplacer toReplace, String replacement) {
       return new OptionalFileReplacer(file, new OptionalReplacer(toReplace, replacement));
diff --git a/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleReplacementsFactory.java b/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleReplacementsFactory.java
index 59f7957f5..ce4abf2b2 100644
--- a/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleReplacementsFactory.java
+++ b/src/main/java/tech/jhipster/lite/module/domain/replacement/JHipsterModuleReplacementsFactory.java
@@ -80,6 +80,14 @@ public abstract class JHipsterModuleReplacementsFactory {
       return self();
     }
 
+    protected ReplacementsBuilder replacements() {
+      return replacements;
+    }
+
+    protected JHipsterProjectFilePath file() {
+      return file;
+    }
+
     @SuppressWarnings("unchecked")
     private Builder self() {
       return (Builder) this;
diff --git a/src/main/resources/generator/client/vue/eslint.config.js.mustache b/src/main/resources/generator/client/vue/eslint.config.js.mustache
deleted file mode 100644
index bba528024..000000000
--- a/src/main/resources/generator/client/vue/eslint.config.js.mustache
+++ /dev/null
@@ -1,39 +0,0 @@
-import globals from 'globals';
-import typescript from 'typescript-eslint';
-import js from '@eslint/js';
-import vue from 'eslint-plugin-vue';
-
-export default typescript.config(
-  {
-    languageOptions: {
-      globals: {
-        ...globals.node,
-      },
-    },
-  },
-  {
-    ignores: ['{{projectBuildDirectory}}/'],
-  },
-  js.configs.recommended,
-  ...typescript.configs.recommended.map(config => (config.name === 'typescript-eslint/base' ? config : { ...config, files: ['**/*.ts'] })),
-  ...vue.configs['flat/recommended'],
-  {
-    files: ['**/*.vue'],
-    languageOptions: {
-      parserOptions: { parser: '@typescript-eslint/parser' },
-      globals: { ...globals.browser },
-    },
-  },
-  {
-    files: ['src/*/webapp/**/*.vue', 'src/*/webapp/**/*.ts'],
-    languageOptions: {
-      globals: { ...globals.browser },
-    },
-    rules: {
-      quotes: ['error', 'single', { avoidEscape: true }],
-      '@typescript-eslint/no-explicit-any': 'off',
-      'vue/html-self-closing': 'off',
-      '@typescript-eslint/no-empty-object-type': 'off',
-    },
-  },
-);
diff --git a/src/main/resources/generator/client/vue/tsconfig.json b/src/main/resources/generator/client/vue/tsconfig.json
deleted file mode 100644
index 6bef65ee0..000000000
--- a/src/main/resources/generator/client/vue/tsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "extends": "@vue/tsconfig/tsconfig.dom.json",
-  "compilerOptions": {
-    "allowJs": true,
-    "sourceMap": true,
-    "types": ["vite/client", "vitest/globals"],
-    "baseUrl": ".",
-    "paths": {
-      "@/*": ["src/main/webapp/app/*"]
-    }
-  },
-  "include": ["src/main/webapp/**/*", "src/test/webapp/unit/**/*"]
-}
diff --git a/src/main/resources/generator/typescript/eslint.config.js.mustache b/src/main/resources/generator/typescript/eslint.config.js.mustache
index 8fc3802f7..1dbd6fc1f 100644
--- a/src/main/resources/generator/typescript/eslint.config.js.mustache
+++ b/src/main/resources/generator/typescript/eslint.config.js.mustache
@@ -1,5 +1,4 @@
 import js from '@eslint/js';
-import imports from 'eslint-plugin-import-x';
 import globals from 'globals';
 import typescript from 'typescript-eslint';
 
@@ -12,35 +11,10 @@ export default typescript.config(
     },
   },
   {
-    ignores: [
-      '{{projectBuildDirectory}}/'
-    ],
+    ignores: ['{{projectBuildDirectory}}/'],
   },
   js.configs.recommended,
   ...typescript.configs.recommended.map(config => (config.name === 'typescript-eslint/base' ? config : { ...config, files: ['**/*.ts'] })),
-  {
-    extends: [imports.flatConfigs.recommended, imports.flatConfigs.typescript],
-    languageOptions: {
-      // import plugin does not use ecmaVersion and sourceType from languageOptions object
-      parserOptions: {
-        ecmaVersion: 2022,
-        sourceType: 'module',
-      },
-    },
-    rules: {
-      'import-x/no-unresolved': 'off',
-      'import-x/order': [
-        'error',
-        {
-          alphabetize: {
-            order: 'asc',
-            caseInsensitive: true,
-          },
-          'newlines-between': 'always',
-        },
-      ],
-    },
-  },
   {
     files: ['src/*/webapp/**/*.ts'],
     languageOptions: {


Test patch:
diff --git a/src/main/resources/generator/client/vue/vitest.config.ts.mustache b/src/main/resources/generator/client/vue/vitest.config.ts.mustache
deleted file mode 100644
index 4f9241071..000000000
--- a/src/main/resources/generator/client/vue/vitest.config.ts.mustache
+++ /dev/null
@@ -1,50 +0,0 @@
-/// <reference types="vitest" />
-
-import tsconfigPaths from 'vite-tsconfig-paths';
-import { defineConfig, configDefaults } from 'vitest/config';
-import vue from '@vitejs/plugin-vue';
-
-export default defineConfig({
-  plugins: [vue(), tsconfigPaths()],
-  test: {
-    reporters: ['verbose', 'vitest-sonar-reporter'],
-    outputFile: {
-      'vitest-sonar-reporter': '{{projectBuildDirectory}}/test-results/TESTS-results-sonar.xml',
-    },
-    globals: true,
-    logHeapUsage: true,
-    poolOptions: {
-      threads: {
-        minThreads: 1,
-        maxThreads: 2,
-      },
-    },
-    environment: 'jsdom',
-    cache: false,
-    include: ['src/test/webapp/unit/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
-    coverage: {
-      include: ['src/main/webapp/**/*.ts?(x)'],
-      exclude: [
-        ...configDefaults.coverage.exclude as string[],
-        'src/main/webapp/app/main.ts',
-        'src/main/webapp/app/injections.ts',
-        'src/main/webapp/app/router.ts',
-        'src/main/webapp/**/*.component.ts',
-      ],
-      provider: 'istanbul',
-      reportsDirectory: '{{projectBuildDirectory}}/test-results/',
-      reporter: ['html', 'json-summary', 'text', 'text-summary', 'lcov', 'clover'],
-      thresholds: {
-        perFile: true,
-        autoUpdate: true,
-        100: true,
-      },
-      watermarks: {
-        statements: [100, 100],
-        branches: [100, 100],
-        functions: [100, 100],
-        lines: [100, 100],
-      },
-    },
-  },
-});
diff --git a/src/main/resources/generator/typescript/vitest.config.ts.mustache b/src/main/resources/generator/typescript/vitest.config.ts.mustache
index 2a0eaee09..0dc4d9d3e 100644
--- a/src/main/resources/generator/typescript/vitest.config.ts.mustache
+++ b/src/main/resources/generator/typescript/vitest.config.ts.mustache
@@ -22,7 +22,6 @@ export default defineConfig({
     cache: false,
     include: ['src/test/webapp/unit/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
     coverage: {
-      all: true,
       thresholds: {
         perFile: true,
         autoUpdate: true,
@@ -32,7 +31,6 @@ export default defineConfig({
       exclude: [
         ...configDefaults.coverage.exclude as string[],
       ],
-      clean: true,
       provider: 'istanbul',
       reportsDirectory: '{{projectBuildDirectory}}/test-results/',
       reporter: ['html', 'json-summary', 'text', 'text-summary', 'lcov', 'clover'],
diff --git a/src/test/features/client/vue.feature b/src/test/features/client/vue.feature
index 3092bb915..ecbfa3541 100644
--- a/src/test/features/client/vue.feature
+++ b/src/test/features/client/vue.feature
@@ -2,9 +2,10 @@ Feature: Vue.js modules
 
   Scenario: Should apply vue core module
     When I apply modules to default project
-      | init     |
-      | prettier |
-      | vue-core |
+      | init       |
+      | prettier   |
+      | typescript |
+      | vue-core   |
     Then I should have files in "src/main/webapp/app"
       | AppVue.vue |
 
diff --git a/src/test/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.java
index 0c753d47a..4aa78c54e 100644
--- a/src/test/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.java
+++ b/src/test/java/tech/jhipster/lite/generator/client/vue/core/domain/VueModulesFactoryTest.java
@@ -24,46 +24,31 @@ class VueModulesFactoryTest {
     JHipsterModule module = factory.buildVueModule(properties);
 
     //@formatter:off
-    assertThatModuleWithFiles(module, packageJsonFile(), lintStagedConfigFile())
+    assertThatModuleWithFiles(module, packageJsonFile(), lintStagedConfigFile(), tsConfigFile(), vitestConfigFile(), eslintConfigFile())
       .hasFiles("documentation/vue.md")
       .hasFile("package.json")
+        .notContaining(nodeDependency("@tsconfig/recommended"))
         .containing(nodeDependency("vue"))
-        .containing(nodeDependency("@typescript-eslint/parser"))
         .containing(nodeDependency("@vitejs/plugin-vue"))
-        .containing(nodeDependency("typescript-eslint"))
-        .containing(nodeDependency("globals"))
         .containing(nodeDependency("@vue/test-utils"))
         .containing(nodeDependency("@vue/tsconfig"))
-        .containing(nodeDependency("@vitest/coverage-istanbul"))
-        .containing(nodeDependency("eslint"))
-        .containing(nodeDependency("eslint-config-prettier"))
         .containing(nodeDependency("eslint-plugin-vue"))
         .containing(nodeDependency("jsdom"))
-        .containing(nodeDependency("typescript"))
         .containing(nodeDependency("vite"))
-        .containing(nodeDependency("vite-tsconfig-paths"))
-        .containing(nodeDependency("vitest"))
-        .containing(nodeDependency("vitest-sonar-reporter"))
         .containing(nodeDependency("vue-tsc"))
         .containing(nodeDependency("@types/sinon"))
         .containing(nodeDependency("sinon"))
         .containing(nodeDependency("axios"))
         .containing(nodeDependency("vue-router"))
-        .containing(nodeDependency("npm-run-all2"))
+        .containing(nodeDependency("piqure"))
         .containing(nodeScript("build", "npm-run-all build:*"))
         .containing(nodeScript("build:tsc", "vue-tsc -p tsconfig.build.json --noEmit"))
         .containing(nodeScript("build:vite", "vite build --emptyOutDir"))
         .containing(nodeScript("dev", "npm-run-all --parallel dev:*"))
         .containing(nodeScript("dev:vite", "vite"))
-        .containing(nodeScript("watch", "npm-run-all --parallel watch:*"))
         .containing(nodeScript("watch:tsc", "npm run build:tsc -- --watch"))
-        .containing(nodeScript("watch:test", "vitest --"))
-        .containing(nodeDependency("piqure"))
-        .containing(nodeScript("lint", "eslint ."))
         .containing(nodeScript("preview", "vite preview"))
         .containing(nodeScript("start", "vite"))
-        .containing(nodeScript("test", "npm run watch:test"))
-        .containing(nodeScript("test:coverage", "vitest run --coverage"))
         .and()
       .hasFile(".lintstagedrc.cjs")
         .containing(
@@ -75,7 +60,49 @@ class VueModulesFactoryTest {
             """
         )
       .and()
-      .hasPrefixedFiles("", ".npmrc", "eslint.config.js", "tsconfig.json", "tsconfig.build.json", "vite.config.ts", "vitest.config.ts")
+      .hasPrefixedFiles("", "eslint.config.js", "tsconfig.build.json", "vite.config.ts", "vitest.config.ts")
+      .hasFile("tsconfig.json")
+        .containing("\"extends\": \"@vue/tsconfig/tsconfig.dom.json\"")
+        .containing("\"allowJs\": true,")
+        .containing("\"sourceMap\": true,")
+        .containing("\"types\": [\"vite/client\", ")
+        .and()
+      .hasFile("vitest.config.ts")
+        .containing("import vue from '@vitejs/plugin-vue';")
+        .containing("plugins: [vue(), tsconfigPaths()],")
+        .containing("environment: 'jsdom',")
+        .containing("""
+                exclude: [
+                  ...configDefaults.coverage.exclude as string[],
+                  'src/main/webapp/app/main.ts',
+                  'src/main/webapp/app/injections.ts',
+                  'src/main/webapp/app/router.ts',
+                  'src/main/webapp/**/*.component.ts',
+          """
+        )
+        .and()
+      .hasFile("eslint.config.js")
+        .containing("import vue from 'eslint-plugin-vue';")
+        .containing("""
+          ...vue.configs['flat/recommended'],
+          {
+            files: ['**/*.vue'],
+            languageOptions: {
+              parserOptions: { parser: '@typescript-eslint/parser' },
+              globals: { ...globals.browser },
+            },
+          },
+        """
+        )
+      .containing("""
+              rules: {
+                quotes: ['error', 'single', { avoidEscape: true }],
+                '@typescript-eslint/no-empty-object-type': 'off',
+                '@typescript-eslint/no-explicit-any': 'off',
+                'vue/html-self-closing': 'off',
+          """
+        )
+      .and()
       .hasFiles("src/main/webapp/app/shared/http/infrastructure/secondary/AxiosHttp.ts")
       .hasFiles("src/main/webapp/index.html")
       .hasPrefixedFiles("src/main/webapp/app", "env.d.ts", "AppVue.vue", "injections.ts", "router.ts", "main.ts")
diff --git a/src/test/java/tech/jhipster/lite/generator/typescript/domain/core/TypescriptModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/typescript/domain/core/TypescriptModuleFactoryTest.java
index 0bf06af36..d73723f17 100644
--- a/src/test/java/tech/jhipster/lite/generator/typescript/domain/core/TypescriptModuleFactoryTest.java
+++ b/src/test/java/tech/jhipster/lite/generator/typescript/domain/core/TypescriptModuleFactoryTest.java
@@ -31,7 +31,6 @@ class TypescriptModuleFactoryTest {
       .containing(nodeDependency("@vitest/coverage-istanbul"))
       .containing(nodeDependency("eslint"))
       .containing(nodeDependency("eslint-config-prettier"))
-      .containing(nodeDependency("eslint-plugin-import-x"))
       .containing(nodeDependency("globals"))
       .containing(nodeDependency("typescript-eslint"))
       .containing(nodeDependency("vite-tsconfig-paths"))
@@ -44,6 +43,6 @@ class TypescriptModuleFactoryTest {
       .containing(nodeScript("watch:tsc", "tsc --noEmit --watch"))
       .containing(nodeScript("lint", "eslint ."))
       .and()
-      .hasPrefixedFiles("", "eslint.config.js", "tsconfig.json");
+      .hasPrefixedFiles("", ".npmrc", "eslint.config.js", "tsconfig.json");
   }
 }
diff --git a/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/JHipsterModulesAssertions.java b/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/JHipsterModulesAssertions.java
index 1b2dbd1d3..cb33baf5c 100644
--- a/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/JHipsterModulesAssertions.java
+++ b/src/test/java/tech/jhipster/lite/module/infrastructure/secondary/JHipsterModulesAssertions.java
@@ -1,7 +1,7 @@
 package tech.jhipster.lite.module.infrastructure.secondary;
 
-import static org.assertj.core.api.Assertions.*;
-import static tech.jhipster.lite.TestFileUtils.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static tech.jhipster.lite.TestFileUtils.contentNormalizingNewLines;
 
 import java.io.IOException;
 import java.nio.file.*;
@@ -60,6 +60,18 @@ public final class JHipsterModulesAssertions {
     return file("src/test/resources/projects/init/.lintstagedrc.cjs", ".lintstagedrc.cjs");
   }
 
+  public static ModuleFile tsConfigFile() {
+    return file("src/main/resources/generator/typescript/tsconfig.json", "tsconfig.json");
+  }
+
+  public static ModuleFile vitestConfigFile() {
+    return file("src/main/resources/generator/typescript/vitest.config.ts.mustache", "vitest.config.ts");
+  }
+
+  public static ModuleFile eslintConfigFile() {
+    return file("src/main/resources/generator/typescript/eslint.config.js.mustache", "eslint.config.js");
+  }
+
   public static ModuleFile emptyLintStagedConfigFile() {
     return file("src/test/resources/projects/init/.lintstagedrc.empty.cjs", ".lintstagedrc.cjs");
   }
diff --git a/tests-ci/generate.sh b/tests-ci/generate.sh
index c6cc189b5..fba0ab04a 100755
--- a/tests-ci/generate.sh
+++ b/tests-ci/generate.sh
@@ -180,6 +180,7 @@ elif [[ $application == 'fullapp' ]]; then
 
   frontend_server_plugin
   applyModules \
+    "typescript" \
     "vue-core" \
     "cypress-component-tests" \
     "playwright-e2e"
@@ -411,6 +412,7 @@ elif [[ $application == 'vueapp' ]]; then
 
   frontend_server_plugin
   applyModules \
+    "typescript" \
     "vue-core" \
     "vue-pinia" \
     "playwright-component-tests" \