diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17a64fb3add11318d595791012a38e2c6b68688f..85bccf7d1996fd37c12b58cf1b33baf251b72923 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # CHANGELOG
 
+## 0.2.4
+- Improved layout of `server-appengine`.
+
 ## 0.2.3+1
 - fixed an issue with the Polymer template
 
diff --git a/README.md b/README.md
index 3c42a1e79b12dff6aac206d2c52023ad6541e123..4c08fc28b2ac08781d50a6c53552b4904cddf8f1 100644
--- a/README.md
+++ b/README.md
@@ -27,25 +27,32 @@ Requirements:
 
 To install:
 
-    $> pub global activate stagehand
+```console
+> pub global activate stagehand
+```
 
-To update:
+To update, run activate again:
 
-    # activate stagehand again
-    $> pub global activate stagehand
+```console
+> pub global activate stagehand
+```
 
 ## Usage
 
 Stagehand will generate a project skeleton into the current directory. As an
 example, here is how you create a package with Stagehand:
 
-    $> mkdir fancy_project
-    $> cd fancy_project
-    $> stagehand package-simple
+```console
+> mkdir fancy_project
+> cd fancy_project
+> stagehand package-simple
+```
 
 And to list all of the project templates:
 
-    $> stagehand
+```console
+> stagehand
+```
 
 ## Goals
 
diff --git a/lib/generators/console_full_data.dart b/lib/generators/console_full_data.dart
index f53bc15be9b080381f1a4219f8354fe4c6283d63..7b8f18f211b31aa3febf85e97a5fdb522cd95af9 100644
--- a/lib/generators/console_full_data.dart
+++ b/lib/generators/console_full_data.dart
@@ -2,7 +2,7 @@
 // All rights reserved. Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-List<String> data = [
