Skip to content

Commit 357adcf

Browse files
authored
feat: Enable validating in memory package.json (#15537)
If no package.json in app folder, generate a package.json in memory to validate against. closes #15536
1 parent d96c0f0 commit 357adcf

File tree

4 files changed

+193
-95
lines changed

4 files changed

+193
-95
lines changed

flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ public NodeTasks(Options options) {
100100
// determine if we need a rebuild as the check happens immediately
101101
// and no update tasks are executed before it.
102102
if (!options.productionMode && options.isDevBundleBuild()) {
103-
if (TaskRunDevBundleBuild.needsBuild(options.getNpmFolder(),
104-
frontendDependencies)) {
103+
if (TaskRunDevBundleBuild.needsBuild(options,
104+
frontendDependencies, classFinder)) {
105105
options.runNpmInstall(true);
106106
options.copyTemplates(true);
107107
} else {

flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskRunDevBundleBuild.java

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.slf4j.LoggerFactory;
3434

3535
import com.vaadin.flow.server.ExecutionFailedException;
36+
import com.vaadin.flow.server.frontend.scanner.ClassFinder;
3637
import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner;
3738
import com.vaadin.flow.shared.util.SharedUtil;
3839

@@ -70,13 +71,14 @@ public void execute() throws ExecutionFailedException {
7071
"build");
7172
}
7273

73-
public static boolean needsBuild(File npmFolder,
74-
FrontendDependenciesScanner frontendDependencies) {
74+
public static boolean needsBuild(Options options,
75+
FrontendDependenciesScanner frontendDependencies,
76+
ClassFinder finder) {
7577
getLogger().info("Checking if an express mode bundle build is needed");
7678

7779
try {
78-
boolean needsBuild = needsBuildInternal(npmFolder,
79-
frontendDependencies);
80+
boolean needsBuild = needsBuildInternal(options,
81+
frontendDependencies, finder);
8082
if (needsBuild) {
8183
getLogger().info("An express mode bundle build is needed");
8284
} else {
@@ -91,10 +93,10 @@ public static boolean needsBuild(File npmFolder,
9193
}
9294
}
9395

94-
protected static boolean needsBuildInternal(File npmFolder,
95-
FrontendDependenciesScanner frontendDependencies)
96-
throws IOException {
97-
96+
protected static boolean needsBuildInternal(Options options,
97+
FrontendDependenciesScanner frontendDependencies,
98+
ClassFinder finder) throws IOException {
99+
File npmFolder = options.getNpmFolder();
98100
if (!FrontendUtils.getDevBundleFolder(npmFolder).exists()
99101
&& !hasJarBundle()) {
100102
return true;
@@ -106,7 +108,8 @@ protected static boolean needsBuildInternal(File npmFolder,
106108
return true;
107109
}
108110

109-
JsonObject packageJson = getPackageJson(npmFolder);
111+
JsonObject packageJson = getPackageJson(options, frontendDependencies,
112+
finder);
110113
JsonObject statsJson = Json.parse(statsJsonContent);
111114

112115
// Get scanned @NpmPackage annotations
@@ -218,8 +221,10 @@ private static boolean versionAccepted(String expected, String actual) {
218221
return expectedVersion.isEqualTo(actualVersion);
219222
}
220223

221-
private static JsonObject getPackageJson(File npmFolder) {
222-
File packageJsonFile = new File(npmFolder, "package.json");
224+
private static JsonObject getPackageJson(Options options,
225+
FrontendDependenciesScanner frontendDependencies,
226+
ClassFinder finder) {
227+
File packageJsonFile = new File(options.getNpmFolder(), "package.json");
223228

224229
if (packageJsonFile.exists()) {
225230
try {
@@ -229,7 +234,34 @@ private static JsonObject getPackageJson(File npmFolder) {
229234
getLogger().warn("Failed to read package.json", e);
230235
}
231236
} else {
232-
// new NodeUpdater();
237+
JsonObject packageJson = getDefaultPackageJson(options,
238+
frontendDependencies, finder);
239+
if (packageJson != null) {
240+
return packageJson;
241+
}
242+
}
243+
return null;
244+
}
245+
246+
protected static JsonObject getDefaultPackageJson(Options options,
247+
FrontendDependenciesScanner frontendDependencies,
248+
ClassFinder finder) {
249+
NodeUpdater nodeUpdater = new NodeUpdater(finder, frontendDependencies,
250+
options) {
251+
@Override
252+
public void execute() {
253+
}
254+
};
255+
try {
256+
JsonObject packageJson = nodeUpdater.getPackageJson();
257+
nodeUpdater.updateDefaultDependencies(packageJson);
258+
final String hash = TaskUpdatePackages
259+
.generatePackageJsonHash(packageJson);
260+
packageJson.getObject(NodeUpdater.VAADIN_DEP_KEY)
261+
.put(NodeUpdater.HASH_KEY, hash);
262+
return packageJson;
263+
} catch (IOException e) {
264+
getLogger().warn("Failed to generate package.json", e);
233265
}
234266
return null;
235267
}

flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskRunDevBundleBuildTest.java

Lines changed: 147 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,41 @@
99

1010
import org.apache.commons.io.FileUtils;
1111
import org.junit.Assert;
12+
import org.junit.Before;
1213
import org.junit.Rule;
1314
import org.junit.Test;
1415
import org.junit.rules.TemporaryFolder;
16+
import org.mockito.Mock;
1517
import org.mockito.MockedStatic;
1618
import org.mockito.Mockito;
1719

20+
import com.vaadin.flow.di.Lookup;
21+
import com.vaadin.flow.server.frontend.scanner.ClassFinder;
1822
import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner;
1923

24+
import elemental.json.JsonObject;
25+
2026
public class TaskRunDevBundleBuildTest {
2127

2228
@Rule
2329
public TemporaryFolder temporaryFolder = new TemporaryFolder();
2430

31+
private Options options;
32+
33+
ClassFinder finder;
34+
35+
@Before
36+
public void init() {
37+
options = new Options(Mockito.mock(Lookup.class),
38+
temporaryFolder.getRoot()).withBuildDirectory("target");
39+
finder = Mockito.mock(ClassFinder.class);
40+
}
41+
2542
@Test
2643
public void noDevBundle_bundleCompilationRequires() throws IOException {
2744
final boolean needsBuild = TaskRunDevBundleBuild.needsBuildInternal(
28-
temporaryFolder.getRoot(),
29-
Mockito.mock(FrontendDependenciesScanner.class));
45+
options, Mockito.mock(FrontendDependenciesScanner.class),
46+
finder);
3047
Assert.assertTrue("Bundle should require creation if not available",
3148
needsBuild);
3249
}
@@ -43,8 +60,8 @@ public void devBundleStatsJsonMissing_bundleCompilationRequires()
4360
.thenReturn(null);
4461

4562
final boolean needsBuild = TaskRunDevBundleBuild.needsBuildInternal(
46-
temporaryFolder.getRoot(),
47-
Mockito.mock(FrontendDependenciesScanner.class));
63+
options, Mockito.mock(FrontendDependenciesScanner.class),
64+
finder);
4865
Assert.assertTrue("Missing stats.json should require bundling",
4966
needsBuild);
5067
}
@@ -82,7 +99,7 @@ public void hashesMatch_noNpmPackages_noCompilationRequired()
8299
+ "}");
83100

84101
final boolean needsBuild = TaskRunDevBundleBuild
85-
.needsBuildInternal(temporaryFolder.getRoot(), depScanner);
102+
.needsBuildInternal(options, depScanner, finder);
86103
Assert.assertFalse("Missing stats.json should require bundling",
87104
needsBuild);
88105
}
@@ -122,7 +139,7 @@ public void hashesMatch_statsMissingNpmPackages_compilationRequired()
122139
+ "}");
123140

124141
final boolean needsBuild = TaskRunDevBundleBuild
125-
.needsBuildInternal(temporaryFolder.getRoot(), depScanner);
142+
.needsBuildInternal(options, depScanner, finder);
126143
Assert.assertTrue("Missing npmPackage should require bundling",
127144
needsBuild);
128145
}
@@ -160,7 +177,7 @@ public void hashesMatch_statsMissingPackageJsonPackage_compilationRequired()
160177
+ "}");
161178

162179
final boolean needsBuild = TaskRunDevBundleBuild
163-
.needsBuildInternal(temporaryFolder.getRoot(), depScanner);
180+
.needsBuildInternal(options, depScanner, finder);
164181
Assert.assertTrue("Bundle missing module dependency should rebuild",
165182
needsBuild);
166183
}
@@ -199,7 +216,7 @@ public void hashesMatch_packageJsonMissingNpmPackages_statsHasJsonPackages_noCom
199216
+ "}");
200217

201218
final boolean needsBuild = TaskRunDevBundleBuild
202-
.needsBuildInternal(temporaryFolder.getRoot(), depScanner);
219+
.needsBuildInternal(options, depScanner, finder);
203220
Assert.assertFalse(
204221
"Not missing npmPackage in stats.json should not require compilation",
205222
needsBuild);
@@ -235,7 +252,7 @@ public void hashesMatch_packageJsonHasRange_statsHasFixed_noCompilationRequired(
235252
+ "}");
236253

237254
final boolean needsBuild = TaskRunDevBundleBuild
238-
.needsBuildInternal(temporaryFolder.getRoot(), depScanner);
255+
.needsBuildInternal(options, depScanner, finder);
239256
Assert.assertFalse(
240257
"Not missing npmPackage in stats.json should not require compilation",
241258
needsBuild);
@@ -271,7 +288,7 @@ public void hashesMatch_packageJsonHasTildeRange_statsHasNewerFixed_noCompilatio
271288
+ "}");
272289

273290
boolean needsBuild = TaskRunDevBundleBuild
274-
.needsBuildInternal(temporaryFolder.getRoot(), depScanner);
291+
.needsBuildInternal(options, depScanner, finder);
275292
Assert.assertFalse(
276293
"No compilation if tilde range only patch update",
277294
needsBuild);
@@ -283,8 +300,8 @@ public void hashesMatch_packageJsonHasTildeRange_statsHasNewerFixed_noCompilatio
283300
+ " \"packageJsonHash\": \"af45419b27dcb44b875197df4347b97316cc8fa6055458223a73aedddcfe7cc6\"\n"
284301
+ "}");
285302

286-
needsBuild = TaskRunDevBundleBuild
287-
.needsBuildInternal(temporaryFolder.getRoot(), depScanner);
303+
needsBuild = TaskRunDevBundleBuild.needsBuildInternal(options,
304+
depScanner, finder);
288305
Assert.assertTrue(
289306
"Compilation required if minor version change for tilde range",
290307
needsBuild);
@@ -320,7 +337,7 @@ public void hashesMatch_packageJsonHasCaretRange_statsHasNewerFixed_noCompilatio
320337
+ "}");
321338

