Add baseDir option

Previously the generated archive wasn't self contained and the user had
to create a dedicated directory before extracting it in case the archive
utility does not handle that use case (MacOS supports that by default).

A baseDir request attribute has been added and generate a base directory
in the archive when set. It is enabled by default in the web UI and takes
the same value as the artifactId. Other clients are unaffected by this
change (and shouldn't probably).

Note that the legacy HTML form isn't impacted by this change either as
older STS versions expect the archive to have a very specific format.

curl-based tools can use that new baseDir request parameter to specify
that a base directory is required, something like

curl https://start.spring.io/starter.tgz -d baseDir=my-project

Closes gh-65
This commit is contained in:
Stephane Nicoll
2015-01-15 11:06:22 +01:00
parent 81df61931a
commit 3b0509a9bb
8 changed files with 78 additions and 16 deletions

View File

@@ -7,6 +7,9 @@ order.
=== Release 1.0.0 (In progress)
* https://github.com/spring-io/initializr/issues/65[#64]: project archives generated by the web UI now contain
a base directory equals to the `artifactId`. By default, no base directory gets created if the project is
generated by another means; use the new `baseDir` to control that behaviour.
* https://github.com/spring-io/initializr/issues/46[#46]: projects generated against Spring 1.2+ are using the
new `@SpringBootApplication`
* https://github.com/spring-io/initializr/issues/64[#64]: Groovy-based projects are now working properly with

View File

@@ -74,10 +74,12 @@ class ProjectGenerator {
File generateProjectStructure(ProjectRequest request) {
def model = initializeModel(request)
def dir = File.createTempFile('tmp', '', new File(tmpdir))
addTempFile(dir.name, dir)
dir.delete()
dir.mkdirs()
def rootDir = File.createTempFile('tmp', '', new File(tmpdir))
addTempFile(rootDir.name, rootDir)
rootDir.delete()
rootDir.mkdirs()
def dir = initializerProjectDir(rootDir, request)
if ('gradle'.equals(request.build)) {
def gradle = new String(doGenerateGradleBuild(model))
@@ -119,7 +121,7 @@ class ProjectGenerator {
new File(dir, 'src/main/resources/static').mkdirs()
}
invokeListeners(request)
dir
rootDir
}
@@ -183,6 +185,16 @@ class ProjectGenerator {
template 'starter-build.gradle', model
}
private File initializerProjectDir(File rootDir, ProjectRequest request) {
if (request.baseDir) {
File dir = new File(rootDir, request.baseDir)
dir.mkdir()
return dir
} else {
return rootDir
}
}
def write(File target, String templateName, def model) {
def tmpl = templateName.endsWith('.groovy') ? templateName + '.tmpl' : templateName
def body = template tmpl, model

View File

@@ -53,6 +53,9 @@ class ProjectRequest {
String packageName
String javaVersion
// The base directory to create in the archive - no baseDir by default
String baseDir
// Resolved dependencies based on the ids provided by either "style" or "dependencies"
List<InitializrMetadata.Dependency> resolvedDependencies

View File

@@ -26,6 +26,7 @@
</div>
</div>
<form id="form" class="form-horizontal" action="/starter.zip" method="get" role="form">
<input id="baseDir" name="baseDir" type="hidden" value="${defaults.artifactId}">
<div class="row">
<div class="col-sm-6">
<h3>Project metadata</h3>
@@ -38,7 +39,7 @@
<div class="form-group">
<label for="artifactId" class="col-md-3 control-label">Artifact</label>
<div class="col-md-8">
<input id="artifactId" class="form-control" type="text" value="${defaults.artifactId}" name="artifactId">
<input id="artifactId" class="form-control" type="text" value="${defaults.artifactId}" name="artifactId" onchange="javascript:updateBaseDir()">
</div>
</div>
<div class="form-group">
@@ -62,7 +63,7 @@
<div class="form-group">
<label for="type" class="col-md-3 control-label">Type</label>
<div class="col-md-8">
<select class="form-control" id="type" name="type" onchange="javascript:updateForm(this)">
<select class="form-control" id="type" name="type" onchange="javascript:updateAction(this)">
<% types.each { %>
<option data-action="${it.action}" value="${it.id}" ${it.default==true ? ' selected' : ''}>${it.name}</option>
<% } %>
@@ -162,9 +163,14 @@
</form>
</div>
<script>
var updateForm = function(sel) {
var updateAction = function(sel) {
var selected = sel.options[sel.selectedIndex];
sel.form.action = selected.getAttribute("data-action");
};
var updateBaseDir = function() {
var artifactId = document.getElementById("artifactId");
var baseDir = document.getElementById("baseDir");
baseDir.value = artifactId.value
}
</script>
</body>

View File

@@ -205,6 +205,15 @@ class ProjectGeneratorTests {
.doesNotContain('@EnableAutoConfiguration', '@Configuration', '@ComponentScan')
}
@Test
void customBaseDirectory() {
def request = createProjectRequest()
request.baseDir = 'my-project'
generateProject(request).hasBaseDir('my-project')
.isJavaProject()
.isMavenProject()
}
PomAssert generateMavenPom(ProjectRequest request) {
def content = new String(projectGenerator.generateMavenPom(request))
new PomAssert(content).validateProjectRequest(request)

View File

@@ -19,6 +19,7 @@ package io.spring.initializr.test
import io.spring.initializr.InitializrMetadata
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertTrue
/**
* Various project based assertions.
@@ -40,6 +41,21 @@ class ProjectAssert {
this.dir = dir
}
/**
* Validate that the project contains a base directory with the specified name.
* <p>When extracting such archive, a directory with the specified {@code name}
* will be created with the content of the project instead of extracting it in
* the directory itself.
* @param name the expected name of the base directory
* @return an updated project assert on that base directory
*/
ProjectAssert hasBaseDir(String name) {
File projectDir = file(name)
assertTrue "No directory $name found in $dir.absolutePath", projectDir.exists()
assertTrue "$name is not a directory", projectDir.isDirectory()
new ProjectAssert(projectDir) // Replacing the root dir so that other assertions match the root
}
/**
* Return a {@link PomAssert} for this project.
*/

View File

@@ -67,7 +67,7 @@ abstract class AbstractInitializerControllerFormIntegrationTests extends Abstrac
@Test
void createDefaultJavaProject() {
def page = home()
def projectAssert = zipProjectAssert(page.generateProject().contentAsStream.bytes)
def projectAssert = zipProjectAssert(page, page.generateProject())
projectAssert.isMavenProject().isJavaProject().hasStaticAndTemplatesResources(false)
.pomAssert().hasDependenciesCount(2)
.hasSpringBootStarterRootDependency().hasSpringBootStarterDependency('test')
@@ -77,7 +77,7 @@ abstract class AbstractInitializerControllerFormIntegrationTests extends Abstrac
void createDefaultGroovyProject() {
def page = home()
page.language = 'groovy'
def projectAssert = zipProjectAssert(page.generateProject().contentAsStream.bytes)
def projectAssert = zipProjectAssert(page, page.generateProject())
projectAssert.isMavenProject().isGroovyProject().hasStaticAndTemplatesResources(false)
.pomAssert().hasDependenciesCount(3)
.hasSpringBootStarterRootDependency().hasSpringBootStarterDependency('test')
@@ -92,7 +92,7 @@ abstract class AbstractInitializerControllerFormIntegrationTests extends Abstrac
String value = webResponse.getResponseHeaderValue('Content-Disposition')
assertEquals 'attachment; filename="foo-bar.zip"', value
def projectAssert = zipProjectAssert(webResponse)
def projectAssert = zipProjectAssert(page, webResponse)
projectAssert.isMavenProject().isJavaProject('MyProjectApplication')
.hasStaticAndTemplatesResources(true)
@@ -108,7 +108,7 @@ abstract class AbstractInitializerControllerFormIntegrationTests extends Abstrac
String value = webResponse.getResponseHeaderValue('Content-Disposition')
assertEquals 'attachment; filename="foo-bar.zip"', value
def projectAssert = zipProjectAssert(webResponse)
def projectAssert = zipProjectAssert(page, webResponse)
projectAssert.isMavenProject().isGroovyProject('MyProjectApplication')
.hasStaticAndTemplatesResources(true)
@@ -141,7 +141,7 @@ abstract class AbstractInitializerControllerFormIntegrationTests extends Abstrac
void createWarProject() {
def page = home()
page.packaging = 'war'
def projectAssert = zipProjectAssert(page.generateProject())
def projectAssert = zipProjectAssert(page, page.generateProject())
projectAssert.isMavenProject().isJavaWarProject()
.pomAssert().hasPackaging('war').hasDependenciesCount(3)
.hasSpringBootStarterDependency('web') // Added with war packaging
@@ -153,7 +153,7 @@ abstract class AbstractInitializerControllerFormIntegrationTests extends Abstrac
def page = home()
page.type = projectType
page.dependencies << 'data-jpa'
def projectAssert = zipProjectAssert(page.generateProject())
def projectAssert = zipProjectAssert(page, page.generateProject())
projectAssert.isGradleProject().isJavaProject().hasStaticAndTemplatesResources(false)
}
@@ -163,7 +163,10 @@ abstract class AbstractInitializerControllerFormIntegrationTests extends Abstrac
createHomePage(home)
}
ProjectAssert zipProjectAssert(WebResponse webResponse) {
/**
* Initialize a {@link ProjectAssert} for the specified {@link HomePage} and {@link WebResponse}.
*/
protected ProjectAssert zipProjectAssert(HomePage page, WebResponse webResponse) {
zipProjectAssert(webResponse.contentAsStream.bytes)
}

View File

@@ -16,7 +16,10 @@
package io.spring.initializr.web
import com.gargoylesoftware.htmlunit.WebResponse
import com.gargoylesoftware.htmlunit.html.HtmlPage
import io.spring.initializr.InitializrMetadata
import io.spring.initializr.test.ProjectAssert
import io.spring.initializr.web.support.DefaultHomePage
import io.spring.initializr.web.support.HomePage
@@ -27,7 +30,6 @@ import io.spring.initializr.web.support.HomePage
*/
class MainControllerFormIntegrationTests extends AbstractInitializerControllerFormIntegrationTests {
@Override
void createSimpleGradleProject() {
createSimpleGradleProject('gradle-project')
@@ -43,4 +45,12 @@ class MainControllerFormIntegrationTests extends AbstractInitializerControllerFo
new DefaultHomePage(home)
}
@Override
protected ProjectAssert zipProjectAssert(HomePage page, WebResponse webResponse) {
ProjectAssert projectAssert = super.zipProjectAssert(page, webResponse)
// we require self contained archive by default
String dirName = page.artifactId ?: InitializrMetadata.Defaults.DEFAULT_NAME
projectAssert.hasBaseDir(dirName)
}
}