+const List<String> data = const <String>[
   ".gitignore",
   "text",
   """LmJ1aWxkbG9nCi5EU19TdG9yZQouaWRlYQoucGFja2FnZXMKLnB1Yi8KYnVpbGQvCnBhY2thZ2Vz
diff --git a/lib/generators/console_simple_data.dart b/lib/generators/console_simple_data.dart
index 7dfac3584cb2d2e2542b862c57d7d29c0dfba327..f169b94fd0cd27276035d3feb6137b4b26dbc699 100644
--- a/lib/generators/console_simple_data.dart
+++ b/lib/generators/console_simple_data.dart
@@ -2,7 +2,7 @@
 // All rights reserved. Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-List<String> data = [
+const List<String> data = const <String>[
   "bin/main.dart",
   "text",
   "bWFpbihMaXN0PFN0cmluZz4gYXJncykgewogIHByaW50KCdIZWxsbyB3b3JsZCEnKTsKfQo=",
diff --git a/lib/generators/package_simple_data.dart b/lib/generators/package_simple_data.dart
index 66b40a58332b5f14598b979b34614c52d29f33a6..7e80219ffac59cdf12624d65aa9d0022ee3b0a79 100644
--- a/lib/generators/package_simple_data.dart
+++ b/lib/generators/package_simple_data.dart
@@ -2,7 +2,7 @@
 // All rights reserved. Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-List<String> data = [
+const List<String> data = const <String>[
   ".gitignore",
   "text",
   """LmJ1aWxkbG9nCi5EU19TdG9yZQouaWRlYQoucGFja2FnZXMKLnB1Yi8KYnVpbGQvCnBhY2thZ2Vz
diff --git a/lib/generators/server_appengine_data.dart b/lib/generators/server_appengine_data.dart
index f555ab264e885bb97fd39ad9b0c2378344f1bc93..0057aa04202b9e9364a30dc49cfeb653c560aff9 100644
--- a/lib/generators/server_appengine_data.dart
+++ b/lib/generators/server_appengine_data.dart
@@ -2,7 +2,7 @@
 // All rights reserved. Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-List<String> data = [
+const List<String> data = const <String>[
   ".gitignore",
   "text",
   """LmJ1aWxkbG9nCi5EU19TdG9yZQouaWRlYQoucGFja2FnZXMKLnB1Yi8KYnVpbGQvCnBhY2thZ2Vz
@@ -129,36 +129,37 @@ bGljZW5zZSB0aGF0IGNhbiBiZSBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlLgoKbGlicmFyeSB7
 e3Byb2plY3ROYW1lfX0ubWVtY2FjaGU7CgppbXBvcnQgJ2RhcnQ6YXN5bmMnOwppbXBvcnQgJ2Rh
 cnQ6aW8nOwoKaW1wb3J0ICdwYWNrYWdlOmFwcGVuZ2luZS9hcHBlbmdpbmUuZGFydCc7Cgpjb25z
 dCBTdHJpbmcgREVGQVVMVF9LRVkgPSAnaGVsbG8nOwpib29sIGNhY2hlSW5pdGlhbGl6ZWQgPSBm
-YWxzZTsKCi8vLyBJbml0aWFsaXplIHRoZSBjYWNoZS4KRnV0dXJlIGluaXRpYWxpemUoKSB7CiAg
-Ly8gSWYgdGhlIGNhY2hlIGlzIGFscmVhZHkgaW5pdGlhbGl6ZWQsIGp1c3QgcmV0dXJuLgogIGlm
-IChjYWNoZUluaXRpYWxpemVkKSB7CiAgICByZXR1cm4gbmV3IEZ1dHVyZS52YWx1ZSgpOwogIH0K
-CiAgLy8gVGhlIEFwcEVuZ2luZSBlbnZpcm9ubWVudCBoYXMgYSBwcmVjb25maWd1cmVkICdjb250
-ZXh0JyB3aGljaCBwcm92aWRlcwogIC8vIGF1dGhvcml6ZWQgYWNjZXNzIHRvIHRoZSBkZWZhdWx0
-IGFwaSBzZXJ2aWNlcy4KICB2YXIgbWVtY2FjaGUgPSBjb250ZXh0LnNlcnZpY2VzLm1lbWNhY2hl
-OwoKICAvLyBJbml0aWFsaXplIHRoZSBjYWNoZSBhbmQgc2V0IHRoZSBkZWZhdWx0IHZhbHVlLgog
-IHJldHVybiBtZW1jYWNoZS5jbGVhcigpCiAgICAgIC50aGVuKChfKSA9PiBtZW1jYWNoZS5zZXQo
-REVGQVVMVF9LRVksICd0aGVyZSEnKSkKICAgICAgLnRoZW4oKF8pID0+IGNhY2hlSW5pdGlhbGl6
-ZWQgPSB0cnVlKTsKfQoKLy8vIENsZWFycyB0aGUgY2FjaGUgYW5kIHJlc2V0cyB0aGUgZGVmYXVs
-dC4KRnV0dXJlIGNsZWFyKCkgewogIGNhY2hlSW5pdGlhbGl6ZWQgPSBmYWxzZTsKICByZXR1cm4g
-aW5pdGlhbGl6ZSgpOwp9CgovLy8gSGVscGVyIG1ldGhvZCB0byB3cml0ZSBhIHNldCBvZiBrZXkv
-dmFsdWUgcGFpcnMgdG8gdGhlIG1lbWNhY2hlLgp2b2lkIHdyaXRlKEh0dHBSZXNwb25zZSByZXNw
-b25zZSwgTWFwPFN0cmluZywgU3RyaW5nPiB2YWx1ZU1hcCkgewogIHZhciBtZW1jYWNoZSA9IGNv
-bnRleHQuc2VydmljZXMubWVtY2FjaGU7CiAgRnV0dXJlLmZvckVhY2godmFsdWVNYXAua2V5cywg
-KGtleSkgewogICAgdmFyIHZhbHVlID0gdmFsdWVNYXBba2V5XTsKICAgIHJldHVybiBtZW1jYWNo
-ZS5zZXQoa2V5LCB2YWx1ZSkKICAgICAgICAudGhlbigoXykgPT4gcmVzcG9uc2Uud3JpdGVsbign
-IiR7a2V5fSI6ICIke3ZhbHVlfSInKSk7CiAgfSkud2hlbkNvbXBsZXRlKHJlc3BvbnNlLmNsb3Nl
-KTsKfQoKLy8vIEhlbHBlciBtZXRob2QgdG8gcmVhZCBhIHNldCBvZiB2YWx1ZXMgZnJvbSB0aGUg
-bWVtY2FjaGUuCnZvaWQgcmVhZChIdHRwUmVzcG9uc2UgcmVzcG9uc2UsIEl0ZXJhYmxlPFN0cmlu
-Zz4ga2V5cykgewogIHZhciBtZW1jYWNoZSA9IGNvbnRleHQuc2VydmljZXMubWVtY2FjaGU7CiAg
-RnV0dXJlLmZvckVhY2goa2V5cywgKGtleSkgPT4gbWVtY2FjaGUuZ2V0KGtleSkKICAgICAgLnRo
-ZW4oKHZhbHVlKSA9PiByZXNwb25zZS53cml0ZWxuKCciJHtrZXl9IjogIiR7dmFsdWV9IicpKQog
-ICAgICAuY2F0Y2hFcnJvcigoXykgPT4gcmVzcG9uc2Uud3JpdGVsbignIiR7a2V5fSI6IHZhbHVl
-IG5vdCBmb3VuZCEnKSkpCiAgICAud2hlbkNvbXBsZXRlKHJlc3BvbnNlLmNsb3NlKTsKfQo=""",
+YWxzZTsKCi8vLyBJbml0aWFsaXplIHRoZSBjYWNoZS4KRnV0dXJlIGluaXRpYWxpemUoKSBhc3lu
+YyB7CiAgLy8gSWYgdGhlIGNhY2hlIGlzIGFscmVhZHkgaW5pdGlhbGl6ZWQsIGp1c3QgcmV0dXJu
+LgogIGlmIChjYWNoZUluaXRpYWxpemVkKSByZXR1cm47CgogIC8vIFRoZSBBcHBFbmdpbmUgZW52
+aXJvbm1lbnQgaGFzIGEgcHJlY29uZmlndXJlZCAnY29udGV4dCcgd2hpY2ggcHJvdmlkZXMKICAv
+LyBhdXRob3JpemVkIGFjY2VzcyB0byB0aGUgZGVmYXVsdCBhcGkgc2VydmljZXMuCiAgdmFyIG1l
+bWNhY2hlID0gY29udGV4dC5zZXJ2aWNlcy5tZW1jYWNoZTsKCiAgLy8gSW5pdGlhbGl6ZSB0aGUg
+Y2FjaGUgYW5kIHNldCB0aGUgZGVmYXVsdCB2YWx1ZS4KICBhd2FpdCBtZW1jYWNoZS5jbGVhcigp
+OwogIGF3YWl0IG1lbWNhY2hlLnNldChERUZBVUxUX0tFWSwgJ3RoZXJlIScpOwogIGNhY2hlSW5p
+dGlhbGl6ZWQgPSB0cnVlOwp9CgovLy8gQ2xlYXJzIHRoZSBjYWNoZSBhbmQgcmVzZXRzIHRoZSBk
+ZWZhdWx0LgpGdXR1cmUgY2xlYXIoKSBhc3luYyB7CiAgY2FjaGVJbml0aWFsaXplZCA9IGZhbHNl
+OwogIGF3YWl0IGluaXRpYWxpemUoKTsKfQoKLy8vIEhlbHBlciBtZXRob2QgdG8gd3JpdGUgYSBz
+ZXQgb2Yga2V5L3ZhbHVlIHBhaXJzIHRvIHRoZSBtZW1jYWNoZS4Kdm9pZCB3cml0ZShIdHRwUmVz
+cG9uc2UgcmVzcG9uc2UsIE1hcDxTdHJpbmcsIFN0cmluZz4gdmFsdWVNYXApIHsKICB2YXIgbWVt
+Y2FjaGUgPSBjb250ZXh0LnNlcnZpY2VzLm1lbWNhY2hlOwogIEZ1dHVyZS5mb3JFYWNoKHZhbHVl
+TWFwLmtleXMsIChrZXkpIHsKICAgIHZhciB2YWx1ZSA9IHZhbHVlTWFwW2tleV07CiAgICByZXR1
+cm4gbWVtY2FjaGUKICAgICAgICAuc2V0KGtleSwgdmFsdWUpCiAgICAgICAgLnRoZW4oKF8pID0+
+IHJlc3BvbnNlLndyaXRlbG4oJyIke2tleX0iOiAiJHt2YWx1ZX0iJykpOwogIH0pLndoZW5Db21w
+bGV0ZShyZXNwb25zZS5jbG9zZSk7Cn0KCi8vLyBIZWxwZXIgbWV0aG9kIHRvIHJlYWQgYSBzZXQg
+b2YgdmFsdWVzIGZyb20gdGhlIG1lbWNhY2hlLgp2b2lkIHJlYWQoSHR0cFJlc3BvbnNlIHJlc3Bv
+bnNlLCBJdGVyYWJsZTxTdHJpbmc+IGtleXMpIHsKICB2YXIgbWVtY2FjaGUgPSBjb250ZXh0LnNl
+cnZpY2VzLm1lbWNhY2hlOwogIEZ1dHVyZQogICAgICAuZm9yRWFjaCgKICAgICAgICAgIGtleXMs
+CiAgICAgICAgICAoa2V5KSA9PiBtZW1jYWNoZQogICAgICAgICAgICAgIC5nZXQoa2V5KQogICAg
+ICAgICAgICAgIC50aGVuKCh2YWx1ZSkgPT4gcmVzcG9uc2Uud3JpdGVsbignIiR7a2V5fSI6ICIk
+e3ZhbHVlfSInKSkKICAgICAgICAgICAgICAuY2F0Y2hFcnJvcigKICAgICAgICAgICAgICAgICAg
+KF8pID0+IHJlc3BvbnNlLndyaXRlbG4oJyIke2tleX0iOiB2YWx1ZSBub3QgZm91bmQhJykpKQog
+ICAgICAud2hlbkNvbXBsZXRlKHJlc3BvbnNlLmNsb3NlKTsKfQo=""",
   "pubspec.yaml",
   "text",
   """bmFtZToge3twcm9qZWN0TmFtZX19CnZlcnNpb246IDAuMC4xCmRlc2NyaXB0aW9uOiBBIHNpbXBs
 ZSBBcHAgRW5naW5lIGFwcGxpY2F0aW9uLgojYXV0aG9yOiB7e2F1dGhvcn19IDxlbWFpbEBleGFt
 cGxlLmNvbT4KI2hvbWVwYWdlOiBodHRwczovL3d3dy5leGFtcGxlLmNvbQoKZW52aXJvbm1lbnQ6
-CiAgc2RrOiAnPj0xLjUuMCA8Mi4wLjAnCgpkZXBlbmRlbmNpZXM6CiAgYXBwZW5naW5lOiAnPj0w
+CiAgc2RrOiAnPj0xLjkuMCA8Mi4wLjAnCgpkZXBlbmRlbmNpZXM6CiAgYXBwZW5naW5lOiAnPj0w
 LjMuMCA8MC40LjAnCg=="""
 ];
diff --git a/lib/generators/server_shelf_data.dart b/lib/generators/server_shelf_data.dart
index a7ca88fdedd6e178a2d0374928183cd937110340..524c0caa2f1c4c60fd7f8f01557a0678d0803bbc 100644
--- a/lib/generators/server_shelf_data.dart
+++ b/lib/generators/server_shelf_data.dart
@@ -2,7 +2,7 @@
 // All rights reserved. Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-List<String> data = [
+const List<String> data = const <String>[
   ".gitignore",
   "text",
   """LmJ1aWxkbG9nCi5EU19TdG9yZQouaWRlYQoucGFja2FnZXMKLnB1Yi8KYnVpbGQvCnBhY2thZ2Vz
diff --git a/lib/generators/web_polymer_data.dart b/lib/generators/web_polymer_data.dart
index ae54954a9abb7d9c864902805875fb18fc839cd1..1f24b52c46502cb0609a338bbe67ebe818d0f796 100644
--- a/lib/generators/web_polymer_data.dart
+++ b/lib/generators/web_polymer_data.dart
@@ -2,7 +2,7 @@
 // All rights reserved. Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-List<String> data = [
+const List<String> data = const <String>[
   ".gitignore",
   "text",
   """LmJ1aWxkbG9nCi5EU19TdG9yZQouaWRlYQoucGFja2FnZXMKLnB1Yi8KYnVpbGQvCnBhY2thZ2Vz
diff --git a/lib/generators/web_simple_data.dart b/lib/generators/web_simple_data.dart
index b3e9085e31753f133232e8dc2838c73108c166e3..d914eef49a5dcaa48187f2ca80d292787d4468fb 100644
--- a/lib/generators/web_simple_data.dart
+++ b/lib/generators/web_simple_data.dart
@@ -2,7 +2,7 @@
 // All rights reserved. Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-List<String> data = [
+const List<String> data = const <String>[
   ".gitignore",
   "text",
   """LmJ1aWxkbG9nCi5EU19TdG9yZQouaWRlYQoucGFja2FnZXMKLnB1Yi8KYnVpbGQvCnBhY2thZ2Vz
diff --git a/templates/server-appengine/lib/memcache.dart b/templates/server-appengine/lib/memcache.dart
index f343064c796954d0ba20340d66835c8e09b22689..aeb58052417a4624cf27cef876d9d3d5bd808a0d 100644
--- a/templates/server-appengine/lib/memcache.dart
+++ b/templates/server-appengine/lib/memcache.dart
@@ -12,26 +12,24 @@ const String DEFAULT_KEY = 'hello';
 bool cacheInitialized = false;
 
 /// Initialize the cache.
-Future initialize() {
+Future initialize() async {
   // If the cache is already initialized, just return.
-  if (cacheInitialized) {
-    return new Future.value();
-  }
+  if (cacheInitialized) return;
 
   // The AppEngine environment has a preconfigured 'context' which provides
   // authorized access to the default api services.
   var memcache = context.services.memcache;
 
   // Initialize the cache and set the default value.
-  return memcache.clear()
-      .then((_) => memcache.set(DEFAULT_KEY, 'there!'))
-      .then((_) => cacheInitialized = true);
+  await memcache.clear();
+  await memcache.set(DEFAULT_KEY, 'there!');
+  cacheInitialized = true;
 }
 
 /// Clears the cache and resets the default.
-Future clear() {
+Future clear() async {
   cacheInitialized = false;
-  return initialize();
+  await initialize();
 }
 
 /// Helper method to write a set of key/value pairs to the memcache.
@@ -39,7 +37,8 @@ void write(HttpResponse response, Map<String, String> valueMap) {
   var memcache = context.services.memcache;
   Future.forEach(valueMap.keys, (key) {
     var value = valueMap[key];
-    return memcache.set(key, value)
+    return memcache
+        .set(key, value)
         .then((_) => response.writeln('"${key}": "${value}"'));
   }).whenComplete(response.close);
 }
@@ -47,8 +46,13 @@ void write(HttpResponse response, Map<String, String> valueMap) {
 /// Helper method to read a set of values from the memcache.
 void read(HttpResponse response, Iterable<String> keys) {
   var memcache = context.services.memcache;
-  Future.forEach(keys, (key) => memcache.get(key)
-      .then((value) => response.writeln('"${key}": "${value}"'))
-      .catchError((_) => response.writeln('"${key}": value not found!')))
-    .whenComplete(response.close);
+  Future
+      .forEach(
+          keys,
+          (key) => memcache
+              .get(key)
+              .then((value) => response.writeln('"${key}": "${value}"'))
+              .catchError(
+                  (_) => response.writeln('"${key}": value not found!')))
+      .whenComplete(response.close);
 }
diff --git a/templates/server-appengine/pubspec.yaml b/templates/server-appengine/pubspec.yaml
index b4ba029660de347ca6fb90fe30d6ef3796e21bbc..260abc43ca16aa555b58960367e4630f4b5fa48b 100644
--- a/templates/server-appengine/pubspec.yaml
+++ b/templates/server-appengine/pubspec.yaml
@@ -5,7 +5,7 @@ description: A simple App Engine application.
 #homepage: https://www.example.com
 
 environment:
-  sdk: '>=1.5.0 <2.0.0'
+  sdk: '>=1.9.0 <2.0.0'
 
 dependencies:
   appengine: '>=0.3.0 <0.4.0'
diff --git a/test/validate_templates.dart b/test/validate_templates.dart
index 5b9b92944162c52b08c3c8cd615b498dd22f2ff0..6f39f90c8aa632c834f3c81206788d06df7ad074 100644
--- a/test/validate_templates.dart
+++ b/test/validate_templates.dart
@@ -9,7 +9,7 @@ library stagehand.test.validate_templates;
 
 import 'dart:io';
 
-import 'package:grinder/grinder.dart';
+import 'package:grinder/grinder.dart' hide fail;
 import 'package:path/path.dart' as path;
 import 'package:stagehand/stagehand.dart' as stagehand;
 import 'package:test/test.dart';
@@ -44,7 +44,7 @@ void _testGenerator(stagehand.Generator generator, Directory tempDir) {
   var pubspecFile = new File(pubspecPath);
 
   if (!pubspecFile.existsSync()) {
-    throw 'A pubspec much be defined!';
+    fail('A pubspec much be defined!');
   }
 
   Pub.get(runOptions: new RunOptions(workingDirectory: tempDir.path));
diff --git a/tool/grind.dart b/tool/grind.dart
index 8d43e3eefa5cc7cf216d005dab4107575528bf4d..17534070b0b19c864811f2453344b9f000cfbc33 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -60,18 +60,14 @@ test() => new TestRunner().testAsync(files: 'test/validate_templates.dart');
 void _concatenateFiles(Directory src, File target) {
   log('Creating ${target.path}');
 
-  List<String> results = [];
-
-  _traverse(src, '', results);
-
-  String str = results.map((s) => '  ${_toStr(s)}').join(',\n');
+  String str = _traverse(src, '').map((s) => '  ${_toStr(s)}').join(',\n');
 
   target.writeAsStringSync("""
 // Copyright (c) 2014, Google Inc. Please see the AUTHORS file for details.
 // All rights reserved. Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-List<String> data = [
+const List<String> data = const <String>[
 ${str}
 ];
 """);
@@ -85,27 +81,22 @@ String _toStr(String s) {
   }
 }
 
-void _traverse(Directory dir, String root, List<String> results) {
+Iterable<String> _traverse(Directory dir, String root) sync* {
   var files = _listSync(dir, recursive: false, followLinks: false);
   for (FileSystemEntity entity in files) {
-    String name = path.basename(entity.path);
-
     if (entity is Link) continue;
 
+    String name = path.basename(entity.path);
     if (name == 'pubspec.lock') continue;
     if (name.startsWith('.') && name != '.gitignore') continue;
 
     if (entity is Directory) {
-      _traverse(entity, '${root}${name}/', results);
+      yield* _traverse(entity, '${root}${name}/');
     } else {
-      File file = entity;
-      String fileType = _isBinaryFile(name) ? 'binary' : 'text';
-      String data = CryptoUtils.bytesToBase64(file.readAsBytesSync(),
+      yield '${root}${name}';
+      yield _isBinaryFile(name) ? 'binary' : 'text';
+      yield BASE64.encode((entity as File).readAsBytesSync(),
           addLineSeparator: true);
-
-      results.add('${root}${name}');
-      results.add(fileType);
-      results.add(data);
     }
   }
 }