Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding an option to redirect all not found to a specific asset #3361

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
5 changes: 3 additions & 2 deletions build_runner/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

- Upgrade to `frontend_service_client` version 3.

## 2.3.0

## 2.3.3-dev
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should remain as it was, there is a section higher up already for 2.3.3-dev

- Add `-d` as a shorthand for `--delete-conflicting-outputs`.
- Added a flag to redirect all not found to a specific
asset: ` not-found-defaults-to`

## 2.2.1

Expand Down
2 changes: 1 addition & 1 deletion build_runner/lib/src/daemon/asset_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class AssetServer {
var cascade = Cascade().add((_) async {
await builder.building;
return Response.notFound('');
}).add(AssetHandler(builder.reader, rootPackage).handle);
}).add(AssetHandler(builder.reader, rootPackage, null).handle);

var pipeline = Pipeline();
if (options.logRequests) {
Expand Down
76 changes: 41 additions & 35 deletions build_runner/lib/src/entrypoint/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const liveReloadOption = 'live-reload';
const logPerformanceOption = 'log-performance';
const logRequestsOption = 'log-requests';
const lowResourcesModeOption = 'low-resources-mode';
const notFoundDefaultsToOption = 'not-found-defaults-to';
const outputOption = 'output';
const releaseOption = 'release';
const trackPerformanceOption = 'track-performance';
Expand Down Expand Up @@ -276,41 +277,45 @@ class ServeOptions extends WatchOptions {
final bool logRequests;
final List<ServeTarget> serveTargets;

ServeOptions._({
required this.hostName,
required this.buildUpdates,
required this.logRequests,
required this.serveTargets,
required Set<BuildFilter> buildFilters,
required bool deleteFilesByDefault,
required bool enableLowResourcesMode,
required String? configKey,
required Set<BuildDirectory> buildDirs,
required bool outputSymlinksOnly,
required bool trackPerformance,
required bool skipBuildScriptCheck,
required bool verbose,
required Map<String, Map<String, dynamic>> builderConfigOverrides,
required bool isReleaseBuild,
required String? logPerformanceDir,
required bool usePollingWatcher,
required List<String> enableExperiments,
}) : super._(
buildFilters: buildFilters,
deleteFilesByDefault: deleteFilesByDefault,
enableLowResourcesMode: enableLowResourcesMode,
configKey: configKey,
buildDirs: buildDirs,
outputSymlinksOnly: outputSymlinksOnly,
trackPerformance: trackPerformance,
skipBuildScriptCheck: skipBuildScriptCheck,
verbose: verbose,
builderConfigOverrides: builderConfigOverrides,
isReleaseBuild: isReleaseBuild,
logPerformanceDir: logPerformanceDir,
usePollingWatcher: usePollingWatcher,
enableExperiments: enableExperiments,
);
// An [AssetId] path to redirect when the file is not found by
// the web server
final String? notFoundDefaultsTo;

ServeOptions._(
{required this.hostName,
required this.buildUpdates,
required this.logRequests,
required this.serveTargets,
required Set<BuildFilter> buildFilters,
required bool deleteFilesByDefault,
required bool enableLowResourcesMode,
required String? configKey,
required Set<BuildDirectory> buildDirs,
required bool outputSymlinksOnly,
required bool trackPerformance,
required bool skipBuildScriptCheck,
required bool verbose,
required Map<String, Map<String, dynamic>> builderConfigOverrides,
required bool isReleaseBuild,
required String? logPerformanceDir,
required bool usePollingWatcher,
required List<String> enableExperiments,
required this.notFoundDefaultsTo})
: super._(
buildFilters: buildFilters,
deleteFilesByDefault: deleteFilesByDefault,
enableLowResourcesMode: enableLowResourcesMode,
configKey: configKey,
buildDirs: buildDirs,
outputSymlinksOnly: outputSymlinksOnly,
trackPerformance: trackPerformance,
skipBuildScriptCheck: skipBuildScriptCheck,
verbose: verbose,
builderConfigOverrides: builderConfigOverrides,
isReleaseBuild: isReleaseBuild,
logPerformanceDir: logPerformanceDir,
usePollingWatcher: usePollingWatcher,
enableExperiments: enableExperiments);

factory ServeOptions.fromParsedArgs(ArgResults argResults,
Iterable<String> positionalArgs, String rootPackage, Command command) {
Expand Down Expand Up @@ -381,6 +386,7 @@ class ServeOptions extends WatchOptions {
logPerformanceDir: argResults[logPerformanceOption] as String?,
usePollingWatcher: argResults[usePollingWatcherOption] as bool,
enableExperiments: argResults[enableExperimentOption] as List<String>,
notFoundDefaultsTo: argResults[notFoundDefaultsToOption] as String?,
);
}
}
Expand Down
5 changes: 4 additions & 1 deletion build_runner/lib/src/entrypoint/serve.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ class ServeCommand extends WatchCommand {
..addFlag(liveReloadOption,
defaultsTo: false,
negatable: false,
help: 'Enables automatic page reloading on rebuilds. ');
help: 'Enables automatic page reloading on rebuilds. ')
..addOption(notFoundDefaultsToOption,
help: 'Redirect not found to a specific asset.');
}

@override
Expand Down Expand Up @@ -98,6 +100,7 @@ class ServeCommand extends WatchCommand {
logPerformanceDir: options.logPerformanceDir,
directoryWatcherFactory: options.directoryWatcherFactory,
buildFilters: options.buildFilters,
notFoundDefaultsTo: options.notFoundDefaultsTo
);

servers.forEach((target, server) {
Expand Down
4 changes: 3 additions & 1 deletion build_runner/lib/src/generate/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ Future<ServeHandler> watch(List<BuilderApplication> builders,
bool? isReleaseBuild,
Map<String, Map<String, dynamic>>? builderConfigOverrides,
String? logPerformanceDir,
Set<BuildFilter>? buildFilters}) =>
Set<BuildFilter>? buildFilters,
String? notFoundDefaultsTo}) =>
watch_impl.watch(
builders,
assumeTty: assumeTty,
Expand All @@ -190,4 +191,5 @@ Future<ServeHandler> watch(List<BuilderApplication> builders,
isReleaseBuild: isReleaseBuild,
logPerformanceDir: logPerformanceDir,
buildFilters: buildFilters,
notFoundDefaultsTo: notFoundDefaultsTo
);
15 changes: 11 additions & 4 deletions build_runner/lib/src/generate/watch_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Future<ServeHandler> watch(
bool? isReleaseBuild,
String? logPerformanceDir,
Set<BuildFilter>? buildFilters,
String? notFoundDefaultsTo
}) async {
builderConfigOverrides ??= const {};
buildDirs ??= <BuildDirectory>{};
Expand Down Expand Up @@ -105,7 +106,8 @@ Future<ServeHandler> watch(
.any((target) => target.outputLocation?.path.isNotEmpty ?? false),
buildDirs,
buildFilters,
isReleaseMode: isReleaseBuild ?? false);
isReleaseMode: isReleaseBuild ?? false,
notFoundDefaultsTo: notFoundDefaultsTo);

unawaited(watch.buildResults.drain().then((_) async {
await terminator.cancel();
Expand Down Expand Up @@ -134,7 +136,8 @@ WatchImpl _runWatch(
bool willCreateOutputDirs,
Set<BuildDirectory> buildDirs,
Set<BuildFilter> buildFilters,
{bool isReleaseMode = false}) =>
{bool isReleaseMode = false,
String? notFoundDefaultsTo}) =>
WatchImpl(
options,
environment,
Expand All @@ -146,7 +149,8 @@ WatchImpl _runWatch(
willCreateOutputDirs,
buildDirs,
buildFilters,
isReleaseMode: isReleaseMode);
isReleaseMode: isReleaseMode,
notFoundDefaultsTo: notFoundDefaultsTo);

class WatchImpl implements BuildState {
BuildImpl? _build;
Expand All @@ -172,6 +176,9 @@ class WatchImpl implements BuildState {
/// The [PackageGraph] for the current program.
final PackageGraph packageGraph;

// An asset path to redirect when the file is not found by the web server
final String? notFoundDefaultsTo;

/// The directories to build upon file changes and where to output them.
final Set<BuildDirectory> _buildDirs;

Expand Down Expand Up @@ -200,7 +207,7 @@ class WatchImpl implements BuildState {
this._willCreateOutputDirs,
this._buildDirs,
this._buildFilters,
{bool isReleaseMode = false})
{bool isReleaseMode = false, this.notFoundDefaultsTo})
: _debounceDelay = options.debounceDelay,
packageGraph = options.packageGraph {
buildResults = _run(
Expand Down
25 changes: 23 additions & 2 deletions build_runner/lib/src/server/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,16 @@ ServeHandler createServeHandler(WatchImpl watch) {
var rootPackage = watch.packageGraph.root.name;
var assetGraphHanderCompleter = Completer<AssetGraphHandler>();
var assetHandlerCompleter = Completer<AssetHandler>();

watch.reader.then((reader) async {
assetHandlerCompleter.complete(AssetHandler(reader, rootPackage));
final notFoundDefaultsToPath = watch.notFoundDefaultsTo;
final notFoundDefaultsTo =
notFoundDefaultsToPath != null && notFoundDefaultsToPath.isNotEmpty
? AssetId(rootPackage, notFoundDefaultsToPath)
: null;

assetHandlerCompleter
.complete(AssetHandler(reader, rootPackage, notFoundDefaultsTo));
assetGraphHanderCompleter
.complete(AssetGraphHandler(reader, rootPackage, watch.assetGraph!));
}).catchError((_) {}); // These errors are separately handled.
Expand Down Expand Up @@ -300,10 +308,11 @@ window.\$dartLoader.forceLoadModule('packages/build_runner/src/server/build_upda
class AssetHandler {
final FinalizedReader _reader;
final String _rootPackage;
final AssetId? notFoundDefaultsTo;

final _typeResolver = MimeTypeResolver();

AssetHandler(this._reader, this._rootPackage);
AssetHandler(this._reader, this._rootPackage, this.notFoundDefaultsTo);

Future<shelf.Response> handle(shelf.Request request, {String rootDir = ''}) =>
(request.url.path.endsWith('/') || request.url.path.isEmpty)
Expand Down Expand Up @@ -332,6 +341,18 @@ class AssetHandler {
return shelf.Response.notFound(
await _findDirectoryList(assetId));
}

final notFoundDefaultsTo = this.notFoundDefaultsTo;

if (notFoundDefaultsTo != null) {
final pathSegments = assetId.pathSegments;

if (!pathSegments.contains('package') &&
!pathSegments.contains('lib')) {
return _handle(request, notFoundDefaultsTo);
}
}

return shelf.Response.notFound('Not Found');
default:
return shelf.Response.notFound('Not Found');
Expand Down
19 changes: 17 additions & 2 deletions build_runner/test/server/serve_handler_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'package:web_socket_channel/web_socket_channel.dart';

void main() {
late ServeHandler serveHandler;
late ServeHandler serveSpaHandler;
late InMemoryRunnerAssetReader reader;
late MockWatchImpl watchImpl;
late AssetGraph assetGraph;
Expand All @@ -46,6 +47,9 @@ void main() {
packageGraph,
assetGraph);
serveHandler = createServeHandler(watchImpl);

watchImpl.notFoundDefaultsTo = 'web/index.html';
serveSpaHandler = createServeHandler(watchImpl);
watchImpl
.addFutureResult(Future.value(BuildResult(BuildStatus.success, [])));
});
Expand All @@ -66,6 +70,13 @@ void main() {
expect(await response.readAsString(), 'content');
});

test('retrieve default asset when not found', () async {
_addSource('a|web/index.html', 'content');
var response = await serveSpaHandler.handlerFor('web')(
Request('GET', Uri.parse('http://server.com/foo.html')));
expect(await response.readAsString(), 'content');
});

test('caching with etags works', () async {
_addSource('a|web/index.html', 'content');
var handler = serveHandler.handlerFor('web');
Expand Down Expand Up @@ -450,14 +461,17 @@ class MockWatchImpl implements WatchImpl {

Future<BuildResult>? _currentBuild;

@override
String? notFoundDefaultsTo;

@override
Future<BuildResult>? get currentBuild => _currentBuild;
@override
set currentBuild(Future<BuildResult>? _) =>
throw UnsupportedError('unsupported!');

final _futureBuildResultsController = StreamController<Future<BuildResult>>();
final _buildResultsController = StreamController<BuildResult>();
final _buildResultsController = StreamController<BuildResult>.broadcast();

@override
Stream<BuildResult> get buildResults => _buildResultsController.stream;
Expand All @@ -475,7 +489,8 @@ class MockWatchImpl implements WatchImpl {
_futureBuildResultsController.add(result);
}

MockWatchImpl(this.reader, this.packageGraph, this.assetGraph) {
MockWatchImpl(this.reader, this.packageGraph, this.assetGraph,
{this.notFoundDefaultsTo}) {
var firstBuild = Completer<BuildResult>();
_currentBuild = firstBuild.future;
_futureBuildResultsController.stream.listen((futureBuildResult) {
Expand Down