diff --git a/.github/workflows/coverage_comment.yml b/.github/workflows/coverage_comment.yml new file mode 100644 index 000000000..f1ed09e42 --- /dev/null +++ b/.github/workflows/coverage_comment.yml @@ -0,0 +1,31 @@ +name: 'coverage_comment' + +# This workflow runs after the 'Verify packages abilities' workflow is completed for a pull request. +# The workflow downloads the coverage report if the 'Verify packages abilities' workflow was successful. +# The workflow then adds a comment to the PR with the coverage report. + +on: + workflow_run: + workflows: ['Verify packages abilities'] + types: + - completed + +jobs: + download_coverage: + runs-on: ubuntu-latest + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + steps: + - name: Download artifact + id: download-artifact + uses: dawidd6/action-download-artifact@v3 + with: + workflow: tests.yml + workflow_conclusion: success + run_id: ${{ github.event.workflow_run.id }} + name: code-coverage-results.md + - name: Add PR comment + uses: marocchino/sticky-pull-request-comment@v2 + with: + number: ${{ github.event.workflow_run.pull_requests[0].number }} + recreate: true + path: code-coverage-results.md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 407f78faa..4afbeafd2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ on: - '6.0.0' paths-ignore: - "**.md" - pull_request_target: + pull_request: branches: - main - '6.0.0' @@ -19,9 +19,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true -permissions: - pull-requests: write - defaults: run: shell: bash -leo pipefail {0} @@ -40,7 +37,7 @@ jobs: - uses: subosito/flutter-action@v2 with: cache: true - flutter-version: ${{ matrix.sdk == 'min' && '2.8.0' || '' }} + flutter-version: ${{ matrix.sdk == 'min' && '3.3.0' || '' }} channel: ${{ matrix.sdk == 'min' && '' || matrix.channel }} - run: dart pub get - uses: bluefireteam/melos-action@v3 @@ -99,17 +96,16 @@ jobs: - name: '[Coverage] Format & print test coverage' if: ${{ matrix.sdk == 'stable' }} run: melos run coverage:show - - name: '[Coverage] Coverage Report' + - name: '[Coverage] Create Report' uses: clearlyip/code-coverage-report-action@v4 id: code_coverage_report if: ${{ matrix.sdk == 'stable' && github.actor != 'dependabot[bot]'}} with: artifact_download_workflow_names: 'Verify packages abilities,coverage_baseline' filename: 'coverage/cobertura.xml' - - name: '[Coverage] Add PR comment' - uses: marocchino/sticky-pull-request-comment@v2 - #Make sure the report was generated and that the event is actually a pull request, run if failed or success - if: ${{ matrix.sdk == 'stable' && github.actor != 'dependabot[bot]' && steps.code_coverage_report.outputs.file != '' && (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && (success() || failure()) }} + - name: '[Coverage] Upload' + if: ${{ matrix.sdk == 'stable' && github.actor != 'dependabot[bot]'}} + uses: actions/upload-artifact@v4 with: - recreate: true - path: code-coverage-results.md + name: code-coverage-results.md + path: code-coverage-results.md \ No newline at end of file diff --git a/.gitignore b/.gitignore index 43efb4a52..5ca9b8532 100644 --- a/.gitignore +++ b/.gitignore @@ -14,17 +14,22 @@ doc/api/ .vscode/ -# FVM -.fvm -.fvmrc - # Miscellaneous .DS_Store -.melos*packages # IDEA configurations /.idea/* !/.idea/dio.iml !/.idea/modules.xml +# Coverage coverage + +# Melos +**/.melos_package +.melos_packages +melos_overrides.yaml + +# FVM Version Cache +.fvm/ +.fvmrc diff --git a/COMPATIBILITY_POLICY.md b/COMPATIBILITY_POLICY.md new file mode 100644 index 000000000..0a1b39bd8 --- /dev/null +++ b/COMPATIBILITY_POLICY.md @@ -0,0 +1,21 @@ +# Compatibility Policy + +As an open-source project, all activities happened when the maintainers have spare time and energy. +The support range is limited due to the above condition. +Therefore, we have a general compatibility policy to help people +that are not actively adapting SDK updates or intended to use any old SDKs to acknowledge the support range. + +## Policy Details + +For all packages, the oldest Dart SDK we typically support +is one that was **released less than 2 years ago**. + +### Exceptions + +- The minimum SDK version will follow the dependencies' requirement. + For example: `http2: ^2.1.0` requires Dart SDK >=3.0.0. +- The implementation can no longer compatible between the latest and previous SDKs. +- Previous SDKs have security issues that require to use a new version. + +To raise your suggestions and reports, use the issue tracker +or contact cfug-team@googlegroups.com if you want to do this privately. diff --git a/README-ZH.md b/README-ZH.md index 1a237f1c6..e1c218415 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -8,15 +8,22 @@ Language: [English](README.md) | 简体中文 > [#dio](https://pub.flutter-io.cn/packages?q=topic%3Adio) 分类标签! > 了解更多:https://dart.cn/tools/pub/pubspec#topics -### dio - -- dio: [链接](dio) - [![Pub](https://img.shields.io/pub/v/dio.svg?label=dev&include_prereleases)](https://pub.flutter-io.cn/packages/dio) +## 版本问题 **在你更新之前:大版本和次要版本可能会包含不兼容的重大改动。
请阅读 [迁移指南][] 了解完整的重大变更内容。** +想要了解我们的兼容性政策,请参阅 [兼容性政策][]文档。 + [迁移指南]: https://pub.flutter-io.cn/documentation/dio/latest/topics/Migration%20Guide-topic.html +[兼容性政策]: COMPATIBILITY_POLICY.md + +## 所有依赖 + +### dio + +- dio: [链接](dio) + [![Pub](https://img.shields.io/pub/v/dio.svg?label=dev&include_prereleases)](https://pub.flutter-io.cn/packages/dio) ### 插件 diff --git a/README.md b/README.md index fe977be73..8f007dba9 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,22 @@ Please move specific paths for project instructions. > topic to your published dio related packages! > See more: https://dart.dev/tools/pub/pubspec#topics -### dio - -- dio: [link](dio) - [![Pub](https://img.shields.io/pub/v/dio.svg?label=dev&include_prereleases)](https://pub.dev/packages/dio) +## Versioning **Before you upgrade: Breaking changes might happen in major and minor versions of packages.
See the [Migration Guide][] for the complete breaking changes list.** +To know about our compatibility policy, see the [Compatibility Policy][] doc. + [Migration Guide]: https://pub.dev/documentation/dio/latest/topics/Migration%20Guide-topic.html +[Compatibility Policy]: COMPATIBILITY_POLICY.md + +## All Packages + +### dio + +- dio: [link](dio) + [![Pub](https://img.shields.io/pub/v/dio.svg?label=dev&include_prereleases)](https://pub.dev/packages/dio) ### Plugins diff --git a/SECURITY.md b/SECURITY.md index 0592d4f43..b1b6e845c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,11 +2,10 @@ ## Supported Versions - -| Version | Supported | -| ------- | ------------------ | -| >=5.0 | :white_check_mark: | -| < 5.0 | :x: | +| Version | Supported | +|---------|-----------| +| >=5.0 | ✅ | +| < 5.0 | ❌ | ## Reporting a Vulnerability diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md index 3ed4648ac..da76f7505 100644 --- a/dio/CHANGELOG.md +++ b/dio/CHANGELOG.md @@ -5,7 +5,7 @@ See the [Migration Guide][] for the complete breaking changes list.** ## Unreleased -*None.* +- Raise the min Dart SDK version to 2.18.0. ## 5.4.3+1 diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index 8631b7135..e749caae0 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -136,43 +136,26 @@ mixin OptionsMixin { /// The base config for the Dio instance, used by [Dio.options]. class BaseOptions extends _RequestConfig with OptionsMixin { BaseOptions({ - String? method, + super.method, Duration? connectTimeout, - Duration? receiveTimeout, - Duration? sendTimeout, + super.receiveTimeout, + super.sendTimeout, String baseUrl = '', Map? queryParameters, - Map? extra, - Map? headers, - bool preserveHeaderCase = false, - ResponseType? responseType = ResponseType.json, - String? contentType, - ValidateStatus? validateStatus, - bool? receiveDataWhenStatusError, - bool? followRedirects, - int? maxRedirects, - bool? persistentConnection, - RequestEncoder? requestEncoder, - ResponseDecoder? responseDecoder, - ListFormat? listFormat, - }) : super( - method: method, - receiveTimeout: receiveTimeout, - sendTimeout: sendTimeout, - extra: extra, - headers: headers, - preserveHeaderCase: preserveHeaderCase, - responseType: responseType, - contentType: contentType, - validateStatus: validateStatus, - receiveDataWhenStatusError: receiveDataWhenStatusError, - followRedirects: followRedirects, - maxRedirects: maxRedirects, - persistentConnection: persistentConnection, - requestEncoder: requestEncoder, - responseDecoder: responseDecoder, - listFormat: listFormat, - ) { + super.extra, + super.headers, + super.preserveHeaderCase = false, + super.responseType = ResponseType.json, + super.contentType, + super.validateStatus, + super.receiveDataWhenStatusError, + super.followRedirects, + super.maxRedirects, + super.persistentConnection, + super.requestEncoder, + super.responseDecoder, + super.listFormat, + }) { this.baseUrl = baseUrl; this.queryParameters = queryParameters ?? {}; this.connectTimeout = connectTimeout; @@ -500,46 +483,28 @@ class RequestOptions extends _RequestConfig with OptionsMixin { this.onReceiveProgress, this.onSendProgress, this.cancelToken, - String? method, - Duration? sendTimeout, - Duration? receiveTimeout, + super.method, + super.sendTimeout, + super.receiveTimeout, Duration? connectTimeout, Map? queryParameters, String? baseUrl, - Map? extra, - Map? headers, - bool? preserveHeaderCase, - ResponseType? responseType, - String? contentType, - ValidateStatus? validateStatus, - bool? receiveDataWhenStatusError, - bool? followRedirects, - int? maxRedirects, - bool? persistentConnection, - RequestEncoder? requestEncoder, - ResponseDecoder? responseDecoder, - ListFormat? listFormat, + super.extra, + super.headers, + super.preserveHeaderCase, + super.responseType, + super.contentType, + super.validateStatus, + super.receiveDataWhenStatusError, + super.followRedirects, + super.maxRedirects, + super.persistentConnection, + super.requestEncoder, + super.responseDecoder, + super.listFormat, bool? setRequestContentTypeWhenNoPayload, StackTrace? sourceStackTrace, - }) : assert(connectTimeout == null || !connectTimeout.isNegative), - super( - method: method, - sendTimeout: sendTimeout, - receiveTimeout: receiveTimeout, - extra: extra, - headers: headers, - preserveHeaderCase: preserveHeaderCase, - responseType: responseType, - contentType: contentType, - validateStatus: validateStatus, - receiveDataWhenStatusError: receiveDataWhenStatusError, - followRedirects: followRedirects, - maxRedirects: maxRedirects, - persistentConnection: persistentConnection, - requestEncoder: requestEncoder, - responseDecoder: responseDecoder, - listFormat: listFormat, - ) { + }) : assert(connectTimeout == null || !connectTimeout.isNegative) { this.sourceStackTrace = sourceStackTrace ?? StackTrace.current; this.queryParameters = queryParameters ?? {}; this.baseUrl = baseUrl ?? ''; diff --git a/dio/pubspec.yaml b/dio/pubspec.yaml index 5ea6f57ee..09a9db9d0 100644 --- a/dio/pubspec.yaml +++ b/dio/pubspec.yaml @@ -17,7 +17,7 @@ repository: https://github.com/cfug/dio/blob/main/dio issue_tracker: https://github.com/cfug/dio/issues environment: - sdk: '>=2.15.0 <4.0.0' + sdk: '>=2.18.0 <4.0.0' dependencies: async: ^2.8.2 diff --git a/dio/test/utils.dart b/dio/test/utils.dart index 556e2cb6f..f4a06d0bc 100644 --- a/dio/test/utils.dart +++ b/dio/test/utils.dart @@ -173,7 +173,7 @@ final Matcher throwsDioExceptionConnectionError = throwsA( /// A stream of chunks of bytes representing a single piece of data. class ByteStream extends StreamView> { - ByteStream(Stream> stream) : super(stream); + ByteStream(super.stream); /// Returns a single-subscription byte stream that will emit the given bytes /// in a single chunk. diff --git a/dio_test/lib/src/test/download_tests.dart b/dio_test/lib/src/test/download_tests.dart index 92fd23278..a14e17048 100644 --- a/dio_test/lib/src/test/download_tests.dart +++ b/dio_test/lib/src/test/download_tests.dart @@ -241,48 +241,32 @@ void downloadTests( ); }); + test('receiveTimeout triggers if gaps are too big', () { + expectLater( + dio.download( + '/drip?delay=0&duration=6&numbytes=3', + p.join(tmp.path, 'download_timeout.md'), + options: Options(receiveTimeout: Duration(seconds: 1)), + ), + throwsDioException( + DioExceptionType.receiveTimeout, + stackTraceContains: 'test/download_tests.dart', + ), + ); + }); + test( - 'timeout', - () async { - final timeoutMatcher = allOf([ - throwsA(isA()), - throwsA( - predicate( - (e) => e.type == DioExceptionType.receiveTimeout, - ), - ), - ]); - await expectLater( - dio.downloadUri( - Uri.parse('/drip').replace( - queryParameters: { - 'delay': '0', - 'duration': '4', - 'numbytes': '2', - }, - ), + 'receiveTimeout does not trigger if constantly getting response bytes', + () { + expectLater( + dio.download( + '/drip?delay=0&duration=6&numbytes=12', p.join(tmp.path, 'download_timeout.md'), options: Options(receiveTimeout: Duration(seconds: 1)), ), - timeoutMatcher, - ); - - // Throws nothing if it constantly gets response bytes. - await dio.downloadUri( - Uri.parse('/drip').replace( - queryParameters: { - 'delay': '0', - 'duration': '5', - 'numbytes': '5', - }, - ), - p.join(tmp.path, 'download_timeout.md'), - options: Options(receiveTimeout: Duration(seconds: 2)), + completes, ); }, - // The download of the main.zip file can be slow, - // so we need to increase the timeout. - timeout: Timeout(Duration(minutes: 1)), ); test('delete on error', () async { diff --git a/dio_test/lib/src/test/upload_tests.dart b/dio_test/lib/src/test/upload_tests.dart index 3d5d964ee..dacd49b50 100644 --- a/dio_test/lib/src/test/upload_tests.dart +++ b/dio_test/lib/src/test/upload_tests.dart @@ -63,7 +63,7 @@ void uploadTests( addTearDown(() => tmp.deleteSync(recursive: true)); final f = File(p.join(tmp.path, 'flutter.png')); - f.createSync(exclusive: false); + f.createSync(); f.writeAsBytesSync(base64Decode(_flutterLogPngBase64)); final contentLength = f.lengthSync(); @@ -92,7 +92,7 @@ void uploadTests( addTearDown(() => tmp.deleteSync(recursive: true)); final f = File(p.join(tmp.path, 'flutter.png')); - f.createSync(exclusive: false); + f.createSync(); f.writeAsBytesSync(base64Decode(_flutterLogPngBase64)); final contentLength = f.lengthSync(); diff --git a/dio_test/pubspec.yaml b/dio_test/pubspec.yaml index 67ab9b5eb..41e875d6d 100644 --- a/dio_test/pubspec.yaml +++ b/dio_test/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/cfug/dio/blob/main/dio_test issue_tracker: https://github.com/cfug/dio/issues environment: - sdk: '>=2.15.0 <4.0.0' + sdk: '>=2.18.0 <4.0.0' dependencies: dio: any diff --git a/example_flutter_app/lib/main.dart b/example_flutter_app/lib/main.dart index 97bc8f2d0..9b6546258 100644 --- a/example_flutter_app/lib/main.dart +++ b/example_flutter_app/lib/main.dart @@ -24,7 +24,10 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - MyHomePage({Key? key, this.title = ''}) : super(key: key); + MyHomePage({ + super.key, + this.title = '', + }); final String title; diff --git a/example_flutter_app/pubspec.yaml b/example_flutter_app/pubspec.yaml index 082705494..5c4e16d09 100644 --- a/example_flutter_app/pubspec.yaml +++ b/example_flutter_app/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.15.0 <4.0.0" + sdk: ">=2.18.0 <4.0.0" dependencies: flutter: diff --git a/melos.yaml b/melos.yaml index 2c802fd86..815b15b65 100644 --- a/melos.yaml +++ b/melos.yaml @@ -61,13 +61,7 @@ scripts: packageFilters: flutter: false dirExists: test - # Old syntax for melos <= 2.9.0 - this can be removed once we bump the minimum Dart SDK. - select-package: - flutter: false - dir-exists: test - ignore: - - 'dio_compatibility_layer' - - 'dio_http2_adapter' + fileExists: .melos_package test:web: name: Dart Web tests run: | @@ -89,32 +83,19 @@ scripts: packageFilters: flutter: false dirExists: test + fileExists: .melos_package ignore: - '*http2*' - '*cookie*' - # Old syntax for melos <= 2.9.0 - this can be removed once we bump the minimum Dart SDK. - select-package: - flutter: false - dir-exists: test - ignore: - - 'dio_compatibility_layer' - - '*http2*' - - '*cookie*' test:flutter: name: Flutter tests exec: flutter test --coverage packageFilters: flutter: true dirExists: test + fileExists: .melos_package ignore: - '*example*' - # Old syntax for melos <= 2.9.0 - this can be removed once we bump the minimum Dart SDK. - # There is no packages to run on min SDK for this command. - select-package: - flutter: true - dir-exists: test - ignore: - - '*' test:coverage: name: Run all tests and display coverage run: | diff --git a/plugins/cookie_manager/CHANGELOG.md b/plugins/cookie_manager/CHANGELOG.md index 81fb704ab..07d126041 100644 --- a/plugins/cookie_manager/CHANGELOG.md +++ b/plugins/cookie_manager/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -*None.* +- Raise the min Dart SDK version to 2.18.0 (implied by the `dio` package). ## 3.1.1 diff --git a/plugins/cookie_manager/pubspec.yaml b/plugins/cookie_manager/pubspec.yaml index 5f56a6b0a..a69185fb9 100644 --- a/plugins/cookie_manager/pubspec.yaml +++ b/plugins/cookie_manager/pubspec.yaml @@ -13,7 +13,7 @@ repository: https://github.com/cfug/dio/blob/main/plugins/cookie_manager issue_tracker: https://github.com/cfug/dio/issues environment: - sdk: ">=2.15.0 <4.0.0" + sdk: ">=2.18.0 <4.0.0" dependencies: cookie_jar: ^4.0.0 diff --git a/plugins/http2_adapter/lib/src/http2_adapter.dart b/plugins/http2_adapter/lib/src/http2_adapter.dart index 34d2cbe27..c3cc1fbda 100644 --- a/plugins/http2_adapter/lib/src/http2_adapter.dart +++ b/plugins/http2_adapter/lib/src/http2_adapter.dart @@ -137,7 +137,7 @@ class Http2Adapter implements HttpClientAdapter { } if (hasRequestData) { - Future requestStreamFuture = requestStream!.listen((data) { + Future requestStreamFuture = requestStream!.listen((data) { stream.outgoingMessages.add(DataStreamMessage(data)); }).asFuture(); final sendTimeout = options.sendTimeout ?? Duration.zero; @@ -145,7 +145,7 @@ class Http2Adapter implements HttpClientAdapter { requestStreamFuture = requestStreamFuture.timeout( sendTimeout, onTimeout: () { - stream.outgoingMessages.close(); + stream.outgoingMessages.close().catchError((_) {}); throw DioException.sendTimeout( timeout: sendTimeout, requestOptions: options, @@ -159,7 +159,7 @@ class Http2Adapter implements HttpClientAdapter { final responseSink = StreamController(); final responseHeaders = Headers(); - final responseCompleter = Completer(); + final responseCompleter = Completer(); late StreamSubscription responseSubscription; bool needRedirect = false; bool needResponse = false; @@ -220,14 +220,15 @@ class Http2Adapter implements HttpClientAdapter { cancelOnError: true, ); - Future responseFuture = responseCompleter.future; + Future responseFuture = responseCompleter.future; if (receiveTimeout > Duration.zero) { responseFuture = responseFuture.timeout( receiveTimeout, onTimeout: () { responseSubscription .cancel() - .whenComplete(() => responseSink.close()); + .catchError((_) {}) + .whenComplete(() => responseSink.close().catchError((_) {})); throw DioException.receiveTimeout( timeout: receiveTimeout, requestOptions: options, diff --git a/pubspec.yaml b/pubspec.yaml index b3184f3db..f246b5a19 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,11 +3,12 @@ publish_to: 'none' repository: https://github.com/cfug/dio environment: - sdk: '>=2.15.0 <4.0.0' + sdk: '>=2.18.0 <4.0.0' dev_dependencies: lints: any melos: any cli_util: any # Required by custom melos command. + path: any # Required by custom melos command. pub_semver: any # Required by custom melos command. yaml: any # Required by custom melos command. diff --git a/scripts/melos_packages.dart b/scripts/melos_packages.dart index 423b38a0a..90d9afbb6 100644 --- a/scripts/melos_packages.dart +++ b/scripts/melos_packages.dart @@ -3,11 +3,18 @@ import 'dart:io'; import 'package:cli_util/cli_logging.dart' show Logger; import 'package:melos/melos.dart' show MelosLogger, MelosWorkspace, MelosWorkspaceConfig; +import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; /// Writes a `.melos_packages` file to the root of the workspace with the /// packages that are compatible with the current Dart SDK version. +/// +/// Additionally creates a `.melos_package` file in each package directory +/// which is compatible with the current Dart SDK version. This is required +/// until the minimum Dart SDK can be updated to a version that allows +/// to run at least melos 4.1.0 - this seems to be Dart 3.0.0. +/// /// This is useful for CI scripts that need to know which packages to run /// melos for using the `MELOS_PACKAGES` environment variable. void main() async { @@ -24,16 +31,29 @@ void main() async { logger: MelosLogger(Logger.standard()), ); final packages = workspace.filteredPackages.values; + + // Delete old melos marker files + for (final package in packages) { + final marker = File(p.join(package.path, '.melos_package')); + if (marker.existsSync()) { + marker.deleteSync(); + } + } + final current = Version.parse( RegExp(r'\d*\.\d*\.\d*').firstMatch(Platform.version)!.group(0)!, ); - final String validPackages = packages - .where((e) => e.pubSpec.environment!.sdkConstraint!.allows(current)) - .map((e) => e.name) - .join(','); + final validPackages = packages + .where((e) => e.pubSpec.environment!.sdkConstraint!.allows(current)); + + // Create melos marker files + for (final package in validPackages) { + File(p.join(package.path, '.melos_package')).createSync(); + } + final validPackagesString = validPackages.map((p) => p.name).join(','); File('$root/.melos_packages') - .writeAsStringSync('MELOS_PACKAGES=$validPackages'); + .writeAsStringSync('MELOS_PACKAGES=$validPackagesString'); } extension YamlUtils on YamlNode {