OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library pub.command.serve; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:io'; | |
9 | |
10 import 'package:barback/barback.dart'; | |
11 import 'package:path/path.dart' as path; | |
12 | |
13 import '../command.dart'; | |
14 import '../entrypoint.dart'; | |
15 import '../exit_codes.dart' as exit_codes; | |
16 import '../log.dart' as log; | |
17 import '../utils.dart'; | |
18 | |
19 final _green = getPlatformString('\u001b[32m'); | |
20 final _red = getPlatformString('\u001b[31m'); | |
21 final _none = getPlatformString('\u001b[0m'); | |
22 | |
23 /// Handles the `serve` pub command. | |
24 class ServeCommand extends PubCommand { | |
25 String get description => "Run a local web development server."; | |
26 String get usage => 'pub serve'; | |
27 | |
28 ServeCommand() { | |
29 commandParser.addOption('port', defaultsTo: '8080', | |
30 help: 'The port to listen on.'); | |
31 } | |
32 | |
33 Future onRun() { | |
34 return PubPackageProvider.create(entrypoint).then((provider) { | |
35 var port; | |
36 try { | |
37 port = int.parse(commandOptions['port']); | |
38 } on FormatException catch(_) { | |
39 log.error('Could not parse port "${commandOptions['port']}"'); | |
40 this.printUsage(); | |
41 exit(exit_codes.USAGE); | |
42 } | |
nweiz
2013/07/25 22:58:58
Style nit: put this in a getter to avoid the awkwa
Bob Nystrom
2013/07/26 20:20:11
It is a bit awkward, but I'm not crazy about a get
| |
43 | |
44 var barback = new Barback(provider); | |
45 | |
46 barback.results.listen((result) { | |
nweiz
2013/07/25 22:58:58
Add a TODO to report errors and successful build c
Bob Nystrom
2013/07/26 20:20:11
Do we actually intend to do that? That seems painf
nweiz
2013/07/29 20:23:14
In my experience, people like it. We can be smart
Bob Nystrom
2013/07/29 21:45:43
Done.
| |
47 if (result.succeeded) { | |
48 log.message("Build completed ${_green}successfully$_none"); | |
49 } else { | |
50 log.message("Build completed with " | |
51 "${_red}${result.errors.length}$_none errors."); | |
52 } | |
53 }); | |
54 | |
55 barback.errors.listen((error) { | |
56 log.error("${_red}Build error:\n$error$_none"); | |
57 }); | |
58 | |
59 // Add all of the visible files. | |
60 for (var package in provider.packages) { | |
61 barback.updateSources(provider.listAssets(package)); | |
nweiz
2013/07/25 22:58:58
Add a TODO to re-update these sources when they ch
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
62 } | |
63 | |
64 HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, port).then((server) { | |
nweiz
2013/07/25 22:58:58
Doesn't "localhost" work these days?
Bob Nystrom
2013/07/26 20:20:11
Not sure. I figured an enum is better than a magic
nweiz
2013/07/29 20:23:14
We're using "localhost" below, and "127.0.0.1" els
Bob Nystrom
2013/07/29 21:45:43
Done.
| |
65 log.message("Serving ${entrypoint.root.name} " | |
66 "on http://localhost:${server.port}..."); | |
nweiz
2013/07/25 22:58:58
The "..." here may make copy/pasting this URL diff
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
67 | |
68 server.listen((HttpRequest request) { | |
nweiz
2013/07/25 22:58:58
Don't type annotate [request].
We should check th
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
69 var id = getIdFromUri(request.uri); | |
70 barback.getAssetById(id).then((asset) { | |
71 log.message( | |
72 "$_green${request.method}$_none ${request.uri} -> $asset"); | |
73 request.response.addStream(asset.read()).then((_) { | |
nweiz
2013/07/25 22:58:58
Add a TODO to set the Content-Type based on the ex
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
74 request.response.close(); | |
nweiz
2013/07/25 22:58:58
I thought [addStream] automatically closed the con
Bob Nystrom
2013/07/26 20:20:11
Nope:
"When the IOSink is bound to a stream (thro
| |
75 }); | |
nweiz
2013/07/25 22:58:58
We should handle errors that come off this future.
Bob Nystrom
2013/07/26 20:20:11
Done.
nweiz
2013/07/29 20:23:14
Unlike errors coming off [getAssetById], errors co
Bob Nystrom
2013/07/29 21:45:43
Took out the 500 stuff. There isn't any tests for
| |
76 }).catchError((error) { | |
77 if (error is! AssetNotFoundException) throw error; | |
nweiz
2013/07/25 22:58:58
This will top-level the error. That's not what we
Bob Nystrom
2013/07/26 20:20:11
Done. I log it and respond with a 500. Seems about
nweiz
2013/07/29 20:23:14
The only error getAssetById is expected to raise i
Bob Nystrom
2013/07/29 21:45:43
Done.
| |
78 log.error("$_red${request.method}$_none ${request.uri} -> $error"); | |
79 request.response.statusCode = 404; | |
80 request.response.reasonPhrase = error.toString(); | |
nweiz
2013/07/25 22:58:58
The reason phrase is expected to be very brief, an
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
81 request.response.close(); | |
82 return; | |
nweiz
2013/07/25 22:58:58
Remove.
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
83 }); | |
84 }); | |
85 }); | |
86 | |
87 // TODO(rnystrom): Hack! Return a future that never completes to leave | |
88 // pub running. | |
89 return new Completer().future; | |
nweiz
2013/07/25 22:58:58
This seems, as you say, hacky. It feels like we sh
Bob Nystrom
2013/07/26 20:20:11
In the above code, I'm catching all errors that co
nweiz
2013/07/29 20:23:14
As with [getAssetById], programmatic errors in bar
Bob Nystrom
2013/07/29 21:45:43
Done.
| |
90 }); | |
91 } | |
92 | |
93 AssetId getIdFromUri(Uri uri) { | |
94 // Strip the leading "/" from the URL. | |
95 var relative = uri.path.substring(1); | |
nweiz
2013/07/25 22:58:58
Rather than explicitly using substring, it seems c
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
96 if (relative == "") relative = "index.html"; | |
nweiz
2013/07/25 22:58:58
Supporting "index.html" only for the root seems wr
Bob Nystrom
2013/07/26 20:20:11
That the nice thing about "/". For that one case,
nweiz
2013/07/29 20:23:14
I think supporting only "/" is actively harmful. U
Bob Nystrom
2013/07/29 21:45:43
Removed it.
| |
97 | |
98 var parts = path.split(relative); | |
nweiz
2013/07/25 22:58:58
This will break on windows. You need to make a URL
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
99 | |
100 // Checks to see if [uri]'s path contains a special "symlink" directory | |
101 // named [name]. If so, maps the path after that to be within [dir]. | |
nweiz
2013/07/25 22:58:58
This isn't strictly accurate; it treats the direct
Bob Nystrom
2013/07/26 20:20:11
Done.
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
102 AssetId _trySpecialUrl(String name, String dir) { | |
103 // Find the package name and the relative path in the package. | |
104 var index = parts.indexOf(name); | |
105 if (index == -1) return null; | |
106 | |
107 var package = parts[index + 1]; | |
108 var assetPath = path.join(dir, path.joinAll(parts.skip(index + 2))); | |
109 return new AssetId(package, assetPath); | |
nweiz
2013/07/25 22:58:58
Right now this will crash on paths like "packages"
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
110 } | |
111 | |
112 // See if it's "packages" URL. | |
113 var id = _trySpecialUrl("packages", "lib"); | |
114 if (id != null) return id; | |
115 | |
116 // See if it's an "assets" URL. | |
117 id = _trySpecialUrl("assets", "asset"); | |
118 if (id != null) return id; | |
119 | |
120 // Otherwise, it's a path in current package's web directory. | |
121 relative = path.join("web", relative); | |
122 return new AssetId(entrypoint.root.name, relative); | |
123 } | |
124 } | |
125 | |
126 class PubPackageProvider implements PackageProvider { | |
nweiz
2013/07/25 22:58:58
This is going to be used elsewhere pretty soon. Mi
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
127 /// The [Entrypoint] package being served. | |
128 final Entrypoint _entrypoint; | |
129 | |
130 /// Maps the names of all of the packages in [_entrypoint]'s transitive | |
131 /// dependency graph to the local path of the directory for that package. | |
132 final Map<String, String> _packageDirs; | |
133 | |
134 /// Creates a new provider for [entrypoint]. | |
135 static Future<PubPackageProvider> create(Entrypoint entrypoint) { | |
136 var packageDirs = <String, String>{}; | |
137 var futures = []; | |
138 | |
139 packageDirs[entrypoint.root.name] = entrypoint.root.dir; | |
140 | |
141 // Cache package directories up front so we can have synchronous access | |
142 // to them. | |
143 // TODO(rnystrom): Handle missing or out of date lockfile. | |
144 entrypoint.loadLockFile().packages.forEach((name, package) { | |
145 var source = entrypoint.cache.sources[package.source]; | |
146 futures.add(source.getDirectory(package).then((packageDir) { | |
147 packageDirs[name] = packageDir; | |
148 })); | |
149 }); | |
nweiz
2013/07/25 22:58:58
Rather than manually expanding it, just use `entry
Bob Nystrom
2013/07/26 20:20:11
packages is a Map, not an Iterable, so I tried thi
| |
150 | |
151 return Future.wait(futures).then((_) { | |
152 return new PubPackageProvider._(entrypoint, packageDirs); | |
153 }); | |
154 } | |
155 | |
156 PubPackageProvider._(this._entrypoint, this._packageDirs); | |
157 | |
158 Iterable<String> get packages => _packageDirs.keys; | |
159 | |
160 List<AssetId> listAssets(String package, {String within}) { | |
nweiz
2013/07/25 22:58:58
Right now [listAssets] is used nowhere in barback.
Bob Nystrom
2013/07/26 20:20:11
It was being used forever ago when I was prototypi
| |
161 var files = <AssetId>[]; | |
162 | |
163 addFiles(String dirPath) { | |
164 var packageDir = _packageDirs[package]; | |
165 var dir = new Directory(path.join(packageDir, dirPath)); | |
166 if (!dir.existsSync()) return; | |
nweiz
2013/07/25 22:58:58
Use [dirExists].
Bob Nystrom
2013/07/29 21:45:43
Done.
| |
167 var entries = dir.listSync(recursive: true); | |
nweiz
2013/07/25 22:58:58
Use [listDir] rather than [Directory.listSync].
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
168 files.addAll(entries | |
169 .where((entry) => entry is File) | |
170 .map((entry) => new AssetId(package, | |
171 path.relative(entry.path, from: packageDir)))); | |
172 } | |
173 | |
174 // If we aren't given a subdirectory, use all externally visible files. | |
175 if (within == null) { | |
176 // Expose the "asset" and "lib" directories. | |
177 addFiles("asset"); | |
178 addFiles("lib"); | |
179 | |
180 // The entrypoint's "web" directory is also visible. | |
181 if (package == _entrypoint.root.name) { | |
182 addFiles("web"); | |
183 } | |
184 } else { | |
185 addFiles(within); | |
186 } | |
187 | |
188 return files; | |
189 } | |
nweiz
2013/07/25 22:58:58
This shouldn't list the contents of packages direc
Bob Nystrom
2013/07/26 20:20:11
Done.
| |
190 | |
191 // TODO(rnystrom): Actually support transformers. | |
192 Iterable<Iterable<Transformer>> getTransformers(String package) => []; | |
193 | |
194 Future<Asset> getAsset(AssetId id) { | |
195 var file = path.normalize(path.join(_packageDirs[id.package], id.path)); | |
nweiz
2013/07/25 22:58:58
What's the value of normalizing the path here?
Bob Nystrom
2013/07/26 20:20:11
Not much. Removed.
| |
196 return new Future.value(new Asset.fromPath(id, file)); | |
197 } | |
198 } | |
OLD | NEW |