diff --git a/packages/framework/CHANGELOG.md b/packages/framework/CHANGELOG.md index a8f7616a..03a9cf61 100644 --- a/packages/framework/CHANGELOG.md +++ b/packages/framework/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 8.2.0 + +* Add `responseHeader` to `AngelHttp` to add headers to HTTP default response +* Add `removeResponseHeader` to `AngelHttp` to remove headers from HTTP default response + ## 8.1.1 * Updated broken image on README.md diff --git a/packages/framework/lib/src/http/angel_http.dart b/packages/framework/lib/src/http/angel_http.dart index f56dc5e0..fe401b84 100644 --- a/packages/framework/lib/src/http/angel_http.dart +++ b/packages/framework/lib/src/http/angel_http.dart @@ -22,9 +22,6 @@ class AngelHttp extends Driver { @override Uri get uri { - //if (server == null) { - // throw ArgumentError("[AngelHttp] Server instance not intialised"); - //} return Uri( scheme: 'http', host: server?.address.address, port: server?.port); } @@ -38,7 +35,7 @@ class AngelHttp extends Driver headers = const {}}) { return AngelHttp._(app, serverGenerator, useZone); } @@ -67,15 +64,6 @@ class AngelHttp extends Driver handleRawRequest(request, request.response); @@ -88,6 +76,20 @@ class AngelHttp extends Driver headers) { + headers.forEach((key, value) { + server?.defaultResponseHeaders.remove(key, value); + }); + } + + /// Add headers to HTTP Response + void responseHeader(Map headers) { + headers.forEach((key, value) { + server?.defaultResponseHeaders.add(key, value); + }); + } + @override Future closeResponse(HttpResponse response) => response.close(); diff --git a/packages/framework/pubspec.yaml b/packages/framework/pubspec.yaml index 3870e4cc..9e1bcc9c 100644 --- a/packages/framework/pubspec.yaml +++ b/packages/framework/pubspec.yaml @@ -1,5 +1,5 @@ name: angel3_framework -version: 8.1.1 +version: 8.2.0 description: A high-powered HTTP server extensible framework with dependency injection, routing and much more. homepage: https://angel3-framework.web.app/ repository: https://github.com/dukefirehawk/angel/tree/master/packages/framework diff --git a/packages/framework/test/all.dart b/packages/framework/test/all.dart index 3ebd5ab9..ae283f1a 100644 --- a/packages/framework/test/all.dart +++ b/packages/framework/test/all.dart @@ -27,6 +27,7 @@ import 'service_map_test.dart' as service_map; import 'services_test.dart' as services; import 'streaming_test.dart' as streaming; import 'view_generator_test.dart' as view_generator; +//import 'response_header_test.dart' as response_header; import 'package:test/test.dart'; /// For running with coverage @@ -36,6 +37,7 @@ void main() { group('accepts', accepts.main); group('anonymous service', anonymous_service.main); group('body', body.main); + //group('response_header', response_header.main); group('controller', controller.main); group('detach', detach.main); group('di', di.main); diff --git a/packages/framework/test/response_header_test.dart b/packages/framework/test/response_header_test.dart new file mode 100644 index 00000000..dd4aaae7 --- /dev/null +++ b/packages/framework/test/response_header_test.dart @@ -0,0 +1,58 @@ +import 'dart:io'; + +import 'package:angel3_container/mirrors.dart'; +import 'package:angel3_framework/angel3_framework.dart'; +import 'package:angel3_framework/src/http/angel_http.dart'; +import 'package:test/test.dart'; + +void main() { + late Angel app; + late AngelHttp http; + late HttpClient client; + + setUp(() async { + app = Angel(reflector: MirrorsReflector()); + http = AngelHttp(app); + + await http.startServer(); + + var formData = {'id': 100, 'name': 'William'}; + app.get('/api/v1/user/list', (RequestContext req, res) async { + //await req.parseBody(); + //res.write('Hello, World!'); + res.json(formData); + }); + + client = HttpClient(); + }); + + tearDown(() async { + client.close(); + await http.close(); + }); + + test('Remove Response Header', () async { + http.removeResponseHeader({'x-frame-options': 'SAMEORIGIN'}); + + var request = await client.get('localhost', 3000, '/api/v1/user/list'); + HttpClientResponse response = await request.close(); + //print(response.headers); + expect(response.headers['x-frame-options'], isNull); + }, skip: true); + + test('Add Response Header', () async { + http.responseHeader({ + 'X-XSRF_TOKEN': + 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e' + }); + + var request = await client.get('localhost', 3000, '/api/v1/user/list'); + HttpClientResponse response = await request.close(); + //print(response.headers); + expect( + response.headers['X-XSRF_TOKEN'], + equals([ + 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e' + ])); + }, skip: true); +} diff --git a/packages/hot/CHANGELOG.md b/packages/hot/CHANGELOG.md index f029da3f..97e0a368 100644 --- a/packages/hot/CHANGELOG.md +++ b/packages/hot/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 8.1.0 + +* Updated `vm_service` to 13.0.0 +* Added configurable HTTP response header + ## 8.0.0 * Require Dart >= 3.0 diff --git a/packages/hot/README.md b/packages/hot/README.md index 45924623..f06952e1 100644 --- a/packages/hot/README.md +++ b/packages/hot/README.md @@ -9,7 +9,7 @@ Supports *hot reloading* of Angel3 servers on file changes. This is faster and more reliable than merely reactively restarting a `Process`. This package only works with the [Angel3 framework](https://pub.dev/packages/angel3_framework). -**Not recommended to use in production, unless you are specifically intending for a "hot code push" in production..** +**Not recommended to use in production, unless you are specifically intending for a "hot code push" in production.** ## Installation @@ -23,11 +23,7 @@ dependencies: ## Usage -This package is dependent on the Dart VM service, so you *must* run Dart with the `--observe` (or `--enable-vm-service`) argument!!! - -Usage is fairly simple. Pass a function that creates an `Angel` server, along with a collection of paths to watch, to the `HotReloader` constructor. The rest is history!!! - -The recommended pattern is to only use hot-reloading in your application entry point. Create your `Angel` instance within a separate function, conventionally named `createServer`. +This package is dependent on the Dart VM service, so you *must* run Dart with the `--observe` (or `--enable-vm-service`) argument. Usage is fairly simple. Pass a function that creates an `Angel` server, along with a collection of paths to watch, to the `HotReloader` constructor. The recommended pattern is to only use hot-reloading in your application entry point. Create your `Angel` instance within a separate function, conventionally named `createServer`. You can watch: diff --git a/packages/hot/lib/angel3_hot.dart b/packages/hot/lib/angel3_hot.dart index 76f4fcbb..ee3858f6 100644 --- a/packages/hot/lib/angel3_hot.dart +++ b/packages/hot/lib/angel3_hot.dart @@ -12,6 +12,7 @@ import 'package:glob/glob.dart'; import 'package:glob/list_local_fs.dart'; import 'package:belatuk_html_builder/elements.dart'; import 'package:io/ansi.dart'; +import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:vm_service/vm_service.dart' as vm; import 'package:vm_service/vm_service_io.dart' as vm; @@ -19,8 +20,6 @@ import 'package:watcher/watcher.dart'; /// A utility class that watches the filesystem for changes, and starts new instances of an Angel server. class HotReloader { - late vm.VmService _client; - vm.IsolateRef? _mainIsolate; final StreamController _onChange = StreamController.broadcast(); final List _paths = []; @@ -28,8 +27,10 @@ class HotReloader { final Queue _requestQueue = Queue(); late HttpServer _io; AngelHttp? _server; - Duration? _timeout; + late Duration _timeout; + late vm.VmService _client; vm.VM? _vmachine; + vm.IsolateRef? _mainIsolate; /// If `true` (default), then developers can `press 'r' to reload` the application on-the-fly. /// @@ -47,7 +48,7 @@ class HotReloader { /// /// If the timeout expires, then the request will be immediately terminated with a `502 Bad Gateway` error. /// Default: `5s` - Duration? get timeout => _timeout; + Duration get timeout => _timeout; /// The Dart VM service host. /// @@ -116,20 +117,22 @@ class HotReloader { Future handleRequest(HttpRequest request) async { if (_server != null) { return await _handle(request); - } else if (timeout == null) { - _requestQueue.add(request); + //} else if (timeout == null) { + // _requestQueue.add(request); } else { _requestQueue.add(request); - Timer(timeout!, () { + Timer(timeout, () { if (_requestQueue.remove(request)) { // Send 502 response sendError(request, HttpStatus.badGateway, '502 Bad Gateway', - 'Request timed out after ${timeout!.inMilliseconds}ms.'); + 'Request timed out after ${timeout.inMilliseconds}ms.'); } }); } } + Logger? get _appLogger => _server?.app.logger; + Future _generateServer() async { var s = await generator(); await Future.forEach(s.startupHooks, s.configure); @@ -138,23 +141,24 @@ class HotReloader { } void _logWarning(String msg) { - if (_server?.app.logger != null) { - _server?.app.logger.warning(msg); + if (_appLogger != null) { + _appLogger?.warning(msg); } else { print(yellow.wrap('WARNING: $msg')); } } void _logInfo(String msg) { - if (_server?.app.logger != null) { - _server?.app.logger.info(msg); + if (_appLogger != null) { + _appLogger?.info(msg); } else { print(lightGray.wrap(msg)); } } /// Starts listening to requests and filesystem events. - Future startServer([address, int? port]) async { + Future startServer( + [String address = '127.0.0.1', int port = 3000]) async { var isHot = true; _server = await _generateServer(); @@ -185,9 +189,10 @@ class HotReloader { _mainIsolate ??= _vmachine?.isolates?.first; if (_vmachine != null) { - for (var isolate in _vmachine!.isolates ?? []) { - if (isolate.id != null) { - await _client.setIsolatePauseMode(isolate.id!, + for (var isolate in _vmachine?.isolates ?? []) { + var isolateId = isolate.id; + if (isolateId != null) { + await _client.setIsolatePauseMode(isolateId, exceptionPauseMode: 'None'); } } @@ -203,7 +208,8 @@ class HotReloader { while (_requestQueue.isNotEmpty) { await _handle(_requestQueue.removeFirst()); } - var server = _io = await HttpServer.bind(address ?? '127.0.0.1', port ?? 0); + var server = _io = await HttpServer.bind(address, port); + //server.defaultResponseHeaders(); server.listen(handleRequest); // Print a Flutter-like prompt... @@ -213,7 +219,7 @@ class HotReloader { Uri? observatoryUri; if (isHot) { - observatoryUri = await dev.Service.getInfo().then((i) => i.serverUri!); + observatoryUri = await dev.Service.getInfo().then((i) => i.serverUri); } print(styleBold.wrap( @@ -260,7 +266,7 @@ class HotReloader { if (ch == $R) { _logInfo('Manually restarting server...\n'); await _killServer(); - await _server!.close(); + await _server?.close(); var addr = _io.address.address; var port = _io.port; await _io.close(force: true); @@ -359,21 +365,23 @@ class HotReloader { scheduleMicrotask(() async { // Disconnect active WebSockets try { - var ws = _server!.app.container.make(); + var ws = _server?.app.container.make(); - for (var client in ws.clients) { - try { - await client.close(); - } catch (e) { - _logWarning( - 'Couldn\'t close WebSocket from session #${client.request.session!.id}: $e'); + if (ws != null) { + for (var client in ws.clients) { + try { + await client.close(); + } catch (e) { + _logWarning( + 'Couldn\'t close WebSocket from session #${client.request.session?.id}: $e'); + } } } // await Future.forEach( // _server.app.shutdownHooks, _server.app.configure); - await _server!.app.close(); - _server!.app.logger.clearListeners(); + await _server?.app.close(); + _server?.app.logger.clearListeners(); } catch (_) { // Fail silently... } @@ -387,11 +395,16 @@ class HotReloader { _server = null; if (hot) { - var report = await _client.reloadSources(_mainIsolate!.id!); + var mainIsolateId = _mainIsolate?.id; + if (mainIsolateId != null) { + var report = await _client.reloadSources(mainIsolateId); - if (report.success != null) { - _logWarning( - 'Hot reload failed - perhaps some sources have not been generated yet.'); + if (report.success != null) { + _logWarning( + 'Hot reload failed - perhaps some sources have not been generated yet.'); + } + } else { + _logWarning('Hot reload failed - isolate id does not exist.'); } } diff --git a/packages/hot/pubspec.yaml b/packages/hot/pubspec.yaml index 1ced19d9..178d09c1 100644 --- a/packages/hot/pubspec.yaml +++ b/packages/hot/pubspec.yaml @@ -1,5 +1,5 @@ name: angel3_hot -version: 8.0.0 +version: 8.1.0 description: Supports hot reloading/hot code push of Angel3 servers on file changes. homepage: https://angel3-framework.web.app/ repository: https://github.com/dukefirehawk/angel/tree/master/packages/hot @@ -13,17 +13,17 @@ dependencies: glob: ^2.1.0 io: ^1.0.0 path: ^1.8.0 - vm_service: ^11.6.0 + vm_service: ^13.0.0 watcher: ^1.0.0 + logging: ^1.2.0 dev_dependencies: http: ^1.0.0 - logging: ^1.2.0 lints: ^2.1.0 -# dependency_overrides: +dependency_overrides: # angel3_container: # path: ../container/angel_container -# angel3_framework: -# path: ../framework + angel3_framework: + path: ../framework # angel3_http_exception: # path: ../http_exception # angel3_model: diff --git a/packages/production/CHANGELOG.md b/packages/production/CHANGELOG.md index d875c098..99f627e3 100644 --- a/packages/production/CHANGELOG.md +++ b/packages/production/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 8.1.0 + +* Added optional `responseHeaders` and `removeResponseHeaders` to `Runner` + ## 8.0.0 * Require Dart >= 3.0 diff --git a/packages/production/README.md b/packages/production/README.md index 1466256a..3d947743 100644 --- a/packages/production/README.md +++ b/packages/production/README.md @@ -9,9 +9,7 @@ Helpers for concurrency, message-passing, rotating loggers, and other production ![Screenshot](angel3-screenshot.png) -This will become the de-facto way to run Angel3 applications in deployed environments, as it takes care of inter-isolate communication, respawning dead processes, and other housekeeping for you automatically. - -Most users will want to use the `Runner` class. +This will become the de-facto way to run Angel3 applications in deployed environments, as it takes care of inter-isolate communication, respawning dead processes, and other housekeeping for you automatically. Most users will want to use the `Runner` class. ## `Runner` @@ -45,15 +43,11 @@ When combined with `systemd`, deploying Angel3 applications on Linux can be very ## Message Passing The `Runner` class uses [`belatuk_pub_sub`]() to coordinate -message passing between isolates. - -When one isolate sends a message, all other isolates will receive the same message, except for the isolate that sent it. - -It is injected into your application's `Container` as `pub_sub.Client`, so you can use it as follows: +message passing between isolates. When one isolate sends a message, all other isolates will receive the same message, except for the isolate that sent it. It is injected into your application's `Container` as `pub_sub.Client`, so you can use it as follows: ```dart // Use the injected `pub_sub.Client` to send messages. -var client = app.container.make(); +var client = app.container.make(); // We can listen for an event to perform some behavior. // @@ -66,6 +60,32 @@ onGreetingChanged }); ``` +## Customising Response Header + +Additional parameters can be passed to the `Runner` class to: + +1. Remove headers from HTTP response. +2. Add headers to HTTP response. + +For example, the following code snippet removes `X-FRAME-OPTIONS` and adds `X-XSRF_TOKEN` to the response header. + +```dart + +void main(List args) { + // Remove default + var removeHeader = {'X-FRAME-OPTIONS': 'SAMEORIGIN'}; + var customHeader = { + 'X-XSRF_TOKEN': + 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e' + }; + + Runner('example', configureServer, + removeResponseHeaders: removeHeader, responseHeaders: customHeader) + .run(args); +} + +``` + ## Run-time Metadata At run-time, you may want to know information about the currently-running instance, for example, which number instance. For this, the `InstanceInfo` class is injected into each instance: diff --git a/packages/production/example/main.dart b/packages/production/example/main.dart index 0b2ebeb6..33e9979b 100644 --- a/packages/production/example/main.dart +++ b/packages/production/example/main.dart @@ -2,13 +2,16 @@ import 'dart:async'; import 'dart:isolate'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_production/angel3_production.dart'; -import 'package:belatuk_pub_sub/belatuk_pub_sub.dart' as pub_sub; +import 'package:belatuk_pub_sub/belatuk_pub_sub.dart'; -void main(List args) => Runner('example', configureServer).run(args); +void main(List args) { + Runner('example', configureServer).run(args); +} Future configureServer(Angel app) async { // Use the injected `pub_sub.Client` to send messages. - var client = app.container.make(); + var client = app.container.make(); + var greeting = 'Hello! This is the default greeting.'; // We can listen for an event to perform some behavior. diff --git a/packages/production/lib/src/options.dart b/packages/production/lib/src/options.dart index 06f7bcb0..5a9f012f 100644 --- a/packages/production/lib/src/options.dart +++ b/packages/production/lib/src/options.dart @@ -32,13 +32,12 @@ class RunnerOptions { ..addOption('key-file', help: 'The PEM key file to read.') ..addOption('key-password', help: 'The PEM key file password.'); - final String? hostname, - certificateFile, - keyFile, - certificatePassword, - keyPassword; + final String hostname; + final String? certificateFile, keyFile, certificatePassword, keyPassword; final int concurrency, port; final bool useZone, respawn, quiet, ssl, http2; + final Map removeResponseHeaders = {}; + final Map responseHeaders = {}; RunnerOptions( {this.hostname = '127.0.0.1', @@ -56,26 +55,18 @@ class RunnerOptions { factory RunnerOptions.fromArgResults(ArgResults argResults) { return RunnerOptions( - hostname: argResults['address'] as String?, + hostname: argResults['address'] as String, port: int.parse(argResults['port'] as String), concurrency: int.parse(argResults['concurrency'] as String), useZone: argResults['use-zone'] as bool? ?? false, respawn: argResults['respawn'] as bool? ?? true, quiet: argResults['quiet'] as bool? ?? false, - certificateFile: argResults.wasParsed('certificate-file') - ? argResults['certificate-file'] as String? - : null, - keyFile: argResults.wasParsed('key-file') - ? argResults['key-file'] as String? - : null, + certificateFile: argResults['certificate-file'] as String?, + keyFile: argResults['key-file'] as String?, ssl: argResults['ssl'] as bool? ?? false, http2: argResults['http2'] as bool? ?? false, - certificatePassword: argResults.wasParsed('certificate-password') - ? argResults['certificate-password'] as String? - : null, - keyPassword: argResults.wasParsed('key-password') - ? argResults['key-password'] as String? - : null, + certificatePassword: argResults['certificate-password'] as String?, + keyPassword: argResults['key-password'] as String?, ); } } diff --git a/packages/production/lib/src/runner.dart b/packages/production/lib/src/runner.dart index 1b70b5b4..7722be85 100644 --- a/packages/production/lib/src/runner.dart +++ b/packages/production/lib/src/runner.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'dart:io'; import 'dart:isolate'; +import 'package:belatuk_pub_sub/belatuk_pub_sub.dart'; +import 'package:belatuk_pub_sub/isolate.dart'; import 'package:intl/intl.dart'; import 'package:angel3_container/angel3_container.dart'; import 'package:angel3_framework/angel3_framework.dart'; @@ -10,8 +12,6 @@ import 'package:args/args.dart'; import 'package:io/ansi.dart'; import 'package:io/io.dart'; import 'package:logging/logging.dart'; -import 'package:belatuk_pub_sub/isolate.dart' as pub_sub; -import 'package:belatuk_pub_sub/belatuk_pub_sub.dart' as pub_sub; import 'instance_info.dart'; import 'options.dart'; @@ -24,35 +24,21 @@ class Runner { final AngelConfigurer configureServer; final Reflector reflector; + final Map removeResponseHeaders; + final Map responseHeaders; + Runner(this.name, this.configureServer, - {this.reflector = const EmptyReflector()}); + {this.reflector = const EmptyReflector(), + this.removeResponseHeaders = const {}, + this.responseHeaders = const {}}); - static const String asciiArt2 = ''' - - ___ _ ________________ _____ - / | / | / / ____/ ____/ / |__ / - / /| | / |/ / / __/ __/ / / /_ < - / ___ |/ /| / /_/ / /___/ /______/ / -/_/ |_/_/ |_/\\____/_____/_____/____/ - -'''; - - static const String asciiArt = ''' + static const String _asciiArt = ''' _ _ _ ____ _____ _ _____ / \\ | \\ | |/ ___| ____| | |___ / / _ \\ | \\| | | _| _| | | |_ \\ / ___ \\| |\\ | |_| | |___| |___ ___) | /_/ \\_\\_| \\_|\\____|_____|_____|____/ -'''; - - static const String asciiArtOld = ''' -____________ ________________________ -___ |__ | / /_ ____/__ ____/__ / -__ /| |_ |/ /_ / __ __ __/ __ / -_ ___ | /| / / /_/ / _ /___ _ /___ -/_/ |_/_/ |_/ ____/ /_____/ /_____/ - '''; static final DateFormat _defaultDateFormat = @@ -173,11 +159,14 @@ _ ___ | /| / / /_/ / _ /___ _ /___ /// Starts a number of isolates, running identical instances of an Angel application. Future run(List args) async { - pub_sub.Server? server; + Server? server; try { var argResults = RunnerOptions.argParser.parse(args); + var options = RunnerOptions.fromArgResults(argResults); + options.responseHeaders.addAll(responseHeaders); + options.removeResponseHeaders.addAll(removeResponseHeaders); if (options.ssl || options.http2) { if (options.certificateFile == null) { @@ -188,7 +177,7 @@ _ ___ | /| / / /_/ / _ /___ _ /___ } print(darkGray.wrap( - '$asciiArt\n\nA batteries-included, full-featured, full-stack framework in Dart.\n\nhttps://angel3-framework.web.app\n')); + '$_asciiArt\n\nA batteries-included, full-featured, full-stack framework in Dart.\n\nhttps://angel3-framework.web.app\n')); if (argResults['help'] == true) { stdout @@ -199,12 +188,12 @@ _ ___ | /| / / /_/ / _ /___ _ /___ print('Starting `$name` application...'); - var adapter = pub_sub.IsolateAdapter(); - server = pub_sub.Server([adapter]); + var adapter = IsolateAdapter(); + server = Server([adapter]); // Register clients for (var i = 0; i < Platform.numberOfProcessors; i++) { - server.registerClient(pub_sub.ClientInfo('client$i')); + server.registerClient(ClientInfo('client$i')); } server.start(); @@ -240,11 +229,10 @@ _ ___ | /| / / /_/ / _ /___ _ /___ )); zone.run(() async { - var client = - pub_sub.IsolateClient('client${argsWithId.id}', args.pubSubSendPort); + var client = IsolateClient('client${argsWithId.id}', args.pubSubSendPort); var app = Angel(reflector: args.reflector) - ..container.registerSingleton(client) + ..container.registerSingleton(client) ..container.registerSingleton(InstanceInfo(id: argsWithId.id)); app.shutdownHooks.add((_) => client.close()); @@ -292,6 +280,13 @@ _ ___ | /| / / /_/ / _ /___ _ /___ } await driver.startServer(args.options.hostname, args.options.port); + + // Only apply the headers to AngelHttp instance + if (driver is AngelHttp) { + driver.responseHeader(args.options.responseHeaders); + driver.removeResponseHeader(args.options.removeResponseHeaders); + } + serverUrl = driver.uri; if (args.options.ssl || args.options.http2) { serverUrl = serverUrl.replace(scheme: 'https'); diff --git a/packages/production/pubspec.yaml b/packages/production/pubspec.yaml index d5013d0c..94c6b2ec 100644 --- a/packages/production/pubspec.yaml +++ b/packages/production/pubspec.yaml @@ -1,5 +1,5 @@ name: angel3_production -version: 8.0.0 +version: 8.1.0 description: Helpers for concurrency, message-passing, rotating loggers, and other production functionality in Angel3. homepage: https://angel3-framework.web.app repository: https://github.com/dukefirehawk/angel/tree/master/packages/production @@ -7,7 +7,7 @@ environment: sdk: '>=3.0.0 <4.0.0' dependencies: angel3_container: ^8.0.0 - angel3_framework: ^8.0.0 + angel3_framework: ^8.2.0 belatuk_pub_sub: ^6.0.0 args: ^2.4.0 io: ^1.0.0 @@ -15,11 +15,11 @@ dependencies: intl: ^0.18.0 dev_dependencies: lints: ^2.1.0 -# dependency_overrides: +dependency_overrides: # angel3_container: # path: ../container/angel_container -# angel3_framework: -# path: ../framework + angel3_framework: + path: ../framework # angel3_http_exception: # path: ../http_exception # angel3_model: