├── .gitattributes ├── .github ├── dependabot.yaml └── workflows │ └── dart.yml ├── .gitignore ├── .gitpod.yml ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── benchmark └── bench.dart ├── bin ├── server_cloud_run.dart └── server_dev.dart ├── cloud_run.Dockerfile ├── cloud_run_beta.Dockerfile ├── cloud_run_dev.Dockerfile ├── cloud_run_master.Dockerfile ├── cloud_run_old.Dockerfile ├── doc ├── generated │ ├── dartservices.dart │ └── dartservices.json └── images │ └── compile-10qps-loadtesting.png ├── flutter-sdk-version.yaml ├── lib ├── services_cloud_run.dart ├── services_dev.dart ├── src │ ├── analysis_server.dart │ ├── analysis_servers.dart │ ├── bench.dart │ ├── common.dart │ ├── common_server_api.dart │ ├── common_server_api.g.dart │ ├── common_server_impl.dart │ ├── compiler.dart │ ├── config.dart │ ├── github_oauth_handler.dart │ ├── project.dart │ ├── project_creator.dart │ ├── protos │ │ ├── dart_services.pb.dart │ │ ├── dart_services.pbenum.dart │ │ ├── dart_services.pbjson.dart │ │ └── dart_services.pbserver.dart │ ├── pub.dart │ ├── scheduler.dart │ ├── sdk.dart │ ├── server_cache.dart │ ├── shelf_cors.dart │ └── utils.dart └── version.dart ├── protos └── dart_services.proto ├── pubspec.lock ├── pubspec.yaml ├── test ├── all.dart ├── analysis_server_test.dart ├── bench_test.dart ├── common_server_api_protobuf_test.dart ├── common_server_api_test.dart ├── common_test.dart ├── compiler_test.dart ├── flutter_analysis_server_test.dart ├── flutter_web_test.dart ├── gae_deployed_test.dart ├── integration.dart ├── project_creator_test.dart ├── pub_test.dart ├── redis_cache_test.dart ├── shelf_cors_test.dart └── utils_test.dart └── tool ├── dart_cloud_run.sh ├── dart_run.sh ├── deploy.sh ├── fuzz_driver.dart ├── grind.dart ├── load_driver.dart ├── pub_dependencies_beta.json ├── pub_dependencies_dev.json ├── pub_dependencies_master.json ├── pub_dependencies_old.json ├── pub_dependencies_stable.json ├── travis.sh ├── update_sdk.dart └── warmup.dart /.gitattributes: -------------------------------------------------------------------------------- 1 | # For a detailed description of the content in this file, please see: 2 | # https://git-scm.com/docs/gitattributes 3 | 4 | *.css text eol=lf 5 | *.dart text eol=lf 6 | *.html text eol=lf 7 | *.json text eol=lf 8 | *.md text eol=lf 9 | *.yaml text eol=lf 10 | *.sh text eol=lf 11 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | enable-beta-ecosystems: true 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | labels: 9 | - "autosubmit" 10 | - package-ecosystem: "pub" 11 | directory: "/" 12 | schedule: 13 | interval: "daily" 14 | - package-ecosystem: "pub" 15 | directory: "/example/" 16 | schedule: 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: dart-services 2 | 3 | on: 4 | # Run CI on pushes to the master branch, and on PRs against master. 5 | push: 6 | branches: [master] 7 | pull_request: 8 | branches: [master] 9 | workflow_dispatch: 10 | schedule: 11 | - cron: "0 0 * * *" # Every day at midnight 12 | 13 | jobs: 14 | # Check code formatting, static analysis, and build on a single OS (linux) 15 | # against Dart stable and beta. 16 | analyze: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | sdk: [stable, beta, dev, old, master] 22 | steps: 23 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 24 | - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f 25 | with: 26 | sdk: stable 27 | - name: Install dependencies 28 | run: sudo apt-get install -y protobuf-compiler redis 29 | - name: Install dependencies 30 | run: dart pub get 31 | - name: Analyze code 32 | run: dart analyze --fatal-infos . 33 | - name: Prepare Flutter 34 | run: | 35 | dart run tool/update_sdk.dart ${{ matrix.sdk }} 36 | export PATH=$PATH:$PWD/flutter-sdks/${{ matrix.sdk }}/bin 37 | flutter doctor -v 38 | flutter config --enable-web 39 | - name: Run tests 40 | run: | 41 | export PATH=$PATH:$HOME/.pub-cache/bin 42 | dart pub global activate protoc_plugin 43 | ./tool/travis.sh ${{ matrix.sdk }} 44 | 45 | # Run the benchmarks on the bots to ensure they don't regress. 46 | benchmark: 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 50 | - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f 51 | - run: sudo apt-get install -y protobuf-compiler redis 52 | - run: dart pub get 53 | - name: Download the Flutter SDK 54 | run: | 55 | dart run tool/update_sdk.dart stable 56 | export PATH=$PATH:$PWD/flutter-sdks/stable/bin 57 | flutter doctor -v 58 | - name: Prep the repo 59 | env: 60 | FLUTTER_CHANNEL: stable 61 | run: dart pub run grinder buildbot 62 | - name: Run benchmarks 63 | run: dart benchmark/bench.dart 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don’t commit the following directories created by pub. 2 | build/ 3 | .dart_tool/ 4 | .packages 5 | .pub/ 6 | 7 | # Or the files created by dart2js. 8 | *.dart.js 9 | *.js.deps 10 | *.js.map 11 | 12 | # Other files. 13 | coverage.json 14 | lcov.info 15 | .idea/ 16 | 17 | # Redis snapshot persistance file 18 | dump.rdb 19 | 20 | # compilation artifacts 21 | artifacts/ 22 | project_templates/ 23 | flutter-sdks/ 24 | local_pub_cache/ 25 | 26 | # local configuration 27 | config.properties 28 | 29 | # Editor configuration 30 | .vscode/ 31 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | # List the ports you want to expose and what to do when they are served. See https://www.gitpod.io/docs/config-ports/ 5 | ports: 6 | - port: 8080 7 | onOpen: "open-browser" 8 | - port: 8082 9 | onOpen: "ignore" 10 | - port: 9501 11 | onOpen: "ignore" 12 | - port: 9502 13 | onOpen: "ignore" 14 | - port: 9503 15 | onOpen: "ignore" 16 | - port: 9504 17 | onOpen: "ignore" 18 | 19 | 20 | vscode: 21 | extensions: 22 | - dart-code.dart-code@3.20.1:gKwMOzlkrgxrb7aPrMdQ8w== -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the Dart Services project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | George Moschovitis 8 | Tim Maffett 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.0.1 4 | 5 | * Initial version, created by Stagehand. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dart Services 2 | 3 | **Note:** This is no longer the source of truth for Dart Services. Please see [github.com/dart-lang/dart-pad/tree/main/pkgs/dart_services][] for the new source of truth for this project. 4 | 5 | [github.com/dart-lang/dart-pad/tree/main/pkgs/dart_services]: https://github.com/dart-lang/dart-pad/tree/main/pkgs/dart_services 6 | 7 | A server backend to support DartPad. 8 | 9 | [![Build Status](https://github.com/dart-lang/dart-services/workflows/dart-services/badge.svg)](https://github.com/dart-lang/dart-services/actions?workflow=dart-services) 10 | [![Coverage Status](https://coveralls.io/repos/dart-lang/dart-services/badge.svg?branch=master)](https://coveralls.io/r/dart-lang/dart-services?branch=master) 11 | [![Uptime Status](https://img.shields.io/badge/uptime-Pingdom-blue.svg)](http://stats.pingdom.com/8n3tfpl1u0j9) 12 | 13 | ## What is it? What does it do? 14 | 15 | This project is a small, stateless Dart server, which powers the front-end of DartPad. 16 | It provides many of DartPad's features, including static analysis (errors and warnings), 17 | compilation to JavaScript, code completion, dartdoc information, code formatting, and 18 | quick fixes for issues. 19 | 20 | ## Getting set up 21 | 22 | This project is built with [grinder](https://pub.dev/packages/grinder). To install, please run: 23 | 24 | ```bash 25 | $ dart pub global activate grinder 26 | ``` 27 | 28 | The dart-services v2 API is defined in terms of Protobuf, which requires the 29 | installation of the Protobuf `protoc` compiler. Please see [Protocol 30 | Buffers](https://developers.google.com/protocol-buffers/) for detailed 31 | installation instructions. On macOS, you may also install with Homebrew via: 32 | 33 | ```bash 34 | $ brew install protobuf 35 | ``` 36 | 37 | The Dart protoc plugin is also required for the above `protoc` compiler 38 | to generate Dart code. To install, please run: 39 | 40 | ```bash 41 | $ dart pub global activate protoc_plugin 42 | ``` 43 | 44 | ## Initialize Flutter 45 | 46 | The Flutter SDK needs to be downloaded and setup. 47 | 48 | ```bash 49 | $ dart pub install 50 | $ dart run tool/update_sdk.dart stable 51 | ``` 52 | 53 | ## Build the subsidiary files 54 | 55 | The Dart Services server depends on generated files. Run the following to generate all the required binaries. 56 | 57 | ```bash 58 | $ FLUTTER_CHANNEL="stable" grind deploy 59 | ``` 60 | 61 | ## Running 62 | 63 | To run the server, run: 64 | 65 | ```bash 66 | $ FLUTTER_CHANNEL="stable" grind serve 67 | ``` 68 | 69 | The server will run from port 8082 and export several JSON APIs, like 70 | `/api/compile` and `/api/analyze`. 71 | 72 | ## Testing 73 | 74 | To run tests: 75 | 76 | `FLUTTER_CHANNEL="stable" grind test` for unit tests 77 | 78 | or: 79 | 80 | `grind deploy` for all tests and checks. 81 | 82 | dart-services requires the `redis` package, including the `redis-server` binary, 83 | to be installed to run tests. `sudo apt-get install redis-server` will install 84 | this on Ubuntu, but see [Redis' Quick Start guide](https://redis.io/topics/quickstart) for other platforms. 85 | 86 | ## Related projects 87 | 88 | See also the [dart-pad](https://github.com/dart-lang/dart-pad) repo. 89 | 90 | ## Issues and bugs 91 | 92 | Please file reports on the 93 | [GitHub Issue Tracker for DartPad](https://github.com/dart-lang/dart-pad/issues). 94 | 95 | ## License and Contributing 96 | 97 | Contributions welcome! Please read this short 98 | [guide](https://github.com/dart-lang/dart-services/wiki/Contributing) first. 99 | You can view our license 100 | [here](https://github.com/dart-lang/dart-services/blob/master/LICENSE). 101 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-casts: true 6 | strict-inference: true 7 | strict-raw-types: true 8 | exclude: 9 | - doc/generated/** 10 | - flutter-sdks/** 11 | - local_pub_cache/** 12 | - project_templates/** 13 | errors: 14 | lines_longer_than_80_chars: ignore 15 | 16 | linter: 17 | rules: 18 | - prefer_final_in_for_each 19 | - prefer_final_locals 20 | - prefer_relative_imports 21 | -------------------------------------------------------------------------------- /benchmark/bench.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | // ignore_for_file: only_throw_errors 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:dart_services/src/analysis_server.dart'; 10 | import 'package:dart_services/src/bench.dart'; 11 | import 'package:dart_services/src/common.dart'; 12 | import 'package:dart_services/src/compiler.dart'; 13 | import 'package:dart_services/src/protos/dart_services.pb.dart' as proto; 14 | import 'package:dart_services/src/sdk.dart'; 15 | import 'package:logging/logging.dart'; 16 | 17 | void main(List args) async { 18 | final json = args.contains('--json'); 19 | final harness = BenchmarkHarness(asJson: json); 20 | final compiler = Compiler(Sdk.create(stableChannel)); 21 | 22 | Logger.root.level = Level.WARNING; 23 | Logger.root.onRecord.listen((LogRecord record) { 24 | print(record); 25 | if (record.stackTrace != null) print(record.stackTrace); 26 | }); 27 | 28 | final benchmarks = [ 29 | AnalyzerBenchmark('hello', sampleCode), 30 | AnalyzerBenchmark('hellohtml', sampleCodeWeb), 31 | AnalyzerBenchmark('sunflower', _sunflower), 32 | AnalyzerBenchmark('spinning_square', _spinningSquare), 33 | AnalysisServerBenchmark('hello', sampleCode), 34 | AnalysisServerBenchmark('hellohtml', sampleCodeWeb), 35 | AnalysisServerBenchmark('sunflower', _sunflower), 36 | AnalysisServerBenchmark('spinning_square', _spinningSquare), 37 | Dart2jsBenchmark('hello', sampleCode, compiler), 38 | Dart2jsBenchmark('hellohtml', sampleCodeWeb, compiler), 39 | Dart2jsBenchmark('sunflower', _sunflower, compiler), 40 | // TODO: dart-services dart2js compile path doesn't currently support 41 | // compiling Flutter apps. 42 | // Dart2jsBenchmark('spinning_square', _spinningSquare, compiler), 43 | DevCompilerBenchmark('hello', sampleCode, compiler), 44 | DevCompilerBenchmark('hellohtml', sampleCodeWeb, compiler), 45 | DevCompilerBenchmark('sunflower', _sunflower, compiler), 46 | DevCompilerBenchmark('spinning_square', _spinningSquare, compiler), 47 | ]; 48 | 49 | await harness.benchmark(benchmarks); 50 | await compiler.dispose(); 51 | } 52 | 53 | class AnalyzerBenchmark extends Benchmark { 54 | final String source; 55 | final AnalysisServerWrapper analysisServer; 56 | 57 | AnalyzerBenchmark( 58 | String name, 59 | this.source, 60 | ) : analysisServer = DartAnalysisServerWrapper( 61 | dartSdkPath: Sdk.create(stableChannel).dartSdkPath), 62 | super('analyzer.$name'); 63 | 64 | @override 65 | Future init() => analysisServer.init(); 66 | 67 | @override 68 | Future perform() => analysisServer.analyze(source); 69 | 70 | @override 71 | Future tearDown() => analysisServer.shutdown(); 72 | } 73 | 74 | class Dart2jsBenchmark extends Benchmark { 75 | final String source; 76 | final Compiler compiler; 77 | 78 | Dart2jsBenchmark(String name, this.source, this.compiler) 79 | : super('dart2js.$name'); 80 | 81 | @override 82 | Future perform() { 83 | return compiler.compile(source).then((CompilationResults result) { 84 | if (!result.success) throw result; 85 | }); 86 | } 87 | } 88 | 89 | class DevCompilerBenchmark extends Benchmark { 90 | final String source; 91 | final Compiler compiler; 92 | 93 | DevCompilerBenchmark(String name, this.source, this.compiler) 94 | : super('dartdevc.$name'); 95 | 96 | @override 97 | Future perform() { 98 | return compiler.compileDDC(source).then((DDCCompilationResults result) { 99 | if (!result.success) throw result; 100 | }); 101 | } 102 | } 103 | 104 | class AnalysisServerBenchmark extends Benchmark { 105 | final String source; 106 | final AnalysisServerWrapper analysisServer; 107 | 108 | AnalysisServerBenchmark(String name, this.source) 109 | : analysisServer = DartAnalysisServerWrapper( 110 | dartSdkPath: Sdk.create(stableChannel).dartSdkPath), 111 | super('completion.$name'); 112 | 113 | @override 114 | Future init() => analysisServer.init(); 115 | 116 | @override 117 | Future perform() => 118 | analysisServer.complete(source, 30); 119 | 120 | @override 121 | Future tearDown() => analysisServer.shutdown(); 122 | } 123 | 124 | final String _sunflower = ''' 125 | library sunflower; 126 | 127 | import 'dart:html'; 128 | import 'dart:math' as math; 129 | 130 | main() { 131 | Sunflower(); 132 | } 133 | 134 | class Sunflower { 135 | static const String orange = "orange"; 136 | static const seedRadius = 2; 137 | static const scaleFactor = 4; 138 | static const tau = math.pi * 2; 139 | static const maxD = 300; 140 | 141 | late CanvasRenderingContext2D ctx; 142 | late num xc, yc; 143 | num seeds = 0; 144 | late num phi; 145 | 146 | Sunflower() { 147 | phi = (math.sqrt(5) + 1) / 2; 148 | 149 | CanvasElement canvas = querySelector("#canvas") as CanvasElement; 150 | xc = yc = maxD / 2; 151 | ctx = canvas.getContext("2d") as CanvasRenderingContext2D; 152 | 153 | var slider = querySelector("#slider") as InputElement; 154 | slider.onChange.listen((Event e) { 155 | seeds = int.parse(slider.value!); 156 | drawFrame(); 157 | }); 158 | 159 | seeds = int.parse(slider.value!); 160 | 161 | drawFrame(); 162 | } 163 | 164 | // Draw the complete figure for the current number of seeds. 165 | void drawFrame() { 166 | ctx.clearRect(0, 0, maxD, maxD); 167 | for (var i = 0; i < seeds; i++) { 168 | var theta = i * tau / phi; 169 | var r = math.sqrt(i) * scaleFactor; 170 | var x = xc + r * math.cos(theta); 171 | var y = yc - r * math.sin(theta); 172 | drawSeed(x, y); 173 | } 174 | } 175 | 176 | // Draw a small circle representing a seed centered at (x,y). 177 | void drawSeed(num x, num y) { 178 | ctx.beginPath(); 179 | ctx.lineWidth = 2; 180 | ctx.fillStyle = orange; 181 | ctx.strokeStyle = orange; 182 | ctx.arc(x, y, seedRadius, 0, tau, false); 183 | ctx.fill(); 184 | ctx.closePath(); 185 | ctx.stroke(); 186 | } 187 | } 188 | '''; 189 | 190 | final _spinningSquare = ''' 191 | import 'package:flutter/material.dart'; 192 | 193 | class SpinningSquare extends StatefulWidget { 194 | @override 195 | SpinningSquareState createState() => SpinningSquareState(); 196 | } 197 | 198 | class SpinningSquareState extends State 199 | with SingleTickerProviderStateMixin { 200 | late AnimationController _animation; 201 | 202 | @override 203 | void initState() { 204 | super.initState(); 205 | // We use 3600 milliseconds instead of 1800 milliseconds because 0.0 -> 1.0 206 | // represents an entire turn of the square whereas in the other examples 207 | // we used 0.0 -> math.pi, which is only half a turn. 208 | _animation = AnimationController( 209 | duration: const Duration(milliseconds: 3600), 210 | vsync: this, 211 | )..repeat(); 212 | } 213 | 214 | @override 215 | Widget build(BuildContext context) { 216 | return RotationTransition( 217 | turns: _animation, 218 | child: Container( 219 | width: 200.0, 220 | height: 200.0, 221 | color: const Color(0xFF00FF00), 222 | ), 223 | ); 224 | } 225 | 226 | @override 227 | void dispose() { 228 | _animation.dispose(); 229 | super.dispose(); 230 | } 231 | } 232 | 233 | main() async { 234 | runApp(Center(child: SpinningSquare())); 235 | } 236 | '''; 237 | -------------------------------------------------------------------------------- /bin/server_cloud_run.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | /// A dev-time only server; see `bin/server.dart` for the GAE server. 6 | library; 7 | 8 | import 'dart:async'; 9 | 10 | import 'package:dart_services/services_cloud_run.dart' as services_cloud_run; 11 | 12 | Future main(List args) async { 13 | return services_cloud_run.main(args); 14 | } 15 | -------------------------------------------------------------------------------- /bin/server_dev.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | /// A dev-time only server; see `bin/server.dart` for the GAE server. 6 | library; 7 | 8 | import 'dart:async'; 9 | 10 | import 'package:dart_services/services_dev.dart' as services_dev; 11 | 12 | Future main(List args) async { 13 | await services_dev.main(args); 14 | } 15 | -------------------------------------------------------------------------------- /cloud_run.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dart:stable 2 | 3 | # We install unzip and remove the apt-index again to keep the 4 | # docker image diff small. 5 | RUN apt-get update && \ 6 | apt-get install -y unzip && \ 7 | rm -rf /var/lib/apt/lists/* 8 | 9 | WORKDIR /app 10 | RUN groupadd --system dart && \ 11 | useradd --no-log-init --system --home /home/dart --create-home -g dart dart 12 | RUN chown dart:dart /app 13 | 14 | # Work around https://github.com/dart-lang/sdk/issues/47093 15 | RUN find /usr/lib/dart -type d -exec chmod 755 {} \; 16 | 17 | # Switch to a new, non-root user to use the flutter tool. 18 | # The Flutter tool won't perform its actions when run as root. 19 | USER dart 20 | 21 | COPY --chown=dart:dart tool/dart_cloud_run.sh /dart_runtime/ 22 | RUN chmod a+x /dart_runtime/dart_cloud_run.sh 23 | COPY --chown=dart:dart pubspec.* /app/ 24 | RUN dart pub get 25 | COPY --chown=dart:dart . /app 26 | RUN dart pub get --offline 27 | 28 | ENV PATH="/home/dart/.pub-cache/bin:${PATH}" 29 | ENV FLUTTER_CHANNEL="stable" 30 | 31 | # Set the Flutter SDK up for web compilation. 32 | RUN dart pub run grinder setup-flutter-sdk 33 | 34 | # Build the dill file 35 | RUN dart pub run grinder build-storage-artifacts validate-storage-artifacts 36 | 37 | # Clear out any arguments the base images might have set and ensure we start 38 | # the Dart app using custom script enabling debug modes. 39 | CMD [] 40 | 41 | ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", \ 42 | "--redis-url", "redis://10.0.0.4:6379", "--channel", "stable"] 43 | -------------------------------------------------------------------------------- /cloud_run_beta.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dart:beta 2 | 3 | # We install unzip and remove the apt-index again to keep the 4 | # docker image diff small. 5 | RUN apt-get update && \ 6 | apt-get install -y unzip && \ 7 | rm -rf /var/lib/apt/lists/* 8 | 9 | WORKDIR /app 10 | RUN groupadd --system dart && \ 11 | useradd --no-log-init --system --home /home/dart --create-home -g dart dart 12 | RUN chown dart:dart /app 13 | 14 | # Work around https://github.com/dart-lang/sdk/issues/47093 15 | RUN find /usr/lib/dart -type d -exec chmod 755 {} \; 16 | 17 | # Switch to a new, non-root user to use the flutter tool. 18 | # The Flutter tool won't perform its actions when run as root. 19 | USER dart 20 | 21 | COPY --chown=dart:dart tool/dart_cloud_run.sh /dart_runtime/ 22 | RUN chmod a+x /dart_runtime/dart_cloud_run.sh 23 | COPY --chown=dart:dart pubspec.* /app/ 24 | RUN dart pub get 25 | COPY --chown=dart:dart . /app 26 | RUN dart pub get --offline 27 | 28 | ENV PATH="/home/dart/.pub-cache/bin:${PATH}" 29 | ENV FLUTTER_CHANNEL="beta" 30 | 31 | # Set the Flutter SDK up for web compilation. 32 | RUN dart pub run grinder setup-flutter-sdk 33 | 34 | # Build the dill file 35 | RUN dart pub run grinder build-storage-artifacts validate-storage-artifacts 36 | 37 | # Clear out any arguments the base images might have set and ensure we start 38 | # the Dart app using custom script enabling debug modes. 39 | CMD [] 40 | 41 | ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", \ 42 | "--redis-url", "redis://10.0.0.4:6379", "--channel", "beta"] 43 | -------------------------------------------------------------------------------- /cloud_run_dev.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dart:stable 2 | 3 | # We install unzip and remove the apt-index again to keep the 4 | # docker image diff small. 5 | RUN apt-get update && \ 6 | apt-get install -y unzip && \ 7 | rm -rf /var/lib/apt/lists/* 8 | 9 | WORKDIR /app 10 | RUN groupadd --system dart && \ 11 | useradd --no-log-init --system --home /home/dart --create-home -g dart dart 12 | RUN chown dart:dart /app 13 | 14 | # Work around https://github.com/dart-lang/sdk/issues/47093 15 | RUN find /usr/lib/dart -type d -exec chmod 755 {} \; 16 | 17 | # Switch to a new, non-root user to use the flutter tool. 18 | # The Flutter tool won't perform its actions when run as root. 19 | USER dart 20 | 21 | COPY --chown=dart:dart tool/dart_cloud_run.sh /dart_runtime/ 22 | RUN chmod a+x /dart_runtime/dart_cloud_run.sh 23 | COPY --chown=dart:dart pubspec.* /app/ 24 | RUN dart pub get 25 | COPY --chown=dart:dart . /app 26 | RUN dart pub get --offline 27 | 28 | ENV PATH="/home/dart/.pub-cache/bin:${PATH}" 29 | ENV FLUTTER_CHANNEL="dev" 30 | 31 | # Set the Flutter SDK up for web compilation. 32 | RUN dart pub run grinder setup-flutter-sdk 33 | 34 | # Build the dill file 35 | RUN dart pub run grinder build-storage-artifacts validate-storage-artifacts 36 | 37 | # Clear out any arguments the base images might have set and ensure we start 38 | # the Dart app using custom script enabling debug modes. 39 | CMD [] 40 | 41 | ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", \ 42 | "--redis-url", "redis://10.0.0.4:6379", "--channel", "dev"] 43 | -------------------------------------------------------------------------------- /cloud_run_master.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dart:beta 2 | 3 | # We install unzip and remove the apt-index again to keep the 4 | # docker image diff small. 5 | RUN apt-get update && \ 6 | apt-get install -y unzip && \ 7 | rm -rf /var/lib/apt/lists/* 8 | 9 | WORKDIR /app 10 | RUN groupadd --system dart && \ 11 | useradd --no-log-init --system --home /home/dart --create-home -g dart dart 12 | RUN chown dart:dart /app 13 | 14 | # Work around https://github.com/dart-lang/sdk/issues/47093 15 | RUN find /usr/lib/dart -type d -exec chmod 755 {} \; 16 | 17 | # Switch to a new, non-root user to use the flutter tool. 18 | # The Flutter tool won't perform its actions when run as root. 19 | USER dart 20 | 21 | COPY --chown=dart:dart tool/dart_cloud_run.sh /dart_runtime/ 22 | RUN chmod a+x /dart_runtime/dart_cloud_run.sh 23 | COPY --chown=dart:dart pubspec.* /app/ 24 | RUN dart pub get 25 | COPY --chown=dart:dart . /app 26 | RUN dart pub get --offline 27 | 28 | ENV PATH="/home/dart/.pub-cache/bin:${PATH}" 29 | ENV FLUTTER_CHANNEL="master" 30 | 31 | # Set the Flutter SDK up for web compilation. 32 | RUN dart pub run grinder setup-flutter-sdk 33 | 34 | # Build the dill file 35 | RUN dart pub run grinder build-storage-artifacts validate-storage-artifacts 36 | 37 | # Clear out any arguments the base images might have set and ensure we start 38 | # the Dart app using custom script enabling debug modes. 39 | CMD [] 40 | 41 | ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", \ 42 | "--redis-url", "redis://10.0.0.4:6379", "--channel", "master"] 43 | -------------------------------------------------------------------------------- /cloud_run_old.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dart:stable 2 | 3 | # We install unzip and remove the apt-index again to keep the 4 | # docker image diff small. 5 | RUN apt-get update && \ 6 | apt-get install -y unzip && \ 7 | rm -rf /var/lib/apt/lists/* 8 | 9 | WORKDIR /app 10 | RUN groupadd --system dart && \ 11 | useradd --no-log-init --system --home /home/dart --create-home -g dart dart 12 | RUN chown dart:dart /app 13 | 14 | # Work around https://github.com/dart-lang/sdk/issues/47093 15 | RUN find /usr/lib/dart -type d -exec chmod 755 {} \; 16 | 17 | # Switch to a new, non-root user to use the flutter tool. 18 | # The Flutter tool won't perform its actions when run as root. 19 | USER dart 20 | 21 | COPY --chown=dart:dart tool/dart_cloud_run.sh /dart_runtime/ 22 | RUN chmod a+x /dart_runtime/dart_cloud_run.sh 23 | COPY --chown=dart:dart pubspec.* /app/ 24 | RUN dart pub get 25 | COPY --chown=dart:dart . /app 26 | RUN dart pub get --offline 27 | 28 | ENV PATH="/home/dart/.pub-cache/bin:${PATH}" 29 | ENV FLUTTER_CHANNEL="old" 30 | 31 | # Set the Flutter SDK up for web compilation. 32 | RUN dart pub run grinder setup-flutter-sdk 33 | 34 | # Build the dill file 35 | RUN dart pub run grinder build-storage-artifacts validate-storage-artifacts 36 | 37 | # Clear out any arguments the base images might have set and ensure we start 38 | # the Dart app using custom script enabling debug modes. 39 | CMD [] 40 | 41 | ENTRYPOINT ["/dart_runtime/dart_cloud_run.sh", "--port", "${PORT}", \ 42 | "--redis-url", "redis://10.0.0.4:6379", "--channel", "old"] 43 | -------------------------------------------------------------------------------- /doc/generated/dartservices.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "discovery#restDescription", 3 | "etag": "89266e60165edb921ae3223edd57f8029631982b", 4 | "discoveryVersion": "v1", 5 | "id": "dartservices:v1", 6 | "name": "dartservices", 7 | "version": "v1", 8 | "revision": "0", 9 | "protocol": "rest", 10 | "baseUrl": "/api/dartservices/v1/", 11 | "basePath": "/api/dartservices/v1/", 12 | "rootUrl": "/", 13 | "servicePath": "api/dartservices/v1/", 14 | "parameters": {}, 15 | "schemas": { 16 | "SourceRequest": { 17 | "id": "SourceRequest", 18 | "type": "object", 19 | "properties": { 20 | "source": { 21 | "type": "string", 22 | "description": "The Dart source.", 23 | "required": true 24 | }, 25 | "offset": { 26 | "type": "integer", 27 | "description": "An optional offset into the source code.", 28 | "format": "int32" 29 | } 30 | } 31 | }, 32 | "AnalysisResults": { 33 | "id": "AnalysisResults", 34 | "type": "object", 35 | "properties": { 36 | "issues": { 37 | "type": "array", 38 | "items": { 39 | "$ref": "AnalysisIssue" 40 | } 41 | }, 42 | "packageImports": { 43 | "type": "array", 44 | "description": "The package imports parsed from the source.", 45 | "items": { 46 | "type": "string" 47 | } 48 | } 49 | } 50 | }, 51 | "AnalysisIssue": { 52 | "id": "AnalysisIssue", 53 | "type": "object", 54 | "properties": { 55 | "kind": { 56 | "type": "string" 57 | }, 58 | "line": { 59 | "type": "integer", 60 | "format": "int32" 61 | }, 62 | "message": { 63 | "type": "string" 64 | }, 65 | "sourceName": { 66 | "type": "string" 67 | }, 68 | "hasFixes": { 69 | "type": "boolean" 70 | }, 71 | "charStart": { 72 | "type": "integer", 73 | "format": "int32" 74 | }, 75 | "charLength": { 76 | "type": "integer", 77 | "format": "int32" 78 | } 79 | } 80 | }, 81 | "CompileRequest": { 82 | "id": "CompileRequest", 83 | "type": "object", 84 | "properties": { 85 | "source": { 86 | "type": "string", 87 | "description": "The Dart source.", 88 | "required": true 89 | }, 90 | "returnSourceMap": { 91 | "type": "boolean", 92 | "description": "Return the Dart to JS source map; optional (defaults to false)." 93 | } 94 | } 95 | }, 96 | "CompileResponse": { 97 | "id": "CompileResponse", 98 | "type": "object", 99 | "properties": { 100 | "result": { 101 | "type": "string" 102 | }, 103 | "sourceMap": { 104 | "type": "string" 105 | } 106 | } 107 | }, 108 | "CompileDDCResponse": { 109 | "id": "CompileDDCResponse", 110 | "type": "object", 111 | "properties": { 112 | "result": { 113 | "type": "string" 114 | }, 115 | "modulesBaseUrl": { 116 | "type": "string" 117 | } 118 | } 119 | }, 120 | "CompleteResponse": { 121 | "id": "CompleteResponse", 122 | "type": "object", 123 | "properties": { 124 | "replacementOffset": { 125 | "type": "integer", 126 | "description": "The offset of the start of the text to be replaced.", 127 | "format": "int32" 128 | }, 129 | "replacementLength": { 130 | "type": "integer", 131 | "description": "The length of the text to be replaced.", 132 | "format": "int32" 133 | }, 134 | "completions": { 135 | "type": "array", 136 | "items": { 137 | "type": "object", 138 | "additionalProperties": { 139 | "type": "string" 140 | } 141 | } 142 | } 143 | } 144 | }, 145 | "FixesResponse": { 146 | "id": "FixesResponse", 147 | "type": "object", 148 | "properties": { 149 | "fixes": { 150 | "type": "array", 151 | "items": { 152 | "$ref": "ProblemAndFixes" 153 | } 154 | } 155 | } 156 | }, 157 | "ProblemAndFixes": { 158 | "id": "ProblemAndFixes", 159 | "type": "object", 160 | "properties": { 161 | "fixes": { 162 | "type": "array", 163 | "items": { 164 | "$ref": "CandidateFix" 165 | } 166 | }, 167 | "problemMessage": { 168 | "type": "string" 169 | }, 170 | "offset": { 171 | "type": "integer", 172 | "format": "int32" 173 | }, 174 | "length": { 175 | "type": "integer", 176 | "format": "int32" 177 | } 178 | } 179 | }, 180 | "CandidateFix": { 181 | "id": "CandidateFix", 182 | "type": "object", 183 | "properties": { 184 | "message": { 185 | "type": "string" 186 | }, 187 | "edits": { 188 | "type": "array", 189 | "items": { 190 | "$ref": "SourceEdit" 191 | } 192 | }, 193 | "selectionOffset": { 194 | "type": "integer", 195 | "format": "int32" 196 | }, 197 | "linkedEditGroups": { 198 | "type": "array", 199 | "items": { 200 | "$ref": "LinkedEditGroup" 201 | } 202 | } 203 | } 204 | }, 205 | "SourceEdit": { 206 | "id": "SourceEdit", 207 | "type": "object", 208 | "properties": { 209 | "offset": { 210 | "type": "integer", 211 | "format": "int32" 212 | }, 213 | "length": { 214 | "type": "integer", 215 | "format": "int32" 216 | }, 217 | "replacement": { 218 | "type": "string" 219 | } 220 | } 221 | }, 222 | "LinkedEditGroup": { 223 | "id": "LinkedEditGroup", 224 | "type": "object", 225 | "properties": { 226 | "positions": { 227 | "type": "array", 228 | "description": "The positions of the regions that should be edited simultaneously.", 229 | "items": { 230 | "type": "integer", 231 | "format": "int32" 232 | } 233 | }, 234 | "length": { 235 | "type": "integer", 236 | "description": "The length of the regions that should be edited simultaneously.", 237 | "format": "int32" 238 | }, 239 | "suggestions": { 240 | "type": "array", 241 | "description": "Pre-computed suggestions for what every region might want to be changed to.", 242 | "items": { 243 | "$ref": "LinkedEditSuggestion" 244 | } 245 | } 246 | } 247 | }, 248 | "LinkedEditSuggestion": { 249 | "id": "LinkedEditSuggestion", 250 | "type": "object", 251 | "properties": { 252 | "value": { 253 | "type": "string", 254 | "description": "The value that could be used to replace all of the linked edit regions." 255 | }, 256 | "kind": { 257 | "type": "string", 258 | "description": "The kind of value being proposed." 259 | } 260 | } 261 | }, 262 | "AssistsResponse": { 263 | "id": "AssistsResponse", 264 | "type": "object", 265 | "properties": { 266 | "assists": { 267 | "type": "array", 268 | "items": { 269 | "$ref": "CandidateFix" 270 | } 271 | } 272 | } 273 | }, 274 | "FormatResponse": { 275 | "id": "FormatResponse", 276 | "type": "object", 277 | "properties": { 278 | "newString": { 279 | "type": "string", 280 | "description": "The formatted source code." 281 | }, 282 | "offset": { 283 | "type": "integer", 284 | "description": "The (optional) new offset of the cursor; can be `null`.", 285 | "format": "int32" 286 | } 287 | } 288 | }, 289 | "DocumentResponse": { 290 | "id": "DocumentResponse", 291 | "type": "object", 292 | "properties": { 293 | "info": { 294 | "type": "object", 295 | "additionalProperties": { 296 | "type": "string" 297 | } 298 | } 299 | } 300 | }, 301 | "VersionResponse": { 302 | "id": "VersionResponse", 303 | "type": "object", 304 | "properties": { 305 | "sdkVersion": { 306 | "type": "string", 307 | "description": "The Dart SDK version that DartServices is compatible with. This will be a semver string." 308 | }, 309 | "sdkVersionFull": { 310 | "type": "string", 311 | "description": "The full Dart SDK version that DartServices is compatible with." 312 | }, 313 | "runtimeVersion": { 314 | "type": "string", 315 | "description": "The Dart SDK version that the server is running on. This will start with a semver string, and have a space and other build details appended." 316 | }, 317 | "appEngineVersion": { 318 | "type": "string", 319 | "description": "The App Engine version." 320 | }, 321 | "servicesVersion": { 322 | "type": "string", 323 | "description": "The dart-services backend version." 324 | }, 325 | "flutterVersion": { 326 | "type": "string", 327 | "description": "The Flutter SDK version." 328 | }, 329 | "flutterDartVersion": { 330 | "type": "string", 331 | "description": "The Flutter SDK's Dart version." 332 | }, 333 | "flutterDartVersionFull": { 334 | "type": "string", 335 | "description": "The Flutter SDK's full Dart version." 336 | } 337 | } 338 | } 339 | }, 340 | "methods": { 341 | "analyze": { 342 | "id": "CommonServer.analyze", 343 | "path": "analyze", 344 | "httpMethod": "POST", 345 | "description": "Analyze the given Dart source code and return any resulting analysis errors or warnings.", 346 | "parameters": {}, 347 | "parameterOrder": [], 348 | "request": { 349 | "$ref": "SourceRequest" 350 | }, 351 | "response": { 352 | "$ref": "AnalysisResults" 353 | } 354 | }, 355 | "compile": { 356 | "id": "CommonServer.compile", 357 | "path": "compile", 358 | "httpMethod": "POST", 359 | "description": "Compile the given Dart source code and return the resulting JavaScript; this uses the dart2js compiler.", 360 | "parameters": {}, 361 | "parameterOrder": [], 362 | "request": { 363 | "$ref": "CompileRequest" 364 | }, 365 | "response": { 366 | "$ref": "CompileResponse" 367 | } 368 | }, 369 | "compileDDC": { 370 | "id": "CommonServer.compileDDC", 371 | "path": "compileDDC", 372 | "httpMethod": "POST", 373 | "description": "Compile the given Dart source code and return the resulting JavaScript; this uses the DDC compiler.", 374 | "parameters": {}, 375 | "parameterOrder": [], 376 | "request": { 377 | "$ref": "CompileRequest" 378 | }, 379 | "response": { 380 | "$ref": "CompileDDCResponse" 381 | } 382 | }, 383 | "complete": { 384 | "id": "CommonServer.complete", 385 | "path": "complete", 386 | "httpMethod": "POST", 387 | "description": "Get the valid code completion results for the given offset.", 388 | "parameters": {}, 389 | "parameterOrder": [], 390 | "request": { 391 | "$ref": "SourceRequest" 392 | }, 393 | "response": { 394 | "$ref": "CompleteResponse" 395 | } 396 | }, 397 | "fixes": { 398 | "id": "CommonServer.fixes", 399 | "path": "fixes", 400 | "httpMethod": "POST", 401 | "description": "Get any quick fixes for the given source code location.", 402 | "parameters": {}, 403 | "parameterOrder": [], 404 | "request": { 405 | "$ref": "SourceRequest" 406 | }, 407 | "response": { 408 | "$ref": "FixesResponse" 409 | } 410 | }, 411 | "assists": { 412 | "id": "CommonServer.assists", 413 | "path": "assists", 414 | "httpMethod": "POST", 415 | "description": "Get assists for the given source code location.", 416 | "parameters": {}, 417 | "parameterOrder": [], 418 | "request": { 419 | "$ref": "SourceRequest" 420 | }, 421 | "response": { 422 | "$ref": "AssistsResponse" 423 | } 424 | }, 425 | "format": { 426 | "id": "CommonServer.format", 427 | "path": "format", 428 | "httpMethod": "POST", 429 | "description": "Format the given Dart source code and return the results. If an offset is supplied in the request, the new position for that offset in the formatted code will be returned.", 430 | "parameters": {}, 431 | "parameterOrder": [], 432 | "request": { 433 | "$ref": "SourceRequest" 434 | }, 435 | "response": { 436 | "$ref": "FormatResponse" 437 | } 438 | }, 439 | "document": { 440 | "id": "CommonServer.document", 441 | "path": "document", 442 | "httpMethod": "POST", 443 | "description": "Return the relevant dartdoc information for the element at the given offset.", 444 | "parameters": {}, 445 | "parameterOrder": [], 446 | "request": { 447 | "$ref": "SourceRequest" 448 | }, 449 | "response": { 450 | "$ref": "DocumentResponse" 451 | } 452 | }, 453 | "version": { 454 | "id": "CommonServer.version", 455 | "path": "version", 456 | "httpMethod": "GET", 457 | "description": "Return the current SDK version for DartServices.", 458 | "parameters": {}, 459 | "parameterOrder": [], 460 | "response": { 461 | "$ref": "VersionResponse" 462 | } 463 | } 464 | }, 465 | "resources": {} 466 | } 467 | -------------------------------------------------------------------------------- /doc/images/compile-10qps-loadtesting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-archive/dart-services/bbe414656194c52739c529cfaf036b1e98f57e36/doc/images/compile-10qps-loadtesting.png -------------------------------------------------------------------------------- /flutter-sdk-version.yaml: -------------------------------------------------------------------------------- 1 | # The versions of the Flutter SDK to use. 2 | 3 | # The dart-services server will automatically create or configure the 4 | # flutter-sdks/ directory based on this configuration file at startup. 5 | 6 | # In order to update the SDK manually, run 'dart tool/update_sdk.dart'. 7 | 8 | flutter_sdk: 9 | stable: 10 | flutter_version: 3.10.6 11 | dart_language_version: 3.0.0 12 | beta: 13 | flutter_version: 3.13.0-0.1.pre 14 | dart_language_version: 3.1.0-0 15 | dev: 16 | flutter_version: 3.10.6 17 | dart_language_version: 3.0.0 18 | old: 19 | flutter_version: 3.7.12 20 | dart_language_version: 2.19.0 21 | master: 22 | flutter_version: 3.13.0-2.0.pre 23 | dart_language_version: 3.1.0-0 24 | -------------------------------------------------------------------------------- /lib/services_cloud_run.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | /// A server for Cloud Run. 6 | library; 7 | 8 | import 'dart:async'; 9 | import 'dart:io'; 10 | 11 | import 'package:args/args.dart'; 12 | import 'package:logging/logging.dart'; 13 | import 'package:shelf/shelf.dart'; 14 | import 'package:shelf/shelf_io.dart' as shelf; 15 | 16 | import 'src/common_server_api.dart'; 17 | import 'src/common_server_impl.dart'; 18 | import 'src/github_oauth_handler.dart'; 19 | import 'src/sdk.dart'; 20 | import 'src/server_cache.dart'; 21 | import 'src/shelf_cors.dart' as shelf_cors; 22 | 23 | final Logger _logger = Logger('services'); 24 | 25 | Future main(List args) async { 26 | final parser = ArgParser() 27 | ..addOption('channel', mandatory: true) 28 | ..addOption('port', abbr: 'p') 29 | ..addOption('redis-url'); 30 | final results = parser.parse(args); 31 | 32 | final channel = results['channel'] as String; 33 | final sdk = Sdk.create(channel); 34 | 35 | // Cloud Run supplies the port to bind to in the environment. 36 | // Allow command line arg to override environment. 37 | final port = int.tryParse(results['port'] as String? ?? '') ?? 38 | int.tryParse(Platform.environment['PORT'] ?? ''); 39 | if (port == null) { 40 | stdout.writeln('Could not parse port value from either environment ' 41 | '"PORT" or from command line argument "--port".'); 42 | exit(1); 43 | } 44 | 45 | final redisServerUri = results['redis-url'] as String; 46 | 47 | Logger.root.level = Level.FINER; 48 | Logger.root.onRecord.listen((LogRecord record) { 49 | print(record); 50 | if (record.stackTrace != null) print(record.stackTrace); 51 | }); 52 | 53 | final cloudRunEnvVars = Platform.environment.entries 54 | .where((entry) => entry.key.startsWith('K_')) 55 | .map((entry) => '${entry.key}: ${entry.value}') 56 | .join('\n'); 57 | 58 | _logger.info('''Initializing dart-services: 59 | port: $port 60 | sdkPath: ${sdk.dartSdkPath} 61 | redisServerUri: $redisServerUri 62 | Cloud Run Environment variables: 63 | $cloudRunEnvVars'''); 64 | 65 | await GitHubOAuthHandler.initFromEnvironmentalVars(); 66 | 67 | await EndpointsServer.serve(port, redisServerUri, sdk); 68 | _logger.info('Listening on port $port'); 69 | } 70 | 71 | class EndpointsServer { 72 | static Future serve( 73 | int port, String redisServerUri, Sdk sdk) async { 74 | final endpointsServer = EndpointsServer._(redisServerUri, sdk); 75 | 76 | await endpointsServer.init(); 77 | endpointsServer.server = await shelf.serve( 78 | endpointsServer.handler, 79 | InternetAddress.anyIPv4, 80 | port, 81 | ); 82 | return endpointsServer; 83 | } 84 | 85 | late final HttpServer server; 86 | 87 | late final Pipeline pipeline; 88 | late final Handler handler; 89 | 90 | late final CommonServerApi commonServerApi; 91 | late final CommonServerImpl _commonServerImpl; 92 | 93 | EndpointsServer._(String? redisServerUri, Sdk sdk) { 94 | _commonServerImpl = CommonServerImpl( 95 | redisServerUri == null 96 | ? InMemoryCache() 97 | : RedisCache( 98 | redisServerUri, 99 | sdk, 100 | // The name of the Cloud Run revision being run, for more detail please see: 101 | // https://cloud.google.com/run/docs/reference/container-contract#env-vars 102 | Platform.environment['K_REVISION'], 103 | ), 104 | sdk, 105 | ); 106 | commonServerApi = CommonServerApi(_commonServerImpl); 107 | 108 | // Set cache for GitHub OAuth and add GitHub OAuth routes to our router. 109 | GitHubOAuthHandler.setCache(redisServerUri == null 110 | ? InMemoryCache() 111 | : RedisCache( 112 | redisServerUri, 113 | sdk, 114 | // The name of the Cloud Run revision being run, for more detail please see: 115 | // https://cloud.google.com/run/docs/reference/container-contract#env-vars 116 | Platform.environment['K_REVISION'], 117 | )); 118 | GitHubOAuthHandler.addRoutes(commonServerApi.router); 119 | 120 | pipeline = const Pipeline() 121 | .addMiddleware(logRequests()) 122 | .addMiddleware(_createCustomCorsHeadersMiddleware()); 123 | 124 | handler = pipeline.addHandler(commonServerApi.router.call); 125 | } 126 | 127 | Future init() => _commonServerImpl.init(); 128 | 129 | Middleware _createCustomCorsHeadersMiddleware() { 130 | return shelf_cors.createCorsHeadersMiddleware(corsHeaders: { 131 | 'Access-Control-Allow-Origin': '*', 132 | 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 133 | 'Access-Control-Allow-Headers': 134 | 'Origin, X-Requested-With, Content-Type, Accept, x-goog-api-client' 135 | }); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/services_dev.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | /// A dev-time only server. 6 | library; 7 | 8 | import 'dart:async'; 9 | import 'dart:io'; 10 | 11 | import 'package:args/args.dart'; 12 | import 'package:logging/logging.dart'; 13 | import 'package:shelf/shelf.dart'; 14 | import 'package:shelf/shelf_io.dart' as shelf; 15 | 16 | import 'src/common_server_api.dart'; 17 | import 'src/common_server_impl.dart'; 18 | import 'src/github_oauth_handler.dart'; 19 | import 'src/sdk.dart'; 20 | import 'src/server_cache.dart'; 21 | import 'src/shelf_cors.dart' as shelf_cors; 22 | 23 | final Logger _logger = Logger('services'); 24 | 25 | Future main(List args) async { 26 | final parser = ArgParser(); 27 | parser 28 | ..addOption('channel', mandatory: true) 29 | ..addOption('port', abbr: 'p', defaultsTo: '8080') 30 | ..addOption('server-url', defaultsTo: 'http://localhost') 31 | ..addFlag('null-safety'); 32 | 33 | final result = parser.parse(args); 34 | final port = int.tryParse(result['port'] as String); 35 | if (port == null) { 36 | stdout.writeln( 37 | 'Could not parse port value "${result['port']}" into a number.'); 38 | exit(1); 39 | } 40 | 41 | Logger.root.level = Level.FINER; 42 | Logger.root.onRecord.listen((LogRecord record) { 43 | print(record); 44 | if (record.stackTrace != null) print(record.stackTrace); 45 | }); 46 | 47 | await GitHubOAuthHandler.initFromEnvironmentalVars(); 48 | 49 | await EndpointsServer.serve(port, Sdk.create(result['channel'] as String), 50 | result['null-safety'] as bool); 51 | _logger.info('Listening on port $port'); 52 | } 53 | 54 | class EndpointsServer { 55 | static Future serve( 56 | int port, Sdk sdk, bool nullSafety) async { 57 | final endpointsServer = EndpointsServer._(sdk, nullSafety); 58 | await shelf.serve(endpointsServer.handler, InternetAddress.anyIPv4, port); 59 | return endpointsServer; 60 | } 61 | 62 | late final Pipeline pipeline; 63 | late final Handler handler; 64 | late final CommonServerApi commonServerApi; 65 | 66 | EndpointsServer._(Sdk sdk, bool nullSafety) { 67 | final commonServerImpl = CommonServerImpl( 68 | _Cache(), 69 | sdk, 70 | ); 71 | commonServerApi = CommonServerApi(commonServerImpl); 72 | commonServerImpl.init(); 73 | 74 | // Set cache for GitHub OAuth and add GitHub OAuth routes to our router. 75 | GitHubOAuthHandler.setCache(InMemoryCache()); 76 | GitHubOAuthHandler.addRoutes(commonServerApi.router); 77 | 78 | pipeline = const Pipeline() 79 | .addMiddleware(logRequests()) 80 | .addMiddleware(_createCustomCorsHeadersMiddleware()); 81 | 82 | handler = pipeline.addHandler(commonServerApi.router.call); 83 | } 84 | 85 | Middleware _createCustomCorsHeadersMiddleware() { 86 | return shelf_cors.createCorsHeadersMiddleware(corsHeaders: { 87 | 'Access-Control-Allow-Origin': '*', 88 | 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 89 | 'Access-Control-Allow-Headers': 90 | 'Origin, X-Requested-With, Content-Type, Accept, x-goog-api-client' 91 | }); 92 | } 93 | } 94 | 95 | class _Cache implements ServerCache { 96 | @override 97 | Future get(String key) => Future.value(null); 98 | 99 | @override 100 | Future set(String key, String value, {Duration? expiration}) => 101 | Future.value(); 102 | 103 | @override 104 | Future remove(String key) => Future.value(); 105 | 106 | @override 107 | Future shutdown() => Future.value(); 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/analysis_servers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, 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 | /// A wrapper around an analysis server instance. 6 | library; 7 | 8 | import 'dart:async'; 9 | import 'dart:io'; 10 | 11 | import 'package:analyzer/dart/ast/ast.dart'; 12 | import 'package:logging/logging.dart'; 13 | 14 | import 'analysis_server.dart'; 15 | import 'common.dart'; 16 | import 'common_server_impl.dart' show BadRequest; 17 | import 'project.dart' as project; 18 | import 'protos/dart_services.pb.dart' as proto; 19 | import 'pub.dart'; 20 | 21 | final Logger _logger = Logger('analysis_servers'); 22 | 23 | class AnalysisServersWrapper { 24 | final String _dartSdkPath; 25 | 26 | AnalysisServersWrapper(this._dartSdkPath); 27 | 28 | late DartAnalysisServerWrapper _dartAnalysisServer; 29 | late FlutterAnalysisServerWrapper _flutterAnalysisServer; 30 | 31 | // If non-null, this value indicates that the server is starting/restarting 32 | // and holds the time at which that process began. If null, the server is 33 | // ready to handle requests. 34 | DateTime? _restartingSince = DateTime.now(); 35 | 36 | bool get isRestarting => _restartingSince != null; 37 | 38 | // If the server has been trying and failing to restart for more than a half 39 | // hour, something is seriously wrong. 40 | bool get isHealthy => 41 | _restartingSince == null || 42 | DateTime.now().difference(_restartingSince!).inMinutes < 30; 43 | 44 | Future warmup() async { 45 | _logger.info('Beginning AnalysisServersWrapper init().'); 46 | _dartAnalysisServer = DartAnalysisServerWrapper(dartSdkPath: _dartSdkPath); 47 | _flutterAnalysisServer = 48 | FlutterAnalysisServerWrapper(dartSdkPath: _dartSdkPath); 49 | 50 | await _dartAnalysisServer.init(); 51 | _logger.info('Dart analysis server initialized.'); 52 | 53 | await _flutterAnalysisServer.init(); 54 | _logger.info('Flutter analysis server initialized.'); 55 | 56 | unawaited(_dartAnalysisServer.onExit.then((int code) { 57 | _logger.severe('dartAnalysisServer exited, code: $code'); 58 | if (code != 0) { 59 | exit(code); 60 | } 61 | })); 62 | 63 | unawaited(_flutterAnalysisServer.onExit.then((int code) { 64 | _logger.severe('flutterAnalysisServer exited, code: $code'); 65 | if (code != 0) { 66 | exit(code); 67 | } 68 | })); 69 | 70 | _restartingSince = null; 71 | } 72 | 73 | Future _restart() async { 74 | _logger.warning('Restarting'); 75 | await shutdown(); 76 | _logger.info('shutdown'); 77 | 78 | await warmup(); 79 | _logger.warning('Restart complete'); 80 | } 81 | 82 | Future shutdown() { 83 | _restartingSince = DateTime.now(); 84 | 85 | return Future.wait(>[ 86 | _flutterAnalysisServer.shutdown(), 87 | _dartAnalysisServer.shutdown(), 88 | ]); 89 | } 90 | 91 | AnalysisServerWrapper _getCorrectAnalysisServer(List imports, 92 | {required bool devMode}) { 93 | return project.usesFlutterWeb(imports, devMode: devMode) 94 | ? _flutterAnalysisServer 95 | : _dartAnalysisServer; 96 | } 97 | 98 | Future analyze(String source, 99 | {required bool devMode}) => 100 | analyzeFiles({kMainDart: source}, kMainDart, devMode: devMode); 101 | 102 | Future analyzeFiles( 103 | Map sources, String activeSourceName, 104 | {required bool devMode}) => 105 | _perfLogAndRestart( 106 | sources, 107 | activeSourceName, 108 | 0, 109 | (List imports, Location location) => 110 | _getCorrectAnalysisServer(imports, devMode: devMode) 111 | .analyzeFiles(sources, imports: imports), 112 | 'analysis', 113 | 'Error during analyze on "${sources[activeSourceName]}"', 114 | devMode: devMode); 115 | 116 | Future complete(String source, int offset, 117 | {required bool devMode}) => 118 | completeFiles({kMainDart: source}, kMainDart, offset, devMode: devMode); 119 | 120 | Future completeFiles( 121 | Map sources, String activeSourceName, int offset, 122 | {required bool devMode}) => 123 | _perfLogAndRestart( 124 | sources, 125 | activeSourceName, 126 | offset, 127 | (List imports, Location location) => 128 | _getCorrectAnalysisServer(imports, devMode: devMode) 129 | .completeFiles(sources, location), 130 | 'completions', 131 | 'Error during complete on "${sources[activeSourceName]}" at $offset', 132 | devMode: devMode); 133 | 134 | Future getFixes(String source, int offset, 135 | {required bool devMode}) => 136 | getFixesMulti({kMainDart: source}, kMainDart, offset, devMode: devMode); 137 | 138 | Future getFixesMulti( 139 | Map sources, String activeSourceName, int offset, 140 | {required bool devMode}) => 141 | _perfLogAndRestart( 142 | sources, 143 | activeSourceName, 144 | offset, 145 | (List imports, Location location) => 146 | _getCorrectAnalysisServer(imports, devMode: devMode) 147 | .getFixesMulti(sources, location), 148 | 'fixes', 149 | 'Error during fixes on "${sources[activeSourceName]}" at $offset', 150 | devMode: devMode); 151 | 152 | Future getAssists(String source, int offset, 153 | {required bool devMode}) => 154 | getAssistsMulti({kMainDart: source}, kMainDart, offset, devMode: devMode); 155 | 156 | Future getAssistsMulti( 157 | Map sources, String activeSourceName, int offset, 158 | {required bool devMode}) => 159 | _perfLogAndRestart( 160 | sources, 161 | activeSourceName, 162 | offset, 163 | (List imports, Location location) => 164 | _getCorrectAnalysisServer(imports, devMode: devMode) 165 | .getAssistsMulti(sources, location), 166 | 'assists', 167 | 'Error during assists on "${sources[activeSourceName]}" at $offset', 168 | devMode: devMode); 169 | 170 | Future format(String source, int offset, 171 | {required bool devMode}) => 172 | _format2({kMainDart: source}, kMainDart, offset, devMode: devMode); 173 | 174 | Future _format2( 175 | Map sources, String activeSourceName, int offset, 176 | {required bool devMode}) => 177 | _perfLogAndRestart( 178 | sources, 179 | activeSourceName, 180 | offset, 181 | (List imports, Location _) => 182 | _getCorrectAnalysisServer(imports, devMode: devMode) 183 | .format(sources[activeSourceName]!, offset), 184 | 'format', 185 | 'Error during format on "${sources[activeSourceName]}" at $offset', 186 | devMode: devMode); 187 | 188 | Future> dartdoc(String source, int offset, 189 | {required bool devMode}) => 190 | dartdocMulti({kMainDart: source}, kMainDart, offset, devMode: devMode); 191 | 192 | Future> dartdocMulti( 193 | Map sources, String activeSourceName, int offset, 194 | {required bool devMode}) => 195 | _perfLogAndRestart( 196 | sources, 197 | activeSourceName, 198 | offset, 199 | (List imports, Location location) => 200 | _getCorrectAnalysisServer(imports, devMode: devMode) 201 | .dartdocMulti(sources, location), 202 | 'dartdoc', 203 | 'Error during dartdoc on "${sources[activeSourceName]}" at $offset', 204 | devMode: devMode); 205 | 206 | Future _perfLogAndRestart( 207 | Map sources, 208 | String activeSourceName, 209 | int offset, 210 | Future Function(List, Location) body, 211 | String action, 212 | String errorDescription, 213 | {required bool devMode}) async { 214 | activeSourceName = sanitizeAndCheckFilenames(sources, activeSourceName); 215 | final imports = getAllImportsForFiles(sources); 216 | final location = Location(activeSourceName, offset); 217 | await _checkPackageReferences(sources, imports, devMode: devMode); 218 | try { 219 | final watch = Stopwatch()..start(); 220 | final response = await body(imports, location); 221 | _logger.info('PERF: Computed $action in ${watch.elapsedMilliseconds}ms.'); 222 | return response; 223 | } catch (e, st) { 224 | _logger.severe(errorDescription, e, st); 225 | await _restart(); 226 | rethrow; 227 | } 228 | } 229 | 230 | /// Check that the set of packages referenced is valid. 231 | Future _checkPackageReferences( 232 | Map sources, List imports, 233 | {required bool devMode}) async { 234 | final unsupportedImports = project.getUnsupportedImports(imports, 235 | sourcesFileList: sources.keys.toList(), devMode: devMode); 236 | 237 | if (unsupportedImports.isNotEmpty) { 238 | // TODO(srawlins): Do the work so that each unsupported input is its own 239 | // error, with a proper SourceSpan. 240 | final unsupportedUris = 241 | unsupportedImports.map((import) => import.uri.stringValue); 242 | throw BadRequest('Unsupported import(s): $unsupportedUris'); 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /lib/src/bench.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | /// A benchmark library. This library supports running benchmarks which can 6 | /// run asynchronously. 7 | library; 8 | 9 | import 'dart:async'; 10 | import 'dart:convert' show json; 11 | 12 | abstract class Benchmark { 13 | final String name; 14 | 15 | Benchmark(this.name); 16 | 17 | Future init() => Future.value(); 18 | 19 | Future perform(); 20 | 21 | /// Called once when this benchmark will no longer be used. 22 | Future tearDown() => Future.value(); 23 | 24 | @override 25 | String toString() => name; 26 | } 27 | 28 | typedef BenchmarkLogger = void Function(String str); 29 | 30 | class BenchmarkHarness { 31 | final bool asJson; 32 | final BenchmarkLogger logger; 33 | 34 | BenchmarkHarness({required this.asJson, this.logger = print}); 35 | 36 | Future benchmark(List benchmarks) async { 37 | log('Running ${benchmarks.length} benchmarks.'); 38 | log(''); 39 | 40 | final results = []; 41 | 42 | await Future.forEach(benchmarks, (Benchmark benchmark) => benchmark.init()); 43 | 44 | return Future.forEach(benchmarks, (Benchmark benchmark) { 45 | return benchmarkSingle(benchmark).then(results.add); 46 | }).then((_) { 47 | if (asJson) { 48 | logger(json.encode(results 49 | .map((BenchMarkResult r) => 50 | {r.benchmark.name: r.averageMilliseconds()}) 51 | .toList())); 52 | } 53 | }); 54 | } 55 | 56 | Future benchmarkSingle(Benchmark benchmark) { 57 | return _warmup(benchmark).then((_) { 58 | return _measure(benchmark); 59 | }).then((BenchMarkResult result) { 60 | logResult(result); 61 | return result; 62 | }).whenComplete(() { 63 | return benchmark.tearDown().catchError((dynamic e) => null); 64 | }); 65 | } 66 | 67 | void log(String message) { 68 | if (!asJson) logger(message); 69 | } 70 | 71 | void logResult(BenchMarkResult result) { 72 | if (!asJson) logger(result.toString()); 73 | } 74 | 75 | Future _warmup(Benchmark benchmark) { 76 | return _time(benchmark, 2, 1000); 77 | } 78 | 79 | Future _measure(Benchmark benchmark) { 80 | return _time(benchmark, 10, 2000, 10000); 81 | } 82 | 83 | Future _time( 84 | Benchmark benchmark, int minIterations, int minMillis, 85 | [int? maxMillis]) { 86 | final result = BenchMarkResult(benchmark); 87 | final timer = Stopwatch()..start(); 88 | 89 | return Future.doWhile(() { 90 | if (result.iteration >= minIterations && 91 | timer.elapsedMilliseconds >= minMillis) { 92 | return false; 93 | } 94 | 95 | if (maxMillis != null && timer.elapsedMilliseconds >= maxMillis) { 96 | return false; 97 | } 98 | 99 | result.iteration++; 100 | return benchmark.perform().then((_) => true); 101 | }).then((_) { 102 | timer.stop(); 103 | result.microseconds = timer.elapsedMicroseconds; 104 | return result; 105 | }); 106 | } 107 | } 108 | 109 | class BenchMarkResult { 110 | final Benchmark benchmark; 111 | int iteration = 0; 112 | int microseconds = 0; 113 | 114 | BenchMarkResult(this.benchmark); 115 | 116 | double averageMilliseconds() => (microseconds / iteration) / 1000.0; 117 | 118 | @override 119 | String toString() => '[${benchmark.name.padRight(26)}: ' 120 | '${averageMilliseconds().toStringAsFixed(3).padLeft(8)}ms]'; 121 | } 122 | -------------------------------------------------------------------------------- /lib/src/common_server_api.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, 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 | import 'dart:async'; 6 | import 'dart:convert'; 7 | 8 | import 'package:protobuf/protobuf.dart'; 9 | import 'package:shelf/shelf.dart'; 10 | import 'package:shelf_router/shelf_router.dart'; 11 | 12 | import 'common_server_impl.dart' show BadRequest, CommonServerImpl; 13 | import 'protos/dart_services.pb.dart' as proto; 14 | 15 | export 'common_server_impl.dart' show log; 16 | 17 | part 'common_server_api.g.dart'; // generated with 'pub run build_runner build' 18 | 19 | const protobufContentType = 'application/x-protobuf'; 20 | const jsonContentType = 'application/json; charset=utf-8'; 21 | const protoApiUrlPrefix = '/api/dartservices/'; 22 | 23 | class CommonServerApi { 24 | final CommonServerImpl _impl; 25 | 26 | CommonServerApi(this._impl); 27 | 28 | @Route.post('$protoApiUrlPrefix/analyze') 29 | Future analyze(Request request, String apiVersion) => 30 | _processRequest(request, 31 | decodeFromJSON: (json) => 32 | proto.SourceRequest.create()..mergeFromProto3Json(json), 33 | decodeFromProto: proto.SourceRequest.fromBuffer, 34 | transform: _impl.analyze); 35 | 36 | @Route.post('$protoApiUrlPrefix/compile') 37 | Future compile(Request request, String apiVersion) => 38 | _processRequest(request, 39 | decodeFromJSON: (json) => 40 | proto.CompileRequest.create()..mergeFromProto3Json(json), 41 | decodeFromProto: proto.CompileRequest.fromBuffer, 42 | transform: _impl.compile); 43 | 44 | @Route.post('$protoApiUrlPrefix/compileDDC') 45 | Future compileDDC(Request request, String apiVersion) => 46 | _processRequest(request, 47 | decodeFromJSON: (json) => 48 | proto.CompileDDCRequest.create()..mergeFromProto3Json(json), 49 | decodeFromProto: proto.CompileDDCRequest.fromBuffer, 50 | transform: _impl.compileDDC); 51 | 52 | @Route.post('$protoApiUrlPrefix/complete') 53 | Future complete(Request request, String apiVersion) => 54 | _processRequest(request, 55 | decodeFromJSON: (json) => 56 | proto.SourceRequest.create()..mergeFromProto3Json(json), 57 | decodeFromProto: proto.SourceRequest.fromBuffer, 58 | transform: _impl.complete); 59 | 60 | @Route.post('$protoApiUrlPrefix/fixes') 61 | Future fixes(Request request, String apiVersion) => 62 | _processRequest(request, 63 | decodeFromJSON: (json) => 64 | proto.SourceRequest.create()..mergeFromProto3Json(json), 65 | decodeFromProto: proto.SourceRequest.fromBuffer, 66 | transform: _impl.fixes); 67 | 68 | @Route.post('$protoApiUrlPrefix/assists') 69 | Future assists(Request request, String apiVersion) => 70 | _processRequest(request, 71 | decodeFromJSON: (json) => 72 | proto.SourceRequest.create()..mergeFromProto3Json(json), 73 | decodeFromProto: proto.SourceRequest.fromBuffer, 74 | transform: _impl.assists); 75 | 76 | @Route.post('$protoApiUrlPrefix/format') 77 | Future format(Request request, String apiVersion) => 78 | _processRequest(request, 79 | decodeFromJSON: (json) => 80 | proto.SourceRequest.create()..mergeFromProto3Json(json), 81 | decodeFromProto: proto.SourceRequest.fromBuffer, 82 | transform: _impl.format); 83 | 84 | @Route.post('$protoApiUrlPrefix/document') 85 | Future document(Request request, String apiVersion) => 86 | _processRequest(request, 87 | decodeFromJSON: (json) => 88 | proto.SourceRequest.create()..mergeFromProto3Json(json), 89 | decodeFromProto: proto.SourceRequest.fromBuffer, 90 | transform: _impl.document); 91 | 92 | @Route.post('$protoApiUrlPrefix/version') 93 | Future versionPost(Request request, String apiVersion) => 94 | _processRequest(request, 95 | decodeFromJSON: (json) => 96 | proto.VersionRequest.create()..mergeFromProto3Json(json), 97 | decodeFromProto: proto.VersionRequest.fromBuffer, 98 | transform: _impl.version); 99 | 100 | @Route.get('$protoApiUrlPrefix/version') 101 | Future versionGet(Request request, String apiVersion) => 102 | _processRequest(request, 103 | decodeFromJSON: (json) => 104 | proto.VersionRequest.create()..mergeFromProto3Json(json), 105 | decodeFromProto: proto.VersionRequest.fromBuffer, 106 | transform: _impl.version); 107 | 108 | // Beginning of multi file map end points: 109 | @Route.post('$protoApiUrlPrefix/analyzeFiles') 110 | Future analyzeFiles(Request request, String apiVersion) => 111 | _processRequest(request, 112 | decodeFromJSON: (json) => 113 | proto.SourceFilesRequest.create()..mergeFromProto3Json(json), 114 | decodeFromProto: proto.SourceFilesRequest.fromBuffer, 115 | transform: _impl.analyzeFiles); 116 | 117 | @Route.post('$protoApiUrlPrefix/compileFiles') 118 | Future compileFiles(Request request, String apiVersion) => 119 | _processRequest(request, 120 | decodeFromJSON: (json) => 121 | proto.CompileFilesRequest.create()..mergeFromProto3Json(json), 122 | decodeFromProto: proto.CompileFilesRequest.fromBuffer, 123 | transform: _impl.compileFiles); 124 | 125 | @Route.post('$protoApiUrlPrefix/compileFilesDDC') 126 | Future compileFilesDDC(Request request, String apiVersion) => 127 | _processRequest(request, 128 | decodeFromJSON: (json) => 129 | proto.CompileFilesDDCRequest.create()..mergeFromProto3Json(json), 130 | decodeFromProto: proto.CompileFilesDDCRequest.fromBuffer, 131 | transform: _impl.compileFilesDDC); 132 | 133 | @Route.post('$protoApiUrlPrefix/completeFiles') 134 | Future completeFiles(Request request, String apiVersion) => 135 | _processRequest(request, 136 | decodeFromJSON: (json) => 137 | proto.SourceFilesRequest.create()..mergeFromProto3Json(json), 138 | decodeFromProto: proto.SourceFilesRequest.fromBuffer, 139 | transform: _impl.completeFiles); 140 | 141 | @Route.post('$protoApiUrlPrefix/fixesFiles') 142 | Future fixesFiles(Request request, String apiVersion) => 143 | _processRequest(request, 144 | decodeFromJSON: (json) => 145 | proto.SourceFilesRequest.create()..mergeFromProto3Json(json), 146 | decodeFromProto: proto.SourceFilesRequest.fromBuffer, 147 | transform: _impl.fixesFiles); 148 | 149 | @Route.post('$protoApiUrlPrefix/assistsFiles') 150 | Future assistsFiles(Request request, String apiVersion) => 151 | _processRequest(request, 152 | decodeFromJSON: (json) => 153 | proto.SourceFilesRequest.create()..mergeFromProto3Json(json), 154 | decodeFromProto: proto.SourceFilesRequest.fromBuffer, 155 | transform: _impl.assistsFiles); 156 | 157 | @Route.post('$protoApiUrlPrefix/documentFiles') 158 | Future documentFiles(Request request, String apiVersion) => 159 | _processRequest(request, 160 | decodeFromJSON: (json) => 161 | proto.SourceFilesRequest.create()..mergeFromProto3Json(json), 162 | decodeFromProto: proto.SourceFilesRequest.fromBuffer, 163 | transform: _impl.documentFiles); 164 | // End of Multi file files={} file map end points. 165 | 166 | /// The (lazily-constructed) router. 167 | late final Router router = _$CommonServerApiRouter(this); 168 | 169 | // We are serving requests that are arriving in both Protobuf binary encoding, 170 | // and Protobuf JSON encoding. To handle this we need the ability to decode 171 | // the requests and encode the responses. We also need to know how to do the 172 | // work the request is requesting. 173 | 174 | Future _processRequest( 175 | Request request, { 176 | required I Function(List bytes) decodeFromProto, 177 | required I Function(Object json) decodeFromJSON, 178 | required Future Function(I input) transform, 179 | }) async { 180 | if (request.mimeType == protobufContentType) { 181 | // Dealing with binary Protobufs 182 | final body = []; 183 | await for (final chunk in request.read()) { 184 | body.addAll(chunk); 185 | } 186 | try { 187 | final response = await transform(decodeFromProto(body)); 188 | return Response.ok( 189 | response.writeToBuffer(), 190 | headers: _protobufHeaders, 191 | ); 192 | } on BadRequest catch (e) { 193 | return Response(400, 194 | headers: _protobufHeaders, 195 | body: (proto.BadRequest.create() 196 | ..error = (proto.ErrorMessage.create()..message = e.cause)) 197 | .writeToBuffer()); 198 | } 199 | } else { 200 | // Dealing with JSON encoded Protobufs 201 | final body = await request.readAsString(); 202 | try { 203 | final response = await transform( 204 | decodeFromJSON(body.isNotEmpty ? json.decode(body) as Object : {})); 205 | return Response.ok( 206 | _jsonEncoder.convert(response.toProto3Json()), 207 | encoding: utf8, 208 | headers: _jsonHeaders, 209 | ); 210 | } on BadRequest catch (e) { 211 | return Response(400, 212 | headers: _jsonHeaders, 213 | encoding: utf8, 214 | body: _jsonEncoder.convert((proto.BadRequest.create() 215 | ..error = (proto.ErrorMessage.create()..message = e.cause)) 216 | .toProto3Json())); 217 | } 218 | } 219 | } 220 | 221 | final JsonEncoder _jsonEncoder = const JsonEncoder.withIndent(' '); 222 | 223 | static const _jsonHeaders = { 224 | 'Access-Control-Allow-Origin': '*', 225 | 'Content-Type': jsonContentType 226 | }; 227 | 228 | static const _protobufHeaders = { 229 | 'Access-Control-Allow-Origin': '*', 230 | 'Content-Type': protobufContentType 231 | }; 232 | } 233 | -------------------------------------------------------------------------------- /lib/src/common_server_api.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'common_server_api.dart'; 4 | 5 | // ************************************************************************** 6 | // ShelfRouterGenerator 7 | // ************************************************************************** 8 | 9 | Router _$CommonServerApiRouter(CommonServerApi service) { 10 | final router = Router(); 11 | router.add( 12 | 'POST', 13 | r'/api/dartservices//analyze', 14 | service.analyze, 15 | ); 16 | router.add( 17 | 'POST', 18 | r'/api/dartservices//compile', 19 | service.compile, 20 | ); 21 | router.add( 22 | 'POST', 23 | r'/api/dartservices//compileDDC', 24 | service.compileDDC, 25 | ); 26 | router.add( 27 | 'POST', 28 | r'/api/dartservices//complete', 29 | service.complete, 30 | ); 31 | router.add( 32 | 'POST', 33 | r'/api/dartservices//fixes', 34 | service.fixes, 35 | ); 36 | router.add( 37 | 'POST', 38 | r'/api/dartservices//assists', 39 | service.assists, 40 | ); 41 | router.add( 42 | 'POST', 43 | r'/api/dartservices//format', 44 | service.format, 45 | ); 46 | router.add( 47 | 'POST', 48 | r'/api/dartservices//document', 49 | service.document, 50 | ); 51 | router.add( 52 | 'POST', 53 | r'/api/dartservices//version', 54 | service.versionPost, 55 | ); 56 | router.add( 57 | 'GET', 58 | r'/api/dartservices//version', 59 | service.versionGet, 60 | ); 61 | router.add( 62 | 'POST', 63 | r'/api/dartservices//analyzeFiles', 64 | service.analyzeFiles, 65 | ); 66 | router.add( 67 | 'POST', 68 | r'/api/dartservices//compileFiles', 69 | service.compileFiles, 70 | ); 71 | router.add( 72 | 'POST', 73 | r'/api/dartservices//compileFilesDDC', 74 | service.compileFilesDDC, 75 | ); 76 | router.add( 77 | 'POST', 78 | r'/api/dartservices//completeFiles', 79 | service.completeFiles, 80 | ); 81 | router.add( 82 | 'POST', 83 | r'/api/dartservices//fixesFiles', 84 | service.fixesFiles, 85 | ); 86 | router.add( 87 | 'POST', 88 | r'/api/dartservices//assistsFiles', 89 | service.assistsFiles, 90 | ); 91 | router.add( 92 | 'POST', 93 | r'/api/dartservices//documentFiles', 94 | service.documentFiles, 95 | ); 96 | return router; 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/config.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, 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 | import 'dart:io'; 6 | 7 | /// A simple, properties file based configuration class. 8 | /// 9 | /// We expect an (optional) file in the root of the directory, names 'config.properties'. 10 | class Config { 11 | static Config? _singleton; 12 | 13 | static Config getConfig() { 14 | if (_singleton == null) { 15 | _singleton = Config._(); 16 | 17 | final file = File('config.properties'); 18 | if (file.existsSync()) { 19 | _singleton!._load(file.readAsLinesSync()); 20 | } 21 | } 22 | 23 | return _singleton!; 24 | } 25 | 26 | final Map _values = {}; 27 | 28 | Config._(); 29 | 30 | String? getValue(String key) => _values[key]; 31 | 32 | void _load(List lines) { 33 | _values.clear(); 34 | 35 | for (var line in lines) { 36 | line = line.trim(); 37 | if (line.isEmpty) { 38 | continue; 39 | } 40 | final index = line.indexOf('='); 41 | if (index == -1) { 42 | continue; 43 | } 44 | _values[line.substring(0, index)] = line.substring(index + 1); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/project.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, 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 | import 'dart:io'; 6 | 7 | import 'package:analyzer/dart/ast/ast.dart'; 8 | import 'package:path/path.dart' as path; 9 | 10 | /// Sets of project template directory paths. 11 | class ProjectTemplates { 12 | ProjectTemplates._({ 13 | required this.dartPath, 14 | required this.flutterPath, 15 | required this.firebasePath, 16 | required this.firebaseDeprecatedPath, 17 | required this.summaryFilePath, 18 | }); 19 | 20 | factory ProjectTemplates() { 21 | final basePath = _baseTemplateProject(); 22 | final summaryFilePath = path.join( 23 | 'artifacts', 24 | 'flutter_web.dill', 25 | ); 26 | return ProjectTemplates._( 27 | dartPath: path.join(basePath, 'dart_project'), 28 | flutterPath: path.join(basePath, 'flutter_project'), 29 | firebasePath: path.join(basePath, 'firebase_project'), 30 | firebaseDeprecatedPath: 31 | path.join(basePath, 'firebase_deprecated_project'), 32 | summaryFilePath: summaryFilePath, 33 | ); 34 | } 35 | 36 | /// The path to the plain Dart project template path. 37 | final String dartPath; 38 | 39 | /// The path to the Flutter (without Firebase) project template path. 40 | final String flutterPath; 41 | 42 | /// The path to the Firebase (with Flutter) project template path. 43 | final String firebasePath; 44 | 45 | /// The path to the deprecated Firebase (with Flutter) project template path. 46 | final String firebaseDeprecatedPath; 47 | 48 | /// The path to summary files. 49 | final String summaryFilePath; 50 | 51 | static ProjectTemplates projectTemplates = ProjectTemplates(); 52 | 53 | static String _baseTemplateProject() => 54 | path.join(Directory.current.path, 'project_templates'); 55 | } 56 | 57 | /// The set of Firebase packages which are used in both deprecated Firebase 58 | /// projects and "pure Dart" Flutterfire projects. 59 | const Set coreFirebasePackages = { 60 | 'firebase_core', 61 | }; 62 | 63 | /// The set of Firebase packages which can be registered in the generated 64 | /// registrant file. Theoretically this should be _all_ plugins, but there 65 | /// are bugs. See https://github.com/dart-lang/dart-pad/issues/2033 and 66 | /// https://github.com/FirebaseExtended/flutterfire/issues/3962. 67 | const Set registerableFirebasePackages = { 68 | 'cloud_firestore', 69 | 'firebase_auth', 70 | }; 71 | 72 | /// The set of Firebase packages which indicate that Firebase is being used. 73 | const Set firebasePackages = { 74 | ...coreFirebasePackages, 75 | ...registerableFirebasePackages, 76 | }; 77 | 78 | /// The set of supported Flutter-oriented packages. 79 | Set supportedFlutterPackages({required bool devMode}) => { 80 | 'animations', 81 | 'creator', 82 | 'firebase_analytics', 83 | 'firebase_database', 84 | 'firebase_messaging', 85 | 'firebase_storage', 86 | 'flame', 87 | 'flame_fire_atlas', 88 | 'flame_forge2d', 89 | 'flame_splash_screen', 90 | 'flame_tiled', 91 | 'flutter_adaptive_scaffold', 92 | 'flutter_bloc', 93 | 'flutter_hooks', 94 | 'flutter_lints', 95 | 'flutter_map', 96 | 'flutter_processing', 97 | 'flutter_riverpod', 98 | 'flutter_svg', 99 | 'go_router', 100 | 'google_fonts', 101 | 'hooks_riverpod', 102 | 'provider', 103 | 'riverpod_navigator', 104 | 'shared_preferences', 105 | 'video_player', 106 | if (devMode) ...[], 107 | }; 108 | 109 | /// The set of packages which indicate that Flutter Web is being used. 110 | Set _packagesIndicatingFlutter({required bool devMode}) => { 111 | 'flutter', 112 | 'flutter_test', 113 | ...supportedFlutterPackages(devMode: devMode), 114 | ...firebasePackages, 115 | }; 116 | 117 | /// The set of basic Dart (non-Flutter) packages which can be directly imported 118 | /// into a script. 119 | Set supportedBasicDartPackages({required bool devMode}) => { 120 | 'basics', 121 | 'bloc', 122 | 'characters', 123 | 'collection', 124 | 'cross_file', 125 | 'dartz', 126 | 'english_words', 127 | 'equatable', 128 | 'fast_immutable_collections', 129 | 'http', 130 | 'intl', 131 | 'js', 132 | 'lints', 133 | 'matcher', 134 | 'meta', 135 | 'path', 136 | 'petitparser', 137 | 'quiver', 138 | 'riverpod', 139 | 'rohd', 140 | 'rohd_vf', 141 | 'rxdart', 142 | 'timezone', 143 | 'tuple', 144 | 'vector_math', 145 | 'yaml', 146 | 'yaml_edit', 147 | if (devMode) ...[] 148 | }; 149 | 150 | /// A set of all allowed `dart:` imports. Currently includes non-VM libraries 151 | /// listed as the [doc](https://api.dart.dev/stable/index.html) categories. 152 | const Set _allowedDartImports = { 153 | 'dart:async', 154 | 'dart:collection', 155 | 'dart:convert', 156 | 'dart:core', 157 | 'dart:developer', 158 | 'dart:math', 159 | 'dart:typed_data', 160 | 'dart:html', 161 | 'dart:indexed_db', 162 | 'dart:js', 163 | 'dart:js_util', 164 | 'dart:svg', 165 | 'dart:web_audio', 166 | 'dart:web_gl', 167 | 'dart:ui', 168 | }; 169 | 170 | /// Returns whether [imports] denote use of Flutter Web. 171 | bool usesFlutterWeb(Iterable imports, 172 | {required bool devMode}) { 173 | return imports.any((import) { 174 | final uriString = import.uri.stringValue; 175 | if (uriString == null) return false; 176 | if (uriString == 'dart:ui') return true; 177 | 178 | final packageName = _packageNameFromPackageUri(uriString); 179 | return packageName != null && 180 | _packagesIndicatingFlutter(devMode: devMode).contains(packageName); 181 | }); 182 | } 183 | 184 | /// Returns whether [imports] denote use of Firebase. 185 | bool usesFirebase(Iterable imports) { 186 | return imports.any((import) { 187 | final uriString = import.uri.stringValue; 188 | if (uriString == null) return false; 189 | 190 | final packageName = _packageNameFromPackageUri(uriString); 191 | return packageName != null && firebasePackages.contains(packageName); 192 | }); 193 | } 194 | 195 | /// If [uriString] represents a 'package:' URI, then returns the package name; 196 | /// otherwise `null`. 197 | String? _packageNameFromPackageUri(String uriString) { 198 | final uri = Uri.tryParse(uriString); 199 | if (uri == null) return null; 200 | if (uri.scheme != 'package') return null; 201 | if (uri.pathSegments.isEmpty) return null; 202 | return uri.pathSegments.first; 203 | } 204 | 205 | /// Goes through imports list and returns list of unsupported imports. 206 | /// Optional [sourcesFileList] contains a list of the source filenames 207 | /// which are all part of this overall sources file set (these are to 208 | /// be allowed). 209 | /// Note: The filenames in [sourcesFileList] were sanitized of any 210 | /// 'package:'/etc syntax as the file set arrives from the endpoint, 211 | /// and before being passed to [getUnsupportedImports]. 212 | /// This is done so the list can't be used to bypass unsupported imports. 213 | /// The function [sanitizeAndCheckFilenames()] was used to sanitize the 214 | /// filenames. 215 | List getUnsupportedImports(List imports, 216 | {List? sourcesFileList, required bool devMode}) { 217 | return imports.where((import) { 218 | final uriString = import.uri.stringValue; 219 | if (uriString == null || uriString.isEmpty) { 220 | return false; 221 | } 222 | // All non-VM 'dart:' imports are ok. 223 | if (uriString.startsWith('dart:')) { 224 | return !_allowedDartImports.contains(uriString); 225 | } 226 | // Filenames from within this compilation files={} sources file set 227 | // are OK. (These filenames have been sanitized to prevent 'package:' 228 | // (and other) prefixes, so the a filename cannot be used to bypass 229 | // import restrictions (see comment above)). 230 | if (sourcesFileList != null && sourcesFileList.contains(uriString)) { 231 | return false; 232 | } 233 | 234 | final uri = Uri.tryParse(uriString); 235 | if (uri == null) return false; 236 | 237 | // We allow a specific set of package imports. 238 | if (uri.scheme == 'package') { 239 | if (uri.pathSegments.isEmpty) return true; 240 | final package = uri.pathSegments.first; 241 | return !isSupportedPackage(package, devMode: devMode); 242 | } 243 | 244 | // Don't allow file imports. 245 | return true; 246 | }).toList(); 247 | } 248 | 249 | bool isSupportedPackage(String package, {required bool devMode}) => 250 | _packagesIndicatingFlutter(devMode: devMode).contains(package) || 251 | supportedBasicDartPackages(devMode: devMode).contains(package); 252 | -------------------------------------------------------------------------------- /lib/src/project_creator.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, 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 | import 'dart:convert' show jsonDecode; 6 | import 'dart:io'; 7 | 8 | import 'package:path/path.dart' as path; 9 | import 'project.dart'; 10 | import 'sdk.dart'; 11 | import 'utils.dart'; 12 | 13 | typedef LogFunction = void Function(String); 14 | 15 | class ProjectCreator { 16 | final Sdk _sdk; 17 | 18 | final String _templatesPath; 19 | 20 | /// The Dart language version to use. 21 | final String _dartLanguageVersion; 22 | 23 | final File _dependenciesFile; 24 | 25 | final LogFunction _log; 26 | 27 | ProjectCreator( 28 | this._sdk, 29 | this._templatesPath, { 30 | required String dartLanguageVersion, 31 | required File dependenciesFile, 32 | required LogFunction log, 33 | }) : _dartLanguageVersion = dartLanguageVersion, 34 | _dependenciesFile = dependenciesFile, 35 | _log = log; 36 | 37 | /// Builds a basic Dart project template directory, complete with `pubspec.yaml` 38 | /// and `analysis_options.yaml`. 39 | Future buildDartProjectTemplate() async { 40 | final projectPath = path.join(_templatesPath, 'dart_project'); 41 | final projectDirectory = Directory(projectPath); 42 | await projectDirectory.create(recursive: true); 43 | final dependencies = 44 | _dependencyVersions(supportedBasicDartPackages(devMode: _sdk.devMode)); 45 | File(path.join(projectPath, 'pubspec.yaml')) 46 | .writeAsStringSync(createPubspec( 47 | includeFlutterWeb: false, 48 | dartLanguageVersion: _dartLanguageVersion, 49 | dependencies: dependencies, 50 | )); 51 | 52 | // todo: run w/ the correct sdk 53 | final exitCode = await _runDartPubGet(projectDirectory); 54 | if (exitCode != 0) throw StateError('pub get failed ($exitCode)'); 55 | 56 | var contents = ''' 57 | include: package:lints/recommended.yaml 58 | linter: 59 | rules: 60 | avoid_print: false 61 | '''; 62 | if (_sdk.experiments.isNotEmpty) { 63 | contents += ''' 64 | analyzer: 65 | enable-experiment: 66 | ${_sdk.experiments.map((experiment) => ' - $experiment').join('\n')} 67 | '''; 68 | } 69 | File(path.join(projectPath, 'analysis_options.yaml')) 70 | .writeAsStringSync(contents); 71 | } 72 | 73 | /// Builds a Flutter project template directory, complete with `pubspec.yaml`, 74 | /// `analysis_options.yaml`, and `web/index.html`. 75 | /// 76 | /// Depending on [firebaseStyle], Firebase packages are included in 77 | /// `pubspec.yaml` which affects how `flutter packages get` will register 78 | /// plugins. 79 | Future buildFlutterProjectTemplate( 80 | {required FirebaseStyle firebaseStyle}) async { 81 | final projectDirName = firebaseStyle == FirebaseStyle.none 82 | ? 'flutter_project' 83 | : 'firebase_project'; 84 | final projectPath = path.join( 85 | _templatesPath, 86 | projectDirName, 87 | ); 88 | await Directory(projectPath).create(recursive: true); 89 | await Directory(path.join(projectPath, 'lib')).create(); 90 | await Directory(path.join(projectPath, 'web')).create(); 91 | await File(path.join(projectPath, 'web', 'index.html')).create(); 92 | var packages = { 93 | ...supportedBasicDartPackages(devMode: _sdk.devMode), 94 | ...supportedFlutterPackages(devMode: _sdk.devMode), 95 | if (firebaseStyle != FirebaseStyle.none) ...coreFirebasePackages, 96 | if (firebaseStyle == FirebaseStyle.flutterFire) 97 | ...registerableFirebasePackages, 98 | }; 99 | final dependencies = _dependencyVersions(packages); 100 | File(path.join(projectPath, 'pubspec.yaml')) 101 | .writeAsStringSync(createPubspec( 102 | includeFlutterWeb: true, 103 | dartLanguageVersion: _dartLanguageVersion, 104 | dependencies: dependencies, 105 | )); 106 | 107 | final exitCode = await runFlutterPackagesGet( 108 | _sdk.flutterToolPath, 109 | projectPath, 110 | log: _log, 111 | ); 112 | if (exitCode != 0) throw StateError('flutter pub get failed ($exitCode)'); 113 | 114 | // Working around Flutter 3.3's deprecation of generated_plugin_registrant.dart 115 | // Context: https://github.com/flutter/flutter/pull/106921 116 | 117 | final pluginRegistrant = File(path.join( 118 | projectPath, '.dart_tool', 'dartpad', 'web_plugin_registrant.dart')); 119 | if (pluginRegistrant.existsSync()) { 120 | Directory(path.join(projectPath, 'lib')).createSync(); 121 | pluginRegistrant.copySync( 122 | path.join(projectPath, 'lib', 'generated_plugin_registrant.dart')); 123 | } 124 | 125 | if (firebaseStyle != FirebaseStyle.none) { 126 | // `flutter packages get` has been run with a _subset_ of all supported 127 | // Firebase packages, the ones that don't require a Firebase app to be 128 | // configured in JavaScript, before executing Dart. Now add the full set of 129 | // supported Firebase pacakges. This workaround is a very fragile hack. 130 | packages = { 131 | ...supportedBasicDartPackages(devMode: _sdk.devMode), 132 | ...supportedFlutterPackages(devMode: _sdk.devMode), 133 | ...firebasePackages, 134 | }; 135 | final dependencies = _dependencyVersions(packages); 136 | File(path.join(projectPath, 'pubspec.yaml')) 137 | .writeAsStringSync(createPubspec( 138 | includeFlutterWeb: true, 139 | dartLanguageVersion: _dartLanguageVersion, 140 | dependencies: dependencies, 141 | )); 142 | 143 | final exitCode = await runFlutterPackagesGet( 144 | _sdk.flutterToolPath, 145 | projectPath, 146 | log: _log, 147 | ); 148 | if (exitCode != 0) throw StateError('flutter pub get failed ($exitCode)'); 149 | } 150 | var contents = ''' 151 | include: package:flutter_lints/flutter.yaml 152 | linter: 153 | rules: 154 | avoid_print: false 155 | use_key_in_widget_constructors: false 156 | '''; 157 | if (_sdk.experiments.isNotEmpty) { 158 | contents += ''' 159 | analyzer: 160 | enable-experiment: 161 | ${_sdk.experiments.map((experiment) => ' - $experiment').join('\n')} 162 | '''; 163 | } 164 | File(path.join(projectPath, 'analysis_options.yaml')) 165 | .writeAsStringSync(contents); 166 | } 167 | 168 | Future _runDartPubGet(Directory dir) async { 169 | final process = await runWithLogging( 170 | path.join(_sdk.dartSdkPath, 'bin', 'dart'), 171 | arguments: ['pub', 'get'], 172 | workingDirectory: dir.path, 173 | environment: {'PUB_CACHE': _pubCachePath}, 174 | log: _log, 175 | ); 176 | return process.exitCode; 177 | } 178 | 179 | Map _dependencyVersions(Iterable packages) { 180 | final allVersions = 181 | parsePubDependenciesFile(dependenciesFile: _dependenciesFile); 182 | final result = { 183 | for (final package in packages) package: allVersions[package] ?? 'any', 184 | }; 185 | 186 | // Overwrite with important constraints. 187 | for (final entry in overrideVersionConstraints().entries) { 188 | if (result.containsKey(entry.key)) { 189 | result[entry.key] = entry.value; 190 | } 191 | } 192 | 193 | return result; 194 | } 195 | } 196 | 197 | /// A mapping of version constraints for certain packages. 198 | Map overrideVersionConstraints() { 199 | // Ensure that pub version solving keeps these at sane minimum versions. 200 | return { 201 | 'firebase_auth': '^4.2.0', 202 | 'firebase_auth_web': '^5.2.0', 203 | 'cloud_firestore_platform_interface': '^5.10.0', 204 | }; 205 | } 206 | 207 | /// Parses [dependenciesFile] as a JSON Map of Strings. 208 | Map parsePubDependenciesFile({required File dependenciesFile}) { 209 | final packageVersions = 210 | jsonDecode(dependenciesFile.readAsStringSync()) as Map; 211 | return packageVersions.cast(); 212 | } 213 | 214 | /// Build a return a `pubspec.yaml` file. 215 | String createPubspec({ 216 | required bool includeFlutterWeb, 217 | required String dartLanguageVersion, 218 | Map dependencies = const {}, 219 | }) { 220 | var content = ''' 221 | name: dartpad_sample 222 | environment: 223 | sdk: ^$dartLanguageVersion 224 | dependencies: 225 | '''; 226 | 227 | if (includeFlutterWeb) { 228 | content += ''' 229 | flutter: 230 | sdk: flutter 231 | flutter_test: 232 | sdk: flutter 233 | '''; 234 | } 235 | dependencies.forEach((name, version) { 236 | content += ' $name: $version\n'; 237 | }); 238 | 239 | return content; 240 | } 241 | 242 | Future runFlutterPackagesGet( 243 | String flutterToolPath, 244 | String projectPath, { 245 | required LogFunction log, 246 | }) async { 247 | final process = await runWithLogging(flutterToolPath, 248 | arguments: ['packages', 'get'], 249 | workingDirectory: projectPath, 250 | environment: {'PUB_CACHE': _pubCachePath}, 251 | log: log); 252 | return process.exitCode; 253 | } 254 | 255 | /// Builds the local pub cache directory and returns the path. 256 | String get _pubCachePath { 257 | final pubCachePath = path.join(Directory.current.path, 'local_pub_cache'); 258 | Directory(pubCachePath).createSync(); 259 | return pubCachePath; 260 | } 261 | 262 | enum FirebaseStyle { 263 | /// Indicates that no Firebase is used. 264 | none, 265 | 266 | /// Indicates that the "pure Dart" Flutterfire packages are used. 267 | flutterFire, 268 | } 269 | -------------------------------------------------------------------------------- /lib/src/protos/dart_services.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: protos/dart_services.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | -------------------------------------------------------------------------------- /lib/src/protos/dart_services.pbserver.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: protos/dart_services.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types 8 | // ignore_for_file: constant_identifier_names 9 | // ignore_for_file: deprecated_member_use_from_same_package, library_prefixes 10 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 11 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 12 | 13 | export 'dart_services.pb.dart'; 14 | -------------------------------------------------------------------------------- /lib/src/pub.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | import 'dart:io'; 6 | 7 | import 'package:analyzer/dart/analysis/utilities.dart'; 8 | import 'package:analyzer/dart/ast/ast.dart'; 9 | import 'package:path/path.dart' as path; 10 | import 'package:yaml/yaml.dart'; 11 | 12 | import 'project.dart' as project; 13 | 14 | /// Extract all imports from [dartSource] source code. 15 | List getAllImportsFor(String? dartSource) { 16 | if (dartSource == null) return []; 17 | 18 | final unit = parseString(content: dartSource, throwIfDiagnostics: false).unit; 19 | return unit.directives.whereType().toList(); 20 | } 21 | 22 | /// Takes a map {"filename":"sourcecode"..."filenameN":"sourcecodeN"} 23 | /// of source files and extracts the imports from each file's sourcecode and 24 | /// returns an overall list of all imports across all files in the set. 25 | List getAllImportsForFiles(Map files) { 26 | return [ 27 | for (final sourcecode in files.values) ...getAllImportsFor(sourcecode) 28 | ]; 29 | } 30 | 31 | /// Flutter packages which do not have version numbers in pubspec.lock. 32 | const _flutterPackages = [ 33 | 'flutter', 34 | 'flutter_test', 35 | 'flutter_web_plugins', 36 | 'sky_engine', 37 | ]; 38 | 39 | /// This is expensive to calculate; they require reading from disk. 40 | /// None of them changes during execution. 41 | final Map _nullSafePackageVersions = 42 | packageVersionsFromPubspecLock( 43 | project.ProjectTemplates.projectTemplates.firebasePath); 44 | 45 | /// Returns a mapping of Pub package name to package version. 46 | Map getPackageVersions() => _nullSafePackageVersions; 47 | 48 | /// Returns a mapping of Pub package name to package version, retrieving data 49 | /// from the project template's `pubspec.lock` file. 50 | Map packageVersionsFromPubspecLock(String templatePath) { 51 | final pubspecLockPath = File(path.join(templatePath, 'pubspec.lock')); 52 | final pubspecLock = loadYamlDocument(pubspecLockPath.readAsStringSync()); 53 | final pubSpecLockContents = pubspecLock.contents as YamlMap; 54 | final packages = pubSpecLockContents['packages'] as YamlMap; 55 | final packageVersions = {}; 56 | 57 | packages.forEach((nameKey, packageValue) { 58 | final name = nameKey as String; 59 | if (_flutterPackages.contains(name)) { 60 | return; 61 | } 62 | final package = packageValue as YamlMap; 63 | final source = package['source']; 64 | if (source is! String || source != 'hosted') { 65 | // `name` is not hosted. Might be a local or git dependency. 66 | return; 67 | } 68 | final version = package['version']; 69 | if (version is String) { 70 | packageVersions[name] = version; 71 | } else { 72 | throw StateError( 73 | '$name does not have a well-formatted version: $version'); 74 | } 75 | }); 76 | 77 | return packageVersions; 78 | } 79 | 80 | extension ImportIterableExtensions on Iterable { 81 | /// Returns the names of packages that are referenced in this collection. 82 | /// These package names are sanitized defensively. 83 | Iterable filterSafePackages() { 84 | return where((import) => !import.uri.stringValue!.startsWith('package:../')) 85 | .map((import) => Uri.parse(import.uri.stringValue!)) 86 | .where((uri) => uri.scheme == 'package' && uri.pathSegments.isNotEmpty) 87 | .map((uri) => uri.pathSegments.first); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/scheduler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | import 'dart:async'; 6 | import 'dart:collection'; 7 | 8 | class TaskScheduler { 9 | final Queue<_Task> _taskQueue = Queue<_Task>(); 10 | bool _isActive = false; 11 | 12 | int get queueCount => _taskQueue.length; 13 | 14 | Future _performTask(Task task) { 15 | return task.perform().timeout(task.timeoutDuration); 16 | } 17 | 18 | Future schedule(Task task) { 19 | if (!_isActive) { 20 | _isActive = true; 21 | return _performTask(task).whenComplete(_next); 22 | } 23 | final taskResult = Completer(); 24 | _taskQueue.add(_Task(task, taskResult)); 25 | return taskResult.future; 26 | } 27 | 28 | void _next() { 29 | assert(_isActive); 30 | if (_taskQueue.isEmpty) { 31 | _isActive = false; 32 | return; 33 | } 34 | final first = _taskQueue.removeFirst(); 35 | first.taskResult.complete(_performTask(first.task).whenComplete(_next)); 36 | } 37 | } 38 | 39 | // Internal unit of scheduling. 40 | class _Task { 41 | final Task task; 42 | final Completer taskResult; 43 | _Task(this.task, this.taskResult); 44 | } 45 | 46 | // Public working data structure. 47 | abstract class Task { 48 | Future perform(); 49 | late Duration timeoutDuration; 50 | } 51 | 52 | class ClosureTask extends Task { 53 | final Future Function() _closure; 54 | 55 | ClosureTask(this._closure, {required Duration timeoutDuration}) { 56 | this.timeoutDuration = timeoutDuration; 57 | } 58 | 59 | @override 60 | Future perform() { 61 | return _closure(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/sdk.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017, 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 | import 'dart:async'; 6 | import 'dart:convert' show utf8; 7 | import 'dart:io'; 8 | 9 | import 'package:path/path.dart' as path; 10 | import 'package:yaml/yaml.dart'; 11 | 12 | const stableChannel = 'stable'; 13 | 14 | class Sdk { 15 | static Sdk? _instance; 16 | 17 | final String sdkPath; 18 | 19 | /// The path to the Flutter binaries. 20 | final String _flutterBinPath; 21 | 22 | /// The path to the Dart SDK. 23 | final String dartSdkPath; 24 | 25 | /// The current version of the SDK, including any `-dev` suffix. 26 | final String versionFull; 27 | 28 | final String flutterVersion; 29 | 30 | /// The current version of the Flutter engine 31 | final String engineVersion; 32 | 33 | /// The current version of the SDK, not including any `-dev` suffix. 34 | final String version; 35 | 36 | /// Is this SDK being used in development mode. True if channel is `dev`. 37 | bool get devMode => _channel == 'dev'; 38 | 39 | /// Is this the old channel 40 | bool get oldChannel => _channel == 'old'; 41 | 42 | /// Is this the stable channel 43 | bool get stableChannel => _channel == 'stable'; 44 | 45 | /// Is this the beta channel 46 | bool get betaChannel => _channel == 'beta'; 47 | 48 | /// Is this the master channel 49 | bool get masterChannel => _channel == 'master'; 50 | 51 | // Which channel is this SDK? 52 | final String _channel; 53 | 54 | /// Experiments that this SDK is configured with 55 | List get experiments { 56 | if (masterChannel) return const ['inline-class']; 57 | return const []; 58 | } 59 | 60 | factory Sdk.create(String channel) { 61 | final sdkPath = path.join(Sdk._flutterSdksPath, channel); 62 | final flutterBinPath = path.join(sdkPath, 'bin'); 63 | final dartSdkPath = path.join(flutterBinPath, 'cache', 'dart-sdk'); 64 | final engineVersionPath = 65 | path.join(flutterBinPath, 'internal', 'engine.version'); 66 | return _instance ??= Sdk._( 67 | sdkPath: sdkPath, 68 | flutterBinPath: flutterBinPath, 69 | dartSdkPath: dartSdkPath, 70 | versionFull: _readVersionFile(dartSdkPath), 71 | flutterVersion: _readVersionFile(sdkPath), 72 | engineVersion: _readFile(engineVersionPath), 73 | channel: channel, 74 | ); 75 | } 76 | 77 | Sdk._({ 78 | required this.sdkPath, 79 | required String flutterBinPath, 80 | required this.dartSdkPath, 81 | required this.versionFull, 82 | required this.flutterVersion, 83 | required this.engineVersion, 84 | required String channel, 85 | }) : _flutterBinPath = flutterBinPath, 86 | _channel = channel, 87 | version = versionFull.contains('-') 88 | ? versionFull.substring(0, versionFull.indexOf('-')) 89 | : versionFull; 90 | 91 | /// The path to the 'flutter' tool (binary). 92 | String get flutterToolPath => path.join(_flutterBinPath, 'flutter'); 93 | 94 | String get flutterWebSdkPath { 95 | // The Flutter web SDK path changed. The SDK version test here will go 96 | // through the master, beta, stable, then old waterfall. Well, the last step 97 | // is the removal of this test, but you get the idea. 98 | if (oldChannel) { 99 | return path.join(_flutterBinPath, 'cache', 'flutter_web_sdk', 100 | 'flutter_web_sdk', 'kernel'); 101 | } 102 | 103 | return path.join(_flutterBinPath, 'cache', 'flutter_web_sdk', 'kernel'); 104 | } 105 | 106 | static String _readVersionFile(String filePath) => 107 | _readFile(path.join(filePath, 'version')); 108 | 109 | /// Get the path to the Flutter SDKs. 110 | static String get _flutterSdksPath => 111 | path.join(Directory.current.path, 'flutter-sdks'); 112 | } 113 | 114 | const channels = ['stable', 'beta', 'dev', 'old', 'master']; 115 | 116 | class DownloadingSdkManager { 117 | final String channel; 118 | final String flutterVersion; 119 | 120 | DownloadingSdkManager._(this.channel, this.flutterVersion); 121 | 122 | factory DownloadingSdkManager(String channel) { 123 | if (!channels.contains(channel)) { 124 | throw StateError('Unknown channel name: $channel'); 125 | } 126 | final flutterVersion = 127 | _readVersionMap(channel)['flutter_version'] as String; 128 | return DownloadingSdkManager._(channel, flutterVersion); 129 | } 130 | 131 | /// Creates a Flutter SDK in `flutter-sdks/` that is configured using the 132 | /// `flutter-sdk-version.yaml` file. 133 | /// 134 | /// Note that this is an expensive operation. 135 | Future createFromConfigFile() async { 136 | final sdk = await _cloneSdkIfNecessary(channel); 137 | 138 | // git checkout master 139 | await sdk.checkout('master'); 140 | // git fetch --tags 141 | await sdk.fetchTags(); 142 | // git checkout 1.25.0-8.1.pre 143 | await sdk.checkout(flutterVersion); 144 | 145 | // Force downloading of Dart SDK before constructing the Sdk singleton. 146 | final exitCode = await sdk.init(); 147 | if (exitCode != 0) { 148 | throw StateError('Initializing Flutter SDK resulted in error: $exitCode'); 149 | } 150 | 151 | return sdk.flutterSdkPath; 152 | } 153 | 154 | Future<_DownloadedFlutterSdk> _cloneSdkIfNecessary(String channel) async { 155 | final sdkPath = path.join(Sdk._flutterSdksPath, channel); 156 | final sdk = _DownloadedFlutterSdk(sdkPath); 157 | 158 | if (!Directory(sdk.flutterSdkPath).existsSync()) { 159 | // This takes perhaps ~20 seconds. 160 | await sdk.clone( 161 | [ 162 | 'https://github.com/flutter/flutter', 163 | sdk.flutterSdkPath, 164 | ], 165 | cwd: Directory.current.path, 166 | ); 167 | } 168 | 169 | return sdk; 170 | } 171 | } 172 | 173 | String readDartLanguageVersion(String channelName) => 174 | _readVersionMap(channelName)['dart_language_version'] as String; 175 | 176 | /// Read and return the Flutter SDK configuration file info 177 | /// (`flutter-sdk-version.yaml`). 178 | Map _readVersionMap(String channelName) { 179 | final file = File(path.join(Directory.current.path, _flutterSdkConfigFile)); 180 | final sdkConfig = 181 | (loadYaml(file.readAsStringSync()) as Map).cast(); 182 | 183 | if (!sdkConfig.containsKey('flutter_sdk')) { 184 | throw StateError("No key 'flutter_sdk' found in '$_flutterSdkConfigFile'"); 185 | } 186 | final flutterConfig = sdkConfig['flutter_sdk'] as Map; 187 | if (!flutterConfig.containsKey(channelName)) { 188 | throw StateError("No key '$channelName' found in '$_flutterSdkConfigFile'"); 189 | } 190 | final channelConfig = flutterConfig[channelName] as Map; 191 | if (!channelConfig.containsKey('flutter_version')) { 192 | throw StateError( 193 | "No key 'flutter_version' found in '$_flutterSdkConfigFile'"); 194 | } 195 | if (!channelConfig.containsKey('dart_language_version')) { 196 | throw StateError( 197 | "No key 'dart_language_version' found in '$_flutterSdkConfigFile'"); 198 | } 199 | return channelConfig.cast(); 200 | } 201 | 202 | const String _flutterSdkConfigFile = 'flutter-sdk-version.yaml'; 203 | 204 | class _DownloadedFlutterSdk { 205 | final String flutterSdkPath; 206 | 207 | _DownloadedFlutterSdk(this.flutterSdkPath); 208 | 209 | Future init() => 210 | // `flutter --version` takes ~28s. 211 | _execLog(path.join('bin', 'flutter'), ['--version'], flutterSdkPath); 212 | 213 | String get sdkPath => path.join(flutterSdkPath, 'bin', 'cache', 'dart-sdk'); 214 | 215 | String get versionFull => _readFile(path.join(sdkPath, 'version')); 216 | 217 | String get flutterVersion => _readFile(path.join(flutterSdkPath, 'version')); 218 | 219 | /// Perform a git clone, logging the command and any output, and throwing an 220 | /// exception if there are any issues with the clone. 221 | Future clone(List args, {required String cwd}) async { 222 | await _execLog('git', ['clone', ...args], cwd, throwOnError: true); 223 | } 224 | 225 | Future checkout(String branch) async { 226 | await _execLog('git', ['checkout', branch], flutterSdkPath, 227 | throwOnError: true); 228 | } 229 | 230 | Future fetchTags() async { 231 | await _execLog('git', ['fetch', '--tags'], flutterSdkPath, 232 | throwOnError: true); 233 | } 234 | 235 | Future pull() async { 236 | await _execLog('git', ['pull'], flutterSdkPath, throwOnError: true); 237 | } 238 | 239 | Future trackChannel(String channel) async { 240 | // git checkout --track -b beta origin/beta 241 | await _execLog( 242 | 'git', 243 | [ 244 | 'checkout', 245 | '--track', 246 | '-b', 247 | channel, 248 | 'origin/$channel', 249 | ], 250 | flutterSdkPath, 251 | throwOnError: true); 252 | } 253 | 254 | Future checkChannelAvailableLocally(String channel) async { 255 | // git show-ref --verify --quiet refs/heads/beta 256 | final result = await _execLog( 257 | 'git', 258 | [ 259 | 'show-ref', 260 | '--verify', 261 | '--quiet', 262 | 'refs/heads/$channel', 263 | ], 264 | flutterSdkPath, 265 | ); 266 | 267 | return result == 0; 268 | } 269 | 270 | Future _execLog( 271 | String executable, 272 | List arguments, 273 | String cwd, { 274 | bool throwOnError = false, 275 | }) async { 276 | print('$executable ${arguments.join(' ')}'); 277 | 278 | final process = await Process.start( 279 | executable, 280 | arguments, 281 | workingDirectory: cwd, 282 | ); 283 | process.stdout 284 | .transform(utf8.decoder) 285 | .listen((string) => stdout.write(string)); 286 | process.stderr 287 | .transform(utf8.decoder) 288 | .listen((string) => stderr.write(string)); 289 | 290 | final code = await process.exitCode; 291 | if (throwOnError && code != 0) { 292 | throw ProcessException( 293 | executable, 294 | arguments, 295 | 'Error running ${[executable, ...arguments].take(2).join(' ')}', 296 | code); 297 | } 298 | return code; 299 | } 300 | } 301 | 302 | String _readFile(String filePath) => File(filePath).readAsStringSync().trim(); 303 | -------------------------------------------------------------------------------- /lib/src/server_cache.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | import 'dart:async'; 6 | import 'dart:math'; 7 | 8 | import 'package:quiver/cache.dart'; 9 | import 'package:resp_client/resp_client.dart'; 10 | import 'package:resp_client/resp_commands.dart'; 11 | import 'package:resp_client/resp_server.dart'; 12 | 13 | import 'common_server_impl.dart' show log; 14 | import 'sdk.dart'; 15 | 16 | abstract class ServerCache { 17 | Future get(String key); 18 | 19 | Future set(String key, String value, {Duration expiration}); 20 | 21 | Future remove(String key); 22 | 23 | Future shutdown(); 24 | } 25 | 26 | /// A redis-backed implementation of [ServerCache]. 27 | class RedisCache implements ServerCache { 28 | RespClient? redisClient; 29 | RespServerConnection? _connection; 30 | 31 | final Uri redisUri; 32 | 33 | final Sdk _sdk; 34 | 35 | // Version of the server to add with keys. 36 | final String? serverVersion; 37 | 38 | // pseudo-random is good enough. 39 | final Random randomSource = Random(); 40 | static const int _connectionRetryBaseMs = 250; 41 | static const int _connectionRetryMaxMs = 60000; 42 | static const Duration cacheOperationTimeout = Duration(milliseconds: 10000); 43 | 44 | RedisCache(String redisUriString, this._sdk, this.serverVersion) 45 | : redisUri = Uri.parse(redisUriString) { 46 | _reconnect(); 47 | } 48 | 49 | Completer _connected = Completer(); 50 | 51 | /// Completes when and if the redis server connects. This future is reset 52 | /// on disconnection. Mostly for testing. 53 | Future get connected => _connected.future; 54 | 55 | Completer _disconnected = Completer()..complete(); 56 | 57 | /// Completes when the server is disconnected (begins completed). This 58 | /// future is reset on connection. Mostly for testing. 59 | Future get disconnected => _disconnected.future; 60 | 61 | String? __logPrefix; 62 | 63 | String get _logPrefix => 64 | __logPrefix ??= 'RedisCache [$redisUri] ($serverVersion)'; 65 | 66 | bool _isConnected() => redisClient != null && !_isShutdown; 67 | bool _isShutdown = false; 68 | 69 | /// If you will no longer be using the [RedisCache] instance, call this to 70 | /// prevent reconnection attempts. All calls to get/remove/set on this object 71 | /// will return null after this. Future completes when disconnection is complete. 72 | @override 73 | Future shutdown() { 74 | log.info('$_logPrefix: shutting down...'); 75 | _isShutdown = true; 76 | _connection?.close(); 77 | return disconnected; 78 | } 79 | 80 | /// Call when an active connection has disconnected. 81 | void _resetConnection() { 82 | assert(_connected.isCompleted && !_disconnected.isCompleted); 83 | _connected = Completer(); 84 | _connection = null; 85 | redisClient = null; 86 | _disconnected.complete(); 87 | } 88 | 89 | /// Call when a new connection is established. 90 | void _setUpConnection(RespServerConnection newConnection) { 91 | assert(_disconnected.isCompleted && !_connected.isCompleted); 92 | _disconnected = Completer(); 93 | _connection = newConnection; 94 | redisClient = RespClient(_connection!); 95 | _connected.complete(); 96 | } 97 | 98 | /// Begin a reconnection loop asynchronously to maintain a connection to the 99 | /// redis server. Never stops trying until shutdown() is called. 100 | void _reconnect([int retryTimeoutMs = _connectionRetryBaseMs]) { 101 | if (_isShutdown) { 102 | return; 103 | } 104 | log.info('$_logPrefix: reconnecting to $redisUri...'); 105 | var nextRetryMs = retryTimeoutMs; 106 | if (retryTimeoutMs < _connectionRetryMaxMs / 2) { 107 | // 1 <= (randomSource.nextDouble() + 1) < 2 108 | nextRetryMs = (retryTimeoutMs * (randomSource.nextDouble() + 1)).toInt(); 109 | } 110 | (redisUri.hasPort 111 | ? connectSocket(redisUri.host, port: redisUri.port) 112 | : connectSocket(redisUri.host)) 113 | .then((newConnection) { 114 | log.info('$_logPrefix: Connected to redis server'); 115 | _setUpConnection(newConnection); 116 | // If the client disconnects, discard the client and try to connect again. 117 | 118 | newConnection.outputSink.done.then((_) { 119 | _resetConnection(); 120 | log.warning('$_logPrefix: connection terminated, reconnecting'); 121 | _reconnect(); 122 | }).catchError((dynamic e) { 123 | _resetConnection(); 124 | log.warning( 125 | '$_logPrefix: connection terminated with error $e, reconnecting'); 126 | _reconnect(); 127 | }); 128 | }) 129 | .timeout(const Duration(milliseconds: _connectionRetryMaxMs)) 130 | .catchError((_) { 131 | log.severe( 132 | '$_logPrefix: Unable to connect to redis server, reconnecting in ${nextRetryMs}ms ...'); 133 | Future.delayed(Duration(milliseconds: nextRetryMs)).then((_) { 134 | _reconnect(nextRetryMs); 135 | }); 136 | }); 137 | } 138 | 139 | /// Build a key that includes the server version, Dart SDK version, and 140 | /// Flutter SDK version. 141 | /// 142 | /// We don't use the existing key directly so that different AppEngine 143 | /// versions using the same redis cache do not have collisions. 144 | String _genKey(String key) { 145 | // the `rc` here is a differentiator to keep the `resp_client` documents 146 | // separate from the `dartis` documents. 147 | return 'server:rc:$serverVersion:' 148 | 'dart:${_sdk.versionFull}:' 149 | 'flutter:${_sdk.flutterVersion}+$key'; 150 | } 151 | 152 | @override 153 | Future get(String key) async { 154 | String? value; 155 | key = _genKey(key); 156 | if (!_isConnected()) { 157 | log.warning('$_logPrefix: no cache available when getting key $key'); 158 | } else { 159 | final commands = RespCommandsTier2(redisClient!); 160 | try { 161 | value = await commands.get(key).timeout(cacheOperationTimeout, 162 | onTimeout: () async { 163 | log.warning('$_logPrefix: timeout on get operation for key $key'); 164 | await _connection?.close(); 165 | return null; 166 | }); 167 | } catch (e) { 168 | log.warning('$_logPrefix: error on get operation for key $key: $e'); 169 | } 170 | } 171 | return value; 172 | } 173 | 174 | @override 175 | Future remove(String key) async { 176 | key = _genKey(key); 177 | if (!_isConnected()) { 178 | log.warning('$_logPrefix: no cache available when removing key $key'); 179 | return null; 180 | } 181 | 182 | final commands = RespCommandsTier2(redisClient!); 183 | try { 184 | return commands.del([key]).timeout(cacheOperationTimeout, 185 | onTimeout: () async { 186 | log.warning('$_logPrefix: timeout on remove operation for key $key'); 187 | await _connection?.close(); 188 | return 0; // 0 keys deleted 189 | }); 190 | } catch (e) { 191 | log.warning('$_logPrefix: error on remove operation for key $key: $e'); 192 | } 193 | } 194 | 195 | @override 196 | Future set(String key, String value, {Duration? expiration}) async { 197 | key = _genKey(key); 198 | if (!_isConnected()) { 199 | log.warning('$_logPrefix: no cache available when setting key $key'); 200 | return; 201 | } 202 | 203 | final commands = RespCommandsTier2(redisClient!); 204 | try { 205 | return Future.sync(() async { 206 | await commands.set(key, value); 207 | if (expiration != null) { 208 | await commands.pexpire(key, expiration); 209 | } 210 | }).timeout(cacheOperationTimeout, onTimeout: () { 211 | log.warning('$_logPrefix: timeout on set operation for key $key'); 212 | _connection?.close(); 213 | }); 214 | } catch (e) { 215 | log.warning('$_logPrefix: error on set operation for key $key: $e'); 216 | } 217 | } 218 | } 219 | 220 | /// An in-memory implementation of [ServerCache] which doesn't support 221 | /// expiration of entries based on time. 222 | class InMemoryCache implements ServerCache { 223 | // TODO: This is the only use of package:quiver; consider in-lining it. 224 | /// Wrapping an internal cache with a maximum size of 512 entries. 225 | final Cache _lru = 226 | MapCache.lru(maximumSize: 512); 227 | 228 | @override 229 | Future get(String key) async => _lru.get(key); 230 | 231 | @override 232 | Future set(String key, String value, {Duration? expiration}) async => 233 | _lru.set(key, value); 234 | 235 | @override 236 | Future remove(String key) async => _lru.invalidate(key); 237 | 238 | @override 239 | Future shutdown() => Future.value(); 240 | } 241 | -------------------------------------------------------------------------------- /lib/src/shelf_cors.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, 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 | import 'dart:async'; 6 | import 'package:shelf/shelf.dart'; 7 | 8 | /// Middleware which adds [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) 9 | /// to shelf responses. Also handles preflight (OPTIONS) requests. 10 | /// 11 | /// By default, allows access from everywhere. 12 | Middleware createCorsHeadersMiddleware( 13 | {Map corsHeaders = const { 14 | 'Access-Control-Allow-Origin': '*' 15 | }}) { 16 | // Handle preflight (OPTIONS) requests by just adding headers and an empty 17 | // response. 18 | FutureOr handleOptionsRequest(Request request) { 19 | if (request.method == 'OPTIONS') { 20 | return Response.ok(null, headers: corsHeaders); 21 | } else { 22 | return null; 23 | } 24 | } 25 | 26 | FutureOr addCorsHeaders(Response response) => 27 | response.change(headers: corsHeaders); 28 | 29 | return createMiddleware( 30 | requestHandler: handleOptionsRequest, responseHandler: addCorsHeaders); 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, 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 | import 'dart:io'; 6 | 7 | import 'package:path/path.dart' as path; 8 | 9 | /// Normalizes any "paths" from [text], replacing the segments before the last 10 | /// separator with either "dart:core" or "package:flutter", or removes them, 11 | /// according to their content. 12 | /// 13 | /// ## Examples: 14 | /// 15 | /// "Unused import: '/path/foo.dart'" -> "Unused import: 'foo.dart'" 16 | /// 17 | /// "Unused import: '/path/to/dart/lib/core/world.dart'" -> 18 | /// "Unused import: 'dart:core/world.dart'" 19 | /// 20 | /// "Unused import: 'package:flutter/material.dart'" -> 21 | /// "Unused import: 'package:flutter/material.dart'" 22 | String normalizeFilePaths(String text) { 23 | return text.replaceAllMapped(_possiblePathPattern, (match) { 24 | final possiblePath = match.group(0)!; 25 | 26 | final uri = Uri.tryParse(possiblePath); 27 | if (uri != null && uri.hasScheme) { 28 | return possiblePath; 29 | } 30 | 31 | final pathComponents = path.split(possiblePath); 32 | final basename = path.basename(possiblePath); 33 | 34 | if (pathComponents.contains('flutter')) { 35 | return path.join('package:flutter', basename); 36 | } 37 | 38 | if (pathComponents.contains('lib') && pathComponents.contains('core')) { 39 | return path.join('dart:core', basename); 40 | } 41 | 42 | return basename; 43 | }); 44 | } 45 | 46 | Future runWithLogging( 47 | String executable, { 48 | List arguments = const [], 49 | String? workingDirectory, 50 | Map environment = const {}, 51 | required void Function(String) log, 52 | }) async { 53 | log([ 54 | 'Running $executable ${arguments.join(' ')}', 55 | if (workingDirectory != null) "from directory: '$workingDirectory'", 56 | if (environment.isNotEmpty) 'with additional environment: $environment', 57 | ].join('\n ')); 58 | 59 | final process = await Process.start(executable, arguments, 60 | workingDirectory: workingDirectory, 61 | environment: environment, 62 | includeParentEnvironment: true, 63 | runInShell: Platform.isWindows); 64 | process.stdout.listen((out) => log(systemEncoding.decode(out))); 65 | process.stderr.listen((err) => log(systemEncoding.decode(err))); 66 | return process; 67 | } 68 | 69 | /// A pattern which matches a possible path. 70 | /// 71 | /// This pattern is essentially "possibly some letters and colons, followed by a 72 | /// slash, followed by non-whitespace." 73 | final _possiblePathPattern = RegExp(r'[a-zA-Z:]*\/\S*'); 74 | -------------------------------------------------------------------------------- /lib/version.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | // TODO: We need a build process to update this number. Something like a grinder 6 | // task that will stamp the source file as part of a deploy, or a milestone task. 7 | 8 | /// The version of the dart-services backend. 9 | const String servicesVersion = 'M1.2'; 10 | -------------------------------------------------------------------------------- /protos/dart_services.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, 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 | syntax = "proto3"; 6 | 7 | package dart_services.api; 8 | 9 | message CompileRequest { 10 | // The Dart source. 11 | string source = 1; 12 | 13 | // Return the Dart to JS source map; optional (defaults to false). 14 | bool returnSourceMap = 2; 15 | } 16 | 17 | /// Compile request for a multiple file set. 18 | message CompileFilesRequest { 19 | // The Dart source files set. map of { filename1:sourcecode1 .. filenameN:sourcecodeN } 20 | map files = 1; 21 | 22 | // Return the Dart to JS source map; optional (defaults to false). 23 | bool returnSourceMap = 2; 24 | } 25 | 26 | message CompileDDCRequest { 27 | // The Dart source. 28 | string source = 1; 29 | } 30 | 31 | /// DDC Compile request for a multiple file set. 32 | message CompileFilesDDCRequest { 33 | // The Dart source files set. map of { filename1:sourcecode1 .. filenameN:sourcecodeN } 34 | map files = 1; 35 | } 36 | 37 | message SourceRequest { 38 | // The Dart source. 39 | string source = 1; 40 | 41 | // The offset within source to operate at. 42 | int32 offset = 2; 43 | } 44 | 45 | /// Multiple file set of dart source for analysis, completion, fixes, etc. 46 | message SourceFilesRequest { 47 | // The Dart source files set. map of { filename1:sourcecode1 .. filenameN:sourcecodeN } 48 | map files = 1; 49 | 50 | // active (within editor) source filename key within files map 51 | string activeSourceName = 2; 52 | 53 | // The offset within active source file to operate at. 54 | int32 offset = 3; 55 | } 56 | 57 | message AnalysisResults { 58 | repeated AnalysisIssue issues = 1; 59 | 60 | // The package imports parsed from the source. 61 | repeated string packageImports = 2; 62 | 63 | // Make this response compatible with BadRequest 64 | ErrorMessage error = 99; 65 | } 66 | 67 | message AnalysisIssue { 68 | string kind = 1; 69 | int32 line = 2; 70 | string message = 3; 71 | string sourceName = 4; 72 | bool hasFixes = 5; 73 | int32 charStart = 6; 74 | int32 charLength = 7; 75 | string url = 8; 76 | repeated DiagnosticMessage diagnosticMessages = 9; 77 | string correction = 10; 78 | int32 column = 11; 79 | string code = 12; 80 | } 81 | 82 | message DiagnosticMessage { 83 | string message = 1; 84 | int32 line = 2; 85 | int32 charStart = 3; 86 | int32 charLength = 4; 87 | } 88 | 89 | message VersionRequest {} 90 | 91 | message CompileResponse { 92 | string result = 1; 93 | string sourceMap = 2; 94 | 95 | // Make this response compatible with BadRequest 96 | ErrorMessage error = 99; 97 | } 98 | 99 | message CompileDDCResponse { 100 | string result = 1; 101 | string modulesBaseUrl = 2; 102 | 103 | // Make this response compatible with BadRequest 104 | ErrorMessage error = 99; 105 | } 106 | 107 | message DocumentResponse { 108 | map info = 1; 109 | 110 | // Make this response compatible with BadRequest 111 | ErrorMessage error = 99; 112 | } 113 | 114 | message CompleteResponse { 115 | // The offset of the start of the text to be replaced. 116 | int32 replacementOffset = 1; 117 | 118 | // The length of the text to be replaced. 119 | int32 replacementLength = 2; 120 | 121 | repeated Completion completions = 3; 122 | 123 | // Make this response compatible with BadRequest 124 | ErrorMessage error = 99; 125 | } 126 | 127 | message Completion { map completion = 1; } 128 | 129 | message FixesResponse { 130 | repeated ProblemAndFixes fixes = 1; 131 | 132 | // Make this response compatible with BadRequest 133 | ErrorMessage error = 99; 134 | } 135 | 136 | // Represents a problem detected during analysis, and a set of possible ways of 137 | // resolving the problem. 138 | message ProblemAndFixes { 139 | repeated CandidateFix fixes = 1; 140 | string problemMessage = 2; 141 | int32 offset = 3; 142 | int32 length = 4; 143 | } 144 | 145 | // Represents a possible way of solving an Analysis Problem. 146 | message CandidateFix { 147 | string message = 1; 148 | repeated SourceEdit edits = 2; 149 | int32 selectionOffset = 3; 150 | repeated LinkedEditGroup linkedEditGroups = 4; 151 | } 152 | 153 | // Represents a single edit-point change to a source file. 154 | message SourceEdit { 155 | int32 offset = 1; 156 | int32 length = 2; 157 | string replacement = 3; 158 | } 159 | 160 | message LinkedEditGroup { 161 | // The positions of the regions that should be edited simultaneously. 162 | repeated int32 positions = 1; 163 | 164 | // The length of the regions that should be edited simultaneously. 165 | int32 length = 2; 166 | 167 | // Pre-computed suggestions for what every region might want to be changed to. 168 | repeated LinkedEditSuggestion suggestions = 3; 169 | } 170 | 171 | message LinkedEditSuggestion { 172 | // The value that could be used to replace all of the linked edit regions. 173 | string value = 1; 174 | 175 | // The kind of value being proposed. 176 | string kind = 2; 177 | } 178 | 179 | // Represents a reformatting of the code. 180 | message FormatResponse { 181 | // The formatted source code. 182 | string newString = 1; 183 | 184 | // The (optional) new offset of the cursor; can be `null`. 185 | int32 offset = 2; 186 | 187 | // Make this response compatible with BadRequest 188 | ErrorMessage error = 99; 189 | } 190 | 191 | // The response from the `/assists` service call. 192 | message AssistsResponse { 193 | repeated CandidateFix assists = 1; 194 | 195 | // Make this response compatible with BadRequest 196 | ErrorMessage error = 99; 197 | } 198 | 199 | // The response from the `/version` service call. 200 | message VersionResponse { 201 | // The Dart SDK version that DartServices is compatible with. 202 | string sdkVersion = 1; 203 | 204 | // The full Dart SDK version that DartServices is compatible with. 205 | string sdkVersionFull = 2; 206 | 207 | // The Dart SDK version that the server is running on. This will start with a 208 | // semver string, and have a space and other build details appended. 209 | string runtimeVersion = 3; 210 | 211 | // Removed. 212 | string appEngineVersion = 4 [deprecated = true]; 213 | 214 | // Removed. 215 | string servicesVersion = 5 [deprecated = true]; 216 | 217 | // The Flutter SDK's version. 218 | string flutterVersion = 6; 219 | 220 | // The Flutter SDK's Dart version. 221 | string flutterDartVersion = 7; 222 | 223 | // The Flutter SDK's full Dart version. 224 | string flutterDartVersionFull = 8; 225 | 226 | // Package version numbers. 227 | // 228 | // Each package found in pubspec.lock is included, mapping the package name 229 | // to the package version. 230 | // 231 | // Deprecated: Use `packageInfo` data if available. 232 | map packageVersions = 9; 233 | 234 | // Package information. 235 | // 236 | // Each package found in `pubspec.lock` is included. 237 | repeated PackageInfo packageInfo = 10; 238 | 239 | // Experiments that this server is running 240 | repeated string experiment = 11; 241 | 242 | // The Flutter engine SHA, located in bin/internal/engine.version. 243 | string flutterEngineSha = 12; 244 | 245 | // Make this response compatible with BadRequest 246 | ErrorMessage error = 99; 247 | } 248 | 249 | message PackageInfo { 250 | // The name of this package. 251 | string name = 1; 252 | 253 | // The selected version of this package. 254 | string version = 2; 255 | 256 | // Whether this package is supported as a directly importable package, 257 | // or simply available as a transitive dependency of a supported package. 258 | bool supported = 3; 259 | } 260 | 261 | // Response from the server when errors are thrown internally 262 | message BadRequest { ErrorMessage error = 99; } 263 | 264 | // Individual error messages. 265 | message ErrorMessage { string message = 1; } 266 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_services 2 | publish_to: none 3 | 4 | environment: 5 | sdk: ^3.0.1 6 | 7 | dependencies: 8 | analysis_server_lib: ^0.2.4 9 | analyzer: ^5.13.0 10 | args: ^2.4.2 11 | bazel_worker: ^1.0.2 12 | convert: ^3.1.1 13 | crypto: ^3.0.3 14 | encrypt: ^5.0.1 15 | http: ^1.1.0 16 | logging: ^1.2.0 17 | meta: ^1.9.1 18 | path: ^1.8.3 19 | protobuf: ^3.0.0 20 | quiver: ^3.2.1 21 | resp_client: ^1.2.0 22 | shelf: ^1.4.1 23 | shelf_router: ^1.1.4 24 | yaml: ^3.1.2 25 | 26 | dev_dependencies: 27 | angel3_mock_request: ^8.0.0 28 | async: ^2.11.0 29 | build_runner: ^2.4.6 30 | coverage: ^1.6.3 31 | dart_flutter_team_lints: ^1.0.0 32 | grinder: ^0.9.4 33 | package_config: ^2.1.0 34 | shelf_router_generator: ^1.0.6 35 | synchronized: ^3.1.0 36 | test: ^1.24.4 37 | test_descriptor: ^2.0.1 38 | 39 | executables: 40 | services: null 41 | -------------------------------------------------------------------------------- /test/all.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | import 'analysis_server_test.dart' as analysis_server_test; 6 | import 'bench_test.dart' as bench_test; 7 | import 'common_server_api_protobuf_test.dart' 8 | as common_server_api_protobuf_test; 9 | import 'common_server_api_test.dart' as common_server_api_test; 10 | import 'common_test.dart' as common_test; 11 | import 'compiler_test.dart' as compiler_test; 12 | import 'flutter_analysis_server_test.dart' as flutter_analysis_server_test; 13 | import 'flutter_web_test.dart' as flutter_web_test; 14 | import 'gae_deployed_test.dart' as gae_deployed_test; 15 | import 'project_creator_test.dart' as project_creator_test; 16 | import 'pub_test.dart' as pub_test; 17 | import 'redis_cache_test.dart' as redis_test; 18 | import 'shelf_cors_test.dart' as shelf_cors_test; 19 | 20 | void main() async { 21 | analysis_server_test.defineTests(); 22 | bench_test.defineTests(); 23 | common_server_api_test.defineTests(); 24 | common_server_api_protobuf_test.defineTests(); 25 | common_test.defineTests(); 26 | compiler_test.defineTests(); 27 | flutter_analysis_server_test.defineTests(); 28 | flutter_web_test.defineTests(); 29 | gae_deployed_test.defineTests(); 30 | project_creator_test.defineTests(); 31 | pub_test.defineTests(); 32 | redis_test.defineTests(); 33 | shelf_cors_test.defineTests(); 34 | } 35 | -------------------------------------------------------------------------------- /test/bench_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | import 'dart:async'; 6 | 7 | import 'package:dart_services/src/bench.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() => defineTests(); 11 | 12 | void defineTests() { 13 | group('BenchmarkHarness', () { 14 | test('single', () { 15 | final harness = BenchmarkHarness(asJson: true); 16 | final benchmark = MockBenchmark(); 17 | 18 | return harness.benchmarkSingle(benchmark).then((BenchMarkResult result) { 19 | expect(result.iteration, greaterThan(1)); 20 | expect(result.microseconds, greaterThan(1)); 21 | expect(result.toString(), isNotNull); 22 | expect(benchmark.count, greaterThan(80)); 23 | expect(benchmark.toString(), 'mock'); 24 | }); 25 | }); 26 | 27 | test('many', () { 28 | final harness = BenchmarkHarness(asJson: true, logger: (_) {}); 29 | final benchmarks = [MockBenchmark(), MockBenchmark()]; 30 | 31 | return harness.benchmark(benchmarks).then((_) { 32 | expect(benchmarks[0].count, greaterThan(80)); 33 | expect(benchmarks[1].count, greaterThan(80)); 34 | }); 35 | }); 36 | }); 37 | } 38 | 39 | class MockBenchmark extends Benchmark { 40 | int count = 0; 41 | 42 | MockBenchmark() : super('mock'); 43 | 44 | @override 45 | Future perform() { 46 | count++; 47 | return Future.delayed(Duration(milliseconds: 10)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/common_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | // ignore_for_file: prefer_single_quotes 6 | 7 | import 'package:dart_services/src/common.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() => defineTests(); 11 | 12 | void defineTests() { 13 | group('Lines', () { 14 | test('empty string', () { 15 | final lines = Lines(''); 16 | expect(lines.getLineForOffset(0), 0); 17 | expect(lines.getLineForOffset(1), 0); 18 | }); 19 | 20 | test('getLineForOffset', () { 21 | final lines = Lines('one\ntwo\nthree'); 22 | expect(lines.getLineForOffset(0), 0); 23 | expect(lines.getLineForOffset(1), 0); 24 | expect(lines.getLineForOffset(2), 0); 25 | expect(lines.getLineForOffset(3), 0); 26 | expect(lines.getLineForOffset(4), 1); 27 | expect(lines.getLineForOffset(5), 1); 28 | expect(lines.getLineForOffset(6), 1); 29 | expect(lines.getLineForOffset(7), 1); 30 | expect(lines.getLineForOffset(8), 2); 31 | expect(lines.getLineForOffset(9), 2); 32 | expect(lines.getLineForOffset(10), 2); 33 | expect(lines.getLineForOffset(11), 2); 34 | expect(lines.getLineForOffset(12), 2); 35 | expect(lines.getLineForOffset(13), 2); 36 | 37 | expect(lines.getLineForOffset(14), 2); 38 | }); 39 | }); 40 | 41 | test('stripMatchingQuotes', () { 42 | expect(stripMatchingQuotes(""), ""); 43 | expect(stripMatchingQuotes("'"), "'"); 44 | expect(stripMatchingQuotes("''"), ""); 45 | expect(stripMatchingQuotes("'abc'"), "abc"); 46 | 47 | expect(stripMatchingQuotes(''), ''); 48 | expect(stripMatchingQuotes('"'), '"'); 49 | expect(stripMatchingQuotes('""'), ''); 50 | expect(stripMatchingQuotes('"abc"'), 'abc'); 51 | }); 52 | 53 | test('vmVersion', () { 54 | expect(vmVersion, isNotNull); 55 | expect( 56 | vmVersion, 57 | anyOf(startsWith('2.'), startsWith('3.')), 58 | ); 59 | }); 60 | 61 | test('countLines', () { 62 | final sources = { 63 | 'file1': '\n\n\n\n\n', // 5 lines, 64 | 'file2': r'''THis is line 1, 65 | This is line 2, 66 | THis is line 3''', // 3 lines, 67 | 'file3': 'line1\r\nline2\r\nline 3\r\n', // 3 lines, 68 | 'file4': 'line1\nline2\nline3\n', // 3 lines, 69 | 'file5': 'line1\rline2\rline3\r', // and 3 lines makes 17 total lines. 70 | }; 71 | expect(countLines(sources), 17); 72 | }); 73 | 74 | test('countEOLsInString', () { 75 | expect(countLinesInString('line1\r\nline2\r\nline 3\r\n\r\n'), 4); 76 | expect(countLinesInString('line1\nline2\nline3\nline4\n\n'), 5); 77 | expect(countLinesInString('line1\rline2\rline3\rline4\rline5\r\r'), 6); 78 | expect(countLinesInString('\n\n\n\n\n\n\n'), 7); 79 | expect(countLinesInString('\r\r\r\r\r\r\r\r'), 8); 80 | expect(countLinesInString('\n\n\n\n\n\n\n\n\n'), 9); 81 | expect(countLinesInString(r'''THis is line 1, 82 | This is line 2 83 | '''), 2); 84 | }); 85 | 86 | test('sanitizeAndCheckFilenames', () { 87 | final filesSanitize = { 88 | '..\\.../../$kMainDart': '', 89 | '../various.dart': '' 90 | }; 91 | 92 | sanitizeAndCheckFilenames(filesSanitize); 93 | expect(filesSanitize.keys.elementAt(0), kMainDart); 94 | expect(filesSanitize.keys.elementAt(1), 'various.dart'); 95 | 96 | // Using "part 'various.dart'" to bring in second file. 97 | final filesVar2 = { 98 | 'mymain.dart': 'void main() => ();', 99 | 'various.dart': '', 100 | 'discdata.dart': '' 101 | }; 102 | 103 | var newmain = sanitizeAndCheckFilenames(filesVar2, 'mymain.dart'); 104 | expect(newmain, kMainDart); 105 | expect(filesVar2.keys.elementAt(0), kMainDart); 106 | expect(filesVar2.keys.elementAt(1), 'various.dart'); 107 | 108 | final filesVar3Sani = { 109 | 'package:$kMainDart': '', 110 | 'dart:discdata.dart': '', 111 | 'http://dart:http://various.dart': '' 112 | }; 113 | newmain = sanitizeAndCheckFilenames(filesVar3Sani, 'package:$kMainDart'); 114 | expect(newmain, kMainDart); 115 | expect(filesVar3Sani.keys.elementAt(0), kMainDart); 116 | expect(filesVar3Sani.keys.elementAt(1), 'discdata.dart'); 117 | expect(filesVar3Sani.keys.elementAt(2), 'various.dart'); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /test/flutter_analysis_server_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | import 'dart:io'; 6 | 7 | import 'package:dart_services/src/analysis_server.dart'; 8 | import 'package:dart_services/src/analysis_servers.dart'; 9 | import 'package:dart_services/src/common.dart'; 10 | import 'package:dart_services/src/common_server_impl.dart'; 11 | import 'package:dart_services/src/protos/dart_services.pbserver.dart'; 12 | import 'package:dart_services/src/sdk.dart'; 13 | import 'package:dart_services/src/server_cache.dart'; 14 | import 'package:test/test.dart'; 15 | 16 | final channel = Platform.environment['FLUTTER_CHANNEL'] ?? stableChannel; 17 | void main() => defineTests(); 18 | 19 | void defineTests() { 20 | group('Flutter SDK analysis_server', () { 21 | late AnalysisServerWrapper analysisServer; 22 | 23 | setUp(() async { 24 | final sdk = Sdk.create(channel); 25 | analysisServer = 26 | FlutterAnalysisServerWrapper(dartSdkPath: sdk.dartSdkPath); 27 | await analysisServer.init(); 28 | }); 29 | 30 | tearDown(() async { 31 | await analysisServer.shutdown(); 32 | }); 33 | 34 | test('analyze counter app', () async { 35 | final results = await analysisServer.analyze(sampleCodeFlutterCounter); 36 | expect(results.issues, isEmpty); 37 | }); 38 | 39 | test('analyze Draggable Physics sample', () async { 40 | final results = 41 | await analysisServer.analyze(sampleCodeFlutterDraggableCard); 42 | expect(results.issues, isEmpty); 43 | }); 44 | }); 45 | 46 | group( 47 | 'Flutter SDK analysis_server with analysis ' 48 | 'servers', () { 49 | late AnalysisServersWrapper analysisServersWrapper; 50 | late Sdk sdk; 51 | setUp(() async { 52 | sdk = Sdk.create(channel); 53 | analysisServersWrapper = AnalysisServersWrapper(sdk.dartSdkPath); 54 | await analysisServersWrapper.warmup(); 55 | }); 56 | 57 | tearDown(() async { 58 | await analysisServersWrapper.shutdown(); 59 | }); 60 | 61 | test('reports errors with Flutter code', () async { 62 | late AnalysisResults results; 63 | results = await analysisServersWrapper.analyze(''' 64 | import 'package:flutter/material.dart'; 65 | 66 | String x = 7; 67 | 68 | void main() async { 69 | runApp(MaterialApp( 70 | debugShowCheckedModeBanner: false, home: Scaffold(body: HelloWorld()))); 71 | } 72 | 73 | class HelloWorld extends StatelessWidget { 74 | @override 75 | Widget build(context) => const Center(child: Text('Hello world')); 76 | } 77 | ''', devMode: false); 78 | expect(results.issues, hasLength(1)); 79 | final issue = results.issues[0]; 80 | expect(issue.line, 3); 81 | expect(issue.kind, 'error'); 82 | expect( 83 | issue.message, 84 | "A value of type 'int' can't be assigned to a variable of type " 85 | "'String'."); 86 | }); 87 | 88 | // https://github.com/dart-lang/dart-pad/issues/2005 89 | test('reports lint with Flutter code', () async { 90 | late AnalysisResults results; 91 | results = await analysisServersWrapper.analyze(''' 92 | import 'package:flutter/material.dart'; 93 | 94 | void main() async { 95 | var unknown; 96 | print(unknown); 97 | 98 | runApp(MaterialApp( 99 | debugShowCheckedModeBanner: false, home: Scaffold(body: HelloWorld()))); 100 | } 101 | 102 | class HelloWorld extends StatelessWidget { 103 | @override 104 | Widget build(context) => const Center(child: Text('Hello world')); 105 | } 106 | ''', devMode: false); 107 | expect(results.issues, hasLength(1)); 108 | final issue = results.issues[0]; 109 | expect(issue.line, 4); 110 | expect(issue.kind, 'info'); 111 | expect(issue.message, 112 | 'An uninitialized variable should have an explicit type annotation.'); 113 | }); 114 | 115 | test('analyze counter app', () async { 116 | final results = await analysisServersWrapper 117 | .analyze(sampleCodeFlutterCounter, devMode: false); 118 | expect(results.issues, isEmpty); 119 | }); 120 | 121 | test('analyze Draggable Physics sample', () async { 122 | final results = await analysisServersWrapper 123 | .analyze(sampleCodeFlutterDraggableCard, devMode: false); 124 | expect(results.issues, isEmpty); 125 | }); 126 | 127 | test('analyze counter app', () async { 128 | final results = await analysisServersWrapper 129 | .analyze(sampleCodeFlutterCounter, devMode: false); 130 | expect(results.issues, isEmpty); 131 | }); 132 | 133 | test('analyze Draggable Physics sample', () async { 134 | final results = await analysisServersWrapper 135 | .analyze(sampleCodeFlutterDraggableCard, devMode: false); 136 | expect(results.issues, isEmpty); 137 | }); 138 | }); 139 | 140 | group('CommonServerImpl flutter analyze', () { 141 | late CommonServerImpl commonServerImpl; 142 | 143 | _MockCache cache; 144 | 145 | setUp(() async { 146 | cache = _MockCache(); 147 | final sdk = Sdk.create(channel); 148 | commonServerImpl = CommonServerImpl(cache, sdk); 149 | await commonServerImpl.init(); 150 | }); 151 | 152 | tearDown(() async { 153 | await commonServerImpl.shutdown(); 154 | }); 155 | 156 | test('counter app', () async { 157 | final results = await commonServerImpl 158 | .analyze(SourceRequest()..source = sampleCodeFlutterCounter); 159 | expect(results.issues, isEmpty); 160 | }); 161 | 162 | test('Draggable Physics sample', () async { 163 | final results = await commonServerImpl 164 | .analyze(SourceRequest()..source = sampleCodeFlutterDraggableCard); 165 | expect(results.issues, isEmpty); 166 | }); 167 | }); 168 | 169 | ///---------------------------------------------------------------- 170 | /// Beginning of multi file files={} tests group: 171 | group('MULTI FILE files={} Tests', () { 172 | group('Flutter SDK analysis_server files={} variation', () { 173 | late AnalysisServerWrapper analysisServer; 174 | 175 | setUp(() async { 176 | final sdk = Sdk.create(channel); 177 | analysisServer = 178 | FlutterAnalysisServerWrapper(dartSdkPath: sdk.dartSdkPath); 179 | await analysisServer.init(); 180 | }); 181 | 182 | tearDown(() async { 183 | await analysisServer.shutdown(); 184 | }); 185 | 186 | test('analyzeFiles counter app files={}', () async { 187 | final results = await analysisServer 188 | .analyzeFiles({kMainDart: sampleCodeFlutterCounter}); 189 | expect(results.issues, isEmpty); 190 | }); 191 | 192 | test('analyzeFiles Draggable Physics sample files={}', () async { 193 | final results = await analysisServer 194 | .analyzeFiles({kMainDart: sampleCodeFlutterDraggableCard}); 195 | expect(results.issues, isEmpty); 196 | }); 197 | }); 198 | 199 | group( 200 | 'Flutter SDK analysis_server with analysis files={}' 201 | 'servers', () { 202 | late AnalysisServersWrapper analysisServersWrapper; 203 | late Sdk sdk; 204 | setUp(() async { 205 | sdk = Sdk.create(channel); 206 | analysisServersWrapper = AnalysisServersWrapper(sdk.dartSdkPath); 207 | await analysisServersWrapper.warmup(); 208 | }); 209 | 210 | tearDown(() async { 211 | await analysisServersWrapper.shutdown(); 212 | }); 213 | 214 | test('reports errors with Flutter code files={}', () async { 215 | late AnalysisResults results; 216 | results = await analysisServersWrapper.analyzeFiles({ 217 | kMainDart: ''' 218 | import 'package:flutter/material.dart'; 219 | 220 | String x = 7; 221 | 222 | void main() async { 223 | runApp(MaterialApp( 224 | debugShowCheckedModeBanner: false, home: Scaffold(body: HelloWorld()))); 225 | } 226 | 227 | class HelloWorld extends StatelessWidget { 228 | @override 229 | Widget build(context) => const Center(child: Text('Hello world')); 230 | } 231 | ''' 232 | }, kMainDart, devMode: false); 233 | expect(results.issues, hasLength(1)); 234 | final issue = results.issues[0]; 235 | expect(issue.line, 3); 236 | expect(issue.kind, 'error'); 237 | expect( 238 | issue.message, 239 | "A value of type 'int' can't be assigned to a variable of type " 240 | "'String'."); 241 | }); 242 | 243 | // https://github.com/dart-lang/dart-pad/issues/2005 244 | test('reports lint with Flutter code files={}', () async { 245 | late AnalysisResults results; 246 | results = await analysisServersWrapper.analyzeFiles({ 247 | kMainDart: ''' 248 | import 'package:flutter/material.dart'; 249 | 250 | void main() async { 251 | var unknown; 252 | print(unknown); 253 | 254 | runApp(MaterialApp( 255 | debugShowCheckedModeBanner: false, home: Scaffold(body: HelloWorld()))); 256 | } 257 | 258 | class HelloWorld extends StatelessWidget { 259 | @override 260 | Widget build(context) => const Center(child: Text('Hello world')); 261 | } 262 | ''' 263 | }, kMainDart, devMode: false); 264 | expect(results.issues, hasLength(1)); 265 | final issue = results.issues[0]; 266 | expect(issue.line, 4); 267 | expect(issue.kind, 'info'); 268 | expect(issue.message, 269 | 'An uninitialized variable should have an explicit type annotation.'); 270 | }); 271 | 272 | test('analyzeFiles counter app files={}', () async { 273 | final results = await analysisServersWrapper.analyzeFiles( 274 | {kMainDart: sampleCodeFlutterCounter}, kMainDart, 275 | devMode: false); 276 | expect(results.issues, isEmpty); 277 | }); 278 | 279 | test('analyzeFiles Draggable Physics sample files={}', () async { 280 | final results = await analysisServersWrapper.analyzeFiles( 281 | {kMainDart: sampleCodeFlutterDraggableCard}, kMainDart, 282 | devMode: false); 283 | expect(results.issues, isEmpty); 284 | }); 285 | 286 | test('analyzeFiles counter app files={}', () async { 287 | final results = await analysisServersWrapper.analyzeFiles( 288 | {kMainDart: sampleCodeFlutterCounter}, kMainDart, 289 | devMode: false); 290 | expect(results.issues, isEmpty); 291 | }); 292 | 293 | test('analyzeFiles Draggable Physics sample files={}', () async { 294 | final results = await analysisServersWrapper.analyzeFiles( 295 | {kMainDart: sampleCodeFlutterDraggableCard}, kMainDart, 296 | devMode: false); 297 | expect(results.issues, isEmpty); 298 | }); 299 | }); 300 | 301 | group('CommonServerImpl flutter analyzeFiles files={}', () { 302 | late CommonServerImpl commonServerImpl; 303 | 304 | _MockCache cache; 305 | 306 | setUp(() async { 307 | cache = _MockCache(); 308 | final sdk = Sdk.create(channel); 309 | commonServerImpl = CommonServerImpl(cache, sdk); 310 | await commonServerImpl.init(); 311 | }); 312 | 313 | tearDown(() async { 314 | await commonServerImpl.shutdown(); 315 | }); 316 | 317 | test('counter app files={}', () async { 318 | final results = await commonServerImpl.analyzeFiles(SourceFilesRequest() 319 | ..files.addAll({kMainDart: sampleCodeFlutterCounter}) 320 | ..activeSourceName = kMainDart 321 | ..offset = 0); 322 | expect(results.issues, isEmpty); 323 | }); 324 | 325 | test('Draggable Physics sample files={}', () async { 326 | final results = await commonServerImpl.analyzeFiles(SourceFilesRequest() 327 | ..files.addAll({kMainDart: sampleCodeFlutterDraggableCard}) 328 | ..activeSourceName = kMainDart); 329 | expect(results.issues, isEmpty); 330 | }); 331 | }); 332 | }); 333 | } 334 | 335 | class _MockCache implements ServerCache { 336 | @override 337 | Future get(String key) => Future.value(null); 338 | 339 | @override 340 | Future set(String key, String value, {Duration? expiration}) => 341 | Future.value(); 342 | 343 | @override 344 | Future remove(String key) => Future.value(); 345 | 346 | @override 347 | Future shutdown() => Future.value(); 348 | } 349 | -------------------------------------------------------------------------------- /test/flutter_web_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, 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 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:analyzer/dart/ast/ast.dart'; 9 | import 'package:dart_services/src/project.dart'; 10 | import 'package:path/path.dart' as path; 11 | import 'package:test/test.dart'; 12 | 13 | void main() => defineTests(); 14 | 15 | void defineTests() { 16 | final projectTemplates = ProjectTemplates.projectTemplates; 17 | 18 | group('FlutterWebManager', () { 19 | final dartHtmlImport = _FakeImportDirective('dart:html'); 20 | final dartUiImport = _FakeImportDirective('dart:ui'); 21 | final packageFlutterImport = _FakeImportDirective('package:flutter/'); 22 | 23 | test('inited', () async { 24 | expect(await Directory(projectTemplates.flutterPath).exists(), isTrue); 25 | final file = File(path.join( 26 | projectTemplates.flutterPath, '.dart_tool', 'package_config.json')); 27 | expect(await file.exists(), isTrue); 28 | }); 29 | 30 | test('usesFlutterWeb', () { 31 | expect( 32 | usesFlutterWeb({_FakeImportDirective('')}, devMode: false), isFalse); 33 | expect(usesFlutterWeb({dartHtmlImport}, devMode: false), isFalse); 34 | expect(usesFlutterWeb({dartUiImport}, devMode: false), isTrue); 35 | expect(usesFlutterWeb({packageFlutterImport}, devMode: false), isTrue); 36 | }); 37 | 38 | test('getUnsupportedImport allows current library import', () { 39 | expect(getUnsupportedImports([_FakeImportDirective('')], devMode: false), 40 | isEmpty); 41 | }); 42 | 43 | test('getUnsupportedImport allows dart:html', () { 44 | expect( 45 | getUnsupportedImports([_FakeImportDirective('dart:html')], 46 | devMode: false), 47 | isEmpty); 48 | }); 49 | 50 | test('getUnsupportedImport allows dart:ui', () { 51 | expect(getUnsupportedImports([dartUiImport], devMode: false), isEmpty); 52 | }); 53 | 54 | test('getUnsupportedImport allows package:flutter', () { 55 | expect(getUnsupportedImports([packageFlutterImport], devMode: false), 56 | isEmpty); 57 | }); 58 | 59 | test('getUnsupportedImport allows package:path', () { 60 | final packagePathImport = _FakeImportDirective('package:path'); 61 | expect( 62 | getUnsupportedImports([packagePathImport], devMode: false), isEmpty); 63 | }); 64 | 65 | test('getUnsupportedImport does now allow package:unsupported', () { 66 | final usupportedPackageImport = 67 | _FakeImportDirective('package:unsupported'); 68 | expect(getUnsupportedImports([usupportedPackageImport], devMode: false), 69 | contains(usupportedPackageImport)); 70 | }); 71 | 72 | test('getUnsupportedImport does now allow local imports', () { 73 | final localFooImport = _FakeImportDirective('foo.dart'); 74 | expect(getUnsupportedImports([localFooImport], devMode: false), 75 | contains(localFooImport)); 76 | }); 77 | 78 | test('getUnsupportedImport does not allow VM-only imports', () { 79 | final dartIoImport = _FakeImportDirective('dart:io'); 80 | expect(getUnsupportedImports([dartIoImport], devMode: false), 81 | contains(dartIoImport)); 82 | }); 83 | }); 84 | 85 | group('project inited', () { 86 | test('packagesFilePath', () async { 87 | final packageConfig = File(path.join( 88 | projectTemplates.flutterPath, '.dart_tool', 'package_config.json')); 89 | expect(await packageConfig.exists(), true); 90 | final encoded = await packageConfig.readAsString(); 91 | final contents = jsonDecode(encoded) as Map; 92 | expect(contents['packages'], isNotEmpty); 93 | final packages = contents['packages'] as List; 94 | expect(packages.where((element) => (element as Map)['name'] == 'flutter'), 95 | isNotEmpty); 96 | }); 97 | 98 | test('summaryFilePath', () { 99 | final summaryFilePath = projectTemplates.summaryFilePath; 100 | expect(summaryFilePath, isNotEmpty); 101 | 102 | final file = File(summaryFilePath); 103 | expect(file.existsSync(), isTrue); 104 | }); 105 | }); 106 | } 107 | 108 | class _FakeImportDirective implements ImportDirective { 109 | @override 110 | final _FakeStringLiteral uri; 111 | 112 | _FakeImportDirective(String uri) : uri = _FakeStringLiteral(uri); 113 | 114 | @override 115 | dynamic noSuchMethod(_) => throw UnimplementedError(); 116 | } 117 | 118 | class _FakeStringLiteral implements StringLiteral { 119 | @override 120 | final String stringValue; 121 | 122 | _FakeStringLiteral(this.stringValue); 123 | 124 | @override 125 | dynamic noSuchMethod(_) => throw UnimplementedError(); 126 | } 127 | -------------------------------------------------------------------------------- /test/gae_deployed_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | import 'package:dart_services/src/common.dart' as common; 6 | import 'package:http/http.dart' as http; 7 | import 'package:test/test.dart'; 8 | 9 | final String serverUrl = 'https://liftoff-dev.appspot.com'; 10 | 11 | void main() => defineTests(); 12 | 13 | void defineTests({bool skip = true}) { 14 | group('gae deployed tests', () { 15 | test('analyze end point', analyzeTest, skip: skip); 16 | test('compile end point', compileTest, skip: skip); 17 | test('compileDDC end point', compileDDCTest, skip: skip); 18 | }); 19 | } 20 | 21 | void analyzeTest() { 22 | final url = Uri.parse('$serverUrl/api/analyze'); 23 | final headers = {'Content-Type': 'text/plain; charset=UTF-8'}; 24 | 25 | expect( 26 | http 27 | .post(url, headers: headers, body: common.sampleCodeWeb) 28 | .then((response) { 29 | expect(response.statusCode, 200); 30 | expect(response.body.trim(), '[]'); 31 | return true; 32 | }), 33 | completion(equals(true))); 34 | } 35 | 36 | void compileTest() { 37 | final url = Uri.parse('$serverUrl/api/compile'); 38 | final headers = {'Content-Type': 'text/plain; charset=UTF-8'}; 39 | 40 | expect( 41 | http 42 | .post(url, headers: headers, body: common.sampleCodeWeb) 43 | .then((response) { 44 | expect(response.statusCode, 200); 45 | expect(true, response.body.length > 100); 46 | return true; 47 | }), 48 | completion(equals(true))); 49 | } 50 | 51 | void compileDDCTest() { 52 | final url = Uri.parse('$serverUrl/api/compileDDC'); 53 | final headers = {'Content-Type': 'text/plain; charset=UTF-8'}; 54 | 55 | expect( 56 | http 57 | .post(url, headers: headers, body: common.sampleCodeWeb) 58 | .then((response) { 59 | expect(response.statusCode, 200); 60 | expect(true, response.body.length > 100); 61 | return true; 62 | }), 63 | completion(equals(true))); 64 | } 65 | -------------------------------------------------------------------------------- /test/integration.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | /// Tests which run as part of integration testing; these test high level 6 | /// services. 7 | library; 8 | 9 | import 'gae_deployed_test.dart' as gae_test_test; 10 | 11 | void main() { 12 | gae_test_test.defineTests(skip: false); 13 | } 14 | -------------------------------------------------------------------------------- /test/project_creator_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, 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 | import 'dart:io'; 6 | 7 | import 'package:dart_services/src/project_creator.dart'; 8 | import 'package:dart_services/src/sdk.dart'; 9 | import 'package:test/test.dart'; 10 | import 'package:test_descriptor/test_descriptor.dart' as d; 11 | 12 | final channel = Platform.environment['FLUTTER_CHANNEL'] ?? stableChannel; 13 | final languageVersion = readDartLanguageVersion(channel); 14 | 15 | void main() => defineTests(); 16 | 17 | void defineTests() { 18 | Future projectCreator() async { 19 | final dependenciesFile = d.file('dependencies.json', ''' 20 | { 21 | "meta": "^1.7.0" 22 | } 23 | '''); 24 | await dependenciesFile.create(); 25 | final templatesPath = d.dir('project_templates'); 26 | await templatesPath.create(); 27 | final sdk = Sdk.create(channel); 28 | return ProjectCreator( 29 | sdk, 30 | templatesPath.io.path, 31 | dartLanguageVersion: readDartLanguageVersion(channel), 32 | dependenciesFile: dependenciesFile.io, 33 | log: (_) {}, 34 | ); 35 | } 36 | 37 | group('basic dart project template', () { 38 | setUpAll(() async { 39 | await (await projectCreator()).buildDartProjectTemplate(); 40 | }); 41 | 42 | test('project directory is created', () async { 43 | await d.dir('project_templates', [ 44 | d.dir('dart_project'), 45 | ]).validate(); 46 | }); 47 | 48 | test('pubspec is created', () async { 49 | await d.dir('project_templates', [ 50 | d.dir('dart_project', [ 51 | d.file( 52 | 'pubspec.yaml', 53 | allOf([ 54 | contains('sdk: ^$languageVersion'), 55 | ]), 56 | ), 57 | ]), 58 | ]).validate(); 59 | }); 60 | 61 | test('pub get creates pubspec.lock', () async { 62 | await d.dir('project_templates', [ 63 | d.dir('dart_project', [d.file('pubspec.lock', isNotEmpty)]), 64 | ]).validate(); 65 | }); 66 | 67 | test('recommended lints are enabled', () async { 68 | await d.dir('project_templates', [ 69 | d.dir('dart_project', [ 70 | d.file( 71 | 'analysis_options.yaml', 72 | matches('include: package:lints/recommended.yaml'), 73 | ), 74 | ]), 75 | ]).validate(); 76 | }); 77 | }); 78 | 79 | group('basic Flutter project template', () { 80 | setUpAll(() async { 81 | await (await projectCreator()) 82 | .buildFlutterProjectTemplate(firebaseStyle: FirebaseStyle.none); 83 | }); 84 | 85 | test('project directory is created', () async { 86 | await d.dir('project_templates', [ 87 | d.dir('flutter_project'), 88 | ]).validate(); 89 | }); 90 | 91 | test('Flutter Web directories are created', () async { 92 | await d.dir('project_templates', [ 93 | d.dir('flutter_project', [ 94 | d.dir('lib'), 95 | d.dir('web', [d.file('index.html', isEmpty)]), 96 | ]) 97 | ]).validate(); 98 | }); 99 | 100 | test('pubspec is created', () async { 101 | await d.dir('project_templates', [ 102 | d.dir('flutter_project', [ 103 | d.file( 104 | 'pubspec.yaml', 105 | allOf([ 106 | contains('sdk: ^$languageVersion'), 107 | matches('sdk: flutter'), 108 | ]), 109 | ), 110 | ]), 111 | ]).validate(); 112 | }); 113 | 114 | test('pub get creates pubspec.lock', () async { 115 | await d.dir('project_templates', [ 116 | d.dir('flutter_project', [d.file('pubspec.lock', isNotEmpty)]), 117 | ]).validate(); 118 | }); 119 | 120 | test('flutter lints are enabled', () async { 121 | await d.dir('project_templates', [ 122 | d.dir('flutter_project', [ 123 | d.file( 124 | 'analysis_options.yaml', 125 | matches('include: package:flutter_lints/flutter.yaml'), 126 | ), 127 | ]), 128 | ]).validate(); 129 | }); 130 | 131 | test('plugins are registered', () async { 132 | await d.dir('project_templates', [ 133 | d.dir('flutter_project/lib', [ 134 | d.file( 135 | 'generated_plugin_registrant.dart', 136 | matches('FirebaseCoreWeb.registerWith'), 137 | ), 138 | ]), 139 | ]).validate(); 140 | }); 141 | }); 142 | 143 | group('Firebase project template', () { 144 | setUpAll(() async { 145 | await (await projectCreator()).buildFlutterProjectTemplate( 146 | firebaseStyle: FirebaseStyle.flutterFire); 147 | }); 148 | 149 | test('project directory is created', () async { 150 | await d.dir('project_templates', [ 151 | d.dir('firebase_project'), 152 | ]).validate(); 153 | }); 154 | 155 | test('Flutter Web directories are created', () async { 156 | await d.dir('project_templates', [ 157 | d.dir('firebase_project', [ 158 | d.dir('lib'), 159 | d.dir('web', [d.file('index.html', isEmpty)]), 160 | ]) 161 | ]).validate(); 162 | }); 163 | 164 | test('pubspec is created', () async { 165 | await d.dir('project_templates', [ 166 | d.dir('firebase_project', [ 167 | d.file( 168 | 'pubspec.yaml', 169 | allOf([ 170 | contains('sdk: ^$languageVersion'), 171 | matches('sdk: flutter'), 172 | ]), 173 | ), 174 | ]), 175 | ]).validate(); 176 | }); 177 | 178 | test('pub get creates pubspec.lock', () async { 179 | await d.dir('project_templates', [ 180 | d.dir('firebase_project', [d.file('pubspec.lock', isNotEmpty)]), 181 | ]).validate(); 182 | }); 183 | 184 | test('flutter lints are enabled', () async { 185 | await d.dir('project_templates', [ 186 | d.dir('firebase_project', [ 187 | d.file( 188 | 'analysis_options.yaml', 189 | matches('include: package:flutter_lints/flutter.yaml'), 190 | ), 191 | ]), 192 | ]).validate(); 193 | }); 194 | 195 | test('generated_plugin_registrant.dart is created', () async { 196 | await d.dir('project_templates', [ 197 | d.dir('firebase_project', [ 198 | d.dir('lib', [ 199 | d.file( 200 | 'generated_plugin_registrant.dart', 201 | isNotEmpty, 202 | ), 203 | ]), 204 | ]), 205 | ]).validate(); 206 | }); 207 | 208 | test('plugins are registered', () async { 209 | await d.dir('project_templates', [ 210 | d.dir('firebase_project', [ 211 | d.dir('lib', [ 212 | d.file( 213 | 'generated_plugin_registrant.dart', 214 | allOf([ 215 | matches('FirebaseFirestoreWeb.registerWith'), 216 | matches('FirebaseAnalyticsWeb.registerWith'), 217 | matches('FirebaseCoreWeb.registerWith'), 218 | matches('FirebaseDatabaseWeb.registerWith'), 219 | matches('FirebaseMessagingWeb.registerWith'), 220 | matches('FirebaseStorageWeb.registerWith'), 221 | ]), 222 | ), 223 | ]), 224 | ]), 225 | ]).validate(); 226 | }); 227 | }); 228 | } 229 | -------------------------------------------------------------------------------- /test/pub_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | import 'package:dart_services/src/pub.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() => defineTests(); 9 | 10 | void defineTests() { 11 | group('getAllImportsFor', () { 12 | test('null', () { 13 | expect(getAllImportsFor(null), isEmpty); 14 | }); 15 | 16 | test('empty', () { 17 | expect(getAllImportsFor(''), isEmpty); 18 | expect(getAllImportsFor(' \n '), isEmpty); 19 | }); 20 | 21 | test('bad source', () { 22 | final imports = getAllImportsFor('foo bar;\n baz\nimport mybad;\n'); 23 | expect(imports, hasLength(1)); 24 | expect(imports.single.uri.stringValue, equals('')); 25 | }); 26 | 27 | test('one', () { 28 | const source = ''' 29 | library woot; 30 | import 'dart:math'; 31 | import 'package:foo/foo.dart'; 32 | void main() { } 33 | '''; 34 | expect(getAllImportsFor(source).map((import) => import.uri.stringValue), 35 | unorderedEquals(['dart:math', 'package:foo/foo.dart'])); 36 | }); 37 | 38 | test('two', () { 39 | const source = ''' 40 | library woot; 41 | import 'dart:math'; 42 | import 'package:foo/foo.dart'; 43 | import 'package:bar/bar.dart'; 44 | void main() { } 45 | '''; 46 | expect( 47 | getAllImportsFor(source).map((import) => import.uri.stringValue), 48 | unorderedEquals( 49 | ['dart:math', 'package:foo/foo.dart', 'package:bar/bar.dart'])); 50 | }); 51 | 52 | test('three', () { 53 | const source = ''' 54 | library woot; 55 | import 'dart:math'; 56 | import 'package:foo/foo.dart'; 57 | import 'package:bar/bar.dart';import 'package:baz/baz.dart'; 58 | import 'mybazfile.dart'; 59 | void main() { } 60 | '''; 61 | expect( 62 | getAllImportsFor(source).map((import) => import.uri.stringValue), 63 | unorderedEquals([ 64 | 'dart:math', 65 | 'package:foo/foo.dart', 66 | 'package:bar/bar.dart', 67 | 'package:baz/baz.dart', 68 | 'mybazfile.dart' 69 | ])); 70 | }); 71 | }); 72 | 73 | group('filterSafePackagesFromImports', () { 74 | test('empty', () { 75 | const source = '''import 'package:'; 76 | void main() { } 77 | '''; 78 | expect(getAllImportsFor(source).filterSafePackages(), isEmpty); 79 | }); 80 | 81 | test('simple', () { 82 | const source = ''' 83 | import 'package:foo/foo.dart'; 84 | import 'package:bar/bar.dart'; 85 | void main() { } 86 | '''; 87 | expect(getAllImportsFor(source).filterSafePackages(), 88 | unorderedEquals(['foo', 'bar'])); 89 | }); 90 | 91 | test('defensive', () { 92 | const source = ''' 93 | library woot; 94 | import 'dart:math'; 95 | import 'package:../foo/foo.dart'; 96 | void main() { } 97 | '''; 98 | final imports = getAllImportsFor(source); 99 | expect(imports, hasLength(2)); 100 | expect(imports[0].uri.stringValue, equals('dart:math')); 101 | expect(imports[1].uri.stringValue, equals('package:../foo/foo.dart')); 102 | expect(imports.filterSafePackages(), isEmpty); 103 | }); 104 | 105 | test('negative dart import', () { 106 | const source = ''' 107 | import 'dart:../bar.dart'; 108 | '''; 109 | final imports = getAllImportsFor(source); 110 | expect(imports, hasLength(1)); 111 | expect(imports.single.uri.stringValue, equals('dart:../bar.dart')); 112 | expect(imports.filterSafePackages(), isEmpty); 113 | }); 114 | 115 | test('negative path import', () { 116 | const source = ''' 117 | import '../foo.dart'; 118 | '''; 119 | final imports = getAllImportsFor(source); 120 | expect(imports, hasLength(1)); 121 | expect(imports.single.uri.stringValue, equals('../foo.dart')); 122 | expect(imports.filterSafePackages(), isEmpty); 123 | }); 124 | }); 125 | } 126 | -------------------------------------------------------------------------------- /test/redis_cache_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, 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 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:dart_services/src/common_server_impl.dart'; 9 | import 'package:dart_services/src/sdk.dart'; 10 | import 'package:dart_services/src/server_cache.dart'; 11 | import 'package:logging/logging.dart'; 12 | import 'package:synchronized/synchronized.dart'; 13 | import 'package:test/test.dart'; 14 | 15 | void main() => defineTests(); 16 | 17 | void defineTests() { 18 | /// Integration tests for the RedisCache implementation. 19 | /// 20 | /// We basically assume that redis and dartis work correctly -- this is 21 | /// exercising the connection maintenance and exception handling. 22 | group('RedisCache', () { 23 | // Note: all caches share values between them. 24 | late RedisCache redisCache, redisCacheAlt; 25 | Process? redisProcess, redisAltProcess; 26 | 27 | late Sdk sdk; 28 | 29 | var logMessages = []; 30 | // Critical section handling -- do not run more than one test at a time 31 | // since they talk to the same redis instances. 32 | final singleTestOnly = Lock(); 33 | 34 | // Prevent cases where we might try to reenter addStream for either stdout 35 | // or stderr (which will throw a BadState). 36 | final singleStreamOnly = Lock(); 37 | 38 | Future startRedisProcessAndDrainIO(int port) async { 39 | final newRedisProcess = 40 | await Process.start('redis-server', ['--port', port.toString()]); 41 | unawaited(singleStreamOnly.synchronized(() async { 42 | await stdout.addStream(newRedisProcess.stdout); 43 | })); 44 | unawaited(singleStreamOnly.synchronized(() async { 45 | await stderr.addStream(newRedisProcess.stderr); 46 | })); 47 | return newRedisProcess; 48 | } 49 | 50 | setUpAll(() async { 51 | redisProcess = await startRedisProcessAndDrainIO(9501); 52 | final channel = Platform.environment['FLUTTER_CHANNEL'] ?? stableChannel; 53 | sdk = Sdk.create(channel); 54 | log.onRecord.listen((LogRecord rec) { 55 | logMessages.add('${rec.level.name}: ${rec.time}: ${rec.message}'); 56 | print(logMessages.last); 57 | }); 58 | redisCache = RedisCache('redis://localhost:9501', sdk, 'aversion'); 59 | redisCacheAlt = RedisCache('redis://localhost:9501', sdk, 'bversion'); 60 | await Future.wait([redisCache.connected, redisCacheAlt.connected]); 61 | }); 62 | 63 | tearDown(() async { 64 | if (redisAltProcess != null) { 65 | redisAltProcess!.kill(); 66 | await redisAltProcess!.exitCode; 67 | redisAltProcess = null; 68 | } 69 | }); 70 | 71 | tearDownAll(() async { 72 | log.clearListeners(); 73 | await Future.wait([redisCache.shutdown(), redisCacheAlt.shutdown()]); 74 | redisProcess!.kill(); 75 | await redisProcess!.exitCode; 76 | }); 77 | 78 | test('Verify basic operation of RedisCache', () async { 79 | await singleTestOnly.synchronized(() async { 80 | logMessages = []; 81 | await expectLater(await redisCache.get('unknownkey'), isNull); 82 | await redisCache.set('unknownkey', 'value'); 83 | await expectLater(await redisCache.get('unknownkey'), equals('value')); 84 | await redisCache.remove('unknownkey'); 85 | await expectLater(await redisCache.get('unknownkey'), isNull); 86 | expect(logMessages, isEmpty); 87 | }); 88 | }); 89 | 90 | test('Verify values expire', () async { 91 | await singleTestOnly.synchronized(() async { 92 | logMessages = []; 93 | await redisCache.set('expiringkey', 'expiringValue', 94 | expiration: Duration(milliseconds: 1)); 95 | await Future.delayed(Duration(milliseconds: 100)); 96 | await expectLater(await redisCache.get('expiringkey'), isNull); 97 | expect(logMessages, isEmpty); 98 | }); 99 | }); 100 | 101 | test( 102 | 'Verify two caches with different versions give different results for keys', 103 | () async { 104 | await singleTestOnly.synchronized(() async { 105 | logMessages = []; 106 | await redisCache.set('differentVersionKey', 'value1'); 107 | await redisCacheAlt.set('differentVersionKey', 'value2'); 108 | await expectLater( 109 | await redisCache.get('differentVersionKey'), 'value1'); 110 | await expectLater( 111 | await redisCacheAlt.get('differentVersionKey'), 'value2'); 112 | expect(logMessages, isEmpty); 113 | }); 114 | }); 115 | 116 | test('Verify disconnected cache logs errors and returns nulls', () async { 117 | await singleTestOnly.synchronized(() async { 118 | logMessages = []; 119 | final redisCacheBroken = 120 | RedisCache('redis://localhost:9502', sdk, 'cversion'); 121 | try { 122 | await redisCacheBroken.set('aKey', 'value'); 123 | await expectLater(await redisCacheBroken.get('aKey'), isNull); 124 | await redisCacheBroken.remove('aKey'); 125 | expect( 126 | logMessages.join('\n'), 127 | stringContainsInOrder([ 128 | 'no cache available when setting key server:rc:cversion:dart:', 129 | '+aKey', 130 | 'no cache available when getting key server:rc:cversion:dart:', 131 | '+aKey', 132 | 'no cache available when removing key server:rc:cversion:dart:', 133 | '+aKey', 134 | ])); 135 | } finally { 136 | await redisCacheBroken.shutdown(); 137 | } 138 | }); 139 | }); 140 | 141 | test('Verify cache that starts out disconnected retries and works (slow)', 142 | () async { 143 | await singleTestOnly.synchronized(() async { 144 | logMessages = []; 145 | final redisCacheRepairable = 146 | RedisCache('redis://localhost:9503', sdk, 'cversion'); 147 | try { 148 | // Wait for a retry message. 149 | while (logMessages.length < 2) { 150 | await Future.delayed(Duration(milliseconds: 50)); 151 | } 152 | expect( 153 | logMessages.join('\n'), 154 | stringContainsInOrder([ 155 | 'reconnecting to redis://localhost:9503...\n', 156 | 'Unable to connect to redis server, reconnecting in', 157 | ])); 158 | 159 | // Start a redis server. 160 | redisAltProcess = await startRedisProcessAndDrainIO(9503); 161 | 162 | // Wait for connection. 163 | await redisCacheRepairable.connected; 164 | expect(logMessages.join('\n'), contains('Connected to redis server')); 165 | } finally { 166 | await redisCacheRepairable.shutdown(); 167 | } 168 | }); 169 | }); 170 | 171 | test( 172 | 'Verify that cache that stops responding temporarily times out and can recover', 173 | () async { 174 | await singleTestOnly.synchronized(() async { 175 | logMessages = []; 176 | await redisCache.set('beforeStop', 'truth'); 177 | redisProcess!.kill(ProcessSignal.sigstop); 178 | // Don't fail the test before sending sigcont. 179 | final beforeStop = await redisCache.get('beforeStop'); 180 | await redisCache.disconnected; 181 | redisProcess!.kill(ProcessSignal.sigcont); 182 | expect(beforeStop, isNull); 183 | await redisCache.connected; 184 | await expectLater(await redisCache.get('beforeStop'), equals('truth')); 185 | expect( 186 | logMessages.join('\n'), 187 | stringContainsInOrder([ 188 | 'timeout on get operation for key server:rc:aversion:dart:', 189 | '+beforeStop', 190 | '(aversion): reconnecting', 191 | '(aversion): Connected to redis server', 192 | ])); 193 | }); 194 | }, onPlatform: {'windows': Skip('Windows does not have sigstop/sigcont')}); 195 | 196 | test( 197 | 'Verify cache that starts out connected but breaks retries until reconnection (slow)', 198 | () async { 199 | await singleTestOnly.synchronized(() async { 200 | logMessages = []; 201 | 202 | redisAltProcess = await startRedisProcessAndDrainIO(9504); 203 | final redisCacheHealing = 204 | RedisCache('redis://localhost:9504', sdk, 'cversion'); 205 | try { 206 | await redisCacheHealing.connected; 207 | await redisCacheHealing.set('missingKey', 'value'); 208 | // Kill process out from under the cache. 209 | redisAltProcess!.kill(); 210 | await redisAltProcess!.exitCode; 211 | redisAltProcess = null; 212 | 213 | // Try to talk to the cache and get an error. Wait for the disconnect 214 | // to be recognized. 215 | await expectLater(await redisCacheHealing.get('missingKey'), isNull); 216 | await redisCacheHealing.disconnected; 217 | 218 | // Start the server and verify we connect appropriately. 219 | redisAltProcess = await startRedisProcessAndDrainIO(9504); 220 | await redisCacheHealing.connected; 221 | expect( 222 | logMessages.join('\n'), 223 | stringContainsInOrder([ 224 | 'Connected to redis server', 225 | 'connection terminated with error SocketException', 226 | 'reconnecting to redis://localhost:9504', 227 | ])); 228 | expect(logMessages.last, contains('Connected to redis server')); 229 | } finally { 230 | await redisCacheHealing.shutdown(); 231 | } 232 | }); 233 | }); 234 | }); 235 | } 236 | -------------------------------------------------------------------------------- /test/shelf_cors_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, 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 | import 'package:dart_services/src/shelf_cors.dart' as shelf_cors; 6 | import 'package:shelf/shelf.dart' as shelf; 7 | import 'package:test/test.dart'; 8 | 9 | void main() => defineTests(); 10 | 11 | void defineTests() { 12 | shelf.Response handleAll(shelf.Request request) { 13 | return shelf.Response.ok('OK'); 14 | } 15 | 16 | final request = 17 | shelf.Request('GET', Uri.parse('http://example.com/index.html')); 18 | 19 | group('The corsHeaders middleware', () { 20 | test('adds default CORS headers to the response', () async { 21 | final middleware = shelf_cors.createCorsHeadersMiddleware(); 22 | final handler = middleware(handleAll); 23 | final response = await handler(request); 24 | 25 | expect(response.headers['Access-Control-Allow-Origin'], equals('*')); 26 | }); 27 | 28 | test('adds custom CORS headers to the response', () async { 29 | final corsHeaders = { 30 | 'Access-Control-Allow-Origin': '*', 31 | 'Access-Control-Allow-Methods': 'POST, OPTIONS', 32 | 'Access-Control-Allow-Headers': 33 | 'Origin, X-Requested-With, Content-Type, Accept' 34 | }; 35 | final middleware = 36 | shelf_cors.createCorsHeadersMiddleware(corsHeaders: corsHeaders); 37 | final handler = middleware(handleAll); 38 | final response = await handler(request); 39 | 40 | expect(response.headers['Access-Control-Allow-Origin'], equals('*')); 41 | expect(response.headers['Access-Control-Allow-Methods'], 42 | equals('POST, OPTIONS')); 43 | expect(response.headers['Access-Control-Allow-Headers'], 44 | equals('Origin, X-Requested-With, Content-Type, Accept')); 45 | }); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /test/utils_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, 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 | import 'package:dart_services/src/utils.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | void main() { 9 | void expectNormalizeFilePaths(String input, String output) { 10 | expect(normalizeFilePaths(input), equals(output)); 11 | } 12 | 13 | test('normalizeFilePaths strips a temporary directory path', () { 14 | expectNormalizeFilePaths( 15 | 'List is defined in /var/folders/4p/y54w9nqj0_n6ryqwn7lxqz6800m6cw/T/DartAnalysisWrapperintLAw/main.dart', 16 | 'List is defined in main.dart', 17 | ); 18 | }); 19 | 20 | test('normalizeFilePaths replaces a SDK path with "dart:core"', () { 21 | expectNormalizeFilePaths( 22 | 'List is defined in /path/dart/dart/sdk/lib/core/list.dart', 23 | 'List is defined in dart:core/list.dart', 24 | ); 25 | }); 26 | 27 | test('normalizeFilePaths replaces a specific SDK path with "dart:core"', () { 28 | expectNormalizeFilePaths( 29 | "The argument type 'List (where List is defined in /Users/username/sdk/dart/2.10.5/lib/core/list.dart)' can't be assigned to the parameter type 'List (where List is defined in /var/folders/4p/tmp/T/DartAnalysisWrapperintLAw/main.dart)'.", 30 | "The argument type 'List (where List is defined in dart:core/list.dart)' can't be assigned to the parameter type 'List (where List is defined in main.dart)'.", 31 | ); 32 | }); 33 | 34 | test('normalizeFilePaths keeps a "package:" path intact', () { 35 | expectNormalizeFilePaths( 36 | "Unused import: 'package:flutter/material.dart'.", 37 | "Unused import: 'package:flutter/material.dart'.", 38 | ); 39 | }); 40 | 41 | test('normalizeFilePaths keeps a "dart:core" path intact', () { 42 | expectNormalizeFilePaths( 43 | 'dart:core/foo.dart', 44 | 'dart:core/foo.dart', 45 | ); 46 | }); 47 | 48 | test('normalizeFilePaths keeps a web URL intact', () { 49 | expectNormalizeFilePaths( 50 | 'See http://dart.dev/go/non-promo-property', 51 | 'See http://dart.dev/go/non-promo-property', 52 | ); 53 | }); 54 | 55 | test('normalizeFilePaths strips a Flutter SDK path', () { 56 | expectNormalizeFilePaths( 57 | "The argument type 'StatelessWidget (where StatelessWidget is defined in /Users/username/path/to/dart-services/project_templates/flutter_project/main.dart)' can't be assigned to the parameter type 'StatelessWidget (where StatelessWidget is defined in /Users/username/path/to/dart-services/flutter-sdk/packages/flutter/lib/src/widgets/framework.dart)'.", 58 | "The argument type 'StatelessWidget (where StatelessWidget is defined in main.dart)' can't be assigned to the parameter type 'StatelessWidget (where StatelessWidget is defined in package:flutter/framework.dart)'.", 59 | ); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /tool/dart_cloud_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 4 | # for details. All rights reserved. Use of this source code is governed by a 5 | # BSD-style license that can be found in the LICENSE file. 6 | DBG_OPTION= 7 | # Only enable Dart debugger if DBG_ENABLE is set. 8 | if [ -n "$DBG_ENABLE" ]; then 9 | echo "Enabling Dart debugger" 10 | DBG_OPTION="--debug:${DBG_PORT:-5858}/0.0.0.0" 11 | echo "Starting Dart with additional options $DBG_OPTION" 12 | fi 13 | if [ -n "$DART_VM_OPTIONS" ]; then 14 | echo "Starting Dart with additional options $DART_VM_OPTIONS" 15 | fi 16 | exec dart \ 17 | ${DBG_OPTION} \ 18 | --enable-vm-service:8181/0.0.0.0 \ 19 | ${DART_VM_OPTIONS} \ 20 | bin/server_cloud_run.dart \ 21 | $@ 22 | -------------------------------------------------------------------------------- /tool/dart_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 4 | # for details. All rights reserved. Use of this source code is governed by a 5 | # BSD-style license that can be found in the LICENSE file. 6 | DBG_OPTION= 7 | # Only enable Dart debugger if DBG_ENABLE is set. 8 | if [ -n "$DBG_ENABLE" ] && [ "$GAE_PARTITION" = "dev" ]; then 9 | echo "Enabling Dart debugger" 10 | DBG_OPTION="--debug:${DBG_PORT:-5858}/0.0.0.0" 11 | echo "Starting Dart with additional options $DBG_OPTION" 12 | fi 13 | if [ -n "$DART_VM_OPTIONS" ]; then 14 | echo "Starting Dart with additional options $DART_VM_OPTIONS" 15 | fi 16 | exec /usr/bin/dart \ 17 | ${DBG_OPTION} \ 18 | --enable-vm-service:8181/0.0.0.0 \ 19 | ${DART_VM_OPTIONS} \ 20 | bin/server.dart \ 21 | $@ 22 | -------------------------------------------------------------------------------- /tool/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | $(boot2docker shellinit) 3 | gcloud --verbosity=debug preview app deploy app.yaml 4 | VERSION=$(grep ^version app.yaml | sed 's/version: //') 5 | dart tool/warmup.dart $VERSION 6 | -------------------------------------------------------------------------------- /tool/load_driver.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | import 'dart:async'; 6 | 7 | import 'package:http/http.dart' as http; 8 | 9 | const postPayload = 10 | r'''{"source": "import 'dart:html'; void main() {var count = querySelector('#count');for (int i = 0; i < 4; i++) {count.text = '${i}';print('hello ${i}');}}'''; 11 | const epilogue = '"}'; 12 | 13 | const uri = 'https://dart-services.appspot.com/api/dartservices/v1/compile'; 14 | 15 | int count = 0; 16 | 17 | void main(List args) { 18 | num qps; 19 | 20 | if (args.length == 1) { 21 | qps = num.parse(args[0]); 22 | } else { 23 | qps = 1; 24 | } 25 | 26 | print('QPS: $qps, URI: $uri'); 27 | 28 | final ms = (1000 / qps).floor(); 29 | Timer.periodic(Duration(milliseconds: ms), pingServer); 30 | } 31 | 32 | void pingServer(Timer t) { 33 | count++; 34 | 35 | if (count > 1000) { 36 | t.cancel(); 37 | return; 38 | } 39 | 40 | final sw = Stopwatch()..start(); 41 | 42 | final time = DateTime.now().millisecondsSinceEpoch; 43 | final message = '$postPayload //$time $epilogue'; 44 | print(message); 45 | http.post(Uri.parse(uri), body: message).then((response) { 46 | print('${response.statusCode}, ${sw.elapsedMilliseconds}'); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /tool/pub_dependencies_beta.json: -------------------------------------------------------------------------------- 1 | { 2 | "_fe_analyzer_shared": "61.0.0", 3 | "_flutterfire_internals": "1.3.3", 4 | "analyzer": "5.13.0", 5 | "animations": "2.0.7", 6 | "archive": "3.3.7", 7 | "args": "2.4.2", 8 | "async": "2.11.0", 9 | "basics": "0.10.0", 10 | "bloc": "8.1.2", 11 | "boolean_selector": "2.1.1", 12 | "characters": "1.3.0", 13 | "clock": "1.1.1", 14 | "cloud_firestore": "4.8.2", 15 | "cloud_firestore_platform_interface": "5.15.2", 16 | "cloud_firestore_web": "3.6.2", 17 | "collection": "1.17.2", 18 | "convert": "3.1.1", 19 | "coverage": "1.6.3", 20 | "creator": "0.3.2", 21 | "creator_core": "0.3.2", 22 | "cross_file": "0.3.3+4", 23 | "crypto": "3.0.3", 24 | "csslib": "1.0.0", 25 | "dartz": "0.10.1", 26 | "english_words": "4.0.0", 27 | "equatable": "2.0.5", 28 | "fake_async": "1.3.1", 29 | "fast_immutable_collections": "9.1.5", 30 | "fast_noise": "1.0.1", 31 | "ffi": "2.0.2", 32 | "file": "6.1.4", 33 | "firebase_analytics": "10.4.3", 34 | "firebase_analytics_platform_interface": "3.6.3", 35 | "firebase_analytics_web": "0.5.4+3", 36 | "firebase_auth": "4.6.3", 37 | "firebase_auth_platform_interface": "6.15.3", 38 | "firebase_auth_web": "5.5.3", 39 | "firebase_core": "2.14.0", 40 | "firebase_core_platform_interface": "4.8.0", 41 | "firebase_core_web": "2.6.0", 42 | "firebase_database": "10.2.3", 43 | "firebase_database_platform_interface": "0.2.5+3", 44 | "firebase_database_web": "0.2.3+3", 45 | "firebase_messaging": "14.6.4", 46 | "firebase_messaging_platform_interface": "4.5.3", 47 | "firebase_messaging_web": "3.5.3", 48 | "firebase_storage": "11.2.4", 49 | "firebase_storage_platform_interface": "4.4.3", 50 | "firebase_storage_web": "3.6.4", 51 | "fixnum": "1.1.0", 52 | "flame": "1.8.1", 53 | "flame_fire_atlas": "1.3.7", 54 | "flame_forge2d": "0.14.1", 55 | "flame_splash_screen": "0.1.0", 56 | "flame_tiled": "1.12.0", 57 | "flutter_adaptive_scaffold": "0.1.6", 58 | "flutter_bloc": "8.1.3", 59 | "flutter_hooks": "0.18.6", 60 | "flutter_lints": "2.0.2", 61 | "flutter_map": "5.0.0", 62 | "flutter_processing": "0.2.0", 63 | "flutter_riverpod": "2.3.6", 64 | "flutter_spinkit": "5.2.0", 65 | "flutter_svg": "2.0.7", 66 | "forge2d": "0.11.0", 67 | "frontend_server_client": "3.2.0", 68 | "glob": "2.1.2", 69 | "go_router": "9.0.3", 70 | "google_fonts": "5.1.0", 71 | "hooks_riverpod": "2.3.6", 72 | "html": "0.15.4", 73 | "http": "1.1.0", 74 | "http_multi_server": "3.2.1", 75 | "http_parser": "4.0.2", 76 | "image": "3.3.0", 77 | "intl": "0.18.1", 78 | "io": "1.0.4", 79 | "js": "0.6.7", 80 | "latlong2": "0.9.0", 81 | "lints": "2.1.1", 82 | "lists": "1.0.1", 83 | "logging": "1.2.0", 84 | "matcher": "0.12.16", 85 | "material_color_utilities": "0.5.0", 86 | "meta": "1.9.1", 87 | "mgrs_dart": "2.0.0", 88 | "mime": "1.0.4", 89 | "nested": "1.0.0", 90 | "node_preamble": "2.0.2", 91 | "ordered_set": "5.0.1", 92 | "package_config": "2.1.0", 93 | "path": "1.8.3", 94 | "path_parsing": "1.0.1", 95 | "path_provider": "2.0.15", 96 | "path_provider_android": "2.0.27", 97 | "path_provider_foundation": "2.2.3", 98 | "path_provider_linux": "2.1.11", 99 | "path_provider_platform_interface": "2.0.6", 100 | "path_provider_windows": "2.1.7", 101 | "petitparser": "5.4.0", 102 | "platform": "3.1.0", 103 | "plugin_platform_interface": "2.1.4", 104 | "pointycastle": "3.7.3", 105 | "polylabel": "1.0.1", 106 | "pool": "1.5.1", 107 | "process": "4.2.4", 108 | "proj4dart": "2.1.0", 109 | "provider": "6.0.5", 110 | "pub_semver": "2.1.4", 111 | "quiver": "3.2.1", 112 | "riverpod": "2.3.6", 113 | "riverpod_navigator": "1.0.10", 114 | "riverpod_navigator_core": "1.0.10", 115 | "rohd": "0.4.2", 116 | "rohd_vf": "0.4.1", 117 | "rxdart": "0.27.7", 118 | "shared_preferences": "2.2.0", 119 | "shared_preferences_android": "2.2.0", 120 | "shared_preferences_foundation": "2.3.1", 121 | "shared_preferences_linux": "2.3.0", 122 | "shared_preferences_platform_interface": "2.3.0", 123 | "shared_preferences_web": "2.2.0", 124 | "shared_preferences_windows": "2.3.0", 125 | "shelf": "1.4.1", 126 | "shelf_packages_handler": "3.0.2", 127 | "shelf_static": "1.1.2", 128 | "shelf_web_socket": "1.0.4", 129 | "source_map_stack_trace": "2.1.1", 130 | "source_maps": "0.10.12", 131 | "source_span": "1.10.0", 132 | "stack_trace": "1.11.0", 133 | "state_notifier": "0.7.2+1", 134 | "stream_channel": "2.1.1", 135 | "string_scanner": "1.2.0", 136 | "term_glyph": "1.2.1", 137 | "test": "1.24.3", 138 | "test_api": "0.6.0", 139 | "test_core": "0.5.3", 140 | "tiled": "0.10.1", 141 | "timezone": "0.9.2", 142 | "tuple": "2.0.2", 143 | "typed_data": "1.3.2", 144 | "unicode": "0.3.1", 145 | "vector_graphics": "1.1.7", 146 | "vector_graphics_codec": "1.1.7", 147 | "vector_graphics_compiler": "1.1.7", 148 | "vector_math": "2.1.4", 149 | "video_player": "2.7.0", 150 | "video_player_android": "2.4.9", 151 | "video_player_avfoundation": "2.4.6", 152 | "video_player_platform_interface": "6.1.0", 153 | "video_player_web": "2.0.16", 154 | "vm_service": "11.7.2", 155 | "watcher": "1.1.0", 156 | "web": "0.1.4-beta", 157 | "web_socket_channel": "2.4.0", 158 | "webkit_inspection_protocol": "1.2.0", 159 | "win32": "5.0.5", 160 | "wkt_parser": "2.0.0", 161 | "xdg_directories": "1.0.0", 162 | "xml": "6.3.0", 163 | "yaml": "3.1.2", 164 | "yaml_edit": "2.1.1" 165 | } -------------------------------------------------------------------------------- /tool/pub_dependencies_dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "_fe_analyzer_shared": "61.0.0", 3 | "_flutterfire_internals": "1.3.3", 4 | "analyzer": "5.13.0", 5 | "animations": "2.0.7", 6 | "archive": "3.3.7", 7 | "args": "2.4.2", 8 | "async": "2.11.0", 9 | "basics": "0.10.0", 10 | "bloc": "8.1.2", 11 | "boolean_selector": "2.1.1", 12 | "characters": "1.3.0", 13 | "clock": "1.1.1", 14 | "cloud_firestore": "4.8.2", 15 | "cloud_firestore_platform_interface": "5.15.2", 16 | "cloud_firestore_web": "3.6.2", 17 | "collection": "1.17.1", 18 | "convert": "3.1.1", 19 | "coverage": "1.6.3", 20 | "creator": "0.3.2", 21 | "creator_core": "0.3.2", 22 | "cross_file": "0.3.3+4", 23 | "crypto": "3.0.3", 24 | "csslib": "1.0.0", 25 | "dartz": "0.10.1", 26 | "english_words": "4.0.0", 27 | "equatable": "2.0.5", 28 | "fake_async": "1.3.1", 29 | "fast_immutable_collections": "9.1.5", 30 | "fast_noise": "1.0.1", 31 | "ffi": "2.0.2", 32 | "file": "6.1.4", 33 | "firebase_analytics": "10.4.3", 34 | "firebase_analytics_platform_interface": "3.6.3", 35 | "firebase_analytics_web": "0.5.4+3", 36 | "firebase_auth": "4.6.3", 37 | "firebase_auth_platform_interface": "6.15.3", 38 | "firebase_auth_web": "5.5.3", 39 | "firebase_core": "2.14.0", 40 | "firebase_core_platform_interface": "4.8.0", 41 | "firebase_core_web": "2.6.0", 42 | "firebase_database": "10.2.3", 43 | "firebase_database_platform_interface": "0.2.5+3", 44 | "firebase_database_web": "0.2.3+3", 45 | "firebase_messaging": "14.6.4", 46 | "firebase_messaging_platform_interface": "4.5.3", 47 | "firebase_messaging_web": "3.5.3", 48 | "firebase_storage": "11.2.4", 49 | "firebase_storage_platform_interface": "4.4.3", 50 | "firebase_storage_web": "3.6.4", 51 | "fixnum": "1.1.0", 52 | "flame": "1.8.1", 53 | "flame_fire_atlas": "1.3.7", 54 | "flame_forge2d": "0.14.1", 55 | "flame_splash_screen": "0.1.0", 56 | "flame_tiled": "1.12.0", 57 | "flutter_adaptive_scaffold": "0.1.6", 58 | "flutter_bloc": "8.1.3", 59 | "flutter_hooks": "0.18.6", 60 | "flutter_lints": "2.0.2", 61 | "flutter_map": "5.0.0", 62 | "flutter_processing": "0.2.0", 63 | "flutter_riverpod": "2.3.6", 64 | "flutter_spinkit": "5.2.0", 65 | "flutter_svg": "2.0.7", 66 | "forge2d": "0.11.0", 67 | "frontend_server_client": "3.2.0", 68 | "glob": "2.1.2", 69 | "go_router": "9.0.3", 70 | "google_fonts": "5.1.0", 71 | "hooks_riverpod": "2.3.6", 72 | "html": "0.15.4", 73 | "http": "1.1.0", 74 | "http_multi_server": "3.2.1", 75 | "http_parser": "4.0.2", 76 | "image": "3.3.0", 77 | "intl": "0.18.1", 78 | "io": "1.0.4", 79 | "js": "0.6.7", 80 | "latlong2": "0.9.0", 81 | "lints": "2.1.1", 82 | "lists": "1.0.1", 83 | "logging": "1.2.0", 84 | "matcher": "0.12.15", 85 | "material_color_utilities": "0.2.0", 86 | "meta": "1.9.1", 87 | "mgrs_dart": "2.0.0", 88 | "mime": "1.0.4", 89 | "nested": "1.0.0", 90 | "node_preamble": "2.0.2", 91 | "ordered_set": "5.0.1", 92 | "package_config": "2.1.0", 93 | "path": "1.8.3", 94 | "path_parsing": "1.0.1", 95 | "path_provider": "2.0.15", 96 | "path_provider_android": "2.0.27", 97 | "path_provider_foundation": "2.2.3", 98 | "path_provider_linux": "2.1.11", 99 | "path_provider_platform_interface": "2.0.6", 100 | "path_provider_windows": "2.1.7", 101 | "petitparser": "5.4.0", 102 | "platform": "3.1.0", 103 | "plugin_platform_interface": "2.1.4", 104 | "pointycastle": "3.7.3", 105 | "polylabel": "1.0.1", 106 | "pool": "1.5.1", 107 | "process": "4.2.4", 108 | "proj4dart": "2.1.0", 109 | "provider": "6.0.5", 110 | "pub_semver": "2.1.4", 111 | "quiver": "3.2.1", 112 | "riverpod": "2.3.6", 113 | "riverpod_navigator": "1.0.10", 114 | "riverpod_navigator_core": "1.0.10", 115 | "rohd": "0.4.2", 116 | "rohd_vf": "0.4.1", 117 | "rxdart": "0.27.7", 118 | "shared_preferences": "2.2.0", 119 | "shared_preferences_android": "2.2.0", 120 | "shared_preferences_foundation": "2.3.1", 121 | "shared_preferences_linux": "2.3.0", 122 | "shared_preferences_platform_interface": "2.3.0", 123 | "shared_preferences_web": "2.2.0", 124 | "shared_preferences_windows": "2.3.0", 125 | "shelf": "1.4.1", 126 | "shelf_packages_handler": "3.0.2", 127 | "shelf_static": "1.1.2", 128 | "shelf_web_socket": "1.0.4", 129 | "source_map_stack_trace": "2.1.1", 130 | "source_maps": "0.10.12", 131 | "source_span": "1.9.1", 132 | "stack_trace": "1.11.0", 133 | "state_notifier": "0.7.2+1", 134 | "stream_channel": "2.1.1", 135 | "string_scanner": "1.2.0", 136 | "term_glyph": "1.2.1", 137 | "test": "1.24.1", 138 | "test_api": "0.5.1", 139 | "test_core": "0.5.1", 140 | "tiled": "0.10.1", 141 | "timezone": "0.9.2", 142 | "tuple": "2.0.2", 143 | "typed_data": "1.3.2", 144 | "unicode": "0.3.1", 145 | "vector_graphics": "1.1.7", 146 | "vector_graphics_codec": "1.1.7", 147 | "vector_graphics_compiler": "1.1.7", 148 | "vector_math": "2.1.4", 149 | "video_player": "2.7.0", 150 | "video_player_android": "2.4.9", 151 | "video_player_avfoundation": "2.4.6", 152 | "video_player_platform_interface": "6.1.0", 153 | "video_player_web": "2.0.16", 154 | "vm_service": "11.7.2", 155 | "watcher": "1.1.0", 156 | "web_socket_channel": "2.4.0", 157 | "webkit_inspection_protocol": "1.2.0", 158 | "win32": "5.0.5", 159 | "wkt_parser": "2.0.0", 160 | "xdg_directories": "1.0.0", 161 | "xml": "6.3.0", 162 | "yaml": "3.1.2", 163 | "yaml_edit": "2.1.1" 164 | } -------------------------------------------------------------------------------- /tool/pub_dependencies_master.json: -------------------------------------------------------------------------------- 1 | { 2 | "_fe_analyzer_shared": "62.0.0", 3 | "_flutterfire_internals": "1.3.3", 4 | "analyzer": "6.0.0", 5 | "animations": "2.0.7", 6 | "archive": "3.3.7", 7 | "args": "2.4.2", 8 | "async": "2.11.0", 9 | "basics": "0.10.0", 10 | "bloc": "8.1.2", 11 | "boolean_selector": "2.1.1", 12 | "characters": "1.3.0", 13 | "clock": "1.1.1", 14 | "cloud_firestore": "4.8.2", 15 | "cloud_firestore_platform_interface": "5.15.2", 16 | "cloud_firestore_web": "3.6.2", 17 | "collection": "1.17.2", 18 | "convert": "3.1.1", 19 | "coverage": "1.6.3", 20 | "creator": "0.3.2", 21 | "creator_core": "0.3.2", 22 | "cross_file": "0.3.3+4", 23 | "crypto": "3.0.3", 24 | "csslib": "1.0.0", 25 | "dart_internal": "0.2.8", 26 | "dartz": "0.10.1", 27 | "english_words": "4.0.0", 28 | "equatable": "2.0.5", 29 | "fake_async": "1.3.1", 30 | "fast_immutable_collections": "9.1.5", 31 | "fast_noise": "1.0.1", 32 | "ffi": "2.0.2", 33 | "file": "6.1.4", 34 | "firebase_analytics": "10.4.3", 35 | "firebase_analytics_platform_interface": "3.6.3", 36 | "firebase_analytics_web": "0.5.4+3", 37 | "firebase_auth": "4.6.3", 38 | "firebase_auth_platform_interface": "6.15.3", 39 | "firebase_auth_web": "5.5.3", 40 | "firebase_core": "2.14.0", 41 | "firebase_core_platform_interface": "4.8.0", 42 | "firebase_core_web": "2.6.0", 43 | "firebase_database": "10.2.3", 44 | "firebase_database_platform_interface": "0.2.5+3", 45 | "firebase_database_web": "0.2.3+3", 46 | "firebase_messaging": "14.6.4", 47 | "firebase_messaging_platform_interface": "4.5.3", 48 | "firebase_messaging_web": "3.5.3", 49 | "firebase_storage": "11.2.4", 50 | "firebase_storage_platform_interface": "4.4.3", 51 | "firebase_storage_web": "3.6.4", 52 | "fixnum": "1.1.0", 53 | "flame": "1.8.1", 54 | "flame_fire_atlas": "1.3.7", 55 | "flame_forge2d": "0.14.1", 56 | "flame_splash_screen": "0.1.0", 57 | "flame_tiled": "1.12.0", 58 | "flutter_adaptive_scaffold": "0.1.6", 59 | "flutter_bloc": "8.1.3", 60 | "flutter_hooks": "0.18.6", 61 | "flutter_lints": "2.0.2", 62 | "flutter_map": "5.0.0", 63 | "flutter_processing": "0.2.0", 64 | "flutter_riverpod": "2.3.6", 65 | "flutter_spinkit": "5.2.0", 66 | "flutter_svg": "2.0.7", 67 | "forge2d": "0.11.0", 68 | "frontend_server_client": "3.2.0", 69 | "glob": "2.1.2", 70 | "go_router": "9.0.3", 71 | "google_fonts": "5.1.0", 72 | "hooks_riverpod": "2.3.6", 73 | "html": "0.15.4", 74 | "http": "1.1.0", 75 | "http_multi_server": "3.2.1", 76 | "http_parser": "4.0.2", 77 | "image": "3.3.0", 78 | "intl": "0.18.1", 79 | "io": "1.0.4", 80 | "js": "0.6.7", 81 | "latlong2": "0.9.0", 82 | "lints": "2.1.1", 83 | "lists": "1.0.1", 84 | "logging": "1.2.0", 85 | "matcher": "0.12.16", 86 | "material_color_utilities": "0.5.0", 87 | "meta": "1.9.1", 88 | "mgrs_dart": "2.0.0", 89 | "mime": "1.0.4", 90 | "nested": "1.0.0", 91 | "node_preamble": "2.0.2", 92 | "ordered_set": "5.0.1", 93 | "package_config": "2.1.0", 94 | "path": "1.8.3", 95 | "path_parsing": "1.0.1", 96 | "path_provider": "2.0.15", 97 | "path_provider_android": "2.0.27", 98 | "path_provider_foundation": "2.2.3", 99 | "path_provider_linux": "2.1.11", 100 | "path_provider_platform_interface": "2.0.6", 101 | "path_provider_windows": "2.1.7", 102 | "petitparser": "5.4.0", 103 | "platform": "3.1.0", 104 | "plugin_platform_interface": "2.1.4", 105 | "pointycastle": "3.7.3", 106 | "polylabel": "1.0.1", 107 | "pool": "1.5.1", 108 | "process": "4.2.4", 109 | "proj4dart": "2.1.0", 110 | "provider": "6.0.5", 111 | "pub_semver": "2.1.4", 112 | "quiver": "3.2.1", 113 | "riverpod": "2.3.6", 114 | "riverpod_navigator": "1.0.10", 115 | "riverpod_navigator_core": "1.0.10", 116 | "rohd": "0.4.2", 117 | "rohd_vf": "0.4.1", 118 | "rxdart": "0.27.7", 119 | "shared_preferences": "2.2.0", 120 | "shared_preferences_android": "2.2.0", 121 | "shared_preferences_foundation": "2.3.1", 122 | "shared_preferences_linux": "2.3.0", 123 | "shared_preferences_platform_interface": "2.3.0", 124 | "shared_preferences_web": "2.2.0", 125 | "shared_preferences_windows": "2.3.0", 126 | "shelf": "1.4.1", 127 | "shelf_packages_handler": "3.0.2", 128 | "shelf_static": "1.1.2", 129 | "shelf_web_socket": "1.0.4", 130 | "source_map_stack_trace": "2.1.1", 131 | "source_maps": "0.10.12", 132 | "source_span": "1.10.0", 133 | "stack_trace": "1.11.1", 134 | "state_notifier": "0.7.2+1", 135 | "stream_channel": "2.1.2", 136 | "string_scanner": "1.2.0", 137 | "term_glyph": "1.2.1", 138 | "test": "1.24.4", 139 | "test_api": "0.6.1", 140 | "test_core": "0.5.4", 141 | "tiled": "0.10.1", 142 | "timezone": "0.9.2", 143 | "tuple": "2.0.2", 144 | "typed_data": "1.3.2", 145 | "unicode": "0.3.1", 146 | "vector_graphics": "1.1.7", 147 | "vector_graphics_codec": "1.1.7", 148 | "vector_graphics_compiler": "1.1.7", 149 | "vector_math": "2.1.4", 150 | "video_player": "2.7.0", 151 | "video_player_android": "2.4.9", 152 | "video_player_avfoundation": "2.4.6", 153 | "video_player_platform_interface": "6.1.0", 154 | "video_player_web": "2.0.16", 155 | "vm_service": "11.7.2", 156 | "watcher": "1.1.0", 157 | "web": "0.1.4-beta", 158 | "web_socket_channel": "2.4.0", 159 | "webkit_inspection_protocol": "1.2.0", 160 | "win32": "5.0.5", 161 | "wkt_parser": "2.0.0", 162 | "xdg_directories": "1.0.0", 163 | "xml": "6.3.0", 164 | "yaml": "3.1.2", 165 | "yaml_edit": "2.1.1" 166 | } -------------------------------------------------------------------------------- /tool/pub_dependencies_old.json: -------------------------------------------------------------------------------- 1 | { 2 | "_fe_analyzer_shared": "61.0.0", 3 | "_flutterfire_internals": "1.3.3", 4 | "analyzer": "5.13.0", 5 | "animations": "2.0.7", 6 | "archive": "3.3.7", 7 | "args": "2.4.2", 8 | "async": "2.10.0", 9 | "basics": "0.10.0", 10 | "bloc": "8.1.2", 11 | "boolean_selector": "2.1.1", 12 | "characters": "1.2.1", 13 | "clock": "1.1.1", 14 | "cloud_firestore": "4.8.2", 15 | "cloud_firestore_platform_interface": "5.15.2", 16 | "cloud_firestore_web": "3.6.2", 17 | "collection": "1.17.0", 18 | "convert": "3.1.1", 19 | "coverage": "1.6.3", 20 | "creator": "0.3.2", 21 | "creator_core": "0.3.2", 22 | "cross_file": "0.3.3+4", 23 | "crypto": "3.0.3", 24 | "csslib": "1.0.0", 25 | "dartz": "0.10.1", 26 | "english_words": "4.0.0", 27 | "equatable": "2.0.5", 28 | "fake_async": "1.3.1", 29 | "fast_immutable_collections": "9.1.5", 30 | "fast_noise": "1.0.1", 31 | "ffi": "2.0.2", 32 | "file": "6.1.4", 33 | "firebase_analytics": "10.4.3", 34 | "firebase_analytics_platform_interface": "3.6.3", 35 | "firebase_analytics_web": "0.5.4+3", 36 | "firebase_auth": "4.6.3", 37 | "firebase_auth_platform_interface": "6.15.3", 38 | "firebase_auth_web": "5.5.3", 39 | "firebase_core": "2.14.0", 40 | "firebase_core_platform_interface": "4.8.0", 41 | "firebase_core_web": "2.6.0", 42 | "firebase_database": "10.2.3", 43 | "firebase_database_platform_interface": "0.2.5+3", 44 | "firebase_database_web": "0.2.3+3", 45 | "firebase_messaging": "14.6.4", 46 | "firebase_messaging_platform_interface": "4.5.3", 47 | "firebase_messaging_web": "3.5.3", 48 | "firebase_storage": "11.2.4", 49 | "firebase_storage_platform_interface": "4.4.3", 50 | "firebase_storage_web": "3.6.4", 51 | "fixnum": "1.1.0", 52 | "flame": "1.7.3", 53 | "flame_fire_atlas": "1.3.5", 54 | "flame_forge2d": "0.13.0+1", 55 | "flame_splash_screen": "0.1.0", 56 | "flame_tiled": "1.10.1", 57 | "flutter_adaptive_scaffold": "0.1.4", 58 | "flutter_bloc": "8.1.3", 59 | "flutter_hooks": "0.18.6", 60 | "flutter_lints": "2.0.2", 61 | "flutter_map": "4.0.0", 62 | "flutter_processing": "0.2.0", 63 | "flutter_riverpod": "2.3.6", 64 | "flutter_spinkit": "5.2.0", 65 | "flutter_svg": "2.0.5", 66 | "forge2d": "0.11.0", 67 | "frontend_server_client": "3.2.0", 68 | "glob": "2.1.2", 69 | "go_router": "9.0.3", 70 | "google_fonts": "4.0.4", 71 | "hooks_riverpod": "2.3.6", 72 | "html": "0.15.4", 73 | "http": "0.13.6", 74 | "http_multi_server": "3.2.1", 75 | "http_parser": "4.0.2", 76 | "image": "3.3.0", 77 | "intl": "0.18.1", 78 | "io": "1.0.4", 79 | "js": "0.6.5", 80 | "latlong2": "0.8.2", 81 | "lints": "2.0.1", 82 | "lists": "1.0.1", 83 | "logging": "1.2.0", 84 | "matcher": "0.12.13", 85 | "material_color_utilities": "0.2.0", 86 | "meta": "1.8.0", 87 | "mgrs_dart": "2.0.0", 88 | "mime": "1.0.4", 89 | "nested": "1.0.0", 90 | "node_preamble": "2.0.2", 91 | "ordered_set": "5.0.1", 92 | "package_config": "2.1.0", 93 | "path": "1.8.2", 94 | "path_parsing": "1.0.1", 95 | "path_provider": "2.0.15", 96 | "path_provider_android": "2.0.27", 97 | "path_provider_foundation": "2.2.3", 98 | "path_provider_linux": "2.1.11", 99 | "path_provider_platform_interface": "2.0.6", 100 | "path_provider_windows": "2.1.7", 101 | "petitparser": "5.1.0", 102 | "platform": "3.1.0", 103 | "plugin_platform_interface": "2.1.4", 104 | "pointycastle": "3.7.3", 105 | "polylabel": "1.0.1", 106 | "pool": "1.5.1", 107 | "process": "4.2.4", 108 | "proj4dart": "2.1.0", 109 | "provider": "6.0.5", 110 | "pub_semver": "2.1.4", 111 | "quiver": "3.2.1", 112 | "riverpod": "2.3.6", 113 | "riverpod_navigator": "1.0.10", 114 | "riverpod_navigator_core": "1.0.10", 115 | "rohd": "0.4.2", 116 | "rohd_vf": "0.4.1", 117 | "rxdart": "0.27.7", 118 | "shared_preferences": "2.2.0", 119 | "shared_preferences_android": "2.2.0", 120 | "shared_preferences_foundation": "2.3.1", 121 | "shared_preferences_linux": "2.3.0", 122 | "shared_preferences_platform_interface": "2.3.0", 123 | "shared_preferences_web": "2.2.0", 124 | "shared_preferences_windows": "2.3.0", 125 | "shelf": "1.4.1", 126 | "shelf_packages_handler": "3.0.2", 127 | "shelf_static": "1.1.2", 128 | "shelf_web_socket": "1.0.4", 129 | "source_map_stack_trace": "2.1.1", 130 | "source_maps": "0.10.12", 131 | "source_span": "1.9.1", 132 | "stack_trace": "1.11.0", 133 | "state_notifier": "0.7.2+1", 134 | "stream_channel": "2.1.1", 135 | "string_scanner": "1.2.0", 136 | "term_glyph": "1.2.1", 137 | "test": "1.22.0", 138 | "test_api": "0.4.16", 139 | "test_core": "0.4.20", 140 | "tiled": "0.10.1", 141 | "timezone": "0.9.2", 142 | "tuple": "2.0.2", 143 | "typed_data": "1.3.2", 144 | "unicode": "0.3.1", 145 | "vector_graphics": "1.1.5", 146 | "vector_graphics_codec": "1.1.5", 147 | "vector_graphics_compiler": "1.1.5", 148 | "vector_math": "2.1.4", 149 | "video_player": "2.7.0", 150 | "video_player_android": "2.4.9", 151 | "video_player_avfoundation": "2.4.6", 152 | "video_player_platform_interface": "6.1.0", 153 | "video_player_web": "2.0.16", 154 | "vm_service": "9.4.0", 155 | "watcher": "1.0.2", 156 | "web_socket_channel": "2.4.0", 157 | "webkit_inspection_protocol": "1.2.0", 158 | "win32": "4.1.4", 159 | "wkt_parser": "2.0.0", 160 | "xdg_directories": "1.0.0", 161 | "xml": "6.2.2", 162 | "yaml": "3.1.2", 163 | "yaml_edit": "2.1.1" 164 | } -------------------------------------------------------------------------------- /tool/pub_dependencies_stable.json: -------------------------------------------------------------------------------- 1 | { 2 | "_fe_analyzer_shared": "61.0.0", 3 | "_flutterfire_internals": "1.3.3", 4 | "analyzer": "5.13.0", 5 | "animations": "2.0.7", 6 | "archive": "3.3.7", 7 | "args": "2.4.2", 8 | "async": "2.11.0", 9 | "basics": "0.10.0", 10 | "bloc": "8.1.2", 11 | "boolean_selector": "2.1.1", 12 | "characters": "1.3.0", 13 | "clock": "1.1.1", 14 | "cloud_firestore": "4.8.2", 15 | "cloud_firestore_platform_interface": "5.15.2", 16 | "cloud_firestore_web": "3.6.2", 17 | "collection": "1.17.1", 18 | "convert": "3.1.1", 19 | "coverage": "1.6.3", 20 | "creator": "0.3.2", 21 | "creator_core": "0.3.2", 22 | "cross_file": "0.3.3+4", 23 | "crypto": "3.0.3", 24 | "csslib": "1.0.0", 25 | "dartz": "0.10.1", 26 | "english_words": "4.0.0", 27 | "equatable": "2.0.5", 28 | "fake_async": "1.3.1", 29 | "fast_immutable_collections": "9.1.5", 30 | "fast_noise": "1.0.1", 31 | "ffi": "2.0.2", 32 | "file": "6.1.4", 33 | "firebase_analytics": "10.4.3", 34 | "firebase_analytics_platform_interface": "3.6.3", 35 | "firebase_analytics_web": "0.5.4+3", 36 | "firebase_auth": "4.6.3", 37 | "firebase_auth_platform_interface": "6.15.3", 38 | "firebase_auth_web": "5.5.3", 39 | "firebase_core": "2.14.0", 40 | "firebase_core_platform_interface": "4.8.0", 41 | "firebase_core_web": "2.6.0", 42 | "firebase_database": "10.2.3", 43 | "firebase_database_platform_interface": "0.2.5+3", 44 | "firebase_database_web": "0.2.3+3", 45 | "firebase_messaging": "14.6.4", 46 | "firebase_messaging_platform_interface": "4.5.3", 47 | "firebase_messaging_web": "3.5.3", 48 | "firebase_storage": "11.2.4", 49 | "firebase_storage_platform_interface": "4.4.3", 50 | "firebase_storage_web": "3.6.4", 51 | "fixnum": "1.1.0", 52 | "flame": "1.8.1", 53 | "flame_fire_atlas": "1.3.7", 54 | "flame_forge2d": "0.14.1", 55 | "flame_splash_screen": "0.1.0", 56 | "flame_tiled": "1.12.0", 57 | "flutter_adaptive_scaffold": "0.1.6", 58 | "flutter_bloc": "8.1.3", 59 | "flutter_hooks": "0.18.6", 60 | "flutter_lints": "2.0.2", 61 | "flutter_map": "5.0.0", 62 | "flutter_processing": "0.2.0", 63 | "flutter_riverpod": "2.3.6", 64 | "flutter_spinkit": "5.2.0", 65 | "flutter_svg": "2.0.7", 66 | "forge2d": "0.11.0", 67 | "frontend_server_client": "3.2.0", 68 | "glob": "2.1.2", 69 | "go_router": "9.0.3", 70 | "google_fonts": "5.1.0", 71 | "hooks_riverpod": "2.3.6", 72 | "html": "0.15.4", 73 | "http": "1.1.0", 74 | "http_multi_server": "3.2.1", 75 | "http_parser": "4.0.2", 76 | "image": "3.3.0", 77 | "intl": "0.18.1", 78 | "io": "1.0.4", 79 | "js": "0.6.7", 80 | "latlong2": "0.9.0", 81 | "lints": "2.1.1", 82 | "lists": "1.0.1", 83 | "logging": "1.2.0", 84 | "matcher": "0.12.15", 85 | "material_color_utilities": "0.2.0", 86 | "meta": "1.9.1", 87 | "mgrs_dart": "2.0.0", 88 | "mime": "1.0.4", 89 | "nested": "1.0.0", 90 | "node_preamble": "2.0.2", 91 | "ordered_set": "5.0.1", 92 | "package_config": "2.1.0", 93 | "path": "1.8.3", 94 | "path_parsing": "1.0.1", 95 | "path_provider": "2.0.15", 96 | "path_provider_android": "2.0.27", 97 | "path_provider_foundation": "2.2.3", 98 | "path_provider_linux": "2.1.11", 99 | "path_provider_platform_interface": "2.0.6", 100 | "path_provider_windows": "2.1.7", 101 | "petitparser": "5.4.0", 102 | "platform": "3.1.0", 103 | "plugin_platform_interface": "2.1.4", 104 | "pointycastle": "3.7.3", 105 | "polylabel": "1.0.1", 106 | "pool": "1.5.1", 107 | "process": "4.2.4", 108 | "proj4dart": "2.1.0", 109 | "provider": "6.0.5", 110 | "pub_semver": "2.1.4", 111 | "quiver": "3.2.1", 112 | "riverpod": "2.3.6", 113 | "riverpod_navigator": "1.0.10", 114 | "riverpod_navigator_core": "1.0.10", 115 | "rohd": "0.4.2", 116 | "rohd_vf": "0.4.1", 117 | "rxdart": "0.27.7", 118 | "shared_preferences": "2.2.0", 119 | "shared_preferences_android": "2.2.0", 120 | "shared_preferences_foundation": "2.3.1", 121 | "shared_preferences_linux": "2.3.0", 122 | "shared_preferences_platform_interface": "2.3.0", 123 | "shared_preferences_web": "2.2.0", 124 | "shared_preferences_windows": "2.3.0", 125 | "shelf": "1.4.1", 126 | "shelf_packages_handler": "3.0.2", 127 | "shelf_static": "1.1.2", 128 | "shelf_web_socket": "1.0.4", 129 | "source_map_stack_trace": "2.1.1", 130 | "source_maps": "0.10.12", 131 | "source_span": "1.9.1", 132 | "stack_trace": "1.11.0", 133 | "state_notifier": "0.7.2+1", 134 | "stream_channel": "2.1.1", 135 | "string_scanner": "1.2.0", 136 | "term_glyph": "1.2.1", 137 | "test": "1.24.1", 138 | "test_api": "0.5.1", 139 | "test_core": "0.5.1", 140 | "tiled": "0.10.1", 141 | "timezone": "0.9.2", 142 | "tuple": "2.0.2", 143 | "typed_data": "1.3.2", 144 | "unicode": "0.3.1", 145 | "vector_graphics": "1.1.7", 146 | "vector_graphics_codec": "1.1.7", 147 | "vector_graphics_compiler": "1.1.7", 148 | "vector_math": "2.1.4", 149 | "video_player": "2.7.0", 150 | "video_player_android": "2.4.9", 151 | "video_player_avfoundation": "2.4.6", 152 | "video_player_platform_interface": "6.1.0", 153 | "video_player_web": "2.0.16", 154 | "vm_service": "11.7.2", 155 | "watcher": "1.1.0", 156 | "web_socket_channel": "2.4.0", 157 | "webkit_inspection_protocol": "1.2.0", 158 | "win32": "5.0.5", 159 | "wkt_parser": "2.0.0", 160 | "xdg_directories": "1.0.0", 161 | "xml": "6.3.0", 162 | "yaml": "3.1.2", 163 | "yaml_edit": "2.1.1" 164 | } -------------------------------------------------------------------------------- /tool/travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 4 | # for details. All rights reserved. Use of this source code is governed by a 5 | # BSD-style license that can be found in the LICENSE file. 6 | 7 | # Fast fail the script on failures. 8 | set -e 9 | 10 | channel="$1" 11 | 12 | # Run pub get to fetch packages. 13 | dart pub get 14 | 15 | # Prepare to run unit tests (but do not actually run tests). 16 | FLUTTER_CHANNEL=$channel dart pub run grinder buildbot 17 | 18 | # Ensure that we've uploaded the compilation artifacts to google storage. 19 | FLUTTER_CHANNEL=$channel dart pub run grinder validate-storage-artifacts 20 | 21 | # Enforce dart formatting on lib, test and tool directories. 22 | echo -n "Files that need dart format: " 23 | dart format --set-exit-if-changed lib test tool 24 | echo "All clean" 25 | 26 | # Gather coverage and upload to Coveralls. 27 | if [ "$REPO_TOKEN" ] && [ "$TRAVIS_DART_VERSION" = "dev" ]; then 28 | OBS_PORT=9292 29 | echo "Collecting coverage on port $OBS_PORT..." 30 | 31 | # Start tests in one VM. 32 | FLUTTER_CHANNEL=$channel \ 33 | dart \ 34 | --enable-vm-service=$OBS_PORT \ 35 | --pause-isolates-on-exit \ 36 | test/all.dart & 37 | 38 | # Run the coverage collector to generate the JSON coverage report. 39 | FLUTTER_CHANNEL=$channel \ 40 | pub run coverage:collect_coverage \ 41 | --port=$OBS_PORT \ 42 | --out=coverage.json \ 43 | --wait-paused \ 44 | --resume-isolates 45 | 46 | echo "Generating LCOV report..." 47 | FLUTTER_CHANNEL=$channel \ 48 | pub run coverage:format_coverage \ 49 | --lcov \ 50 | --in=coverage.json \ 51 | --out=lcov.info \ 52 | --packages=.packages \ 53 | --report-on=lib 54 | 55 | coveralls-lcov --repo-token="${REPO_TOKEN}" lcov.info 56 | else 57 | FLUTTER_CHANNEL=$channel dart test/all.dart 58 | fi 59 | -------------------------------------------------------------------------------- /tool/update_sdk.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, 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 | import 'dart:io'; 6 | 7 | import 'package:dart_services/src/sdk.dart'; 8 | 9 | // This tool is used to manually update the `flutter-sdks/` Flutter SDK to match 10 | // the current configuration information in the `flutter-sdk-version.yaml` file. 11 | 12 | void main(List args) async { 13 | if (args.length != 1) { 14 | print('Usage: update_sdk.dart [CHANNEL]'); 15 | exitCode = 1; 16 | return; 17 | } 18 | 19 | final channel = args.single; 20 | final sdkManager = DownloadingSdkManager(channel); 21 | print('Flutter version: ${sdkManager.flutterVersion}'); 22 | final flutterSdkPath = await sdkManager.createFromConfigFile(); 23 | 24 | print('\nSDK setup complete ($flutterSdkPath).'); 25 | } 26 | -------------------------------------------------------------------------------- /tool/warmup.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, 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 | import 'dart:async'; 6 | import 'dart:convert' as convert; 7 | import 'dart:io'; 8 | 9 | import 'package:http/http.dart' as http; 10 | 11 | const basePath = '/api/dartservices/v2/'; 12 | 13 | const count = 200; 14 | 15 | const dartSource = 16 | "import 'dart:html'; void main() { var a = 3; var b = a.abs(); int c = 7;}"; 17 | 18 | const flutterSource = """import 'package:flutter/material.dart'; 19 | 20 | void main() { 21 | final widget = Container(color: Colors.white); 22 | runApp(widget); 23 | } 24 | """; 25 | 26 | const dartData = {'offset': 17, 'source': dartSource}; 27 | const dartCompileData = {'source': dartSource}; 28 | const dartDocData = {'offset': 84, 'source': dartSource}; 29 | const flutterData = {'offset': 96, 'source': flutterSource}; 30 | const flutterCompileDDCData = {'source': flutterSource}; 31 | const flutterDocData = {'offset': 93, 'source': flutterSource}; 32 | 33 | final dartPayload = convert.json.encode(dartData); 34 | final dartCompilePayload = convert.json.encode(dartCompileData); 35 | final dartDocPayload = convert.json.encode(dartDocData); 36 | final flutterPayload = convert.json.encode(flutterData); 37 | final flutterCompileDDCPayload = convert.json.encode(flutterCompileDDCData); 38 | final flutterDocPayload = convert.json.encode(flutterDocData); 39 | 40 | late String uri; 41 | 42 | Future main(List args) async { 43 | String appHost; 44 | 45 | if (args.isNotEmpty) { 46 | appHost = args[0]; 47 | } else { 48 | print('''Pass the fully qualified dart-services hostname (no protocol, no 49 | path) as the first argument when invoking this script. 50 | 51 | For example: 52 | 53 | dart warmup.dart 20200124t152413-dot-dart-services-0.appspot.com 54 | '''); 55 | 56 | exit(1); 57 | } 58 | 59 | if (!appHost.startsWith('http://') && !appHost.startsWith('https://')) { 60 | appHost = 'http://$appHost'; 61 | } 62 | uri = '$appHost$basePath'; 63 | 64 | print('Target URI\n$uri'); 65 | 66 | for (var j = 0; j < count; j++) { 67 | await request('Dart', 'complete', dartPayload); 68 | await request('Dart', 'analyze', dartPayload); 69 | await request('Dart', 'compile', dartCompilePayload); 70 | await request('Dart', 'document', dartDocPayload); 71 | await request('Flutter', 'complete', flutterPayload); 72 | await request('Flutter', 'analyze', flutterPayload); 73 | await request('Flutter', 'compileDDC', flutterCompileDDCPayload); 74 | await request('Flutter', 'document', flutterDocPayload); 75 | } 76 | } 77 | 78 | Future request(String codeType, String verb, String postPayload) async { 79 | final sw = Stopwatch()..start(); 80 | 81 | final response = await http.post( 82 | Uri.parse(uri + verb), 83 | body: postPayload, 84 | headers: {'content-type': 'text/plain; charset=utf-8'}, 85 | ); 86 | 87 | final status = response.statusCode; 88 | 89 | if (status != 200) { 90 | print('$codeType $verb \t $status \t ${response.body} ${response.headers}'); 91 | } else { 92 | print('$codeType $verb \t ${sw.elapsedMilliseconds} \t $status'); 93 | } 94 | 95 | return response.statusCode; 96 | } 97 | --------------------------------------------------------------------------------