• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 16327087579

16 Jul 2025 06:14PM UTC coverage: 68.219% (-0.2%) from 68.446%
16327087579

Pull #1414

github

web-flow
Merge 6c18f8d8a into ab7eb024e
Pull Request #1414: Implement Windows Terminal integration with Git Bash for improved Windows developer experience

3292 of 5236 branches covered (62.87%)

Branch coverage included in aggregate %.

8441 of 11963 relevant lines covered (70.56%)

3.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.37
cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java
1
package com.devonfw.tools.ide.tool;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.nio.file.StandardOpenOption;
6
import java.util.ArrayList;
7
import java.util.Iterator;
8
import java.util.List;
9
import java.util.Set;
10

11
import com.devonfw.tools.ide.cli.CliException;
12
import com.devonfw.tools.ide.commandlet.UpgradeMode;
13
import com.devonfw.tools.ide.common.SimpleSystemPath;
14
import com.devonfw.tools.ide.common.Tag;
15
import com.devonfw.tools.ide.context.IdeContext;
16
import com.devonfw.tools.ide.git.GitContext;
17
import com.devonfw.tools.ide.git.GitContextImpl;
18
import com.devonfw.tools.ide.io.FileAccess;
19
import com.devonfw.tools.ide.io.IniFile;
20
import com.devonfw.tools.ide.io.IniSection;
21
import com.devonfw.tools.ide.os.WindowsHelper;
22
import com.devonfw.tools.ide.os.WindowsPathSyntax;
23
import com.devonfw.tools.ide.process.ProcessMode;
24
import com.devonfw.tools.ide.process.ProcessResult;
25
import com.devonfw.tools.ide.tool.mvn.MvnArtifact;
26
import com.devonfw.tools.ide.tool.mvn.MvnBasedLocalToolCommandlet;
27
import com.devonfw.tools.ide.tool.repository.MavenRepository;
28
import com.devonfw.tools.ide.variable.IdeVariables;
29
import com.devonfw.tools.ide.version.IdeVersion;
30
import com.devonfw.tools.ide.version.VersionIdentifier;
31

32
import com.fasterxml.jackson.core.JsonProcessingException;
33
import com.fasterxml.jackson.databind.JsonNode;
34
import com.fasterxml.jackson.databind.ObjectMapper;
35
import com.fasterxml.jackson.databind.node.ObjectNode;
36

37
/**
38
 * {@link MvnBasedLocalToolCommandlet} for IDEasy (ide-cli).
39
 */
