Skip to content

Commit

Permalink
Fail for non existing folders in dev mode. (#6405)
Browse files Browse the repository at this point in the history
If the npm or frontend folders do not exist in the 
filesystem throw exception (Only fail if the frontend
folder is defined outside of the npm folder)

If the frontend folder is a subfolder to the npm, folder
and the npm folder exists do not fail as we will create
the empty frontend folder.

Fixes #6396
Closes #6336
  • Loading branch information
caalador authored and ujoni committed Sep 6, 2019
1 parent fe9fb4d commit 4d90841
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 60 deletions.
Expand Up @@ -47,8 +47,10 @@

import elemental.json.JsonObject;
import elemental.json.impl.JsonUtil;

import static com.vaadin.flow.plugin.common.FlowPluginFrontendUtils.getClassFinder;
import static com.vaadin.flow.server.Constants.FRONTEND_TOKEN;
import static com.vaadin.flow.server.Constants.GENERATED_TOKEN;
import static com.vaadin.flow.server.Constants.NPM_TOKEN;
import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE;
import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER;
import static com.vaadin.flow.server.frontend.FrontendUtils.FRONTEND;
Expand Down Expand Up @@ -135,7 +137,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
return;
}

addDevModeToken();
updateBuildFile();

long start = System.nanoTime();

Expand Down Expand Up @@ -231,9 +233,10 @@ private String readFullyAndClose(String readErrorMessage,

/**
* Add the devMode token to build token file so we don't try to start the
* dev server.
* dev server. Remove the abstract folder paths as they should not be used
* for prebuilt bundles.
*/
private void addDevModeToken() {
private void updateBuildFile() {
File tokenFile = getTokenFile();
if (!tokenFile.exists()) {
getLog().warn(
Expand All @@ -245,6 +248,10 @@ private void addDevModeToken() {
StandardCharsets.UTF_8.name());
JsonObject buildInfo = JsonUtil.parse(json);

buildInfo.remove(NPM_TOKEN);
buildInfo.remove(GENERATED_TOKEN);
buildInfo.remove(FRONTEND_TOKEN);

buildInfo.put(SERVLET_PARAMETER_ENABLE_DEV_SERVER, false);
FileUtils.write(tokenFile, JsonUtil.stringify(buildInfo, 2) + "\n",
StandardCharsets.UTF_8.name());
Expand Down
Expand Up @@ -41,6 +41,10 @@
import elemental.json.impl.JsonUtil;

import static com.vaadin.flow.plugin.common.FlowPluginFrontendUtils.getClassFinder;
import static com.vaadin.flow.server.Constants.FRONTEND_TOKEN;
import static com.vaadin.flow.server.Constants.GENERATED_TOKEN;
import static com.vaadin.flow.server.Constants.NPM_TOKEN;
import static com.vaadin.flow.server.Constants.RESOURCES_FRONTEND_DEFAULT;
import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE;
import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_PRODUCTION_MODE;
import static com.vaadin.flow.server.frontend.FrontendUtils.FRONTEND;
Expand Down Expand Up @@ -161,9 +165,9 @@ private void propagateBuildInfo() {
JsonObject buildInfo = Json.createObject();
buildInfo.put(SERVLET_PARAMETER_COMPATIBILITY_MODE, compatibility);
buildInfo.put(SERVLET_PARAMETER_PRODUCTION_MODE, productionMode);
buildInfo.put("npmFolder", npmFolder.getAbsolutePath());
buildInfo.put("generatedFolder", generatedFolder.getAbsolutePath());
buildInfo.put("frontendFolder", frontendDirectory.getAbsolutePath());
buildInfo.put(NPM_TOKEN, npmFolder.getAbsolutePath());
buildInfo.put(GENERATED_TOKEN, generatedFolder.getAbsolutePath());
buildInfo.put(FRONTEND_TOKEN, frontendDirectory.getAbsolutePath());
try {
FileUtils.forceMkdir(token.getParentFile());
FileUtils.write(token, JsonUtil.stringify(buildInfo, 2) + "\n",
Expand Down
Expand Up @@ -53,7 +53,6 @@
import elemental.json.Json;
import elemental.json.JsonObject;
import elemental.json.impl.JsonUtil;

import static com.vaadin.flow.server.Constants.PACKAGE_JSON;
import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE;
import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER;
Expand Down Expand Up @@ -322,6 +321,9 @@ public void existingTokenFile_enableDevServerShouldBeAdded()
JsonObject initialBuildInfo = Json.createObject();
initialBuildInfo.put(SERVLET_PARAMETER_COMPATIBILITY_MODE, false);
initialBuildInfo.put(SERVLET_PARAMETER_PRODUCTION_MODE, false);
initialBuildInfo.put("npmFolder", "npm");
initialBuildInfo.put("generatedFolder", "generated");
initialBuildInfo.put("frontendFolder", "frontend");
org.apache.commons.io.FileUtils.forceMkdir(tokenFile.getParentFile());
org.apache.commons.io.FileUtils.write(tokenFile,
JsonUtil.stringify(initialBuildInfo, 2) + "\n", "UTF-8");
Expand All @@ -337,6 +339,12 @@ public void existingTokenFile_enableDevServerShouldBeAdded()
buildInfo.get(SERVLET_PARAMETER_COMPATIBILITY_MODE));
Assert.assertNotNull("productionMode token should be available",
buildInfo.get(SERVLET_PARAMETER_PRODUCTION_MODE));
Assert.assertNull("npmFolder should have been removed",
buildInfo.get("npmFolder"));
Assert.assertNull("generatedFolder should have been removed",
buildInfo.get("generatedFolder"));
Assert.assertNull("frontendFolder should have been removed",
buildInfo.get("frontendFolder"));
}

@Test
Expand Down
Expand Up @@ -36,6 +36,11 @@ public final class Constants implements Serializable {

public static final String SERVLET_PARAMETER_PRODUCTION_MODE = "productionMode";

// Token file keys used for defining folder paths for dev server
public static final String NPM_TOKEN = "npmFolder";
public static final String FRONTEND_TOKEN = "frontendFolder";
public static final String GENERATED_TOKEN = "generatedFolder";

/**
* enable it if your project is a Polymer 2.0 one, should be removed in V15
*
Expand Down
Expand Up @@ -19,7 +19,6 @@
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
Expand All @@ -41,7 +40,8 @@

import elemental.json.JsonObject;
import elemental.json.impl.JsonUtil;

import static com.vaadin.flow.server.Constants.FRONTEND_TOKEN;
import static com.vaadin.flow.server.Constants.NPM_TOKEN;
import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE;
import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER;
import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_PRODUCTION_MODE;
Expand Down Expand Up @@ -74,6 +74,10 @@ public final class DeploymentConfigurationFactory implements Serializable {
+ "the project/working directory. Ensure 'webpack.config.js' is present or trigger creation of "
+ "'flow-build-info.json' via running 'prepare-frontend' Maven goal.";

public static final String DEV_FOLDER_MISSING_MESSAGE =
"Running project in development mode with no access to folder '%s'.%n"
+ "Build project in production mode instead, see https://vaadin.com/docs/v14/flow/production/tutorial-production-mode-basic.html";

private DeploymentConfigurationFactory() {
}

Expand Down Expand Up @@ -161,9 +165,9 @@ protected static Properties createInitParameters(
return initParameters;
}

private static void readBuildInfo(Properties initParameters) { // NOSONAR
private static void readBuildInfo(Properties initParameters) {
String json = null;
try {
String json = null;
// token file location passed via init parameter property
String tokenLocation = initParameters.getProperty(PARAM_TOKEN_FILE);
if (tokenLocation != null) {
Expand All @@ -182,60 +186,69 @@ private static void readBuildInfo(Properties initParameters) { // NOSONAR
json = FrontendUtils.streamToString(resource.openStream());
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}

// Read the json and set the appropriate system properties if not
// already set.
if (json != null) {
JsonObject buildInfo = JsonUtil.parse(json);
if (buildInfo.hasKey(SERVLET_PARAMETER_PRODUCTION_MODE)) {
initParameters.setProperty(
SERVLET_PARAMETER_PRODUCTION_MODE,
String.valueOf(buildInfo.getBoolean(
SERVLET_PARAMETER_PRODUCTION_MODE)));
// Need to be sure that we remove the system property,
// because
// it has priority in the configuration getter
System.clearProperty(
VAADIN_PREFIX + SERVLET_PARAMETER_PRODUCTION_MODE);
}
if (buildInfo.hasKey(SERVLET_PARAMETER_COMPATIBILITY_MODE)) {
initParameters.setProperty(
SERVLET_PARAMETER_COMPATIBILITY_MODE,
String.valueOf(buildInfo.getBoolean(
SERVLET_PARAMETER_COMPATIBILITY_MODE)));
// Need to be sure that we remove the system property,
// because it has priority in the configuration getter
System.clearProperty(VAADIN_PREFIX
+ SERVLET_PARAMETER_COMPATIBILITY_MODE);
}
// Read the json and set the appropriate system properties if not
// already set.
if (json != null) {
JsonObject buildInfo = JsonUtil.parse(json);
if (buildInfo.hasKey(SERVLET_PARAMETER_PRODUCTION_MODE)) {
initParameters.setProperty(SERVLET_PARAMETER_PRODUCTION_MODE,
String.valueOf(buildInfo.getBoolean(
SERVLET_PARAMETER_PRODUCTION_MODE)));
// Need to be sure that we remove the system property,
// because
// it has priority in the configuration getter
System.clearProperty(
VAADIN_PREFIX + SERVLET_PARAMETER_PRODUCTION_MODE);
}
if (buildInfo.hasKey(SERVLET_PARAMETER_COMPATIBILITY_MODE)) {
initParameters.setProperty(SERVLET_PARAMETER_COMPATIBILITY_MODE,
String.valueOf(buildInfo.getBoolean(
SERVLET_PARAMETER_COMPATIBILITY_MODE)));
// Need to be sure that we remove the system property,
// because it has priority in the configuration getter
System.clearProperty(
VAADIN_PREFIX + SERVLET_PARAMETER_COMPATIBILITY_MODE);
}

if (buildInfo.hasKey("npmFolder")) {
initParameters.setProperty(PROJECT_BASEDIR,
buildInfo.getString("npmFolder"));
}
if (buildInfo.hasKey(NPM_TOKEN)) {
initParameters.setProperty(PROJECT_BASEDIR,
buildInfo.getString(NPM_TOKEN));
verifyFolderExists(initParameters,
buildInfo.getString(NPM_TOKEN));
}

if (buildInfo.hasKey("frontendFolder")) {
initParameters.setProperty(FrontendUtils.PARAM_FRONTEND_DIR,
buildInfo.getString("frontendFolder"));
if (buildInfo.hasKey(FRONTEND_TOKEN)) {
initParameters.setProperty(FrontendUtils.PARAM_FRONTEND_DIR,
buildInfo.getString(FRONTEND_TOKEN));
// Only verify frontend folder if it's not a subfolder of the npm folder.
if (!buildInfo.hasKey(NPM_TOKEN) || !buildInfo
.getString(FRONTEND_TOKEN)
.startsWith(buildInfo.getString(NPM_TOKEN))) {
verifyFolderExists(initParameters,
buildInfo.getString(FRONTEND_TOKEN));
}
}

// These should be internal only so if there is a System
// property override then the user probably knows what
// they are doing.
if (buildInfo.hasKey(SERVLET_PARAMETER_ENABLE_DEV_SERVER)) {
initParameters.setProperty(
SERVLET_PARAMETER_ENABLE_DEV_SERVER,
String.valueOf(buildInfo.getBoolean(
SERVLET_PARAMETER_ENABLE_DEV_SERVER)));
}
if (buildInfo.hasKey(SERVLET_PARAMETER_REUSE_DEV_SERVER)) {
initParameters.setProperty(
SERVLET_PARAMETER_REUSE_DEV_SERVER,
String.valueOf(buildInfo.getBoolean(
SERVLET_PARAMETER_REUSE_DEV_SERVER)));
}
// These should be internal only so if there is a System
// property override then the user probably knows what
// they are doing.
if (buildInfo.hasKey(SERVLET_PARAMETER_ENABLE_DEV_SERVER)) {
initParameters.setProperty(SERVLET_PARAMETER_ENABLE_DEV_SERVER,
String.valueOf(buildInfo.getBoolean(
SERVLET_PARAMETER_ENABLE_DEV_SERVER)));
}
if (buildInfo.hasKey(SERVLET_PARAMETER_REUSE_DEV_SERVER)) {
initParameters.setProperty(SERVLET_PARAMETER_REUSE_DEV_SERVER,
String.valueOf(buildInfo.getBoolean(
SERVLET_PARAMETER_REUSE_DEV_SERVER)));
}
}

try {
boolean hasWebPackConfig = hasWebpackConfig(initParameters);
boolean hasTokenFile = json != null;
SerializableConsumer<CompatibilityModeStatus> strategy = value -> verifyMode(
Expand All @@ -247,6 +260,28 @@ private static void readBuildInfo(Properties initParameters) { // NOSONAR

}

/**
* Verify that given folder actually exists on the system if we are not in
* production mode.
* <p>
* If folder doesn't exist throw IllegalStateException saying that this
* should probably be a production mode build.
*
* @param initParameters
* deployment init parameters
* @param folder
* folder to check exists
*/
private static void verifyFolderExists(Properties initParameters,
String folder) {
Boolean productionMode = Boolean.parseBoolean(initParameters
.getProperty(SERVLET_PARAMETER_PRODUCTION_MODE, "false"));
if(!productionMode && !new File(folder).exists()) {
String message = String.format(DEV_FOLDER_MISSING_MESSAGE, folder);
throw new IllegalStateException(message);
}
}

private static void verifyMode(CompatibilityModeStatus value,
boolean hasTokenFile, boolean hasWebpackConfig) {
// Don't handle the case when compatibility mode is enabled.
Expand Down
Expand Up @@ -2,7 +2,6 @@

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;

import java.io.File;
import java.io.IOException;
import java.net.URL;
Expand All @@ -28,6 +27,7 @@

import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_PRODUCTION_MODE;
import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES;
import static com.vaadin.flow.server.DeploymentConfigurationFactory.DEV_FOLDER_MISSING_MESSAGE;
import static com.vaadin.flow.server.frontend.FrontendUtils.PARAM_TOKEN_FILE;
import static com.vaadin.flow.server.frontend.FrontendUtils.TOKEN_FILE;
import static java.util.Collections.emptyMap;
Expand Down Expand Up @@ -307,6 +307,84 @@ public void should_readConfigurationFromTokenFile() throws Exception {
assertTrue(config.isProductionMode());
}

@Test
public void shouldThrow_tokenFileContainsNonExistingNpmFolderInDevMode()
throws Exception {
exception.expect(IllegalStateException.class);
exception.expectMessage(String.format(DEV_FOLDER_MISSING_MESSAGE, "npm"));
FileUtils.writeLines(tokenFile,
Arrays.asList("{", "\"compatibilityMode\": false,",
"\"productionMode\": false,", "\"npmFolder\": \"npm\",",
"\"generatedFolder\": \"generated\",",
"\"frontendFolder\": \"frontend\"", "}"));

createConfig(Collections
.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath()));
}

@Test
public void shouldThrow_tokenFileContainsNonExistingFrontendFolderNoNpmFolder()
throws Exception {
exception.expect(IllegalStateException.class);
exception.expectMessage(String.format(DEV_FOLDER_MISSING_MESSAGE, "frontend"));
FileUtils.writeLines(tokenFile,
Arrays.asList("{", "\"compatibilityMode\": false,",
"\"productionMode\": false,",
"\"frontendFolder\": \"frontend\"", "}"));

createConfig(Collections
.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath()));
}


@Test
public void shouldThrow_tokenFileContainsNonExistingFrontendFolderOutsideNpmSubFolder()
throws Exception {
exception.expect(IllegalStateException.class);
exception.expectMessage(String.format(DEV_FOLDER_MISSING_MESSAGE, "frontend"));
temporaryFolder.newFolder("npm");
String tempFolder = temporaryFolder.getRoot().getAbsolutePath().replace("\\", "/");
FileUtils.writeLines(tokenFile,
Arrays.asList("{", "\"compatibilityMode\": false,",
"\"productionMode\": false,",
"\"npmFolder\": \""+ tempFolder +"/npm\",",
"\"frontendFolder\": \"frontend\"", "}"));

createConfig(Collections
.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath()));
}

@Test
public void shouldNotThrow_tokenFileFrontendFolderInDevMode()
throws Exception {
temporaryFolder.newFolder("npm");
String tempFolder = temporaryFolder.getRoot().getAbsolutePath().replace("\\", "/");
FileUtils.writeLines(tokenFile,
Arrays.asList("{", "\"compatibilityMode\": false,",
"\"productionMode\": false,",
"\"npmFolder\": \""+ tempFolder +"/npm\",",
"\"frontendFolder\": \""+tempFolder+"/npm/frontend\"", "}"));

createConfig(Collections
.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath()));
}

@Test
public void shouldNotThrow_tokenFileFoldersExist()
throws Exception {
temporaryFolder.newFolder("npm");
temporaryFolder.newFolder("frontend");
String tempFolder = temporaryFolder.getRoot().getAbsolutePath().replace("\\", "/");
FileUtils.writeLines(tokenFile,
Arrays.asList("{", "\"compatibilityMode\": false,",
"\"productionMode\": false,",
"\"npmFolder\": \""+ tempFolder +"/npm\",",
"\"frontendFolder\": \""+tempFolder+"/frontend\"", "}"));

createConfig(Collections
.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath()));
}

private DeploymentConfiguration createConfig(Map<String, String> map)
throws Exception {
return DeploymentConfigurationFactory.createDeploymentConfiguration(
Expand Down

0 comments on commit 4d90841

Please sign in to comment.