322339
boolean needsBuild = TaskRunDevBundleBuild
323-
.needsBuildInternal(temporaryFolder.getRoot(), depScanner);
340+
.needsBuildInternal(options, depScanner, finder);
324341
Assert.assertFalse(
325342
"No compilation if caret range only minor version update",
326343
needsBuild);
@@ -332,11 +349,126 @@ public void hashesMatch_packageJsonHasCaretRange_statsHasNewerFixed_noCompilatio
332349
+ " \"packageJsonHash\": \"af45419b27dcb44b875197df4347b97316cc8fa6055458223a73aedddcfe7cc6\"\n"
333350
+ "}");
334351

335-
needsBuild = TaskRunDevBundleBuild
336-
.needsBuildInternal(temporaryFolder.getRoot(), depScanner);
352+
needsBuild = TaskRunDevBundleBuild.needsBuildInternal(options,
353+
depScanner, finder);
337354
Assert.assertTrue(
338355
"Compilation required if major version change for caret range",
339356
needsBuild);
340357
}
341358
}
359+
360+
@Test
361+
public void noPackageJson_defaultPackagesAndModulesInStats_noBuildNeeded()
362+
throws IOException {
363+
final FrontendDependenciesScanner depScanner = Mockito
364+
.mock(FrontendDependenciesScanner.class);
365+
Mockito.when(depScanner.getPackages())
366+
.thenReturn(Collections.singletonMap("@vaadin/text", "1.0.0"));
367+
368+
String defaultHash = TaskRunDevBundleBuild
369+
.getDefaultPackageJson(options, depScanner, finder)
370+
.getObject(NodeUpdater.VAADIN_DEP_KEY)
371+
.getString(NodeUpdater.HASH_KEY);
372+
373+
try (MockedStatic<FrontendUtils> utils = Mockito
374+
.mockStatic(FrontendUtils.class)) {
375+
utils.when(() -> FrontendUtils.getDevBundleFolder(Mockito.any()))
376+
.thenReturn(temporaryFolder.getRoot());
377+
utils.when(() -> FrontendUtils
378+
.findBundleStatsJson(temporaryFolder.getRoot()))
379+
.thenReturn("{\n" + " \"npmModules\": {\n"
380+
+ " \"@polymer/polymer\": \"3.5.1\",\n"
381+
+ " \"@vaadin/common-frontend\": \"0.0.17\",\n"
382+
+ " \"@vaadin/router\": \"1.7.4\",\n"
383+
+ " \"construct-style-sheets-polyfill\": \"3.1.0\",\n"
384+
+ " \"lit\": \"2.4.1\",\n"
385+
+ " \"@vaadin/text\": \"1.0.0\"\n},\n"
386+
+ " \"entryScripts\": [\n"
387+
+ " \"VAADIN/build/indexhtml-aa31f040.js\"\n"
388+
+ " ],\n" + " \"packageJsonHash\": \"" + defaultHash
389+
+ "\"\n" + "}");
390+
391+
final boolean needsBuild = TaskRunDevBundleBuild
392+
.needsBuildInternal(options, depScanner, finder);
393+
Assert.assertFalse(
394+
"Default package.json should be built and validated",
395+
needsBuild);
396+
}
397+
}
398+
399+
@Test
400+
public void noPackageJson_defaultPackagesInStats_missingNpmModules_buildNeeded()
401+
throws IOException {
402+
final FrontendDependenciesScanner depScanner = Mockito
403+
.mock(FrontendDependenciesScanner.class);
404+
Mockito.when(depScanner.getPackages())
405+
.thenReturn(Collections.singletonMap("@vaadin/text", "1.0.0"));
406+
407+
String defaultHash = TaskRunDevBundleBuild
408+
.getDefaultPackageJson(options, depScanner, finder)
409+
.getObject(NodeUpdater.VAADIN_DEP_KEY)
410+
.getString(NodeUpdater.HASH_KEY);
411+
412+
try (MockedStatic<FrontendUtils> utils = Mockito
413+
.mockStatic(FrontendUtils.class)) {
414+
utils.when(() -> FrontendUtils.getDevBundleFolder(Mockito.any()))
415+
.thenReturn(temporaryFolder.getRoot());
416+
utils.when(() -> FrontendUtils
417+
.findBundleStatsJson(temporaryFolder.getRoot()))
418+
.thenReturn("{\n" + " \"npmModules\": {\n"
419+
+ " \"@polymer/polymer\": \"3.5.1\",\n"
420+
+ " \"@vaadin/common-frontend\": \"0.0.17\",\n"
421+
+ " \"@vaadin/router\": \"1.7.4\",\n"
422+
+ " \"construct-style-sheets-polyfill\": \"3.1.0\",\n"
423+
+ " \"lit\": \"2.4.1\"\n" + " },\n"
424+
+ " \"entryScripts\": [\n"
425+
+ " \"VAADIN/build/indexhtml-aa31f040.js\"\n"
426+
+ " ],\n" + " \"packageJsonHash\": \"" + defaultHash
427+
+ "\"\n" + "}");
428+
429+
final boolean needsBuild = TaskRunDevBundleBuild
430+
.needsBuildInternal(options, depScanner, finder);
431+
Assert.assertTrue(
432+
"Missing NpmPackage with default bundle should require rebuild",
433+
needsBuild);
434+
}
435+
}
436+
437+
@Test
438+
public void noPackageJson_defaultPackagesInStats_noBuildNeeded()
439+
throws IOException {
440+
final FrontendDependenciesScanner depScanner = Mockito
441+
.mock(FrontendDependenciesScanner.class);
442+
Mockito.when(depScanner.getPackages())
443+
.thenReturn(Collections.emptyMap());
444+
445+
String defaultHash = TaskRunDevBundleBuild
446+
.getDefaultPackageJson(options, depScanner, finder)
447+
.getObject(NodeUpdater.VAADIN_DEP_KEY)
448+
.getString(NodeUpdater.HASH_KEY);
449+
450+
try (MockedStatic<FrontendUtils> utils = Mockito
451+
.mockStatic(FrontendUtils.class)) {
452+
utils.when(() -> FrontendUtils.getDevBundleFolder(Mockito.any()))
453+
.thenReturn(temporaryFolder.getRoot());
454+
utils.when(() -> FrontendUtils
455+
.findBundleStatsJson(temporaryFolder.getRoot()))
456+
.thenReturn("{\n" + " \"npmModules\": {\n"
457+
+ " \"@polymer/polymer\": \"3.5.1\",\n"
458+
+ " \"@vaadin/common-frontend\": \"0.0.17\",\n"
459+
+ " \"@vaadin/router\": \"1.7.4\",\n"
460+
+ " \"construct-style-sheets-polyfill\": \"3.1.0\",\n"
461+
+ " \"lit\": \"2.4.1\"\n" + " },\n"
462+
+ " \"entryScripts\": [\n"
463+
+ " \"VAADIN/build/indexhtml-aa31f040.js\"\n"
464+
+ " ],\n" + " \"packageJsonHash\": \"" + defaultHash
465+
+ "\"\n" + "}");
466+
467+
final boolean needsBuild = TaskRunDevBundleBuild
468+
.needsBuildInternal(options, depScanner, finder);
469+
Assert.assertFalse(
470+
"Default package.json should be built and validated",
471+
needsBuild);
472+
}
473+
}
342474
}

0 commit comments

Comments
 (0)