diff --git a/frontend-maven-plugin/pom.xml b/frontend-maven-plugin/pom.xml
index df38f20db..6a48535b4 100644
--- a/frontend-maven-plugin/pom.xml
+++ b/frontend-maven-plugin/pom.xml
@@ -138,6 +138,12 @@
run
verify
+
+
+ TESTDIR
+ TESTPROFILE
+
+
diff --git a/frontend-maven-plugin/src/it/mise-config-file/.mise.toml b/frontend-maven-plugin/src/it/mise-config-file/.mise.toml
new file mode 100644
index 000000000..1cd6234fa
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-config-file/.mise.toml
@@ -0,0 +1,10 @@
+[tools]
+java = "temurin-21"
+maven = "3.9"
+pre-commit = "latest"
+ktlint = "latest"
+python = "3.12"
+# node 22.5.1
+node = "22.5.1"
+# node 22.5.1
+yarn = "1.22.22"
diff --git a/frontend-maven-plugin/src/it/mise-config-file/package-lock.json b/frontend-maven-plugin/src/it/mise-config-file/package-lock.json
new file mode 100644
index 000000000..aa1c583f3
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-config-file/package-lock.json
@@ -0,0 +1,12 @@
+{
+ "name": "example",
+ "version": "0.0.1",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "example",
+ "version": "0.0.1"
+ }
+ }
+}
diff --git a/frontend-maven-plugin/src/it/mise-config-file/package.json b/frontend-maven-plugin/src/it/mise-config-file/package.json
new file mode 100644
index 000000000..30551a8fe
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-config-file/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "example",
+ "version": "0.0.1"
+}
diff --git a/frontend-maven-plugin/src/it/mise-config-file/pom.xml b/frontend-maven-plugin/src/it/mise-config-file/pom.xml
new file mode 100644
index 000000000..2dd31616a
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-config-file/pom.xml
@@ -0,0 +1,34 @@
+
+
+ 4.0.0
+
+ com.github.eirslett
+ example
+ 0
+ pom
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+
+ @project.version@
+
+
+ target
+
+
+
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+
+
+
+
+
diff --git a/frontend-maven-plugin/src/it/mise-config-file/verify.groovy b/frontend-maven-plugin/src/it/mise-config-file/verify.groovy
new file mode 100644
index 000000000..b7a37416c
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-config-file/verify.groovy
@@ -0,0 +1,5 @@
+assert new File(basedir, 'target/node').exists() : "Node was not installed in the custom install directory";
+
+String buildLog = new File(basedir, 'build.log').text
+assert buildLog.contains('.mise.toml') : 'The wrong file was used'
+assert buildLog.contains('Installing node version v22.5.1') : 'The correct node version was not detected'
diff --git a/frontend-maven-plugin/src/it/mise-env-config-file/TESTDIR/mise.TESTPROFILE.toml b/frontend-maven-plugin/src/it/mise-env-config-file/TESTDIR/mise.TESTPROFILE.toml
new file mode 100644
index 000000000..1cd6234fa
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-env-config-file/TESTDIR/mise.TESTPROFILE.toml
@@ -0,0 +1,10 @@
+[tools]
+java = "temurin-21"
+maven = "3.9"
+pre-commit = "latest"
+ktlint = "latest"
+python = "3.12"
+# node 22.5.1
+node = "22.5.1"
+# node 22.5.1
+yarn = "1.22.22"
diff --git a/frontend-maven-plugin/src/it/mise-env-config-file/package-lock.json b/frontend-maven-plugin/src/it/mise-env-config-file/package-lock.json
new file mode 100644
index 000000000..aa1c583f3
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-env-config-file/package-lock.json
@@ -0,0 +1,12 @@
+{
+ "name": "example",
+ "version": "0.0.1",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "example",
+ "version": "0.0.1"
+ }
+ }
+}
diff --git a/frontend-maven-plugin/src/it/mise-env-config-file/package.json b/frontend-maven-plugin/src/it/mise-env-config-file/package.json
new file mode 100644
index 000000000..30551a8fe
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-env-config-file/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "example",
+ "version": "0.0.1"
+}
diff --git a/frontend-maven-plugin/src/it/mise-env-config-file/pom.xml b/frontend-maven-plugin/src/it/mise-env-config-file/pom.xml
new file mode 100644
index 000000000..2dd31616a
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-env-config-file/pom.xml
@@ -0,0 +1,34 @@
+
+
+ 4.0.0
+
+ com.github.eirslett
+ example
+ 0
+ pom
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+
+ @project.version@
+
+
+ target
+
+
+
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+
+
+
+
+
diff --git a/frontend-maven-plugin/src/it/mise-env-config-file/verify.groovy b/frontend-maven-plugin/src/it/mise-env-config-file/verify.groovy
new file mode 100644
index 000000000..eaa67afbb
--- /dev/null
+++ b/frontend-maven-plugin/src/it/mise-env-config-file/verify.groovy
@@ -0,0 +1,5 @@
+assert new File(basedir, 'target/node').exists() : "Node was not installed in the custom install directory";
+
+String buildLog = new File(basedir, 'build.log').text
+assert buildLog.contains('TESTDIR/mise.TESTPROFILE.toml') : 'The wrong file was used'
+assert buildLog.contains('Installing node version v22.5.1') : 'The correct node version was not detected'
diff --git a/frontend-plugin-core/pom.xml b/frontend-plugin-core/pom.xml
index 8eae8157a..f108e3b61 100644
--- a/frontend-plugin-core/pom.xml
+++ b/frontend-plugin-core/pom.xml
@@ -77,6 +77,12 @@
33.1.0-jre
provided
+
+ com.github.stefanbirkner
+ system-lambda
+ 1.2.1
+ test
+
diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeVersionDetector.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeVersionDetector.java
index e6d1796d1..fd1cc161d 100644
--- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeVersionDetector.java
+++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeVersionDetector.java
@@ -7,15 +7,18 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import static java.lang.String.format;
import static java.util.Objects.isNull;
import static java.util.Optional.empty;
import static org.slf4j.LoggerFactory.getLogger;
public class NodeVersionDetector {
+ private static final List MISE_CONFIG_FILENAMES = listMiseConfigFilenames();
private static final String TOOL_VERSIONS_FILENAME = ".tool-versions";
public static String getNodeVersion(File workingDir, String providedNodeVersion, String genericNodeVersionFile) throws Exception {
@@ -32,7 +35,9 @@ public static String getNodeVersion(File workingDir, String providedNodeVersion,
throw new Exception("The Node version file doesn't seem to exist: " + genericNodeVersionFileFile);
}
- if (genericNodeVersionFile.endsWith(TOOL_VERSIONS_FILENAME)) {
+ if (genericNodeVersionFile.endsWith(".toml") && genericNodeVersionFile.contains("mise")) {
+ return readMiseConfigTomlFile(genericNodeVersionFileFile, genericNodeVersionFileFile.toPath(), logger);
+ } else if (genericNodeVersionFile.endsWith(TOOL_VERSIONS_FILENAME)) {
return readToolVersionsFile(genericNodeVersionFileFile, genericNodeVersionFileFile.toPath(), logger);
} else {
return readNvmrcFile(genericNodeVersionFileFile, genericNodeVersionFileFile.toPath(), logger);
@@ -48,6 +53,49 @@ public static String getNodeVersion(File workingDir, String providedNodeVersion,
}
}
+ /**
+ * Mise has way too many options, see:
+ * https://mise.jdx.dev/profiles.html
+ * https://mise.jdx.dev/configuration.html#mise-toml
+ */
+ public static List listMiseConfigFilenames() {
+ final String miseConfigDir = System.getenv("MISE_CONFIG_DIR");
+ final String miseEnv = System.getenv("MISE_ENV");
+
+ // The order is important and should respect mises' ordering
+ final List allMiseConfigFilenames = new ArrayList<>();
+
+ allMiseConfigFilenames.add(format("%s/config.%s.toml", miseConfigDir, miseEnv));
+ allMiseConfigFilenames.add(format("%s/mise.%s.toml", miseConfigDir, miseEnv));
+
+ allMiseConfigFilenames.add(".config/mise/config.toml");
+ allMiseConfigFilenames.add("mise/config.toml");
+ allMiseConfigFilenames.add("mise.toml");
+ allMiseConfigFilenames.add(".mise/config.toml");
+ allMiseConfigFilenames.add(".mise.toml");
+ allMiseConfigFilenames.add(".config/mise/config.local.toml");
+ allMiseConfigFilenames.add("mise/config.local.toml");
+ allMiseConfigFilenames.add("mise.local.toml");
+ allMiseConfigFilenames.add(".mise/config.local.toml");
+ allMiseConfigFilenames.add(".mise.local.toml");
+
+ allMiseConfigFilenames.add(format(".config/mise/config.%s.toml", miseEnv));
+ allMiseConfigFilenames.add(format("mise/config.%s.toml", miseEnv));
+ allMiseConfigFilenames.add(format("mise.%s.toml", miseEnv));
+ allMiseConfigFilenames.add(format(".mise/config.%s.toml", miseEnv));
+ allMiseConfigFilenames.add(format(".mise.%s.toml", miseEnv));
+ allMiseConfigFilenames.add(format(".config/mise/config.%s.local.toml", miseEnv));
+ allMiseConfigFilenames.add(format("mise/config.%s.local.toml", miseEnv));
+ allMiseConfigFilenames.add(format(".mise/config.%s.local.toml", miseEnv));
+ allMiseConfigFilenames.add(format(".mise.%s.local.toml", miseEnv));
+
+ return allMiseConfigFilenames;
+ }
+
+ /**
+ * Ordering this hierarchy of reading the files isn't just the most idiomatic, it's also probably the best
+ * for performance.
+ */
public static String recursivelyFindVersion(File directory) throws Exception {
Logger logger = getLogger(NodeVersionDetector.class);
@@ -79,6 +127,20 @@ public static String recursivelyFindVersion(File directory) throws Exception {
if (trimmedLine != null) return trimmedLine;
}
+ for (String miseConfigFilename: MISE_CONFIG_FILENAMES) {
+ // We don't know if MISE_CONFIG_DIR can result in absolute or relative file paths, try to do our best
+ String[] splitMiseConfigFilename = miseConfigFilename.split("/");
+ Path potentiallyAbsoluteFilepath = Paths.get("", splitMiseConfigFilename);
+ Path miseConfigFilePath = potentiallyAbsoluteFilepath.isAbsolute() ?
+ potentiallyAbsoluteFilepath : Paths.get(directoryPath, splitMiseConfigFilename);
+
+ File miseConfigFile = miseConfigFilePath.toFile();
+ if (miseConfigFile.exists()) {
+ String trimmedVersion = readMiseConfigTomlFile(miseConfigFile, miseConfigFilePath, logger);
+ if (trimmedVersion != null) return trimmedVersion;
+ }
+ }
+
File parent = directory.getParentFile();
if (isNull(parent) || directory.equals(parent)) {
throw new Exception("Reach root-level without finding a suitable file");
@@ -131,6 +193,41 @@ static Optional readNvmrcFileLines(List lines) {
return empty();
}
+ /**
+ * If this gets any more complicated we'll add a reader, not sure how strict mise is with the spec, we want to be
+ * at least as loose.
+ */
+ @VisibleForTesting
+ static String readMiseConfigTomlFile(File miseTomlFile, Path miseTomlFilePath, Logger logger) throws Exception {
+ assertNodeVersionFileIsReadable(miseTomlFile);
+
+ List lines = Files.readAllLines(miseTomlFilePath);
+ for (String line: lines) {
+ if (!isNull(line)) {
+ String trimmedLine = line.trim();
+
+ if (trimmedLine.isEmpty()) {
+ continue;
+ }
+
+ if (!trimmedLine.startsWith("node")) { // naturally skips over comments
+ continue;
+ }
+
+ logger.info("Found the version of Node in: " + miseTomlFilePath);
+
+ if (trimmedLine.contains("[")) {
+ throw new Exception("mise file support is limited to a single version");
+ }
+
+ return trimmedLine
+ .replaceAll("node(js)?\\s*=\\s*", "")
+ .replaceAll("\"", ""); // destringify the version
+ }
+ }
+ return null;
+ }
+
private static String readToolVersionsFile(File toolVersionsFile, Path toolVersionsFilePath, Logger logger) throws Exception {
assertNodeVersionFileIsReadable(toolVersionsFile);
diff --git a/frontend-plugin-core/src/test/java/com/github/eirslett/maven/plugins/frontend/lib/NodeVersionDetectorTest.java b/frontend-plugin-core/src/test/java/com/github/eirslett/maven/plugins/frontend/lib/NodeVersionDetectorTest.java
index bbbc1bcb1..1cca97adc 100644
--- a/frontend-plugin-core/src/test/java/com/github/eirslett/maven/plugins/frontend/lib/NodeVersionDetectorTest.java
+++ b/frontend-plugin-core/src/test/java/com/github/eirslett/maven/plugins/frontend/lib/NodeVersionDetectorTest.java
@@ -1,8 +1,18 @@
package com.github.eirslett.maven.plugins.frontend.lib;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import static com.github.eirslett.maven.plugins.frontend.lib.NodeVersionDetector.readNvmrcFileLines;
+import static com.github.eirslett.maven.plugins.frontend.lib.NodeVersionDetector.recursivelyFindVersion;
+import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable;
+import static java.lang.String.format;
+import static java.nio.charset.Charset.defaultCharset;
+import static java.nio.file.StandardOpenOption.WRITE;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -39,4 +49,33 @@ public void testNvmrcFileParsing_shouldIgnoreEmptyLines() {
""
)).get());
}
+
+ @Test
+ public void testAbsoluteMiseConfigFilePath(
+ @TempDir Path tempMiseConfigDir,
+ @TempDir Path tempUnrelatedDirectory
+ ) throws Exception {
+ // setup
+ String expectedVersion = "9.8.7";
+ String miseProfile = "testabsolute";
+
+ Path tempMiseConfigDirAbsolutePath = tempMiseConfigDir.toAbsolutePath();
+ String tempMiseConfigFilename = format("mise.%s.toml", miseProfile);
+ Path tempMiseConfigFilePath = Paths.get(tempMiseConfigDirAbsolutePath.toString(), tempMiseConfigFilename);
+ Path tempMiseConfigFile = Files.createFile(tempMiseConfigFilePath);
+
+ // given
+ withEnvironmentVariable("MISE_CONFIG_DIR", tempMiseConfigDirAbsolutePath.toString())
+ .and("MISE_ENV", miseProfile)
+ .execute(() -> {
+ String miseConfigFileContents = format("node = \"%s\"", expectedVersion);
+ Files.write(tempMiseConfigFile, singletonList(miseConfigFileContents), defaultCharset(), WRITE);
+
+ // when
+ String readVersion = recursivelyFindVersion(tempUnrelatedDirectory.toFile());
+
+ // then
+ assertEquals(expectedVersion, readVersion, "versions didn't match");
+ });
+ }
}