Version 2.14.0-93.0.dev

Merge commit 'd0f7a5b6d1011e60fe70fc6a9339b98b999c873d' into 'dev'
diff --git a/pkg/dartdev/lib/src/templates.dart b/pkg/dartdev/lib/src/templates.dart
index 0b5aa0d..53666a3 100644
--- a/pkg/dartdev/lib/src/templates.dart
+++ b/pkg/dartdev/lib/src/templates.dart
@@ -9,7 +9,7 @@
 import 'templates/console_full.dart';
 import 'templates/console_simple.dart';
 import 'templates/package_simple.dart';
-import 'templates/server_simple.dart';
+import 'templates/server_shelf.dart';
 import 'templates/web_simple.dart';
 
 final _substituteRegExp = RegExp(r'__([a-zA-Z]+)__');
@@ -19,7 +19,7 @@
   ConsoleSimpleGenerator(),
   ConsoleFullGenerator(),
   PackageSimpleGenerator(),
-  ServerSimpleGenerator(),
+  ServerShelfGenerator(),
   WebSimpleGenerator(),
 ];
 
diff --git a/pkg/dartdev/lib/src/templates/server_shelf.dart b/pkg/dartdev/lib/src/templates/server_shelf.dart
new file mode 100644
index 0000000..b3bbba7
--- /dev/null
+++ b/pkg/dartdev/lib/src/templates/server_shelf.dart
@@ -0,0 +1,209 @@
+// Copyright (c) 2021, the Dart project authors. 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.
+
+import '../templates.dart';
+import 'common.dart' as common;
+
+/// A generator for a server app using `package:shelf`.
+class ServerShelfGenerator extends DefaultGenerator {
+  ServerShelfGenerator()
+      : super(
+            'server-shelf', 'Server app', 'A server app using `package:shelf`',
+            categories: const ['dart', 'server']) {
+    addFile('.gitignore', common.gitignore);
+    addFile('analysis_options.yaml', common.analysisOptions);
+    addFile('CHANGELOG.md', common.changelog);
+    addFile('pubspec.yaml', _pubspec);
+    addFile('README.md', _readme);
+    addFile('Dockerfile', _dockerfile);
+    addFile('.dockerignore', _dockerignore);
+    addFile('test/server_test.dart', _test);
+    setEntrypoint(
+      addFile('bin/server.dart', _main),
+    );
+  }
+
+  @override
+  String getInstallInstructions() => '${super.getInstallInstructions()}\n'
+      'run your app using `dart run ${entrypoint.path}`.';
+}
+
+final String _pubspec = '''
+name: __projectName__
+description: A server app using the shelf package and Docker.
+version: 1.0.0
+# homepage: https://www.example.com
+
+environment:
+  sdk: ">=2.12.0 <3.0.0"
+
+dependencies:
+  args: ^2.0.0
+  shelf: ^1.1.0
+  shelf_router: ^1.0.0
+
+dev_dependencies:
+  http: ^0.13.0
+  pedantic: ^1.10.0
+  test_process: ^2.0.0
+  test: ^1.15.0
+''';
+
+final String _readme = '''
+A server app built using [Shelf](https://pub.dev/packages/shelf),
+configured to enable running with [Docker](https://www.docker.com/).
+
+This sample code handles HTTP GET requests to `/` and `/echo/<message>`
+
+# Running the sample
+
+## Running with the Dart SDK
+
+You can run the example with the [Dart SDK](https://dart.dev/get-dart)
+like this:
+
+```
+\$ dart run bin/server.dart
+Server listening on port 8080
+```
+
+And then from a second terminal:
+```
+\$ curl http://0.0.0.0:8080
+Hello, World!
+\$ curl http://0.0.0.0:8080/echo/I_love_Dart
+I_love_Dart
+```
+
+## Running with Docker
+
+If you have [Docker Desktop](https://www.docker.com/get-started) installed, you
+can build and run with the `docker` command:
+
+```
+\$ docker build . -t myserver
+\$ docker run -it -p 8080:8080 myserver
+Server listening on port 8080
+```
+
+And then from a second terminal:
+```
+\$ curl http://0.0.0.0:8080
+Hello, World!
+\$ curl http://0.0.0.0:8080/echo/I_love_Dart
+I_love_Dart
+```
+
+You should see the logging printed in the first terminal:
+```
+2021-05-06T15:47:04.620417  0:00:00.000158 GET     [200] /
+2021-05-06T15:47:08.392928  0:00:00.001216 GET     [200] /echo/I_love_Dart
+```
+''';
+
+final String _main = r'''
+import 'dart:io';
+
+import 'package:shelf/shelf.dart';
+import 'package:shelf/shelf_io.dart';
+import 'package:shelf_router/shelf_router.dart';
+
+// Configure routes.
+final _router = Router()
+  ..get('/', _rootHandler)
+  ..get('/echo/<message>', _echoHandler);
+
+Response _rootHandler(Request req) {
+  return Response.ok('Hello, World!\n');
+}
+
+Response _echoHandler(Request request) {
+  final message = params(request, 'message');
+  return Response.ok('$message\n');
+}
+
+void main(List<String> args) async {
+  // Use any available host or container IP (usually `0.0.0.0`).
+  final ip = InternetAddress.anyIPv4;
+
+  // Configure a pipeline that logs requests.
+  final _handler = Pipeline().addMiddleware(logRequests()).addHandler(_router);
+
+  // For running in containers, we respect the PORT environment variable.
+  final port = int.parse(Platform.environment['PORT'] ?? '8080');
+  final server = await serve(_handler, ip, port);
+  print('Server listening on port ${server.port}');
+}
+''';
+
+final String _dockerfile = r'''
+# Use latest stable channel SDK.
+FROM dart:stable AS build
+
+# Resolve app dependencies.
+WORKDIR /app
+COPY pubspec.* .
+RUN dart pub get
+
+# Copy app source code (except anything in .dockerignore) and AOT compile app.
+COPY . .
+RUN dart compile exe bin/server.dart -o /server
+
+# Build minimal serving image from AOT-compiled `/server`
+# and the pre-built AOT-runtime in the `/runtime/` directory of the base image.
+FROM scratch
+COPY --from=build /runtime/ /
+COPY --from=build /server /bin/
+
+# Start server.
+EXPOSE 8080
+CMD ["/bin/server"]
+''';
+
+final String _dockerignore = r'''
+.dockerignore
+Dockerfile
+build/
+.dart_tool/
+.git/
+.github/
+.gitignore
+.idea/
+.packages
+''';
+
+final String _test = r'''
+import 'package:http/http.dart';
+import 'package:test/test.dart';
+import 'package:test_process/test_process.dart';
+
+void main() {
+  final port = '8080';
+  final host = 'http://0.0.0.0:$port';
+
+  setUp(() async {
+    await TestProcess.start(
+      'dart',
+      ['run', 'bin/server.dart'],
+      environment: {'PORT': port},
+    );
+  });
+
+  test('Root', () async {
+    final response = await get(Uri.parse(host + '/'));
+    expect(response.statusCode, 200);
+    expect(response.body, 'Hello, World!\n');
+  });
+
+  test('Echo', () async {
+    final response = await get(Uri.parse(host + '/echo/hello'));
+    expect(response.statusCode, 200);
+    expect(response.body, 'hello\n');
+  });
+  test('404', () async {
+    final response = await get(Uri.parse(host + '/foobar'));
+    expect(response.statusCode, 404);
+  });
+}
+''';
diff --git a/pkg/dartdev/lib/src/templates/server_simple.dart b/pkg/dartdev/lib/src/templates/server_simple.dart
deleted file mode 100644
index 6688f23..0000000
--- a/pkg/dartdev/lib/src/templates/server_simple.dart
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2021, the Dart project authors. 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.
-
-import '../templates.dart';
-import 'common.dart' as common;
-
-/// A generator for a server app built on `package:shelf`.
-class ServerSimpleGenerator extends DefaultGenerator {
-  ServerSimpleGenerator()
-      : super('server-simple', 'Web Server',
-            'A web server built using package:shelf.',
-            categories: const ['dart', 'server']) {
-    addFile('.gitignore', common.gitignore);
-    addFile('analysis_options.yaml', common.analysisOptions);
-    addFile('CHANGELOG.md', common.changelog);
-    addFile('pubspec.yaml', _pubspec);
-    addFile('README.md', _readme);
-    setEntrypoint(
-      addFile('bin/server.dart', _main),
-    );
-  }
-
-  @override
-  String getInstallInstructions() => '${super.getInstallInstructions()}\n'
-      'run your app using `dart ${entrypoint.path}`.';
-}
-
-final String _pubspec = '''
-name: __projectName__
-description: A web server built using the shelf package.
-version: 1.0.0
-# homepage: https://www.example.com
-
-environment:
-  sdk: ">=2.12.0 <3.0.0"
-
-dependencies:
-  args: ^2.0.0
-  shelf: ^1.1.0
-
-dev_dependencies:
-  pedantic: ^1.10.0
-''';
-
-final String _readme = '''
-A web server built using [Shelf](https://pub.dev/packages/shelf).
-''';
-
-final String _main = r'''
-import 'dart:io';
-
-import 'package:args/args.dart';
-import 'package:shelf/shelf.dart' as shelf;
-import 'package:shelf/shelf_io.dart' as io;
-
-// For Google Cloud Run, set _hostname to '0.0.0.0'.
-const _hostname = 'localhost';
-
-void main(List<String> args) async {
-  var parser = ArgParser()..addOption('port', abbr: 'p');
-  var result = parser.parse(args);
-
-  // For Google Cloud Run, we respect the PORT environment variable
-  var portStr = result['port'] ?? Platform.environment['PORT'] ?? '8080';
-  var port = int.tryParse(portStr);
-
-  if (port == null) {
-    stdout.writeln('Could not parse port value "$portStr" into a number.');
-    // 64: command line usage error
-    exitCode = 64;
-    return;
-  }
-
-  var handler = const shelf.Pipeline()
-      .addMiddleware(shelf.logRequests())
-      .addHandler(_echoRequest);
-
-  var server = await io.serve(handler, _hostname, port);
-  print('Serving at http://${server.address.host}:${server.port}');
-}
-
-shelf.Response _echoRequest(shelf.Request request) =>
-    shelf.Response.ok('Request for "${request.url}"');
-''';
diff --git a/tools/VERSION b/tools/VERSION
index c2db219..99a1532 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 92
+PRERELEASE 93
 PRERELEASE_PATCH 0
\ No newline at end of file