40
public class IdeasyCommandlet extends MvnBasedLocalToolCommandlet {
41

42
  /** The {@link MvnArtifact} for IDEasy. */
43
  public static final MvnArtifact ARTIFACT = MvnArtifact.ofIdeasyCli("*!", "tar.gz", "${os}-${arch}");
6✔
44

45
  private static final String BASH_CODE_SOURCE_FUNCTIONS = "source \"$IDE_ROOT/_ide/installation/functions\"";
46

47
  /** The {@link #getName() tool name}. */
48
  public static final String TOOL_NAME = "ideasy";
49
  public static final String BASHRC = ".bashrc";
50
  public static final String ZSHRC = ".zshrc";
51
  public static final String IDE_BIN = "\\_ide\\bin";
52
  public static final String IDE_INSTALLATION_BIN = "\\_ide\\installation\\bin";
53

54
  private final UpgradeMode mode;
55

56
  /**
57
   * The constructor.
58
   *
59
   * @param context the {@link IdeContext}.
60
   */
61
  public IdeasyCommandlet(IdeContext context) {
62
    this(context, UpgradeMode.STABLE);
4✔
63
  }
1✔
64

65
  /**
66
   * The constructor.
67
   *
68
   * @param context the {@link IdeContext}.
69
   * @param mode the {@link UpgradeMode}.
70
   */
71
  public IdeasyCommandlet(IdeContext context, UpgradeMode mode) {
72

73
    super(context, TOOL_NAME, ARTIFACT, Set.of(Tag.PRODUCTIVITY, Tag.IDE));
8✔
74
    this.mode = mode;
3✔
75
  }
1✔
76

77
  @Override
78
  public VersionIdentifier getInstalledVersion() {
79

80
    return IdeVersion.getVersionIdentifier();
2✔
81
  }
82

83
  @Override
84
  public String getConfiguredEdition() {
85

86
    return this.tool;
×
87
  }
88

89
  @Override
90
  public VersionIdentifier getConfiguredVersion() {
91

92
    UpgradeMode upgradeMode = this.mode;
×
93
    if (upgradeMode == null) {
×
94
      if (IdeVersion.getVersionString().contains("SNAPSHOT")) {
×
95
        upgradeMode = UpgradeMode.SNAPSHOT;
×
96
      } else {
97
        if (IdeVersion.getVersionIdentifier().getDevelopmentPhase().isStable()) {
×
98
          upgradeMode = UpgradeMode.STABLE;
×
99
        } else {
100
          upgradeMode = UpgradeMode.UNSTABLE;
×
101
        }
102
      }
103
    }
104
    return upgradeMode.getVersion();
×
105
  }
106

107
  @Override
108
  public Path getToolPath() {
109

110
    return this.context.getIdeInstallationPath();
×
111
  }
112

113
  @Override
114
  public boolean install(boolean silent) {
115

116
    this.context.requireOnline("upgrade of IDEasy", true);
5✔
117

118
    if (IdeVersion.isUndefined()) {
2!
119
      this.context.warning("You are using IDEasy version {} which indicates local development - skipping upgrade.", IdeVersion.getVersionString());
10✔
120
      return false;
2✔
121
    }
122
    return super.install(silent);
×
123
  }
124

125
  /**
126
   * @return the latest released {@link VersionIdentifier version} of IDEasy.
127
   */
128
  public VersionIdentifier getLatestVersion() {
129

130
    VersionIdentifier currentVersion = IdeVersion.getVersionIdentifier();
2✔
131
    if (IdeVersion.isUndefined()) {
2!
132
      return currentVersion;
2✔
133
    }
134
    VersionIdentifier configuredVersion = getConfiguredVersion();
×
135
    return getToolRepository().resolveVersion(this.tool, getConfiguredEdition(), configuredVersion, this);
×
136
  }
137

138
  /**
139
   * Checks if an update is available and logs according information.
140
   *
141
   * @return {@code true} if an update is available, {@code false} otherwise.
142
   */
143
  public boolean checkIfUpdateIsAvailable() {
144
    VersionIdentifier installedVersion = getInstalledVersion();
3✔
145
    if (this.context.isOffline()) {
4✔
146
      this.context.success("Your version of IDEasy is {}.", installedVersion);
10✔
147
      this.context.warning("Skipping check for newer version of IDEasy because you are offline.");
4✔
148
      return false;
2✔
149
    }
150
    VersionIdentifier latestVersion = getLatestVersion();
3✔
151
    if (installedVersion.equals(latestVersion)) {
4!
152
      this.context.success("Your version of IDEasy is {} which is the latest released version.", installedVersion);
10✔
153
      return false;
2✔
154
    } else {
155
      this.context.interaction("Your version of IDEasy is {} but version {} is available. Please run the following command to upgrade to the latest version:\n"
×
156
          + "ide upgrade", installedVersion, latestVersion);
157
      return true;
×
158
    }
159
  }
160

161
  /**
162
   * Initial installation of IDEasy.
163
   *
164
   * @param cwd the {@link Path} to the current working directory.
165
   * @see com.devonfw.tools.ide.commandlet.InstallCommandlet
166
   */
167
  public void installIdeasy(Path cwd) {
168
    Path ideRoot = determineIdeRoot(cwd);
4✔
169
    Path idePath = ideRoot.resolve(IdeContext.FOLDER_UNDERSCORE_IDE);
4✔
170
    Path installationPath = idePath.resolve(IdeContext.FOLDER_INSTALLATION);
4✔
171
    Path ideasySoftwarePath = idePath.resolve(IdeContext.FOLDER_SOFTWARE).resolve(MavenRepository.ID).resolve(IdeasyCommandlet.TOOL_NAME)
8✔
172
        .resolve(IdeasyCommandlet.TOOL_NAME);
2✔
173
    Path ideasyVersionPath = ideasySoftwarePath.resolve(IdeVersion.getVersionString());
4✔
174
    if (Files.isDirectory(ideasyVersionPath)) {
5!
175
      throw new CliException("IDEasy is already installed at " + ideasyVersionPath + " - if your installation is broken, delete it manually and rerun setup!");
×
176
    }
177
    FileAccess fileAccess = this.context.getFileAccess();
4✔
178
    List<Path> installationArtifacts = new ArrayList<>();
4✔
179
    boolean success = true;
2✔
180
    success &= addInstallationArtifact(cwd, "bin", true, installationArtifacts);
9✔
181
    success &= addInstallationArtifact(cwd, "functions", true, installationArtifacts);
9✔
182
    success &= addInstallationArtifact(cwd, "internal", true, installationArtifacts);
9✔
183
    success &= addInstallationArtifact(cwd, "system", true, installationArtifacts);
9✔
184
    success &= addInstallationArtifact(cwd, "IDEasy.pdf", true, installationArtifacts);
9✔
185
    success &= addInstallationArtifact(cwd, "setup", true, installationArtifacts);
9✔
186
    success &= addInstallationArtifact(cwd, "setup.bat", false, installationArtifacts);
9✔
187
    if (!success) {
2!
188
      throw new CliException("IDEasy release is inconsistent at " + cwd);
×
189
    }
190
    fileAccess.mkdirs(ideasyVersionPath);
3✔
191
    for (Path installationArtifact : installationArtifacts) {
10✔
192
      fileAccess.copy(installationArtifact, ideasyVersionPath);
4✔
193
    }
1✔
194
    this.context.writeVersionFile(IdeVersion.getVersionIdentifier(), ideasyVersionPath);
5✔
195
    fileAccess.symlink(ideasyVersionPath, installationPath);
4✔
196
    addToShellRc(BASHRC, ideRoot, null);
5✔
197
    addToShellRc(ZSHRC, ideRoot, "autoload -U +X bashcompinit && bashcompinit");
5✔
198
    installIdeasyWindowsEnv(ideRoot, installationPath);
4✔
199
    this.context.success("IDEasy has been installed successfully on your system.");
4✔
200
    this.context.warning("IDEasy has been setup for new shells but it cannot work in your current shell(s).\n"
4✔
201
        + "Reboot or open a new terminal to make it work.");
202
  }
1✔
203

204
  private void installIdeasyWindowsEnv(Path ideRoot, Path installationPath) {
205
    if (!this.context.getSystemInfo().isWindows()) {
5✔
206
      return;
1✔
207
    }
208
    WindowsHelper helper = WindowsHelper.get(this.context);
4✔
209
    helper.setUserEnvironmentValue(IdeVariables.IDE_ROOT.getName(), ideRoot.toString());
6✔
210
    String userPath = helper.getUserEnvironmentValue(IdeVariables.PATH.getName());
5✔
211
    if (userPath == null) {
2!
212
      this.context.error("Could not read user PATH from registry!");
×
213
    } else {
214
      this.context.info("Found user PATH={}", userPath);
10✔
215
      Path ideasyBinPath = installationPath.resolve("bin");
4✔
216
      SimpleSystemPath path = SimpleSystemPath.of(userPath, ';');
4✔
217
      if (path.getEntries().isEmpty()) {
4!
218
        this.context.warning("ATTENTION:\n"
×
219
            + "Your user specific PATH variable seems to be empty.\n"
220
            + "You can double check this by pressing [Windows][r] and launch the program SystemPropertiesAdvanced.\n"
221
            + "Then click on 'Environment variables' and check if 'PATH' is set in the 'user variables' from the upper list.\n"
222
            + "In case 'PATH' is defined there non-empty and you get this message, please abort and give us feedback:\n"
223
            + "https://github.com/devonfw/IDEasy/issues\n"
224
            + "Otherwise all is correct and you can continue.");
225
        this.context.askToContinue("Are you sure you want to override your PATH?");
×
226
      } else {
227
        path.removeEntries(s -> s.endsWith(IDE_BIN));
7✔
228
      }
229
      path.getEntries().add(ideasyBinPath.toString());
6✔
230
      helper.setUserEnvironmentValue(IdeVariables.PATH.getName(), path.toString());
6✔
231
      setGitLongpaths();
2✔
232
      setupWindowsTerminal();
2✔
233
    }
234
  }
1✔
235

236
  private void setGitLongpaths() {
237
    GitContext gitContext = new GitContextImpl(this.context);
6✔
238
    gitContext.verifyGitInstalled();
2✔
239
    Path configPath = this.context.getUserHome().resolve(".gitconfig");
6✔
240
    FileAccess fileAccess = this.context.getFileAccess();
4✔
241
    IniFile iniFile = fileAccess.readIniFile(configPath);
4✔
242
    IniSection coreSection = iniFile.getOrCreateSection("core");
4✔
243
    coreSection.getProperties().put("longpaths", "true");
6✔
244
    fileAccess.writeIniFile(iniFile, configPath);
4✔
245
  }
1✔
246

247
  /**
248
   * Sets up Windows Terminal with Git Bash integration.
249
   */
250
  private void setupWindowsTerminal() {
251
    if (!this.context.getSystemInfo().isWindows()) {
5!
252
      return;
×
253
    }
254
    if (!isWindowsTerminalInstalled()) {
3!
255
      installWindowsTerminal();
2✔
256
    }
257
    configureWindowsTerminalGitBash();
2✔
258
  }
1✔
259

260
  /**
261
   * Checks if Windows Terminal is installed.
262
   *
263
   * @return {@code true} if Windows Terminal is installed, {@code false} otherwise.
264
   */
265
  private boolean isWindowsTerminalInstalled() {
266
    try {
267
      ProcessResult result = this.context.newProcess()
4✔
268
          .executable("powershell")
11✔
269
          .addArgs("-Command", "Get-AppxPackage -Name Microsoft.WindowsTerminal")
2✔
270
          .run(ProcessMode.DEFAULT_CAPTURE);
×
271
      return result.isSuccessful() && !result.getOut().isEmpty();
×
272
    } catch (Exception e) {
1✔
273
      this.context.debug("Failed to check Windows Terminal installation: {}", e.getMessage());
11✔
274
      return false;
2✔
275
    }
276
  }
277

278
  /**
279
   * Installs Windows Terminal using winget.
280
   */
281
  private void installWindowsTerminal() {
282
    try {
283
      this.context.info("Installing Windows Terminal...");
4✔
284
      ProcessResult result = this.context.newProcess()
4✔
285
          .executable("winget")
11✔
286
          .addArgs("install", "Microsoft.WindowsTerminal")
2✔
287
          .run(ProcessMode.DEFAULT);
×
288
      if (result.isSuccessful()) {
×
289
        this.context.success("Windows Terminal has been installed successfully.");
×
290
      } else {
291
        this.context.warning("Failed to install Windows Terminal. Please install it manually from Microsoft Store.");
×
292
      }
293
    } catch (Exception e) {
1✔
294
      this.context.warning("Failed to install Windows Terminal: {}. Please install it manually from Microsoft Store.", e.getMessage());
11✔
295
    }
×
296
  }
1✔
297

298
  /**
299
   * Configures Git Bash integration in Windows Terminal.
300
   */
301
  private void configureWindowsTerminalGitBash() {
302
    Path settingsPath = getWindowsTerminalSettingsPath();
3✔
303
    if (settingsPath == null || !Files.exists(settingsPath)) {
2!
304
      this.context.warning("Windows Terminal settings file not found. Cannot configure Git Bash integration.");
4✔
305
      return;
1✔
306
    }
307

308
    try {
309
      String bashPath = this.context.findBash();
×
310
      if (bashPath == null) {
×
311
        this.context.warning("Git Bash not found. Cannot configure Windows Terminal integration.");
×
312
        return;
×
313
      }
314

315
      configureGitBashProfile(settingsPath, bashPath);
×
316
      this.context.success("Git Bash has been configured in Windows Terminal.");
×
317
    } catch (Exception e) {
×
318
      this.context.warning("Failed to configure Git Bash in Windows Terminal: {}", e.getMessage());
×
319
    }
×
320
  }
×
321

322
  /**
323
   * Gets the Windows Terminal settings file path.
324
   *
325
   * @return the {@link Path} to the Windows Terminal settings file, or {@code null} if not found.
326
   */
327
  private Path getWindowsTerminalSettingsPath() {
328
    Path localAppData = this.context.getUserHome().resolve("AppData").resolve("Local");
8✔
329
    Path packagesPath = localAppData.resolve("Packages");
4✔
330
    
331
    // Try the new Windows Terminal package first
332
    Path newTerminalPath = packagesPath.resolve("Microsoft.WindowsTerminal_8wekyb3d8bbwe")
4✔
333
        .resolve("LocalState").resolve("settings.json");
4✔
334
    if (Files.exists(newTerminalPath)) {
5!
335
      return newTerminalPath;
×
336
    }
337
    
338
    // Try the old Windows Terminal Preview package
339
    Path previewPath = packagesPath.resolve("Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe")
4✔
340
        .resolve("LocalState").resolve("settings.json");
4✔
341
    if (Files.exists(previewPath)) {
5!
342
      return previewPath;
×
343
    }
344
    
345
    return null;
2✔
346
  }
347

348
  /**
349
   * Configures Git Bash profile in Windows Terminal settings.
350
   *
351
   * @param settingsPath the {@link Path} to the Windows Terminal settings file.
352
   * @param bashPath the path to the Git Bash executable.
353
   */
354
  private void configureGitBashProfile(Path settingsPath, String bashPath) throws Exception {
355
    FileAccess fileAccess = this.context.getFileAccess();
×
356
    String settingsContent = fileAccess.readFileContent(settingsPath);
×
357
    
358
    ObjectMapper mapper = new ObjectMapper();
×
359
    JsonNode rootNode = mapper.readTree(settingsContent);
×
360
    ObjectNode root = (ObjectNode) rootNode;
×
361
    
362
    // Get or create profiles object
363
    ObjectNode profiles = (ObjectNode) root.get("profiles");
×
364
    if (profiles == null) {
×
365
      profiles = mapper.createObjectNode();
×
366
      root.set("profiles", profiles);
×
367
    }
368
    
369
    // Get or create list array
370
    JsonNode listNode = profiles.get("list");
×
371
    if (listNode == null || !listNode.isArray()) {
×
372
      listNode = mapper.createArrayNode();
×
373
      profiles.set("list", listNode);
×
374
    }
375
    
376
    // Check if Git Bash profile already exists
377
    boolean gitBashExists = false;
×
378
    for (JsonNode profile : listNode) {
×
379
      if (profile.has("name") && "Git Bash".equals(profile.get("name").asText())) {
×
380
        gitBashExists = true;
×
381
        break;
×
382
      }
383
    }
×
384
    
385
    // Add Git Bash profile if it doesn't exist
386
    if (!gitBashExists) {
×
387
      ObjectNode gitBashProfile = mapper.createObjectNode();
×
388
      gitBashProfile.put("guid", "{2c4de342-38b7-51cf-b940-2309a097f518}");
×
389
      gitBashProfile.put("name", "Git Bash");
×
390
      gitBashProfile.put("commandline", bashPath);
×
391
      gitBashProfile.put("icon", "ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png");
×
392
      gitBashProfile.put("startingDirectory", "%USERPROFILE%");
×
393
      
394
      ((com.fasterxml.jackson.databind.node.ArrayNode) listNode).add(gitBashProfile);
×
395
      
396
      // Set Git Bash as default profile
397
      root.put("defaultProfile", "{2c4de342-38b7-51cf-b940-2309a097f518}");
×
398
      
399
      // Write back to file
400
      String updatedContent = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
×
401
      fileAccess.writeFileContent(updatedContent, settingsPath);
×
402
    }
403
  }
×
404

405
  static String removeObsoleteEntryFromWindowsPath(String userPath) {
406
    return removeEntryFromWindowsPath(userPath, IDE_BIN);
4✔
407
  }
408

409
  static String removeEntryFromWindowsPath(String userPath, String suffix) {
410
    int len = userPath.length();
3✔
411
    int start = 0;
2✔
412
    while ((start >= 0) && (start < len)) {
5!
413
      int end = userPath.indexOf(';', start);
5✔
414
      if (end < 0) {
2✔
415
        end = len;
2✔
416
      }
417
      String entry = userPath.substring(start, end);
5✔
418
      if (entry.endsWith(suffix)) {
4✔
419
        String prefix = "";
2✔
420
        int offset = 1;
2✔
421
        if (start > 0) {
2✔
422
          prefix = userPath.substring(0, start - 1);
7✔
423
          offset = 0;
2✔
424
        }
425
        if (end == len) {
3✔
426
          return prefix;
2✔
427
        } else {
428
          return removeEntryFromWindowsPath(prefix + userPath.substring(end + offset), suffix);
10✔
429
        }
430
      }
431
      start = end + 1;
4✔
432
    }
1✔
433
    return userPath;
2✔
434
  }
435

436
  /**
437
   * Adds ourselves to the shell RC (run-commands) configuration file.
438
   *
439
   * @param filename the name of the RC file.
440
   * @param ideRoot the IDE_ROOT {@link Path}.
441
   */
442
  private void addToShellRc(String filename, Path ideRoot, String extraLine) {
443

444
    modifyShellRc(filename, ideRoot, true, extraLine);
6✔
445
  }
1✔
446

447
  private void removeFromShellRc(String filename, Path ideRoot) {
448

449
    modifyShellRc(filename, ideRoot, false, null);
6✔
450
  }
1✔
451

452
  /**
453
   * Adds ourselves to the shell RC (run-commands) configuration file.
454
   *
455
   * @param filename the name of the RC file.
456
   * @param ideRoot the IDE_ROOT {@link Path}.
457
   */
458
  private void modifyShellRc(String filename, Path ideRoot, boolean add, String extraLine) {
459

460
    if (add) {
2✔
461
      this.context.info("Configuring IDEasy in {}", filename);
11✔
462
    } else {
463
      this.context.info("Removing IDEasy from {}", filename);
10✔
464
    }
465
    Path rcFile = this.context.getUserHome().resolve(filename);
6✔
466
    FileAccess fileAccess = this.context.getFileAccess();
4✔
467
    List<String> lines = fileAccess.readFileLines(rcFile);
4✔
468
    if (lines == null) {
2✔
469
      if (!add) {
2!
470
        return;
×
471
      }
472
      lines = new ArrayList<>();
5✔
473
    } else {
474
      // since it is unspecified if the returned List may be immutable we want to get sure
475
      lines = new ArrayList<>(lines);
5✔
476
    }
477
    Iterator<String> iterator = lines.iterator();
3✔
478
    int removeCount = 0;
2✔
479
    while (iterator.hasNext()) {
3✔
480
      String line = iterator.next();
4✔
481
      line = line.trim();
3✔
482
      if (isObsoleteRcLine(line)) {
3✔
483
        this.context.info("Removing obsolete line from {}: {}", filename, line);
14✔
484
        iterator.remove();
2✔
485
        removeCount++;
2✔
486
      } else if (line.equals(extraLine)) {
4✔
487
        extraLine = null;
2✔
488
      }
489
    }
1✔
490
    if (add) {
2✔
491
      if (extraLine != null) {
2!
492
        lines.add(extraLine);
×
493
      }
494
      if (!this.context.getSystemInfo().isWindows()) {
5✔
495
        lines.add("export IDE_ROOT=\"" + WindowsPathSyntax.MSYS.format(ideRoot) + "\"");
7✔
496
      }
497
      lines.add(BASH_CODE_SOURCE_FUNCTIONS);
4✔
498
    }
499
    fileAccess.writeFileLines(lines, rcFile);
4✔
500
    this.context.debug("Successfully updated {}", filename);
10✔
501
  }
1✔
502

503
  private static boolean isObsoleteRcLine(String line) {
504
    if (line.startsWith("alias ide=")) {
4!
505
      return true;
×
506
    } else if (line.startsWith("export IDE_ROOT=")) {
4!
507
      return true;
×
508
    } else if (line.equals("ide")) {
4✔
509
      return true;
2✔
510
    } else if (line.equals("ide init")) {
4✔
511
      return true;
2✔
512
    } else if (line.startsWith("source \"$IDE_ROOT/_ide/")) {
4✔
513
      return true;
2✔
514
    }
515
    return false;
2✔
516
  }
517

518
  private boolean addInstallationArtifact(Path cwd, String artifactName, boolean required, List<Path> installationArtifacts) {
519

520
    Path artifactPath = cwd.resolve(artifactName);
4✔
521
    if (Files.exists(artifactPath)) {
5!
522
      installationArtifacts.add(artifactPath);
5✔
523
    } else if (required) {
×
524
      this.context.error("Missing required file {}", artifactName);
×
525
      return false;
×
526
    }
527
    return true;
2✔
528
  }
529

530
  private Path determineIdeRoot(Path cwd) {
531
    Path ideRoot = this.context.getIdeRoot();
4✔
532
    if (ideRoot == null) {
2!
533
      Path home = this.context.getUserHome();
4✔
534
      Path installRoot = home;
2✔
535
      if (this.context.getSystemInfo().isWindows()) {
5✔
536
        if (!cwd.startsWith(home)) {
4!
537
          installRoot = cwd.getRoot();
×
538
        }
539
      }
540
      ideRoot = installRoot.resolve(IdeContext.FOLDER_PROJECTS);
4✔
541
    } else {
1✔
542
      assert (Files.isDirectory(ideRoot)) : "IDE_ROOT directory does not exist!";
×
543
    }
544
    return ideRoot;
2✔
545
  }
546

547
  /**
548
   * Uninstalls IDEasy entirely from the system.
549
   */
550
  public void uninstallIdeasy() {
551

552
    Path ideRoot = this.context.getIdeRoot();
4✔
553
    removeFromShellRc(BASHRC, ideRoot);
4✔
554
    removeFromShellRc(ZSHRC, ideRoot);
4✔
555
    Path idePath = this.context.getIdePath();
4✔
556
    uninstallIdeasyWindowsEnv(ideRoot);
3✔
557
    uninstallIdeasyIdePath(idePath);
3✔
558
    deleteDownloadCache();
2✔
559
    this.context.success("IDEasy has been uninstalled from your system.");
4✔
560
    this.context.interaction("ATTENTION:\n"
10✔
561
        + "In order to prevent data-loss, we do not delete your projects and git repositories!\n"
562
        + "To entirely get rid of IDEasy, also check your IDE_ROOT folder at:\n"
563
        + "{}", ideRoot);
564
  }
1✔
565

566
  private void deleteDownloadCache() {
567
    Path downloadPath = this.context.getDownloadPath();
4✔
568
    this.context.info("Deleting download cache from {}", downloadPath);
10✔
569
    this.context.getFileAccess().delete(downloadPath);
5✔
570
  }
1✔
571

572
  private void uninstallIdeasyIdePath(Path idePath) {
573
    if (this.context.getSystemInfo().isWindows()) {
5✔
574
      this.context.newProcess().executable("bash").addArgs("-c",
17✔
575
          "sleep 10 && rm -rf \"" + WindowsPathSyntax.MSYS.format(idePath) + "\"").run(ProcessMode.BACKGROUND);
5✔
576
      this.context.interaction("To prevent windows file locking errors, we perform an asynchronous deletion of {} in background now.\n"
11✔
577
          + "Please close all terminals and wait a minute for the deletion to complete before running other commands.", idePath);
578
    } else {
579
      this.context.info("Finally deleting {}", idePath);
10✔
580
      this.context.getFileAccess().delete(idePath);
5✔
581
    }
582
  }
1✔
583

584
  private void uninstallIdeasyWindowsEnv(Path ideRoot) {
585
    if (!this.context.getSystemInfo().isWindows()) {
5✔
586
      return;
1✔
587
    }
588
    WindowsHelper helper = WindowsHelper.get(this.context);
4✔
589
    helper.removeUserEnvironmentValue(IdeVariables.IDE_ROOT.getName());
4✔
590
    String userPath = helper.getUserEnvironmentValue(IdeVariables.PATH.getName());
5✔
591
    if (userPath == null) {
2!
592
      this.context.error("Could not read user PATH from registry!");
×
593
    } else {
594
      this.context.info("Found user PATH={}", userPath);
10✔
595
      String newUserPath = userPath;
2✔
596
      if (!userPath.isEmpty()) {
3!
597
        SimpleSystemPath path = SimpleSystemPath.of(userPath, ';');
4✔
598
        path.removeEntries(s -> s.endsWith(IDE_BIN) || s.endsWith(IDE_INSTALLATION_BIN));
15!
599
        newUserPath = path.toString();
3✔
600
      }
601
      if (newUserPath.equals(userPath)) {
4!
602
        this.context.error("Could not find IDEasy in PATH:\n{}", userPath);
×
603
      } else {
604
        helper.setUserEnvironmentValue(IdeVariables.PATH.getName(), newUserPath);
5✔
605
      }
606
    }
607
  }
1✔
608
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc