diff --git a/config/spotbugs/filter.xml b/config/spotbugs/filter.xml
index 24d313006d0..5c40defe00f 100644
--- a/config/spotbugs/filter.xml
+++ b/config/spotbugs/filter.xml
@@ -93,6 +93,12 @@
+
+
+
+
+
+
diff --git a/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java b/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java
index b9a1d41a879..81fd5dfab09 100644
--- a/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java
+++ b/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java
@@ -6,6 +6,8 @@
import org.junit.jupiter.api.Test;
import software.amazon.smithy.utils.IoUtils;
import software.amazon.smithy.utils.ListUtils;
+
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -54,6 +56,50 @@ public void missingTemplate() {
});
}
+ @Test
+ public void includedFileJson() {
+ IntegUtils.withProject(PROJECT_NAME, templatesDir -> {
+ setupTemplatesDirectory(templatesDir);
+
+ IntegUtils.withTempDir("includedFiles", dir -> {
+ RunResult result = IntegUtils.run(
+ dir, ListUtils.of("init", "-t", "included-file-json", "-u", templatesDir.toString()));
+ assertThat(result.getOutput(),
+ containsString("Smithy project created in directory: "));
+ assertThat(result.getExitCode(), is(0));
+ assertThat(Files.exists(Paths.get(dir.toString(), "included-file-json")), is(true));
+ assertThat(Files.exists(Paths.get(dir.toString(), "included-file-json/smithy-build.json")), is(true));
+ });
+ });
+ }
+
+ @Test
+ public void includedFileGradle() {
+ IntegUtils.withProject(PROJECT_NAME, templatesDir -> {
+ setupTemplatesDirectory(templatesDir);
+
+ IntegUtils.withTempDir("includedFilesGradle", dir -> {
+ RunResult result = IntegUtils.run(
+ dir, ListUtils.of("init", "-t", "included-files-gradle", "-u", templatesDir.toString()));
+ assertThat(result.getOutput(),
+ containsString("Smithy project created in directory: "));
+ assertThat(result.getExitCode(), is(0));
+ assertThat(Files.exists(Paths.get(dir.toString(), "included-files-gradle")), is(true));
+ try {
+ Files.walk(Paths.get(dir.toString())).forEach(System.out::println);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ assertThat(Files.exists(Paths.get(dir.toString(), "included-files-gradle/gradle.properties")), is(true));
+ assertThat(Files.exists(Paths.get(dir.toString(), "included-files-gradle/gradlew")), is(true));
+ assertThat(Files.exists(Paths.get(dir.toString(), "included-files-gradle/gradle")), is(true));
+ assertThat(Files.exists(Paths.get(dir.toString(), "included-files-gradle/gradle/wrapper")), is(true));
+ assertThat(Files.exists(Paths.get(dir.toString(), "included-files-gradle/gradle/wrapper/gradle-wrapper.jar")), is(true));
+ assertThat(Files.exists(Paths.get(dir.toString(), "included-files-gradle/gradle/wrapper/gradle-wrapper.properties")), is(true));
+ });
+ });
+ }
+
@Test
public void unexpectedTemplate() {
IntegUtils.withProject(PROJECT_NAME, templatesDir -> {
@@ -67,11 +113,15 @@ public void unexpectedTemplate() {
.append("Invalid template `blabla`. `Smithy-Examples` provides the following templates:")
.append(System.lineSeparator())
.append(System.lineSeparator())
- .append("NAME DOCUMENTATION")
+ .append("NAME DOCUMENTATION")
+ .append(System.lineSeparator())
+ .append("--------------------- -----------------------------------------------------")
.append(System.lineSeparator())
- .append("-------------- ---------------------------------------------------------------")
+ .append("included-file-json Smithy Quickstart example with json file included. ")
.append(System.lineSeparator())
- .append("quickstart-cli Smithy Quickstart example weather service using the Smithy CLI.")
+ .append("included-files-gradle Smithy Quickstart example with gradle files included.")
+ .append(System.lineSeparator())
+ .append("quickstart-cli Smithy Quickstart example weather service. ")
.append(System.lineSeparator())
.toString();
@@ -133,11 +183,15 @@ public void withListArg() {
"init", "--list", "--url", templatesDir.toString()));
String expectedOutput = new StringBuilder()
- .append("NAME DOCUMENTATION")
+ .append("NAME DOCUMENTATION")
+ .append(System.lineSeparator())
+ .append("--------------------- -----------------------------------------------------")
+ .append(System.lineSeparator())
+ .append("included-file-json Smithy Quickstart example with json file included. ")
.append(System.lineSeparator())
- .append("-------------- ---------------------------------------------------------------")
+ .append("included-files-gradle Smithy Quickstart example with gradle files included.")
.append(System.lineSeparator())
- .append("quickstart-cli Smithy Quickstart example weather service using the Smithy CLI.")
+ .append("quickstart-cli Smithy Quickstart example weather service. ")
.append(System.lineSeparator())
.toString();
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradle.properties b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradle.properties
new file mode 100644
index 00000000000..d2cd2b1f51e
--- /dev/null
+++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradle.properties
@@ -0,0 +1,2 @@
+org.gradle.parallel=true
+org.gradle.jvmargs='-Dfile.encoding=UTF-8'
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradle/wrapper/gradle-wrapper.jar b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000000..7454180f2ae
Binary files /dev/null and b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradle/wrapper/gradle-wrapper.properties b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000000..2e6e5897b52
--- /dev/null
+++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradlew b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradlew
new file mode 100755
index 00000000000..1b6c787337f
--- /dev/null
+++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common-gradle-configs/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common/smithy-build.json b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common/smithy-build.json
new file mode 100644
index 00000000000..703ffb7d769
--- /dev/null
+++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/common/smithy-build.json
@@ -0,0 +1,3 @@
+{
+ "version": "1.0"
+}
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-file-json/README.md b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-file-json/README.md
new file mode 100644
index 00000000000..10da8a83433
--- /dev/null
+++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-file-json/README.md
@@ -0,0 +1,9 @@
+# Quickstart Example
+To run this example you will need the [Smithy CLI](https://smithy.io/2.0/guides/smithy-cli/index.html) installed.
+If you do not have the CLI installed, follow [this guide](https://smithy.io/2.0/guides/smithy-cli/index.html) to install it now.
+
+Once you have the CLI installed run:
+```
+smithy build
+```
+From the root of this directory.
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-file-json/models/weather.smithy b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-file-json/models/weather.smithy
new file mode 100644
index 00000000000..c3ff3caf130
--- /dev/null
+++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-file-json/models/weather.smithy
@@ -0,0 +1,145 @@
+$version: "2"
+namespace example.weather
+
+/// Provides weather forecasts.
+@paginated(
+ inputToken: "nextToken"
+ outputToken: "nextToken"
+ pageSize: "pageSize"
+)
+service Weather {
+ version: "2006-03-01"
+ resources: [City]
+ operations: [GetCurrentTime]
+}
+
+resource City {
+ identifiers: { cityId: CityId }
+ read: GetCity
+ list: ListCities
+ resources: [Forecast]
+}
+
+resource Forecast {
+ identifiers: { cityId: CityId }
+ read: GetForecast,
+}
+
+// "pattern" is a trait.
+@pattern("^[A-Za-z0-9 ]+$")
+string CityId
+
+@readonly
+operation GetCity {
+ input: GetCityInput
+ output: GetCityOutput
+ errors: [NoSuchResource]
+}
+
+@input
+structure GetCityInput {
+ // "cityId" provides the identifier for the resource and
+ // has to be marked as required.
+ @required
+ cityId: CityId
+}
+
+@output
+structure GetCityOutput {
+ // "required" is used on output to indicate if the service
+ // will always provide a value for the member.
+ @required
+ name: String
+
+ @required
+ coordinates: CityCoordinates
+}
+
+// This structure is nested within GetCityOutput.
+structure CityCoordinates {
+ @required
+ latitude: Float
+
+ @required
+ longitude: Float
+}
+
+// "error" is a trait that is used to specialize
+// a structure as an error.
+@error("client")
+structure NoSuchResource {
+ @required
+ resourceType: String
+}
+
+// The paginated trait indicates that the operation may
+// return truncated results.
+@readonly
+@paginated(items: "items")
+operation ListCities {
+ input: ListCitiesInput
+ output: ListCitiesOutput
+}
+
+@input
+structure ListCitiesInput {
+ nextToken: String
+ pageSize: Integer
+}
+
+@output
+structure ListCitiesOutput {
+ nextToken: String
+
+ @required
+ items: CitySummaries
+}
+
+// CitySummaries is a list of CitySummary structures.
+list CitySummaries {
+ member: CitySummary
+}
+
+// CitySummary contains a reference to a City.
+@references([{resource: City}])
+structure CitySummary {
+ @required
+ cityId: CityId
+
+ @required
+ name: String
+}
+
+@readonly
+operation GetCurrentTime {
+ input: GetCurrentTimeInput
+ output: GetCurrentTimeOutput
+}
+
+@input
+structure GetCurrentTimeInput {}
+
+@output
+structure GetCurrentTimeOutput {
+ @required
+ time: Timestamp
+}
+
+@readonly
+operation GetForecast {
+ input: GetForecastInput
+ output: GetForecastOutput
+}
+
+// "cityId" provides the only identifier for the resource since
+// a Forecast doesn't have its own.
+@input
+structure GetForecastInput {
+ @required
+ cityId: CityId
+}
+
+@output
+structure GetForecastOutput {
+ chanceOfRain: Float
+}
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-files-gradle/README.md b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-files-gradle/README.md
new file mode 100644
index 00000000000..10da8a83433
--- /dev/null
+++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-files-gradle/README.md
@@ -0,0 +1,9 @@
+# Quickstart Example
+To run this example you will need the [Smithy CLI](https://smithy.io/2.0/guides/smithy-cli/index.html) installed.
+If you do not have the CLI installed, follow [this guide](https://smithy.io/2.0/guides/smithy-cli/index.html) to install it now.
+
+Once you have the CLI installed run:
+```
+smithy build
+```
+From the root of this directory.
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-files-gradle/models/weather.smithy b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-files-gradle/models/weather.smithy
new file mode 100644
index 00000000000..c3ff3caf130
--- /dev/null
+++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/getting-started-example/included-files-gradle/models/weather.smithy
@@ -0,0 +1,145 @@
+$version: "2"
+namespace example.weather
+
+/// Provides weather forecasts.
+@paginated(
+ inputToken: "nextToken"
+ outputToken: "nextToken"
+ pageSize: "pageSize"
+)
+service Weather {
+ version: "2006-03-01"
+ resources: [City]
+ operations: [GetCurrentTime]
+}
+
+resource City {
+ identifiers: { cityId: CityId }
+ read: GetCity
+ list: ListCities
+ resources: [Forecast]
+}
+
+resource Forecast {
+ identifiers: { cityId: CityId }
+ read: GetForecast,
+}
+
+// "pattern" is a trait.
+@pattern("^[A-Za-z0-9 ]+$")
+string CityId
+
+@readonly
+operation GetCity {
+ input: GetCityInput
+ output: GetCityOutput
+ errors: [NoSuchResource]
+}
+
+@input
+structure GetCityInput {
+ // "cityId" provides the identifier for the resource and
+ // has to be marked as required.
+ @required
+ cityId: CityId
+}
+
+@output
+structure GetCityOutput {
+ // "required" is used on output to indicate if the service
+ // will always provide a value for the member.
+ @required
+ name: String
+
+ @required
+ coordinates: CityCoordinates
+}
+
+// This structure is nested within GetCityOutput.
+structure CityCoordinates {
+ @required
+ latitude: Float
+
+ @required
+ longitude: Float
+}
+
+// "error" is a trait that is used to specialize
+// a structure as an error.
+@error("client")
+structure NoSuchResource {
+ @required
+ resourceType: String
+}
+
+// The paginated trait indicates that the operation may
+// return truncated results.
+@readonly
+@paginated(items: "items")
+operation ListCities {
+ input: ListCitiesInput
+ output: ListCitiesOutput
+}
+
+@input
+structure ListCitiesInput {
+ nextToken: String
+ pageSize: Integer
+}
+
+@output
+structure ListCitiesOutput {
+ nextToken: String
+
+ @required
+ items: CitySummaries
+}
+
+// CitySummaries is a list of CitySummary structures.
+list CitySummaries {
+ member: CitySummary
+}
+
+// CitySummary contains a reference to a City.
+@references([{resource: City}])
+structure CitySummary {
+ @required
+ cityId: CityId
+
+ @required
+ name: String
+}
+
+@readonly
+operation GetCurrentTime {
+ input: GetCurrentTimeInput
+ output: GetCurrentTimeOutput
+}
+
+@input
+structure GetCurrentTimeInput {}
+
+@output
+structure GetCurrentTimeOutput {
+ @required
+ time: Timestamp
+}
+
+@readonly
+operation GetForecast {
+ input: GetForecastInput
+ output: GetForecastOutput
+}
+
+// "cityId" provides the only identifier for the resource since
+// a Forecast doesn't have its own.
+@input
+structure GetForecastInput {
+ @required
+ cityId: CityId
+}
+
+@output
+structure GetForecastOutput {
+ chanceOfRain: Float
+}
diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/smithy-templates.json b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/smithy-templates.json
index 14b138cd934..cde68000b6b 100644
--- a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/smithy-templates.json
+++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/smithy-templates/smithy-templates.json
@@ -2,8 +2,22 @@
"name": "Smithy-Examples",
"templates": {
"quickstart-cli": {
- "documentation": "Smithy Quickstart example weather service using the Smithy CLI.",
+ "documentation": "Smithy Quickstart example weather service.",
"path": "getting-started-example/weather-service"
+ },
+ "included-file-json": {
+ "documentation": "Smithy Quickstart example with json file included.",
+ "path": "getting-started-example/included-file-json",
+ "include": [ "getting-started-example/common/smithy-build.json" ]
+ },
+ "included-files-gradle": {
+ "documentation": "Smithy Quickstart example with gradle files included.",
+ "path": "getting-started-example/included-files-gradle",
+ "include": [
+ "getting-started-example/common-gradle-configs/gradle",
+ "getting-started-example/common-gradle-configs/gradlew",
+ "getting-started-example/common-gradle-configs/gradle.properties"
+ ]
}
}
}
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java
index e7f67a87d81..ac7b67fe005 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java
@@ -20,10 +20,12 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Consumer;
import software.amazon.smithy.cli.ArgumentReceiver;
@@ -46,6 +48,9 @@ final class InitCommand implements Command {
private static final String DOCUMENTATION = "documentation";
private static final String NAME = "name";
+ private static final String TEMPLATES = "templates";
+ private static final String PATH = "path";
+ private static final String INCLUDED = "include";
private final String parentCommandName;
@@ -114,7 +119,7 @@ private String getTemplateList(ObjectNode smithyTemplatesNode, Env env) {
String documentation = entry.getValue()
.expectObjectNode()
.expectMember(DOCUMENTATION, String.format(
- "Missing expected member `%s` from `%s` object", DOCUMENTATION, template))
+ "Missing expected member `%s` from `%s` object", DOCUMENTATION, template))
.expectStringNode()
.getValue();
@@ -156,24 +161,23 @@ private void cloneTemplate(Path temp, ObjectNode smithyTemplatesNode, String tem
}
ObjectNode templatesNode = getTemplatesNode(smithyTemplatesNode);
-
if (!templatesNode.containsMember(template)) {
throw new IllegalArgumentException(String.format(
- "Invalid template `%s`. `%s` provides the following templates:%n%n%s",
- template, getTemplatesName(smithyTemplatesNode), getTemplateList(smithyTemplatesNode, env)));
+ "Invalid template `%s`. `%s` provides the following templates:%n%n%s",
+ template, getTemplatesName(smithyTemplatesNode), getTemplateList(smithyTemplatesNode, env)));
}
- // Retrieve template path from smithy-templates.json
- final String templatePath = templatesNode
- .expectObjectMember(template)
- .expectObjectNode()
- .expectMember("path", String.format("Missing expected member `path` from `%s` object", template))
- .expectStringNode()
- .getValue();
+ ObjectNode templateNode = templatesNode.expectObjectMember(template).expectObjectNode();
+
+ final String templatePath = getTemplatePath(templateNode, template);
+ List includedFiles = getIncludedFiles(templateNode);
// Specify the subdirectory to download
exec(ListUtils.of("git", "sparse-checkout", "set", "--no-cone", templatePath), temp);
-
+ // add any additional files that should be included
+ for (String includedFile : includedFiles) {
+ exec(ListUtils.of("git", "sparse-checkout", "add", "--no-cone", includedFile), temp);
+ }
exec(ListUtils.of("git", "checkout"), temp);
// Use templateName if directory is not specified
@@ -181,7 +185,9 @@ private void cloneTemplate(Path temp, ObjectNode smithyTemplatesNode, String tem
directory = template;
}
- IoUtils.copyDir(Paths.get(temp.toString(), templatePath), Paths.get(directory));
+ final Path dest = Paths.get(directory);
+ IoUtils.copyDir(Paths.get(temp.toString(), templatePath), dest);
+ copyIncludedFiles(temp.toString(), dest.toString(), includedFiles, template, env);
try (ColorBuffer buffer = ColorBuffer.of(env.colors(), env.stderr())) {
buffer.println(String.format("Smithy project created in directory: %s", directory), ColorTheme.SUCCESS);
@@ -197,22 +203,57 @@ private static void loadSmithyTemplateJsonFile(String repositoryUrl, Path root,
exec(ListUtils.of("git", "checkout"), temp);
}
+ private static ObjectNode getSmithyTemplatesNode(Path jsonFilePath) {
+ return readJsonFileAsNode(Paths.get(jsonFilePath.toString(), SMITHY_TEMPLATE_JSON)).expectObjectNode();
+ }
+
private static ObjectNode getTemplatesNode(ObjectNode smithyTemplatesNode) {
return smithyTemplatesNode
- .expectMember("templates", String.format(
- "Missing expected member `templates` from %s", SMITHY_TEMPLATE_JSON))
- .expectObjectNode();
+ .expectMember(TEMPLATES, String.format(
+ "Missing expected member `%s` from %s", TEMPLATES, SMITHY_TEMPLATE_JSON))
+ .expectObjectNode();
}
private static String getTemplatesName(ObjectNode smithyTemplatesNode) {
return smithyTemplatesNode
- .expectMember(NAME, String.format("Missing expected member `%s` from %s", NAME, SMITHY_TEMPLATE_JSON))
- .expectStringNode()
- .getValue();
+ .expectMember(NAME, String.format(
+ "Missing expected member `%s` from %s", NAME, SMITHY_TEMPLATE_JSON))
+ .expectStringNode()
+ .getValue();
}
- private static ObjectNode getSmithyTemplatesNode(Path jsonFilePath) {
- return readJsonFileAsNode(Paths.get(jsonFilePath.toString(), SMITHY_TEMPLATE_JSON)).expectObjectNode();
+ private static String getTemplatePath(ObjectNode templateNode, String templateName) {
+ return templateNode
+ .expectMember(PATH, String.format("Missing expected member `%s` from `%s` object", PATH, templateName))
+ .expectStringNode()
+ .getValue();
+ }
+
+ private static List getIncludedFiles(ObjectNode templateNode) {
+ List includedPaths = new ArrayList<>();
+ templateNode.getArrayMember(INCLUDED, StringNode::getValue, includedPaths::addAll);
+ return includedPaths;
+ }
+
+ private static void copyIncludedFiles(String temp, String dest, List includedFiles,
+ String templateName, Env env) throws IOException {
+ for (String included : includedFiles) {
+ final Path includedPath = Paths.get(temp, included);
+ if (!Files.exists(includedPath)) {
+ try (ColorBuffer buffer = ColorBuffer.of(env.colors(), env.stderr())) {
+ buffer.println(String.format(
+ "File or directory %s is marked for inclusion in template %s but was not found",
+ included, templateName), ColorTheme.WARNING);
+ }
+ }
+
+ Path target = Paths.get(dest, Objects.requireNonNull(includedPath.getFileName()).toString());
+ if (Files.isDirectory(includedPath)) {
+ IoUtils.copyDir(includedPath, target);
+ } else if (Files.isRegularFile(includedPath)) {
+ Files.copy(includedPath, target);
+ }
+ }
}
private static String exec(List args, Path directory) {