Skip to content

Commit

Permalink
Add Smithy CLI init command
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewFossAWS committed Jun 15, 2023
1 parent ad6d20b commit 48cd0bf
Show file tree
Hide file tree
Showing 10 changed files with 511 additions and 1 deletion.
121 changes: 121 additions & 0 deletions smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package software.amazon.smithy.cli;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.utils.IoUtils;
import software.amazon.smithy.utils.ListUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;

public class InitCommandTest {
private static final String PROJECT_NAME = "smithy-templates";

@Test
public void init() {
IntegUtils.withProject(PROJECT_NAME, templatesDir -> {
setupTemplatesDirectory(templatesDir);

IntegUtils.withTempDir("exitZero", dir -> {
RunResult result = IntegUtils.run(
dir, ListUtils.of("init", "-t", "quickstart-cli", "-u", templatesDir.toString()));
assertThat(result.getOutput(),
containsString("Smithy project created in directory: quickstart-cli"));
assertThat(result.getExitCode(), is(0));
assertThat(Files.exists(Paths.get(dir.toString(), "quickstart-cli")), is(true));
});
});
}

@Test
public void missingTemplate() {
IntegUtils.withProject(PROJECT_NAME, templatesDir -> {
setupTemplatesDirectory(templatesDir);

IntegUtils.withTempDir("missingTemplate", dir -> {
RunResult result = IntegUtils.run(
dir, ListUtils.of("init", "-u", templatesDir.toString()));
assertThat(result.getOutput(),
containsString("Please specify a template using `--template` or `-t`"));
assertThat(result.getExitCode(), is(1));
});

IntegUtils.withTempDir("emptyTemplateName", dir -> {
RunResult result = IntegUtils.run(
dir, ListUtils.of("init", "-t", "", "-u", templatesDir.toString()));
assertThat(result.getOutput(),
containsString("Please specify a template using `--template` or `-t`"));
assertThat(result.getExitCode(), is(1));
});
});
}

@Test
public void unexpectedTemplate() {
IntegUtils.withProject(PROJECT_NAME, templatesDir -> {
setupTemplatesDirectory(templatesDir);

IntegUtils.withTempDir("unexpectedTemplate", dir -> {
RunResult result = IntegUtils.run(
dir, ListUtils.of("init", "-t", "blabla", "-u", templatesDir.toString()));
assertThat(result.getOutput(),
containsString("Missing expected member `blabla` from `templates` object ([3, 18])"));
assertThat(result.getExitCode(), is(1));
});
});
}

@Test
public void withDirectoryArg() {
IntegUtils.withProject(PROJECT_NAME, templatesDir -> {
setupTemplatesDirectory(templatesDir);

IntegUtils.withTempDir("withDirectoryArg", dir -> {
RunResult result = IntegUtils.run(dir, ListUtils.of(
"init", "-t", "quickstart-cli", "-o", "hello-world", "-u", templatesDir.toString()));
assertThat(result.getOutput(),
containsString("Smithy project created in directory: hello-world"));
assertThat(result.getExitCode(), is(0));
assertThat(Files.exists(Paths.get(dir.toString(), "hello-world")), is(true));
});
});
}

@Test
public void withLongHandArgs() {
IntegUtils.withProject(PROJECT_NAME, templatesDir -> {
setupTemplatesDirectory(templatesDir);

IntegUtils.withTempDir("withLongHandArgs", dir -> {
RunResult result = IntegUtils.run(dir, ListUtils.of(
"init", "--template", "quickstart-cli", "--output", "hello-world", "--url",
templatesDir.toString()));
assertThat(result.getOutput(),
containsString("Smithy project created in directory: hello-world"));
assertThat(result.getExitCode(), is(0));
assertThat(Files.exists(Paths.get(dir.toString(), "hello-world")), is(true));
});
});
}

private static void run(List<String> args, Path root) {
StringBuilder output = new StringBuilder();
int result = IoUtils.runCommand(args, root, output, Collections.emptyMap());
if (result != 0) {
throw new RuntimeException("Error running command: " + args + ": " + output);
}
}

private void setupTemplatesDirectory(Path dir) {
run(ListUtils.of("git", "init"), dir);
run(ListUtils.of("git", "config", "user.email", "you@example.com"), dir);
run(ListUtils.of("git", "config", "user.name", "Your Name"), dir);
run(ListUtils.of("git", "checkout", "-b", "main"), dir);
run(ListUtils.of("git", "add", "-A"), dir);
run(ListUtils.of("git", "commit", "-m", "Foo"), dir);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
smithyVersion = 1.31.0

clean:
rm -rf weather-service/build/

build: clean
@echo Building getting-started-example...
(cd weather-service; smithy build)
@echo getting-started-example built successfully

test: build
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Getting started Example
This example provides a complete Smithy model for the Weather Service from the
[Smithy Quick Start Guide](https://smithy.io/2.0/quickstart.html).

## Using
To use this example as a template run the following.

```
smithy init --template quickstart-cli
```

*Note*: You will need the [Smithy CLI](https://smithy.io/2.0/guides/smithy-cli/index.html) installed to use this command.
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.


## Building
To build this example run:
```
smithy build
```
From the root of this example directory.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": "1.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "Smithy-Examples",
"templates": {
"quickstart-cli": {
"documentation": "Smithy Quickstart example weather service using the Smithy CLI.",
"path": "getting-started-example/weather-service"
}
}
}
Loading

0 comments on commit 48cd0bf

Please sign in to comment.