├── .github └── FUNDING.yml ├── .gitignore ├── .taskkey ├── CHANGELOG.md ├── LICENSE ├── PRIVACY.md ├── README.md ├── Testing-Readme.md ├── azure-pipelines.yml ├── flutter test output samples.txt ├── icon.png ├── images ├── step_analyze.png ├── step_build.png ├── step_command.png ├── step_env.png ├── step_install.png └── step_test.png ├── package-lock.json ├── publish-build-with-npm.sh ├── publish-build.sh ├── sample_project ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── sampleproject │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── junit.xml ├── lib │ └── main.dart ├── pubspec.yaml └── test │ ├── 1_api_test.dart │ ├── 2_api_test.dart │ ├── 3_services_test.dart │ ├── 4_widget_test.dart │ └── 5_widget_test.dart ├── tasks ├── analyze │ ├── .taskkey │ ├── icon.png │ ├── icon.svg │ ├── index.js │ ├── index.ts │ ├── package-lock.json │ ├── package.json │ ├── task.json │ ├── tsconfig.json │ └── tslint.json ├── build │ ├── icon.png │ ├── icon.svg │ ├── index.js │ ├── index.ts │ ├── package-lock.json │ ├── package.json │ ├── task.json │ ├── tests │ │ ├── apk.js │ │ └── apk.ts │ ├── tsconfig.json │ └── tslint.json ├── command │ ├── icon.png │ ├── icon.svg │ ├── index.js │ ├── index.ts │ ├── package-lock.json │ ├── package.json │ ├── task.json │ ├── tsconfig.json │ └── tslint.json ├── env │ ├── .taskkey │ ├── icon.png │ ├── icon.svg │ ├── index.js │ ├── index.ts │ ├── package-lock.json │ ├── package.json │ ├── task.json │ ├── tests │ │ ├── test.js │ │ └── test.ts │ ├── tsconfig.json │ └── tslint.json ├── install │ ├── .taskkey │ ├── icon.png │ ├── icon.svg │ ├── index.js │ ├── index.ts │ ├── package-lock.json │ ├── package.json │ ├── task.json │ ├── tests │ │ ├── latest.js │ │ └── latest.ts │ ├── tsconfig.json │ └── tslint.json └── test │ ├── icon.png │ ├── icon.svg │ ├── index.js │ ├── index.ts │ ├── package-lock.json │ ├── package.json │ ├── task.json │ ├── tests │ ├── basic.js │ └── basic.ts │ ├── tsconfig.json │ └── tslint.json ├── tsc-build-with-npm.sh └── vss-extension.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | github: hey24sheep 5 | patreon: # Replace with a single Patreon username 6 | open_collective: # Replace with a single Open Collective username 7 | ko_fi: # Replace with a single Ko-fi username 8 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 10 | liberapay: # Replace with a single Liberapay username 11 | issuehunt: # Replace with a single IssueHunt username 12 | otechie: # Replace with a single Otechie username 13 | custom: ['https://ko-fi.com/hey24sheep', 'https://paypal.me/hey24sheep'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | # parcel-bundler cache (https://parceljs.org/) 65 | .cache 66 | 67 | # next.js build output 68 | .next 69 | 70 | # nuxt.js build output 71 | .nuxt 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless 78 | 79 | 80 | # End of https://www.gitignore.io/api/node 81 | 82 | temp 83 | 84 | mintty.exe.stackdump 85 | 86 | *.vsix -------------------------------------------------------------------------------- /.taskkey: -------------------------------------------------------------------------------- 1 | 6d5d6f73-8b3f-49dc-a1f3-bb41e227c6fb -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.3.18] - 12 October 2023 2 | - Extension version bump to 0.3.18 3 | - *Test Task* version bump to "0.3.4" 4 | - *Test Task* logic change to fix false negative test case 5 | 6 | ## [0.3.17] - 12 October 2023 7 | - Extension version bump to 0.3.17 8 | - Updated support links in readme 9 | 10 | ## [0.3.16] - 05 August 2023 11 | - Extension version bump to 0.3.16 12 | - *Build Task* version bump to "0.3.10" 13 | - *Build Task* Added `buildName` and `buildNumber` to `buildWeb` 14 | - *Test Task* version bump to "0.3.3" 15 | - *Test Task* re-written the test suite & case processing and creation 16 | 17 | ## [0.3.15] - 25 July 2023 18 | - Extension version bump to 0.3.15 19 | - *Build Task* version bump to "0.3.9" 20 | - *Build Task* fixes the `extraArgs` handling in `buildWeb` 21 | - *Test Task* version bump to "0.3.2" 22 | - *Test Task* fixes task failing with null error and failing to publish results in simillar cases 23 | - Added a `Testing-Readme` guide for easier test setup 24 | - Sample project updated to latest Flutter 25 | 26 | ## [0.3.14] - 24 July 2023 27 | - Extension version bump to 0.3.14 28 | - **Common** a new separator input parameter is added to *Build*, *Command* & *Analyze* task 29 | - Please refer to the updated `Readme` for the argument names and how to guide 30 | - **NOTE: this update fixes the previous build fails by adding a different way to handle multiple values in extra args or dart define inputs** 31 | 32 | ## [0.3.13] - 22 July 2023 33 | - Extension version bump to 0.3.13 34 | - *Test Task* version bump to "0.3.1" 35 | - *Test Task* added a null check, probably fixes [#83](https://github.com/hey24sheep/azure-flutter-tasks/issues/83) 36 | - *Command Task* version bump to "0.3.1" 37 | - *Command Task* changes to how `arguments` input param are passed for execution 38 | - *Build Task* version bump to "0.3.7" 39 | - *Build Task* changes to how `dartDefine`, `dartDefineMulti` and `extraArgs` input param are passed for execution 40 | - **NOTE: These multi argument input params are now handled in a different way to fix this bug [#89](https://github.com/hey24sheep/azure-flutter-tasks/issues/89)** 41 | 42 | ## [0.3.12] - 13 May 2023 43 | - Extension version bump to 0.3.12 44 | - *Install Task* version bump to "0.3.6" 45 | - *Install Task* prepends path of "Flutter", "FlutterPubCache", "Dart" to main path 46 | - *Env Task* version bump to "0.3.2" 47 | - *Env Task* now prepends path of "Flutter", "FlutterPubCache", "Dart" to main path 48 | 49 | ## [0.3.11] - 15 April 2023 50 | - Extension version bump to 0.3.11 51 | - *Build Task* version bump to "0.3.6" 52 | - *Build Task* added `--target` to `web` build 53 | - *Build Task* added `--build-name` & `--build-number` to `desktop` builds 54 | 55 | ## [0.3.10] - 4 October 2022 56 | - Extension version bump to 0.3.10 57 | - *Build Task* version bump to "0.3.5" 58 | 59 | ## [0.3.9] - 4 October 2022 60 | - Extension version update to 0.3.9 61 | - *Build Task* version update to "0.3.4" 62 | - *Build Task* adds new build target `--profile` 63 | - *Build Task* fixes missing build target `--debug` from `web` & `desktop` builds 64 | - *Install Task* version update to "0.3.5" 65 | - *Install Task* removes 'dev' channel, (has been retired by Google) 66 | 67 | ## [0.3.8] - 12 August 2022 68 | - Extension version update to 0.3.8 69 | - *Env Task* version update to "0.3.1" 70 | - *Env Task* fixes pub cache path and debug output 71 | 72 | ## [0.3.7] - 9 July 2022 73 | - Extension version update to 0.3.7 74 | - *Build Task* version update to "0.3.3" 75 | - *Build Task* fixes `dartDefine` and `dartDefineMulti` not being passed to the desktop builds 76 | 77 | ## [0.3.6] - 17 June 2022 78 | - Extension version update to 0.3.6 79 | - *Install Task* version update to "0.3.4" 80 | - *Install Task* fixes `customVersion` arch type won't apply edge case bug 81 | 82 | ## [0.3.5] - 17 June 2022 83 | - Extension version update to 0.3.5 84 | - *Install Task* version update to "0.3.3" 85 | - *Install Task* fixes cache issue where version key should be valid semver 86 | 87 | ## [0.3.4] - 15 June 2022 88 | - Extension version update to 0.3.4 89 | - *Install Task* version update to "0.3.2" 90 | - *Install Task* now parses version info from `customUrl`, fixes bug [#46](https://github.com/hey24sheep/azure-flutter-tasks/issues/46) 91 | 92 | ## [0.3.3] - 15 June 2022 93 | - Extension version update to 0.3.3 94 | - *Install Task* version update to "0.3.1" 95 | - *Install Task* Adds `customArch` and auto 'arch' detecting ([merged#45](https://github.com/hey24sheep/azure-flutter-tasks/pull/45)) 96 | 97 | ## [0.3.2] - 30 April 2022 98 | - Extension version update to 0.3.2 99 | - *Build Task* version update to "0.3.2" 100 | - *Build Task* Adds `dart-define` to Web and Desktop build 101 | 102 | ## [0.3.1] - 13 March 2022 103 | - Extension Version update to 0.3.1 104 | - *Build Task* version update to "0.3.1" 105 | - *Build Task* : Fixed `dartDefine` not working for IOS and AAB 106 | - *Build Task* : Added new `dartDefineMulti` for multiple "dart-define" arguments, (space separated) use as key1=val1 foo=bar 107 | - FAQ update 108 | - Readme update 109 | 110 | ## [0.3.0] - 13 Nov 2021 111 | - Minor version bump across to "0.3.0" for easy maintainance 112 | - Migrate to Node10 as per Microsoft [deperication notice](https://aka.ms/migrateTaskNode10) 113 | - Fixes issue [#26](https://github.com/hey24sheep/azure-flutter-tasks/issues/26) 114 | - Multiple `extraArgs` not working bug fixed 115 | - Multiple `dartDefine` not working bug fixed 116 | - Fixed few major tests 117 | - Add tests for `Env Task` 118 | - Added changelog -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hey24sheep 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | We're working hard to protect your privacy, while delivering software that brings you the performance, power and convenience you desire. This privacy statement explains many of the data collection and use practices for this extension. This isn't intended to be an exhaustive list and does only apply to this extension. 3 | ## Security of Your Information 4 | We are committed to protecting the security of your information. No data is stored by the extension. 5 | ## Data for Quality and Reliability Improvments 6 | We do not collect any information to improve the quality and reliability of the extension, nor do we collect telemetry about usage, performance and errors. 7 | ## Collection and Use of Your Personal Information 8 | We do not collect any information that identifies you as an individual, except the information Microsoft provides to us for commercial purposes 9 | ## Disclosure to Third Parties 10 | Except as described in this statement, information you provide will not be transferred to third parties, Microsoft excluded, without your consent. We may access or disclose information about you, including the content of your communications, in order to: (a) comply with the law or respond to lawful requests or legal process; (b) protect the rights or property of Microsoft or our customers, including the enforcement of our agreements or policies governing your use of the services; or (c) act on a good faith belief that such access or disclosure is necessary to protect the personal safety of our employees, customers, or the public. We may also disclose personal information as part of a corporate transaction such as a merger or sale of assets. -------------------------------------------------------------------------------- /Testing-Readme.md: -------------------------------------------------------------------------------- 1 | ## How to test locally 2 | 3 | >**Note : Not all tests are written and they cannot mock every scenario** 4 | 5 | ### Task Build - first, build the the task (.ts) to (.js) 6 | - `tsc tasks/test/index.ts` 7 | 8 | ### Test Build - second, build the test (.ts) file to a .js file 9 | 10 | - `tsc tasks/test/tests/basic.ts` 11 | 12 | ### Test Run - thrid, you can run the test (.js) file using the below command 13 | 14 | - `node ./tasks/test/tests/basic.js` 15 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Flutter build 2 | variables: 3 | projectDirectory: sample_project 4 | 5 | jobs: 6 | - job: Android 7 | pool: 8 | vmImage: 'macOS-latest' 9 | steps: 10 | - task: FlutterInstall@0 11 | - task: FlutterBuild@0 12 | inputs: 13 | target: apk 14 | projectDirectory: $(projectDirectory) 15 | - task: FlutterTest@0 16 | inputs: 17 | projectDirectory: $(projectDirectory) 18 | - task: CopyFiles@2 19 | inputs: 20 | contents: '**/*.apk' 21 | targetFolder: '$(build.artifactStagingDirectory)' 22 | - task: PublishBuildArtifacts@1 23 | -------------------------------------------------------------------------------- /flutter test output samples.txt: -------------------------------------------------------------------------------- 1 | 00:00 +3: D:/user/dir/work_dir/azure-flutter-tasks/sample_project/test/widget_test.dart: And another test 2 | 3 | 00:23 +0: /Users/runner/work/1/s/src/app_name/test/XXX/YYY.dart: (setUpAll) 4 | 5 | 00:00 +0: loading D:\a\1\s\project\test\api_services\external_api_services_test.dart -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/icon.png -------------------------------------------------------------------------------- /images/step_analyze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/images/step_analyze.png -------------------------------------------------------------------------------- /images/step_build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/images/step_build.png -------------------------------------------------------------------------------- /images/step_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/images/step_command.png -------------------------------------------------------------------------------- /images/step_env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/images/step_env.png -------------------------------------------------------------------------------- /images/step_install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/images/step_install.png -------------------------------------------------------------------------------- /images/step_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/images/step_test.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azure-flutter-tasks", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /publish-build-with-npm.sh: -------------------------------------------------------------------------------- 1 | # Build TS 2 | npm install -g typescript 3 | cd ./tasks/test 4 | npm install 5 | tsc 6 | cd ../install 7 | npm install 8 | tsc 9 | cd ../build 10 | npm install 11 | tsc 12 | cd ../analyze 13 | npm install 14 | tsc 15 | cd ../command 16 | npm install 17 | tsc 18 | cd ../env 19 | npm install 20 | tsc 21 | cd ../../ 22 | 23 | # Create extension 24 | npm i -g tfx-cli 25 | tfx extension create --manifest-globs vss-extension.json 26 | 27 | # > Author https://marketplace.visualstudio.com/manage/publishers/hey24sheep -------------------------------------------------------------------------------- /publish-build.sh: -------------------------------------------------------------------------------- 1 | # Build TS 2 | cd ./tasks/test 3 | tsc 4 | cd ../install 5 | tsc 6 | cd ../build 7 | tsc 8 | cd ../analyze 9 | tsc 10 | cd ../command 11 | tsc 12 | cd ../env 13 | tsc 14 | cd ../../ 15 | 16 | # install tfx 17 | npm install -g tfx-cli 18 | 19 | # Create extension 20 | tfx extension create --manifest-globs vss-extension.json 21 | 22 | # > Author https://marketplace.visualstudio.com/manage/publishers/hey24sheep -------------------------------------------------------------------------------- /sample_project/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/Flutter/flutter_export_environment.sh 64 | **/ios/ServiceDefinitions.json 65 | **/ios/Runner/GeneratedPluginRegistrant.* 66 | 67 | # Exceptions to above rules. 68 | !**/ios/**/default.mode1v3 69 | !**/ios/**/default.mode2v3 70 | !**/ios/**/default.pbxuser 71 | !**/ios/**/default.perspectivev3 72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 73 | -------------------------------------------------------------------------------- /sample_project/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: a68b03e9cefa549d70885fe5390dee9d2c0ad30b 8 | channel: dev 9 | -------------------------------------------------------------------------------- /sample_project/README.md: -------------------------------------------------------------------------------- 1 | # sample_project 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | For help getting started with Flutter, view our online 8 | [documentation](https://flutter.io/). 9 | -------------------------------------------------------------------------------- /sample_project/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 27 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.sampleproject" 37 | minSdkVersion 16 38 | targetSdkVersion 27 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /sample_project/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /sample_project/android/app/src/main/java/com/example/sampleproject/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.sampleproject; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample_project/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /sample_project/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample_project/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample_project/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample_project/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample_project/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample_project/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /sample_project/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.1.2' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /sample_project/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /sample_project/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /sample_project/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /sample_project/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample_project/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /sample_project/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /sample_project/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample_project/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /sample_project/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample_project/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /sample_project/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/sample_project/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /sample_project/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /sample_project/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /sample_project/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample_project/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | sample_project 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /sample_project/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample_project/junit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample_project/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(new MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | // This widget is the root of your application. 7 | @override 8 | Widget build(BuildContext context) { 9 | return new MaterialApp( 10 | title: 'Flutter Demo', 11 | theme: new ThemeData( 12 | // This is the theme of your application. 13 | // 14 | // Try running your application with "flutter run". You'll see the 15 | // application has a blue toolbar. Then, without quitting the app, try 16 | // changing the primarySwatch below to Colors.green and then invoke 17 | // "hot reload" (press "r" in the console where you ran "flutter run", 18 | // or press Run > Flutter Hot Reload in IntelliJ). Notice that the 19 | // counter didn't reset back to zero; the application is not restarted. 20 | primarySwatch: Colors.blue, 21 | ), 22 | home: new MyHomePage(title: 'Flutter Demo Home Page'), 23 | ); 24 | } 25 | } 26 | 27 | class MyHomePage extends StatefulWidget { 28 | MyHomePage({this.title = 'my home'}) : super(); 29 | 30 | // This widget is the home page of your application. It is stateful, meaning 31 | // that it has a State object (defined below) that contains fields that affect 32 | // how it looks. 33 | 34 | // This class is the configuration for the state. It holds the values (in this 35 | // case the title) provided by the parent (in this case the App widget) and 36 | // used by the build method of the State. Fields in a Widget subclass are 37 | // always marked "final". 38 | 39 | final String title; 40 | 41 | @override 42 | _MyHomePageState createState() => new _MyHomePageState(); 43 | } 44 | 45 | class _MyHomePageState extends State { 46 | int _counter = 0; 47 | 48 | void _incrementCounter() { 49 | setState(() { 50 | // This call to setState tells the Flutter framework that something has 51 | // changed in this State, which causes it to rerun the build method below 52 | // so that the display can reflect the updated values. If we changed 53 | // _counter without calling setState(), then the build method would not be 54 | // called again, and so nothing would appear to happen. 55 | _counter++; 56 | }); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | // This method is rerun every time setState is called, for instance as done 62 | // by the _incrementCounter method above. 63 | // 64 | // The Flutter framework has been optimized to make rerunning build methods 65 | // fast, so that you can just rebuild anything that needs updating rather 66 | // than having to individually change instances of widgets. 67 | return new Scaffold( 68 | appBar: new AppBar( 69 | // Here we take the value from the MyHomePage object that was created by 70 | // the App.build method, and use it to set our appbar title. 71 | title: new Text(widget.title), 72 | ), 73 | body: new Center( 74 | // Center is a layout widget. It takes a single child and positions it 75 | // in the middle of the parent. 76 | child: new Column( 77 | // Column is also layout widget. It takes a list of children and 78 | // arranges them vertically. By default, it sizes itself to fit its 79 | // children horizontally, and tries to be as tall as its parent. 80 | // 81 | // Invoke "debug paint" (press "p" in the console where you ran 82 | // "flutter run", or select "Toggle Debug Paint" from the Flutter tool 83 | // window in IntelliJ) to see the wireframe for each widget. 84 | // 85 | // Column has various properties to control how it sizes itself and 86 | // how it positions its children. Here we use mainAxisAlignment to 87 | // center the children vertically; the main axis here is the vertical 88 | // axis because Columns are vertical (the cross axis would be 89 | // horizontal). 90 | mainAxisAlignment: MainAxisAlignment.center, 91 | children: [ 92 | new Text( 93 | 'You have pushed the button this many times:', 94 | ), 95 | new Text( 96 | '$_counter', 97 | style: Theme.of(context).textTheme.headline4, 98 | ), 99 | ], 100 | ), 101 | ), 102 | floatingActionButton: new FloatingActionButton( 103 | onPressed: _incrementCounter, 104 | tooltip: 'Increment', 105 | child: new Icon(Icons.add), 106 | ), // This trailing comma makes auto-formatting nicer for build methods. 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /sample_project/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sample_project 2 | description: A sample project 3 | publish_to: "none" 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ^3.0.0 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | cupertino_icons: ^1.0.0 13 | intl: ^0.18.0 14 | http: ^1.0.0 15 | json_annotation: any 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | build_runner: ^2.1.8 21 | 22 | flutter: 23 | uses-material-design: true 24 | -------------------------------------------------------------------------------- /sample_project/test/1_api_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:sample_project/main.dart'; 11 | 12 | void main() { 13 | test('1_api_t1', () { 14 | expect('1', '1'); 15 | }); 16 | 17 | test('1_api_t2', () { 18 | expect('1', '1'); 19 | }); 20 | 21 | test('1_api_t3', () { 22 | assert(true, true); 23 | }); 24 | 25 | test('1_api_fail_t4', () { 26 | expect('1', '2'); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /sample_project/test/2_api_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:sample_project/main.dart'; 11 | 12 | void main() { 13 | test('2_api_t1', () { 14 | expect('1', '1'); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /sample_project/test/3_services_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:sample_project/main.dart'; 11 | 12 | void main() { 13 | test('3_service_t1', () { 14 | expect(true, true); 15 | }); 16 | 17 | test('3_service_t2', () { 18 | assert(true, true); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /sample_project/test/4_widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:sample_project/main.dart'; 11 | 12 | void main() { 13 | testWidgets('4_widget_t1', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | 30 | testWidgets('4_widget_t2', (WidgetTester tester) async { 31 | // Build our app and trigger a frame. 32 | await tester.pumpWidget(new MyApp()); 33 | 34 | // Verify that our counter starts at 0. 35 | expect(find.text('0'), findsOneWidget); 36 | expect(find.text('1'), findsNothing); 37 | 38 | // Tap the '+' icon and trigger a frame. 39 | await tester.tap(find.byIcon(Icons.add)); 40 | await tester.pump(); 41 | 42 | // Verify that our counter has incremented. 43 | expect(find.text('0'), findsNothing); 44 | expect(find.text('1'), findsOneWidget); 45 | }); 46 | 47 | testWidgets('4_widget_t3', (WidgetTester tester) async { 48 | //throw Error(); 49 | // Build our app and trigger a frame. 50 | await tester.pumpWidget(new MyApp()); 51 | 52 | // Verify that our counter starts at 0. 53 | expect(find.text('0'), findsOneWidget); 54 | expect(find.text('1'), findsNothing); 55 | 56 | // Tap the '+' icon and trigger a frame. 57 | await tester.tap(find.byIcon(Icons.add)); 58 | await tester.pump(); 59 | 60 | // Verify that our counter has incremented. 61 | expect(find.text('0'), findsNothing); 62 | expect(find.text('1'), findsOneWidget); 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /sample_project/test/5_widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:sample_project/main.dart'; 11 | 12 | void main() { 13 | testWidgets('5_widget_t1', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /tasks/analyze/.taskkey: -------------------------------------------------------------------------------- 1 | 41fdd704-6f7a-4a9a-871b-ad9ffd7555c5 -------------------------------------------------------------------------------- /tasks/analyze/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/tasks/analyze/icon.png -------------------------------------------------------------------------------- /tasks/analyze/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | analyze_icon_1024 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tasks/analyze/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const task = require("azure-pipelines-task-lib/task"); 13 | const path = require("path"); 14 | const FLUTTER_TOOL_PATH_ENV_VAR = 'FlutterToolPath'; 15 | function main() { 16 | return __awaiter(this, void 0, void 0, function* () { 17 | // 1. Check flutter environment 18 | var flutterPath = task.getVariable(FLUTTER_TOOL_PATH_ENV_VAR) || process.env[FLUTTER_TOOL_PATH_ENV_VAR] || task.getInput('flutterDirectory', false); 19 | flutterPath = path.join(flutterPath, "flutter"); 20 | if (!flutterPath) { 21 | throw new Error(`The '${FLUTTER_TOOL_PATH_ENV_VAR}' environment variable must be set before using this task (you can use 'flutterinstall' task).`); 22 | } 23 | // 2. Move current working directory to project 24 | let projectDirectory = task.getPathInput('projectDirectory', false, false); 25 | if (projectDirectory) { 26 | task.debug(`Moving to ${projectDirectory}`); 27 | task.cd(projectDirectory); 28 | } 29 | task.debug(`Project's directory : ${task.cwd()}`); 30 | // 3. Get common input 31 | let pubGet = task.getBoolInput('pubGet', false); 32 | let extraArgs = task.getInput('extraArgs', false); 33 | let extraArgSep = task.getInput('extraArgsSeparator', false); 34 | if (!extraArgSep || extraArgSep === '') { 35 | extraArgSep = " "; 36 | } 37 | // 4. Builds 38 | yield runAnalyze(flutterPath, pubGet, extraArgs, extraArgSep); 39 | task.setResult(task.TaskResult.Succeeded, "Analyze succeeded"); 40 | }); 41 | } 42 | function runAnalyze(flutter, pubGet, extraArgs, extraArgSep) { 43 | return __awaiter(this, void 0, void 0, function* () { 44 | var args = [ 45 | "analyze", 46 | ]; 47 | if (pubGet) { 48 | args.push("--pub"); 49 | } 50 | else { 51 | args.push("--no-pub"); 52 | } 53 | if (extraArgs) { 54 | var splitted = extraArgs.split(extraArgSep); 55 | args.push(...splitted); 56 | } 57 | var result = yield task.exec(flutter, args); 58 | if (result !== 0) { 59 | throw new Error("Analyze Failed"); 60 | } 61 | }); 62 | } 63 | main().catch(error => { 64 | task.setResult(task.TaskResult.Failed, error); 65 | }); 66 | -------------------------------------------------------------------------------- /tasks/analyze/index.ts: -------------------------------------------------------------------------------- 1 | import * as task from "azure-pipelines-task-lib/task"; 2 | import * as path from "path"; 3 | 4 | const FLUTTER_TOOL_PATH_ENV_VAR: string = 'FlutterToolPath'; 5 | 6 | async function main(): Promise { 7 | // 1. Check flutter environment 8 | var flutterPath = task.getVariable(FLUTTER_TOOL_PATH_ENV_VAR) || process.env[FLUTTER_TOOL_PATH_ENV_VAR] || task.getInput('flutterDirectory', false); 9 | flutterPath = path.join(flutterPath, "flutter") 10 | if (!flutterPath) { 11 | throw new Error(`The '${FLUTTER_TOOL_PATH_ENV_VAR}' environment variable must be set before using this task (you can use 'flutterinstall' task).`); 12 | } 13 | 14 | // 2. Move current working directory to project 15 | let projectDirectory = task.getPathInput('projectDirectory', false, false); 16 | if (projectDirectory) { 17 | task.debug(`Moving to ${projectDirectory}`); 18 | task.cd(projectDirectory); 19 | } 20 | task.debug(`Project's directory : ${task.cwd()}`); 21 | 22 | // 3. Get common input 23 | let pubGet = task.getBoolInput('pubGet', false); 24 | let extraArgs = task.getInput('extraArgs', false); 25 | 26 | let extraArgSep = task.getInput('extraArgsSeparator', false); 27 | 28 | if (!extraArgSep || extraArgSep === '') { 29 | extraArgSep = " "; 30 | } 31 | 32 | // 4. Builds 33 | await runAnalyze(flutterPath, pubGet, extraArgs, extraArgSep); 34 | 35 | task.setResult(task.TaskResult.Succeeded, "Analyze succeeded"); 36 | } 37 | 38 | async function runAnalyze(flutter: string, pubGet?: boolean, 39 | extraArgs?: string, 40 | extraArgSep?: string) { 41 | 42 | var args = [ 43 | "analyze", 44 | ]; 45 | 46 | if (pubGet) { 47 | args.push("--pub"); 48 | } else { 49 | args.push("--no-pub"); 50 | } 51 | 52 | if (extraArgs) { 53 | var splitted = extraArgs.split(extraArgSep); 54 | args.push(...splitted); 55 | } 56 | 57 | var result = await task.exec(flutter, args); 58 | 59 | if (result !== 0) { 60 | throw new Error("Analyze Failed"); 61 | } 62 | } 63 | 64 | main().catch(error => { 65 | task.setResult(task.TaskResult.Failed, error); 66 | }); -------------------------------------------------------------------------------- /tasks/analyze/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutteranalyze", 3 | "version": "0.4.0", 4 | "description": "Flutter Analyze Task", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/hey24sheep/azure-flutter-tasks.git" 12 | }, 13 | "author": "Hey24sheep", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/hey24sheep/azure-flutter-tasks/issues" 17 | }, 18 | "homepage": "https://github.com/hey24sheep/azure-flutter-tasks#readme", 19 | "dependencies": { 20 | "azure-pipelines-task-lib": "^4.17.2", 21 | "azure-pipelines-tool-lib": "^2.0.7" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^22.5.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tasks/analyze/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1160e06f-9896-421f-89ff-783ae03781c5", 3 | "name": "FlutterAnalyzeTask", 4 | "friendlyName": "Flutter Analyze Task", 5 | "description": "Analyze a Flutter application project.", 6 | "helpMarkDown": "[More Information](https://github.com/hey24sheep/azure-flutter-tasks)", 7 | "category": "Tool", 8 | "author": "Hey24sheep", 9 | "version": { 10 | "Major": 0, 11 | "Minor": 4, 12 | "Patch": 1 13 | }, 14 | "groups": [], 15 | "instanceNameFormat": "Flutter Analyze", 16 | "inputs": [ 17 | { 18 | "name": "projectDirectory", 19 | "type": "filePath", 20 | "label": "Path to the project folder", 21 | "defaultValue": ".", 22 | "helpMarkDown": "The path to the project folder (which contains the `pubspec.yaml`)", 23 | "required": true 24 | }, 25 | { 26 | "name": "pubGet", 27 | "type": "boolean", 28 | "label": "Run pub get before analyze", 29 | "defaultValue": "true", 30 | "required": false, 31 | "helpMarkDown": "Sets the --[no-]pub arg. Defaults to true" 32 | }, 33 | { 34 | "name": "extraArgs", 35 | "type": "string", 36 | "label": "Extra command arguments", 37 | "defaultValue": "", 38 | "required": false, 39 | "helpMarkDown": "Extra command arguments like --[no-]fatal-infos --fatal-warnings. Note: Space separated" 40 | }, 41 | { 42 | "name": "extraArgsSeparator", 43 | "type": "string", 44 | "label": "Extra (`extraArgs`) argument separator. Default: single space (' ')", 45 | "defaultValue": " ", 46 | "required": false, 47 | "helpMarkDown": "Separator used in params passed to `extraArgs`. Default is single space (' ')" 48 | } 49 | ], 50 | "execution": { 51 | "Node10": { 52 | "target": "index.js" 53 | }, 54 | "Node16": { 55 | "target": "index.js" 56 | }, 57 | "Node20_1": { 58 | "target": "index.js" 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /tasks/analyze/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | }, 6 | "exclude": [ 7 | "node_modules" 8 | ] 9 | } -------------------------------------------------------------------------------- /tasks/analyze/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest" 4 | ], 5 | "rules": { 6 | "member-ordering": [ false ], 7 | "interface-name": [ false ], 8 | "object-literal-sort-keys": false, 9 | 10 | "whitespace": [ 11 | "check-branch", 12 | "check-decl", 13 | "check-operator", 14 | "check-module", 15 | "check-separator", 16 | "check-type" 17 | ], 18 | 19 | "one-line": [ 20 | "check-open-brace", 21 | "check-whitespace" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /tasks/build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/tasks/build/icon.png -------------------------------------------------------------------------------- /tasks/build/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | build_icon_1024 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /tasks/build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutterbuild", 3 | "version": "0.4.0", 4 | "description": "Flutter Build Task", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/hey24sheep/azure-flutter-tasks.git" 12 | }, 13 | "author": "Hey24sheep", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/hey24sheep/azure-flutter-tasks/issues" 17 | }, 18 | "homepage": "https://github.com/hey24sheep/azure-flutter-tasks#readme", 19 | "dependencies": { 20 | "azure-pipelines-task-lib": "^4.17.2", 21 | "azure-pipelines-tool-lib": "^2.0.7" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^22.5.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tasks/build/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "5721365d-ec15-4b77-8d2f-430f93368c1f", 3 | "name": "FlutterBuild", 4 | "friendlyName": "Flutter Build Task", 5 | "description": "Build a Flutter application project.", 6 | "helpMarkDown": "[More Information](https://github.com/hey24sheep/azure-flutter-tasks)", 7 | "category": "Build", 8 | "visibility": [ 9 | "Build" 10 | ], 11 | "author": "Hey24sheep", 12 | "version": { 13 | "Major": 0, 14 | "Minor": 4, 15 | "Patch": 1 16 | }, 17 | "groups": [], 18 | "instanceNameFormat": "Flutter Build $(target)", 19 | "inputs": [ 20 | { 21 | "name": "target", 22 | "type": "pickList", 23 | "label": "Target platform", 24 | "defaultValue": "apk", 25 | "helpMarkDown": "The target platform of your application.", 26 | "required": true, 27 | "options": { 28 | "all": "All Platforms", 29 | "mobile": "All Mobile Platform Only", 30 | "desktop": "All Desktop Platform Only", 31 | "ios": "iOS", 32 | "ipa": "iOS (IPA)", 33 | "apk": "Android (apk)", 34 | "aab": "Android (aab)", 35 | "web": "Web", 36 | "windows": "Desktop (windows)", 37 | "macos": "Desktop (macos)", 38 | "linux": "Desktop (linux)" 39 | } 40 | }, 41 | { 42 | "name": "projectDirectory", 43 | "type": "filePath", 44 | "label": "Path to the project folder", 45 | "defaultValue": ".", 46 | "helpMarkDown": "The path to the project folder (which contains the `pubspec.yaml`)", 47 | "required": true 48 | }, 49 | { 50 | "name": "flutterDirectory", 51 | "type": "filePath", 52 | "label": "Path to the Flutter SDK", 53 | "defaultValue": "", 54 | "helpMarkDown": "Path to the Flutter SDK if not using Flutter Install task before this one.", 55 | "required": false 56 | }, 57 | { 58 | "name": "verboseMode", 59 | "type": "boolean", 60 | "label": "Verbose", 61 | "defaultValue": false, 62 | "helpMarkDown": "The output from this command might aid in diagnosing the problem.", 63 | "required": false 64 | }, 65 | { 66 | "name": "debugMode", 67 | "type": "boolean", 68 | "label": "Debug", 69 | "defaultValue": false, 70 | "helpMarkDown": "Force build type to be Debug instead of Release. For release builds, do not select any of the Debug or Profile mode. This flag cannot be combined with `profileMode=true`", 71 | "required": false 72 | }, 73 | { 74 | "name": "profileMode", 75 | "type": "boolean", 76 | "label": "Profile", 77 | "defaultValue": false, 78 | "helpMarkDown": "Force build type to be Profile. This flag cannot be combined with `debugMode=true`", 79 | "required": false, 80 | "visibleRule": "debugMode = false" 81 | }, 82 | { 83 | "name": "splitPerAbi", 84 | "type": "boolean", 85 | "label": "Split Per ABI", 86 | "defaultValue": false, 87 | "helpMarkDown": "Will enable --split-per-abi option. For more details check https://flutter.dev/docs/deployment/android", 88 | "required": false 89 | }, 90 | { 91 | "name": "buildFlavour", 92 | "type": "string", 93 | "label": "Build Flavour", 94 | "defaultValue": "", 95 | "helpMarkDown": "The flavour of the build, (e.g. production, development). Must match either an Android Gradle flavour or Xcode scheme.", 96 | "required": false 97 | }, 98 | { 99 | "name": "buildNumber", 100 | "type": "string", 101 | "label": "Package build number", 102 | "defaultValue": "", 103 | "required": false, 104 | "helpMarkDown": "An identifier used as an internal version number. Each build must have a unique identifier to differentiate it from previous builds." 105 | }, 106 | { 107 | "name": "buildName", 108 | "type": "string", 109 | "label": "Version number", 110 | "defaultValue": "", 111 | "required": false, 112 | "helpMarkDown": "An `x.y.z` string used as the version number shown to users. For each new version of your app, you should provide a version number to differentiate it from previous versions." 113 | }, 114 | { 115 | "name": "entryPoint", 116 | "type": "string", 117 | "label": "Entry-point", 118 | "defaultValue": "", 119 | "required": false, 120 | "helpMarkDown": "The main entry-point file of the application, as run on the device. (defaults to 'lib/main.dart')" 121 | }, 122 | { 123 | "name": "apkTargetPlatform", 124 | "type": "pickList", 125 | "label": "Target platform architecture", 126 | "defaultValue": "default", 127 | "required": false, 128 | "helpMarkDown": "Sets the target android platform architecture", 129 | "visibleRule": "target = apk", 130 | "options": { 131 | "default" : "Default (set by Flutter)", 132 | "android-arm": "Arm", 133 | "android-arm64": "Arm64", 134 | "android-x86": "Android-X86", 135 | "android-x64": "Android-X64", 136 | "custom": "Custom" 137 | } 138 | }, 139 | { 140 | "name": "customApkTargetPlatform", 141 | "type": "string", 142 | "label": "Custom target platform architecutre", 143 | "defaultValue": "", 144 | "required": true, 145 | "visibleRule":"apkTargetPlatform = custom", 146 | "helpMarkDown": "Enter your --target-platform in here like android-arm,android-arm64,android-x64 (comma separated)" 147 | }, 148 | { 149 | "name": "iosTargetPlatform", 150 | "type": "pickList", 151 | "label": "Target platform architecture", 152 | "defaultValue": "device", 153 | "required": false, 154 | "helpMarkDown": "Sets the target iOS platform", 155 | "visibleRule": "target = ios", 156 | "options": { 157 | "device": "Device", 158 | "simulator": "Simulator" 159 | } 160 | }, 161 | { 162 | "name": "iosCodesign", 163 | "type": "boolean", 164 | "label": "Codesign application bundle", 165 | "defaultValue": "true", 166 | "required": false, 167 | "helpMarkDown": "Indicates whether the application bundle should be codesigned. **Warning: you must install a valid certificate before build with the `Install an Apple Certificate`task**", 168 | "visibleRule": "target = ios" 169 | }, 170 | { 171 | "name": "exportOptionsPlist", 172 | "type": "string", 173 | "label": "Export Options Plist path", 174 | "defaultValue": "", 175 | "required": false, 176 | "helpMarkDown": "--export-options-plist=path/to/ExportOptions.plist. This flag cannot be combined with '--split-debug-info'. Optionally export an IPA with these options. See 'xcodebuild -h' for available exportOptionsPlist keys", 177 | "visibleRule": "target = ipa" 178 | }, 179 | { 180 | "name": "dartDefine", 181 | "type": "string", 182 | "label": "key=val single pair for single --dart-define arg", 183 | "defaultValue": "", 184 | "required": false, 185 | "helpMarkDown": "Parameter passed to --dart-define, use as : 'foo=bar' (use 'dartDefineMulti' for multiple args)" 186 | }, 187 | { 188 | "name": "dartDefineMulti", 189 | "type": "string", 190 | "label": "key=value pairs (space separated) for multiple --dart-define args", 191 | "defaultValue": "", 192 | "required": false, 193 | "helpMarkDown": "Parameters passed to multiple --dart-define, use as : 'foo=bar key1=val1 key2=val2'" 194 | }, 195 | { 196 | "name": "dartDefineMultiArgSeparator", 197 | "type": "string", 198 | "label": "Dart Define (Multi `dartDefineMulti`) argument separator. Default: single space (' ')", 199 | "defaultValue": " ", 200 | "required": false, 201 | "helpMarkDown": "Separator used in params passed to `dartDefineMulti`. Default is single space (' ')" 202 | }, 203 | { 204 | "name": "extraArgs", 205 | "type": "string", 206 | "label": "Extra command arguments", 207 | "defaultValue": "", 208 | "required": false, 209 | "helpMarkDown": "Extra command arguments like --no-tree-shake-icons. Space separated" 210 | }, 211 | { 212 | "name": "extraArgsSeparator", 213 | "type": "string", 214 | "label": "Extra (`extraArgs`) argument separator. Default: single space (' ')", 215 | "defaultValue": " ", 216 | "required": false, 217 | "helpMarkDown": "Separator used in params passed to `extraArgs`. Default is single space (' ')" 218 | } 219 | ], 220 | "execution": { 221 | "Node10": { 222 | "target": "index.js" 223 | }, 224 | "Node16": { 225 | "target": "index.js" 226 | }, 227 | "Node20_1": { 228 | "target": "index.js" 229 | } 230 | } 231 | } -------------------------------------------------------------------------------- /tasks/build/tests/apk.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const mr = require("azure-pipelines-task-lib/mock-run"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | const taskPath = path.join(__dirname, "../index.js"); 7 | var runner = new mr.TaskMockRunner(taskPath); 8 | function assertDirectory(path) { 9 | if (!fs.existsSync(path)) { 10 | fs.mkdirSync(path); 11 | } 12 | } 13 | // ENV 14 | const rootPath = path.join(__dirname, "..", "..", ".."); 15 | const tempPath = path.join(rootPath, "temp"); 16 | const agentPath = path.join(tempPath, "agent"); 17 | const dropPath = path.join(tempPath, "drop"); 18 | process.env["BUILD_BUILDNUMBER"] = "1"; 19 | assertDirectory(tempPath); 20 | assertDirectory(agentPath); 21 | assertDirectory(dropPath); 22 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home")); 23 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools")); 24 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp")); 25 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build")); 26 | // Run install tests 27 | process.env["FlutterToolPath"] = path.join(agentPath, "tools", "Flutter", "2.5.3-stable", "windows", "flutter", "bin"); 28 | runner.setInput("target", "apk"); 29 | runner.setInput("buildName", "com.hey24sheep.vsts"); 30 | runner.setInput("buildNumber", "12"); 31 | runner.setInput("projectDirectory", path.join(rootPath, "sample_project")); 32 | runner.setInput("outputDirectory", dropPath); 33 | runner.setInput("buildFlavour", "development"); 34 | runner.setInput("debugMode", "false"); 35 | runner.run(true); 36 | -------------------------------------------------------------------------------- /tasks/build/tests/apk.ts: -------------------------------------------------------------------------------- 1 | import * as mr from 'azure-pipelines-task-lib/mock-run'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | 5 | const taskPath = path.join(__dirname, "../index.js"); 6 | var runner = new mr.TaskMockRunner(taskPath); 7 | 8 | function assertDirectory(path: string) { 9 | if (!fs.existsSync(path)) { 10 | fs.mkdirSync(path); 11 | } 12 | } 13 | 14 | // ENV 15 | const rootPath = path.join(__dirname, "..", "..", ".."); 16 | const tempPath = path.join(rootPath, "temp"); 17 | const agentPath = path.join(tempPath, "agent"); 18 | const dropPath = path.join(tempPath, "drop"); 19 | process.env["BUILD_BUILDNUMBER"] = "1"; 20 | assertDirectory(tempPath); 21 | assertDirectory(agentPath); 22 | assertDirectory(dropPath); 23 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home")); 24 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools")); 25 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp")); 26 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build")); 27 | 28 | // Run install tests 29 | process.env["FlutterToolPath"] = path.join(agentPath, "tools", "Flutter", "2.5.3-stable", "windows", "flutter", "bin"); 30 | 31 | runner.setInput("target", "apk"); 32 | runner.setInput("buildName", "com.hey24sheep.vsts"); 33 | runner.setInput("buildNumber", "12"); 34 | runner.setInput("projectDirectory", path.join(rootPath, "sample_project")); 35 | runner.setInput("outputDirectory", dropPath); 36 | runner.setInput("buildFlavour", "development"); 37 | runner.setInput("debugMode", "false"); 38 | 39 | runner.run(true); -------------------------------------------------------------------------------- /tasks/build/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | }, 6 | "exclude": [ 7 | "node_modules" 8 | ] 9 | } -------------------------------------------------------------------------------- /tasks/build/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest" 4 | ], 5 | "rules": { 6 | "member-ordering": [ false ], 7 | "interface-name": [ false ], 8 | "object-literal-sort-keys": false, 9 | 10 | "whitespace": [ 11 | "check-branch", 12 | "check-decl", 13 | "check-operator", 14 | "check-module", 15 | "check-separator", 16 | "check-type" 17 | ], 18 | 19 | "one-line": [ 20 | "check-open-brace", 21 | "check-whitespace" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /tasks/command/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/tasks/command/icon.png -------------------------------------------------------------------------------- /tasks/command/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const task = require("azure-pipelines-task-lib/task"); 13 | const path = require("path"); 14 | const FLUTTER_TOOL_PATH_ENV_VAR = 'FlutterToolPath'; 15 | function main() { 16 | return __awaiter(this, void 0, void 0, function* () { 17 | // 1. Check flutter environment 18 | var flutterPath = task.getVariable(FLUTTER_TOOL_PATH_ENV_VAR) || process.env[FLUTTER_TOOL_PATH_ENV_VAR] || task.getInput('flutterDirectory', false); 19 | flutterPath = path.join(flutterPath, "flutter"); 20 | if (!flutterPath) { 21 | throw new Error(`The '${FLUTTER_TOOL_PATH_ENV_VAR}' environment variable must be set before using this task (you can use 'flutterinstall' task).`); 22 | } 23 | // 2. Move current working directory to project 24 | let projectDirectory = task.getPathInput('projectDirectory', false, false); 25 | if (projectDirectory) { 26 | task.debug(`Moving to ${projectDirectory}`); 27 | task.cd(projectDirectory); 28 | } 29 | task.debug(`Project's directory : ${task.cwd()}`); 30 | let args = task.getInput('arguments', false); 31 | let argSep = task.getInput('argSeparator', false); 32 | if (!argSep || argSep === '') { 33 | argSep = " "; 34 | } 35 | let splittedArgs = args.split(argSep) 36 | .map(function (x) { 37 | return x.trim(); 38 | }) 39 | .filter(function (x) { 40 | return x.length; 41 | }); 42 | var result = yield task.exec(flutterPath, splittedArgs); 43 | if (result !== 0) { 44 | task.setResult(task.TaskResult.Failed, "Command execution failed"); 45 | } 46 | else { 47 | task.setResult(task.TaskResult.Succeeded, "Commaned execution succeeded"); 48 | } 49 | }); 50 | } 51 | main().catch(error => { 52 | task.setResult(task.TaskResult.Failed, error); 53 | }); 54 | -------------------------------------------------------------------------------- /tasks/command/index.ts: -------------------------------------------------------------------------------- 1 | import * as task from "azure-pipelines-task-lib/task"; 2 | import * as path from "path"; 3 | 4 | const FLUTTER_TOOL_PATH_ENV_VAR: string = 'FlutterToolPath'; 5 | 6 | async function main(): Promise { 7 | // 1. Check flutter environment 8 | var flutterPath = task.getVariable(FLUTTER_TOOL_PATH_ENV_VAR) || process.env[FLUTTER_TOOL_PATH_ENV_VAR] || task.getInput('flutterDirectory', false); 9 | flutterPath = path.join(flutterPath, "flutter") 10 | if (!flutterPath) { 11 | throw new Error(`The '${FLUTTER_TOOL_PATH_ENV_VAR}' environment variable must be set before using this task (you can use 'flutterinstall' task).`); 12 | } 13 | 14 | // 2. Move current working directory to project 15 | let projectDirectory = task.getPathInput('projectDirectory', false, false); 16 | if (projectDirectory) { 17 | task.debug(`Moving to ${projectDirectory}`); 18 | task.cd(projectDirectory); 19 | } 20 | task.debug(`Project's directory : ${task.cwd()}`); 21 | 22 | let args = task.getInput('arguments', false); 23 | 24 | let argSep = task.getInput('argSeparator', false); 25 | 26 | if (!argSep || argSep === '') { 27 | argSep = " "; 28 | } 29 | 30 | let splittedArgs = args.split(argSep) 31 | .map(function (x) { 32 | return x.trim(); 33 | }) 34 | .filter(function (x) { 35 | return x.length; 36 | }); 37 | 38 | var result = await task.exec(flutterPath, splittedArgs); 39 | 40 | if (result !== 0) { 41 | task.setResult(task.TaskResult.Failed, "Command execution failed"); 42 | } 43 | else { 44 | task.setResult(task.TaskResult.Succeeded, "Commaned execution succeeded"); 45 | } 46 | 47 | } 48 | 49 | main().catch(error => { 50 | task.setResult(task.TaskResult.Failed, error); 51 | }); 52 | -------------------------------------------------------------------------------- /tasks/command/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluttercommand", 3 | "version": "0.4.0", 4 | "description": "Flutter Command Task", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/hey24sheep/azure-flutter-tasks.git" 12 | }, 13 | "author": "Hey24sheep", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/hey24sheep/azure-flutter-tasks/issues" 17 | }, 18 | "homepage": "https://github.com/hey24sheep/azure-flutter-tasks#readme", 19 | "dependencies": { 20 | "azure-pipelines-task-lib": "^4.17.2", 21 | "azure-pipelines-tool-lib": "^2.0.7" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^22.5.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tasks/command/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1416022c-c995-4e2c-85ec-0cd88e54a2b0", 3 | "name": "FlutterCommand", 4 | "friendlyName": "Flutter Command Task", 5 | "description": "Launch a custom Flutter command with custom arguments.", 6 | "helpMarkDown": "Don't forget to insert a 'Flutter Install' task before. [More Information](https://github.com/hey24sheep/azure-flutter-tasks)", 7 | "category": "Tool", 8 | "author": "Hey24sheep", 9 | "version": { 10 | "Major": 0, 11 | "Minor": 4, 12 | "Patch": 1 13 | }, 14 | "groups": [], 15 | "instanceNameFormat": "Flutter Command", 16 | "inputs": [ 17 | { 18 | "name": "projectDirectory", 19 | "type": "filePath", 20 | "label": "Path to the project folder", 21 | "defaultValue": ".", 22 | "helpMarkDown": "The path to the project folder (which contains the `pubspec.yaml`)", 23 | "required": true 24 | }, 25 | { 26 | "name": "flutterDirectory", 27 | "type": "filePath", 28 | "label": "Path to the Flutter SDK", 29 | "defaultValue": "", 30 | "helpMarkDown": "Path to the Flutter SDK if not using Flutter Install task before this one.", 31 | "required": false 32 | }, 33 | { 34 | "name": "arguments", 35 | "type": "string", 36 | "label": "Arguments", 37 | "required": false, 38 | "defaultValue": "", 39 | "helpMarkDown": "Arguments passed to the flutter command." 40 | }, 41 | { 42 | "name": "argSeparator", 43 | "type": "string", 44 | "label": "Arguments (`arguments`) separator. Default: single space (' ')", 45 | "defaultValue": " ", 46 | "required": false, 47 | "helpMarkDown": "Separator used in params passed to `arguments`. Default is single space (' ')" 48 | } 49 | ], 50 | "execution": { 51 | "Node10": { 52 | "target": "index.js" 53 | }, 54 | "Node16": { 55 | "target": "index.js" 56 | }, 57 | "Node20_1": { 58 | "target": "index.js" 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /tasks/command/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | }, 6 | "exclude": [ 7 | "node_modules" 8 | ] 9 | } -------------------------------------------------------------------------------- /tasks/command/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest" 4 | ], 5 | "rules": { 6 | "member-ordering": [ false ], 7 | "interface-name": [ false ], 8 | "object-literal-sort-keys": false, 9 | 10 | "whitespace": [ 11 | "check-branch", 12 | "check-decl", 13 | "check-operator", 14 | "check-module", 15 | "check-separator", 16 | "check-type" 17 | ], 18 | 19 | "one-line": [ 20 | "check-open-brace", 21 | "check-whitespace" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /tasks/env/.taskkey: -------------------------------------------------------------------------------- 1 | c6dbce8b-3a2a-409c-bdca-42280fb41a41 -------------------------------------------------------------------------------- /tasks/env/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/tasks/env/icon.png -------------------------------------------------------------------------------- /tasks/env/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | env_var_icon_1024 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /tasks/env/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const task = require("azure-pipelines-task-lib/task"); 13 | const path = require("path"); 14 | // paths 15 | const FLUTTER_EXE_RELATIVEPATH = 'bin'; 16 | const DART_EXE_RELATIVEPATH = 'cache/dart-sdk/bin'; 17 | const FLUTTER_PUB_CACHE_RELATIVEPATH = '.pub-cache'; 18 | // env variables used as $('') like $(FlutterToolPath)/flutter 19 | const FLUTTER_TOOL_PATH_ENV_VAR = 'FlutterToolPath'; 20 | const FLUTTER_PUBCACHE_PATH_ENV_VAR = 'FlutterPubCachePath'; 21 | const DART_TOOL_PATH_ENV_VAR = 'DartToolPath'; 22 | function main() { 23 | return __awaiter(this, void 0, void 0, function* () { 24 | // 1. Get where to get the path from 25 | let pathType = task.getInput('pathType', true); 26 | var flutterDir = ''; 27 | // 1.1 Getting path 28 | if (pathType === 'customPath') { 29 | flutterDir = task.getInput('customPath', true); 30 | } 31 | else { 32 | flutterDir = task.getPathInput('flutterDirectory', true); 33 | } 34 | // 2. Creating flutter environment variable 35 | let fullFlutterPath = path.join(flutterDir, FLUTTER_EXE_RELATIVEPATH); 36 | task.debug(`Set ${FLUTTER_TOOL_PATH_ENV_VAR} with '${fullFlutterPath}'`); 37 | task.setVariable(FLUTTER_TOOL_PATH_ENV_VAR, fullFlutterPath); 38 | task.prependPath(fullFlutterPath); 39 | // 2.1 Create flutter pub-cache environment variable 40 | let fullPubCachePath = path.join(flutterDir, FLUTTER_PUB_CACHE_RELATIVEPATH); 41 | task.debug(`Set ${FLUTTER_PUBCACHE_PATH_ENV_VAR} with '${fullPubCachePath}'`); 42 | task.setVariable(FLUTTER_PUBCACHE_PATH_ENV_VAR, fullPubCachePath); 43 | task.prependPath(fullPubCachePath); 44 | // 2.2 Create dart environment variable 45 | let fullDartPath = path.join(fullFlutterPath, DART_EXE_RELATIVEPATH); 46 | task.debug(`Set ${DART_TOOL_PATH_ENV_VAR} with '${fullDartPath}'`); 47 | task.setVariable(DART_TOOL_PATH_ENV_VAR, fullDartPath); 48 | task.prependPath(fullDartPath); 49 | task.setResult(task.TaskResult.Succeeded, "Environment Variables Set"); 50 | }); 51 | } 52 | main().catch(error => { 53 | task.setResult(task.TaskResult.Failed, error); 54 | }); 55 | -------------------------------------------------------------------------------- /tasks/env/index.ts: -------------------------------------------------------------------------------- 1 | import * as task from "azure-pipelines-task-lib/task"; 2 | import * as path from 'path'; 3 | 4 | // paths 5 | const FLUTTER_EXE_RELATIVEPATH = 'bin'; 6 | const DART_EXE_RELATIVEPATH = 'cache/dart-sdk/bin'; 7 | const FLUTTER_PUB_CACHE_RELATIVEPATH = '.pub-cache'; 8 | 9 | // env variables used as $('') like $(FlutterToolPath)/flutter 10 | const FLUTTER_TOOL_PATH_ENV_VAR: string = 'FlutterToolPath'; 11 | const FLUTTER_PUBCACHE_PATH_ENV_VAR: string = 'FlutterPubCachePath'; 12 | const DART_TOOL_PATH_ENV_VAR: string = 'DartToolPath'; 13 | 14 | async function main(): Promise { 15 | // 1. Get where to get the path from 16 | let pathType = task.getInput('pathType', true); 17 | var flutterDir = ''; 18 | 19 | // 1.1 Getting path 20 | if (pathType === 'customPath') { 21 | flutterDir = task.getInput('customPath', true); 22 | } else { 23 | flutterDir = task.getPathInput('flutterDirectory', true); 24 | } 25 | 26 | // 2. Creating flutter environment variable 27 | let fullFlutterPath: string = path.join(flutterDir, FLUTTER_EXE_RELATIVEPATH); 28 | task.debug(`Set ${FLUTTER_TOOL_PATH_ENV_VAR} with '${fullFlutterPath}'`); 29 | task.setVariable(FLUTTER_TOOL_PATH_ENV_VAR, fullFlutterPath); 30 | task.prependPath(fullFlutterPath); 31 | 32 | // 2.1 Create flutter pub-cache environment variable 33 | let fullPubCachePath: string = path.join(flutterDir, FLUTTER_PUB_CACHE_RELATIVEPATH); 34 | task.debug(`Set ${FLUTTER_PUBCACHE_PATH_ENV_VAR} with '${fullPubCachePath}'`); 35 | task.setVariable(FLUTTER_PUBCACHE_PATH_ENV_VAR, fullPubCachePath); 36 | task.prependPath(fullPubCachePath); 37 | 38 | // 2.2 Create dart environment variable 39 | let fullDartPath: string = path.join(fullFlutterPath, DART_EXE_RELATIVEPATH); 40 | task.debug(`Set ${DART_TOOL_PATH_ENV_VAR} with '${fullDartPath}'`); 41 | task.setVariable(DART_TOOL_PATH_ENV_VAR, fullDartPath); 42 | task.prependPath(fullDartPath); 43 | 44 | task.setResult(task.TaskResult.Succeeded, "Environment Variables Set"); 45 | } 46 | 47 | main().catch(error => { 48 | task.setResult(task.TaskResult.Failed, error); 49 | }); 50 | -------------------------------------------------------------------------------- /tasks/env/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutterenv", 3 | "version": "0.4.0", 4 | "description": "Flutter Env Task to setup flutter environment variables", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/hey24sheep/azure-flutter-tasks.git" 12 | }, 13 | "author": "Hey24sheep", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/hey24sheep/azure-flutter-tasks/issues" 17 | }, 18 | "homepage": "https://github.com/hey24sheep/azure-flutter-tasks#readme", 19 | "dependencies": { 20 | "azure-pipelines-task-lib": "^4.17.2", 21 | "azure-pipelines-tool-lib": "^2.0.7", 22 | "request": "*", 23 | "request-promise": "*" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^22.5.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tasks/env/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "8c3e9995-7831-4d41-8d3e-bbc1a45224cf", 3 | "name": "FlutterEnv", 4 | "friendlyName": "Flutter Env Task", 5 | "description": "Setup the Flutter environment variables from custom Flutter directory path.", 6 | "helpMarkdown": "For more information, take a look at the Flutter [documentation](https://flutter.io)", 7 | "category": "Tool", 8 | "author": "Hey24sheep", 9 | "version": { 10 | "Major": 0, 11 | "Minor": 4, 12 | "Patch": 1 13 | }, 14 | "instanceNameFormat": "Flutter Env", 15 | "inputs": [ 16 | { 17 | "name": "pathType", 18 | "type": "pickList", 19 | "label": "Flutter path from", 20 | "defaultValue": "customPath", 21 | "helpMarkDown": "Type of flutter path input.", 22 | "required": true, 23 | "options": { 24 | "customPath": "Custom Path", 25 | "flutterDirectory": "File Path" 26 | } 27 | }, 28 | { 29 | "name": "customPath", 30 | "type": "string", 31 | "label": "Custom Path to Flutter directory", 32 | "defaultValue": "", 33 | "required": true, 34 | "helpMarkDown": "Path to Flutter SDK like '$(Agent.ToolsDirectory)//flutter'. Do not give path to 'bin' folder.", 35 | "visibleRule": "pathType = customPath" 36 | }, 37 | { 38 | "name": "flutterDirectory", 39 | "type": "filePath", 40 | "label": "Path to the Flutter SDK", 41 | "defaultValue": "", 42 | "helpMarkDown": "The path to the Flutter SDK. Do not give path to 'bin' folder.", 43 | "required": true, 44 | "visibleRule": "pathType = flutterDirectory" 45 | } 46 | ], 47 | "execution": { 48 | "Node10": { 49 | "target": "index.js" 50 | }, 51 | "Node16": { 52 | "target": "index.js" 53 | }, 54 | "Node20_1": { 55 | "target": "index.js" 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /tasks/env/tests/test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const mr = require("azure-pipelines-task-lib/mock-run"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | const taskPath = path.join(__dirname, "../index.js"); 7 | var runner = new mr.TaskMockRunner(taskPath); 8 | function assertDirectory(path) { 9 | if (!fs.existsSync(path)) { 10 | fs.mkdirSync(path); 11 | } 12 | } 13 | // ENV 14 | const rootPath = path.join(__dirname, "..", "..", ".."); 15 | const tempPath = path.join(rootPath, "temp"); 16 | const agentPath = path.join(tempPath, "agent"); 17 | const dropPath = path.join(tempPath, "drop"); 18 | assertDirectory(tempPath); 19 | assertDirectory(agentPath); 20 | assertDirectory(dropPath); 21 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home")); 22 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools")); 23 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp")); 24 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build")); 25 | runner.setInput("pathType", "customPath"); 26 | runner.setInput("customPath", path.join(agentPath, "tools", "Flutter", "2.5.3-stable", "windows", "flutter")); 27 | runner.run(true); 28 | -------------------------------------------------------------------------------- /tasks/env/tests/test.ts: -------------------------------------------------------------------------------- 1 | import * as mr from 'azure-pipelines-task-lib/mock-run'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | 5 | const taskPath = path.join(__dirname, "../index.js"); 6 | var runner = new mr.TaskMockRunner(taskPath); 7 | 8 | function assertDirectory(path: string) { 9 | if (!fs.existsSync(path)) { 10 | fs.mkdirSync(path); 11 | } 12 | } 13 | 14 | // ENV 15 | const rootPath = path.join(__dirname, "..", "..", ".."); 16 | const tempPath = path.join(rootPath, "temp"); 17 | const agentPath = path.join(tempPath, "agent"); 18 | const dropPath = path.join(tempPath, "drop"); 19 | assertDirectory(tempPath); 20 | assertDirectory(agentPath); 21 | assertDirectory(dropPath); 22 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home")); 23 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools")); 24 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp")); 25 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build")); 26 | 27 | runner.setInput("pathType", "customPath"); 28 | runner.setInput("customPath", path.join(agentPath, "tools", "Flutter", "2.5.3-stable", "windows", "flutter")); 29 | 30 | runner.run(true); -------------------------------------------------------------------------------- /tasks/env/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | }, 6 | "exclude": [ 7 | "node_modules" 8 | ] 9 | } -------------------------------------------------------------------------------- /tasks/env/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest" 4 | ], 5 | "rules": { 6 | "member-ordering": [ false ], 7 | "interface-name": [ false ], 8 | "object-literal-sort-keys": false, 9 | 10 | "whitespace": [ 11 | "check-branch", 12 | "check-decl", 13 | "check-operator", 14 | "check-module", 15 | "check-separator", 16 | "check-type" 17 | ], 18 | 19 | "one-line": [ 20 | "check-open-brace", 21 | "check-whitespace" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /tasks/install/.taskkey: -------------------------------------------------------------------------------- 1 | 6b2b67e2-238f-433a-8236-04d2651a9445 -------------------------------------------------------------------------------- /tasks/install/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/tasks/install/icon.png -------------------------------------------------------------------------------- /tasks/install/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | download_icon_1024 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /tasks/install/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const task = require("azure-pipelines-task-lib/task"); 13 | const tool = require("azure-pipelines-tool-lib/tool"); 14 | const os = require("os"); 15 | const path = require("path"); 16 | const request = require("request-promise"); 17 | const FLUTTER_TOOL_NAME = 'Flutter'; 18 | const FLUTTER_EXE_RELATIVEPATH = 'flutter/bin'; 19 | const DART_EXE_RELATIVEPATH = 'cache/dart-sdk/bin'; 20 | const FLUTTER_PUB_CACHE_RELATIVEPATH = 'flutter/.pub-cache/bin'; 21 | const FLUTTER_TOOL_PATH_ENV_VAR = 'FlutterToolPath'; 22 | const FLUTTER_PUBCACHE_PATH_ENV_VAR = 'FlutterPubCachePath'; 23 | const DART_TOOL_PATH_ENV_VAR = 'DartToolPath'; 24 | function main() { 25 | return __awaiter(this, void 0, void 0, function* () { 26 | task.debug(`[INFO] os.platform() = ${os.platform()}`); 27 | // 1. Getting current platform identifier 28 | let arch = findArchitecture(); 29 | task.debug(`[INFO] Simplified found arch = ${arch}`); 30 | // 2. Building version spec 31 | let mode = task.getInput('mode', true); 32 | let versionSpec = ''; 33 | let downloadUrl = ''; 34 | if (mode === 'auto') { 35 | let channel = task.getInput('channel', true); 36 | let version = task.getInput('version', true); 37 | let versionData = yield findLatestSdkVersion(channel, arch, version); 38 | versionSpec = versionData.semverVersion; 39 | downloadUrl = versionData.downloadUrl; 40 | } 41 | else { 42 | downloadUrl = task.getInput('customUrl', true); 43 | let urlSplits = downloadUrl.split('/'); 44 | let fragSplits = urlSplits[urlSplits.length - 1].split('_'); // str is flutter_windows_3.0.2-stable.zip 45 | fragSplits = fragSplits.pop().split('-'); // str is 3.0.2-stable.zip 46 | fragSplits.pop(); // remove the last stable.zip part 47 | versionSpec = fragSplits.join('-'); 48 | versionSpec = versionSpec.trim(); 49 | task.debug(`Parsed version '${versionSpec}' from custom url`); 50 | } 51 | // 3. Check if already available 52 | task.debug(`Trying to get (${FLUTTER_TOOL_NAME},${versionSpec}, ${arch}) tool from local cache`); 53 | let toolPath = tool.findLocalTool(FLUTTER_TOOL_NAME, versionSpec, arch); 54 | if (!toolPath) { 55 | // 4.1. Downloading SDK 56 | yield downloadAndCacheSdk(versionSpec, arch, downloadUrl); 57 | // 4.2. Verifying that tool is now available 58 | task.debug(`Trying again to get (${FLUTTER_TOOL_NAME},${versionSpec}, ${arch}) tool from local cache`); 59 | toolPath = tool.findLocalTool(FLUTTER_TOOL_NAME, versionSpec, arch); 60 | } 61 | // 5. Creating flutter environment variable 62 | let fullFlutterPath = path.join(toolPath, FLUTTER_EXE_RELATIVEPATH); 63 | task.debug(`Set ${FLUTTER_TOOL_PATH_ENV_VAR} with '${fullFlutterPath}'`); 64 | task.setVariable(FLUTTER_TOOL_PATH_ENV_VAR, fullFlutterPath); 65 | task.prependPath(fullFlutterPath); 66 | // 5.1 Create flutter pub-cache environment variable 67 | let fullPubCachePath = path.join(toolPath, FLUTTER_PUB_CACHE_RELATIVEPATH); 68 | task.debug(`Set ${DART_TOOL_PATH_ENV_VAR} with '${fullPubCachePath}'`); 69 | task.setVariable(FLUTTER_PUBCACHE_PATH_ENV_VAR, fullPubCachePath); 70 | task.prependPath(fullPubCachePath); 71 | // 5.2 Create dart environment variable 72 | let fullDartPath = path.join(fullFlutterPath, DART_EXE_RELATIVEPATH); 73 | task.debug(`Set ${DART_TOOL_PATH_ENV_VAR} with '${fullDartPath}'`); 74 | task.setVariable(DART_TOOL_PATH_ENV_VAR, fullDartPath); 75 | task.prependPath(fullDartPath); 76 | task.setResult(task.TaskResult.Succeeded, "Installed"); 77 | }); 78 | } 79 | function findArchitecture() { 80 | if (os.platform() === 'darwin') 81 | return "macos"; 82 | else if (os.platform() === 'linux') 83 | return "linux"; 84 | return "windows"; 85 | } 86 | function downloadAndCacheSdk(versionSpec, arch, downloadUrl) { 87 | return __awaiter(this, void 0, void 0, function* () { 88 | // 1. Download SDK archive 89 | task.debug(`Starting download archive from '${downloadUrl}'`); 90 | var bundleArchive = yield tool.downloadTool(downloadUrl); 91 | task.debug(`Succeeded to download '${bundleArchive}' archive from '${downloadUrl}'`); 92 | // 2. Extracting SDK bundle 93 | task.debug(`Extracting '${downloadUrl}' archive`); 94 | var bundleDir; 95 | if (downloadUrl.endsWith('tar.xz')) { 96 | bundleDir = yield tool.extractTar(bundleArchive); 97 | } 98 | else { 99 | bundleDir = yield tool.extractZip(bundleArchive); 100 | } 101 | task.debug(`Extracted to '${bundleDir}' '${downloadUrl}' archive`); 102 | // 3. Adding SDK bundle to cache 103 | task.debug(`Adding '${bundleDir}' to cache (${FLUTTER_TOOL_NAME},${versionSpec}, ${arch})`); 104 | tool.cacheDir(bundleDir, FLUTTER_TOOL_NAME, versionSpec, arch); 105 | }); 106 | } 107 | function findLatestSdkVersion(channel, osArch, version) { 108 | return __awaiter(this, void 0, void 0, function* () { 109 | var releasesUrl = `https://storage.googleapis.com/flutter_infra_release/releases/releases_${osArch}.json`; 110 | task.debug(`Finding version '${version}' from '${releasesUrl}'`); 111 | var body = yield request.get(releasesUrl); 112 | var json = JSON.parse(body); 113 | var currentHash = json.current_release[channel]; 114 | var arch = os.arch(); 115 | // if user gave custom arch type, use that 116 | let customArch = task.getInput('customArch', false); 117 | if (customArch && customArch.trim() !== '') { 118 | arch = customArch.trim(); 119 | } 120 | task.debug(`Using Arch type '${arch}'`); 121 | var current = json.releases.find((item) => item.hash === currentHash && item.channel == channel && item.dart_sdk_arch == arch); 122 | // if user selected custom 123 | if (version.toLowerCase() !== 'latest') { 124 | // fetch custom version 125 | version = task.getInput('customVersion', false); 126 | // filter based on custom version given 127 | let filtered = json.releases.filter((item) => item.channel == channel && uniformizeVersion(item.version) == uniformizeVersion(version)); 128 | // if more than 1 release found, that means it has multiple dart_sdk_arch like x64, arm64 129 | if (filtered.length > 1) { 130 | for (var i of filtered) { 131 | // filter releases based on arch type 132 | if (i.dart_sdk_arch && i.dart_sdk_arch === arch) { 133 | current = i; 134 | break; 135 | } 136 | current = i; 137 | } 138 | } 139 | else { 140 | current = filtered[0]; 141 | } 142 | task.debug(`Found custom version '${current.version}'`); 143 | } 144 | task.debug(`Found version hash '${current.hash}'`); 145 | return { 146 | version: current.version + '-' + channel, 147 | downloadUrl: json.base_url + '/' + current.archive, 148 | semverVersion: current.version 149 | }; 150 | }); 151 | } 152 | // Removes the 'v' prefix from given version. 153 | function uniformizeVersion(version) { 154 | if (version.startsWith('v')) { 155 | return version.substring(1); 156 | } 157 | return version; 158 | } 159 | main().catch(error => { 160 | task.setResult(task.TaskResult.Failed, error); 161 | }); 162 | -------------------------------------------------------------------------------- /tasks/install/index.ts: -------------------------------------------------------------------------------- 1 | import * as task from "azure-pipelines-task-lib/task"; 2 | import * as tool from 'azure-pipelines-tool-lib/tool'; 3 | import * as os from 'os'; 4 | import * as path from 'path'; 5 | import * as request from 'request-promise'; 6 | 7 | const FLUTTER_TOOL_NAME: string = 'Flutter'; 8 | const FLUTTER_EXE_RELATIVEPATH = 'flutter/bin'; 9 | const DART_EXE_RELATIVEPATH = 'cache/dart-sdk/bin'; 10 | const FLUTTER_PUB_CACHE_RELATIVEPATH = 'flutter/.pub-cache/bin'; 11 | const FLUTTER_TOOL_PATH_ENV_VAR: string = 'FlutterToolPath'; 12 | const FLUTTER_PUBCACHE_PATH_ENV_VAR: string = 'FlutterPubCachePath'; 13 | const DART_TOOL_PATH_ENV_VAR: string = 'DartToolPath'; 14 | 15 | async function main(): Promise { 16 | task.debug(`[INFO] os.platform() = ${os.platform()}`); 17 | 18 | // 1. Getting current platform identifier 19 | let arch = findArchitecture(); 20 | 21 | task.debug(`[INFO] Simplified found arch = ${arch}`); 22 | 23 | // 2. Building version spec 24 | let mode = task.getInput('mode', true); 25 | let versionSpec = ''; 26 | let downloadUrl = ''; 27 | if (mode === 'auto') { 28 | let channel = task.getInput('channel', true); 29 | let version = task.getInput('version', true); 30 | let versionData = await findLatestSdkVersion(channel, arch, version); 31 | versionSpec = versionData.semverVersion; 32 | downloadUrl = versionData.downloadUrl; 33 | } else { 34 | downloadUrl = task.getInput('customUrl', true); 35 | let urlSplits = downloadUrl.split('/'); 36 | let fragSplits = urlSplits[urlSplits.length - 1].split('_'); // str is flutter_windows_3.0.2-stable.zip 37 | fragSplits = fragSplits.pop().split('-'); // str is 3.0.2-stable.zip 38 | fragSplits.pop(); // remove the last stable.zip part 39 | versionSpec = fragSplits.join('-'); 40 | versionSpec = versionSpec.trim(); 41 | task.debug(`Parsed version '${versionSpec}' from custom url`); 42 | } 43 | 44 | // 3. Check if already available 45 | task.debug(`Trying to get (${FLUTTER_TOOL_NAME},${versionSpec}, ${arch}) tool from local cache`); 46 | let toolPath = tool.findLocalTool(FLUTTER_TOOL_NAME, versionSpec, arch); 47 | 48 | if (!toolPath) { 49 | // 4.1. Downloading SDK 50 | await downloadAndCacheSdk(versionSpec, arch, downloadUrl); 51 | 52 | // 4.2. Verifying that tool is now available 53 | task.debug(`Trying again to get (${FLUTTER_TOOL_NAME},${versionSpec}, ${arch}) tool from local cache`); 54 | toolPath = tool.findLocalTool(FLUTTER_TOOL_NAME, versionSpec, arch); 55 | } 56 | 57 | // 5. Creating flutter environment variable 58 | let fullFlutterPath: string = path.join(toolPath, FLUTTER_EXE_RELATIVEPATH); 59 | task.debug(`Set ${FLUTTER_TOOL_PATH_ENV_VAR} with '${fullFlutterPath}'`); 60 | task.setVariable(FLUTTER_TOOL_PATH_ENV_VAR, fullFlutterPath); 61 | task.prependPath(fullFlutterPath); 62 | 63 | // 5.1 Create flutter pub-cache environment variable 64 | let fullPubCachePath: string = path.join(toolPath, FLUTTER_PUB_CACHE_RELATIVEPATH); 65 | task.debug(`Set ${DART_TOOL_PATH_ENV_VAR} with '${fullPubCachePath}'`); 66 | task.setVariable(FLUTTER_PUBCACHE_PATH_ENV_VAR, fullPubCachePath); 67 | task.prependPath(fullPubCachePath); 68 | 69 | // 5.2 Create dart environment variable 70 | let fullDartPath: string = path.join(fullFlutterPath, DART_EXE_RELATIVEPATH); 71 | task.debug(`Set ${DART_TOOL_PATH_ENV_VAR} with '${fullDartPath}'`); 72 | task.setVariable(DART_TOOL_PATH_ENV_VAR, fullDartPath); 73 | task.prependPath(fullDartPath); 74 | 75 | task.setResult(task.TaskResult.Succeeded, "Installed"); 76 | } 77 | 78 | function findArchitecture() { 79 | if (os.platform() === 'darwin') 80 | return "macos"; 81 | else if (os.platform() === 'linux') 82 | return "linux"; 83 | return "windows"; 84 | } 85 | 86 | async function downloadAndCacheSdk(versionSpec: string, arch: string, downloadUrl: string): Promise { 87 | // 1. Download SDK archive 88 | task.debug(`Starting download archive from '${downloadUrl}'`); 89 | var bundleArchive = await tool.downloadTool(downloadUrl); 90 | task.debug(`Succeeded to download '${bundleArchive}' archive from '${downloadUrl}'`); 91 | 92 | // 2. Extracting SDK bundle 93 | task.debug(`Extracting '${downloadUrl}' archive`); 94 | 95 | var bundleDir: string; 96 | 97 | if (downloadUrl.endsWith('tar.xz')) { 98 | bundleDir = await tool.extractTar(bundleArchive); 99 | } else { 100 | bundleDir = await tool.extractZip(bundleArchive); 101 | } 102 | 103 | task.debug(`Extracted to '${bundleDir}' '${downloadUrl}' archive`); 104 | 105 | // 3. Adding SDK bundle to cache 106 | task.debug(`Adding '${bundleDir}' to cache (${FLUTTER_TOOL_NAME},${versionSpec}, ${arch})`); 107 | tool.cacheDir(bundleDir, FLUTTER_TOOL_NAME, versionSpec, arch); 108 | } 109 | 110 | async function findLatestSdkVersion(channel: string, osArch: string, version: string): Promise<{ downloadUrl: string, version: string, semverVersion: string }> { 111 | var releasesUrl = `https://storage.googleapis.com/flutter_infra_release/releases/releases_${osArch}.json`; 112 | task.debug(`Finding version '${version}' from '${releasesUrl}'`); 113 | var body = await request.get(releasesUrl); 114 | var json = JSON.parse(body); 115 | 116 | var currentHash = json.current_release[channel]; 117 | var arch = os.arch(); 118 | 119 | // if user gave custom arch type, use that 120 | let customArch = task.getInput('customArch', false); 121 | if (customArch && customArch.trim() !== '') { 122 | arch = customArch.trim(); 123 | } 124 | task.debug(`Using Arch type '${arch}'`); 125 | var current = json.releases.find((item) => item.hash === currentHash && item.channel == channel && item.dart_sdk_arch == arch); 126 | 127 | // if user selected custom 128 | if (version.toLowerCase() !== 'latest') { 129 | // fetch custom version 130 | version = task.getInput('customVersion', false); 131 | 132 | // filter based on custom version given 133 | let filtered = json.releases.filter((item) => item.channel == channel && uniformizeVersion(item.version) == uniformizeVersion(version)); 134 | 135 | // if more than 1 release found, that means it has multiple dart_sdk_arch like x64, arm64 136 | if (filtered.length > 1) { 137 | for (var i of filtered) { 138 | // filter releases based on arch type 139 | if (i.dart_sdk_arch && i.dart_sdk_arch === arch) { 140 | current = i; 141 | break; 142 | } 143 | current = i; 144 | } 145 | } else { 146 | current = filtered[0]; 147 | } 148 | 149 | task.debug(`Found custom version '${current.version}'`); 150 | } 151 | 152 | task.debug(`Found version hash '${current.hash}'`); 153 | return { 154 | version: current.version + '-' + channel, 155 | downloadUrl: json.base_url + '/' + current.archive, 156 | semverVersion: current.version 157 | }; 158 | } 159 | 160 | // Removes the 'v' prefix from given version. 161 | function uniformizeVersion(version: string) { 162 | if (version.startsWith('v')) { 163 | return version.substring(1); 164 | } 165 | return version; 166 | } 167 | 168 | main().catch(error => { 169 | task.setResult(task.TaskResult.Failed, error); 170 | }); 171 | -------------------------------------------------------------------------------- /tasks/install/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutterinstall", 3 | "version": "0.4.0", 4 | "description": "Flutter Install Build Task", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/hey24sheep/azure-flutter-tasks.git" 12 | }, 13 | "author": "Hey24sheep", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/hey24sheep/azure-flutter-tasks/issues" 17 | }, 18 | "homepage": "https://github.com/hey24sheep/azure-flutter-tasks#readme", 19 | "dependencies": { 20 | "azure-pipelines-task-lib": "^4.17.2", 21 | "azure-pipelines-tool-lib": "^2.0.7", 22 | "request": "*", 23 | "request-promise": "*" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^22.5.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tasks/install/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "83c91e05-bdf9-42ce-b27e-6dae8771294e", 3 | "name": "FlutterInstall", 4 | "friendlyName": "Flutter Install Task", 5 | "description": "Install the Flutter environment.", 6 | "helpMarkdown": "For more information, take a look at the Flutter [documentation](https://flutter.io)", 7 | "category": "Tool", 8 | "author": "Hey24sheep", 9 | "version": { 10 | "Major": 0, 11 | "Minor": 4, 12 | "Patch": 3 13 | }, 14 | "instanceNameFormat": "Flutter Install", 15 | "inputs": [ 16 | { 17 | "name": "mode", 18 | "type": "pickList", 19 | "label": "Release Url Mode", 20 | "defaultValue": "auto", 21 | "required": true, 22 | "helpMarkDown": "The release url to install Flutter SDK from (https://flutter.dev/docs/development/tools/sdk/releases?tab=windows).", 23 | "options": { 24 | "auto": "Auto", 25 | "custom": "Custom" 26 | } 27 | }, 28 | { 29 | "name": "customUrl", 30 | "type": "string", 31 | "label": "Custom (Flutter SDK Install Url)", 32 | "defaultValue": "", 33 | "required": true, 34 | "visibleRule": "mode = custom", 35 | "helpMarkDown": "Url of the Flutter SDK File (https://flutter.dev/docs/development/tools/sdk/releases?tab=windows). Example URL : 'https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_2.2.1-stable.zip'." 36 | }, 37 | { 38 | "name": "channel", 39 | "type": "pickList", 40 | "label": "Channel", 41 | "defaultValue": "stable", 42 | "required": true, 43 | "helpMarkDown": "The build release channels (https://github.com/flutter/flutter/wiki/Flutter-build-release-channels).", 44 | "options": { 45 | "stable": "Stable", 46 | "beta": "Beta" 47 | }, 48 | "visibleRule": "mode = auto" 49 | }, 50 | { 51 | "name": "version", 52 | "type": "pickList", 53 | "label": "Version", 54 | "defaultValue": "latest", 55 | "required": true, 56 | "helpMarkDown": "Use the latest version of the Flutter SDK or a custom one.", 57 | "options": { 58 | "latest": "Latest", 59 | "custom": "Custom version" 60 | }, 61 | "visibleRule": "mode = auto" 62 | }, 63 | { 64 | "name": "customVersion", 65 | "type": "string", 66 | "label": "Custom version (semver)", 67 | "defaultValue": "", 68 | "required": true, 69 | "helpMarkDown": "The version of the SDK to use like '1.17' or 'v1.12+3.9', etc (in a ..

semver format, from (https://flutter.io/sdk-archive/).", 70 | "visibleRule": "mode = auto && version = custom" 71 | }, 72 | { 73 | "name": "customArch", 74 | "type": "string", 75 | "label": "Custom Arch", 76 | "defaultValue": "", 77 | "required": false, 78 | "helpMarkDown": "The 'dart_sdk_arch' of the SDK to use like 'x64' or 'arm64'. Use this link, (replace 'platform') https://storage.googleapis.com/flutter_infra_release/releases/releases_{platform}.json for example https://storage.googleapis.com/flutter_infra_release/releases/releases_macos.json." 79 | } 80 | ], 81 | "execution": { 82 | "Node10": { 83 | "target": "index.js" 84 | }, 85 | "Node16": { 86 | "target": "index.js" 87 | }, 88 | "Node20_1": { 89 | "target": "index.js" 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /tasks/install/tests/latest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const mr = require("azure-pipelines-task-lib/mock-run"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | const taskPath = path.join(__dirname, "../index.js"); 7 | var runner = new mr.TaskMockRunner(taskPath); 8 | function assertDirectory(path) { 9 | if (!fs.existsSync(path)) { 10 | fs.mkdirSync(path); 11 | } 12 | } 13 | // ENV 14 | const tempPath = path.join(__dirname, "..", "..", "..", "temp"); 15 | const agentPath = path.join(tempPath, "agent"); 16 | process.env["BUILD_BUILDNUMBER"] = "1"; 17 | assertDirectory(tempPath); 18 | assertDirectory(agentPath); 19 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home")); 20 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools")); 21 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp")); 22 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build")); 23 | //let tmr = require('vsts-task-lib/mock-toolrunner'); 24 | //runner.registerMock('vsts-task-lib/toolrunner', tmr); 25 | runner.setInput("mode", "auto"); 26 | runner.setInput("channel", "stable"); 27 | runner.setInput("version", "latest"); 28 | // uncomment to test custom version without "dart_sdk_arch" type 29 | // runner.setInput("version", "custom"); 30 | // runner.setInput("customVersion", "1.22.6"); 31 | // runner.setInput("customVersion", "3.0.2"); 32 | // runner.setInput("customArch", "arm64"); 33 | // to test custom url 34 | // runner.setInput("mode", "custom"); 35 | // runner.setInput("customUrl", "https://storage.googleapis.com/flutter_infra_release/releases/beta/macos/flutter_macos_arm64_3.1.0-9.0.pre-beta.zip"); 36 | // runner.setInput("customUrl", "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.0.2-stable.zip"); 37 | runner.run(true); 38 | -------------------------------------------------------------------------------- /tasks/install/tests/latest.ts: -------------------------------------------------------------------------------- 1 | import * as mr from 'azure-pipelines-task-lib/mock-run'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | 5 | const taskPath = path.join(__dirname, "../index.js"); 6 | var runner = new mr.TaskMockRunner(taskPath); 7 | 8 | function assertDirectory(path: string) { 9 | if (!fs.existsSync(path)) { 10 | fs.mkdirSync(path); 11 | } 12 | } 13 | 14 | // ENV 15 | const tempPath = path.join(__dirname, "..", "..", "..", "temp"); 16 | const agentPath = path.join(tempPath, "agent"); 17 | process.env["BUILD_BUILDNUMBER"] = "1"; 18 | assertDirectory(tempPath); 19 | assertDirectory(agentPath); 20 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home")); 21 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools")); 22 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp")); 23 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build")); 24 | 25 | //let tmr = require('vsts-task-lib/mock-toolrunner'); 26 | //runner.registerMock('vsts-task-lib/toolrunner', tmr); 27 | 28 | runner.setInput("mode", "auto"); 29 | runner.setInput("channel", "stable"); 30 | runner.setInput("version", "latest"); 31 | 32 | // uncomment to test custom version without "dart_sdk_arch" type 33 | // runner.setInput("version", "custom"); 34 | // runner.setInput("customVersion", "1.22.6"); 35 | // runner.setInput("customVersion", "3.0.2"); 36 | // runner.setInput("customArch", "arm64"); 37 | 38 | // to test custom url 39 | // runner.setInput("mode", "custom"); 40 | // runner.setInput("customUrl", "https://storage.googleapis.com/flutter_infra_release/releases/beta/macos/flutter_macos_arm64_3.1.0-9.0.pre-beta.zip"); 41 | // runner.setInput("customUrl", "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.0.2-stable.zip"); 42 | 43 | runner.run(true); -------------------------------------------------------------------------------- /tasks/install/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | }, 6 | "exclude": [ 7 | "node_modules" 8 | ] 9 | } -------------------------------------------------------------------------------- /tasks/install/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest" 4 | ], 5 | "rules": { 6 | "member-ordering": [ false ], 7 | "interface-name": [ false ], 8 | "object-literal-sort-keys": false, 9 | 10 | "whitespace": [ 11 | "check-branch", 12 | "check-decl", 13 | "check-operator", 14 | "check-module", 15 | "check-separator", 16 | "check-type" 17 | ], 18 | 19 | "one-line": [ 20 | "check-open-brace", 21 | "check-whitespace" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /tasks/test/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hey24sheep/azure-flutter-tasks/236631580d91f3bd43d65c41cb949480b6b0ba4c/tasks/test/icon.png -------------------------------------------------------------------------------- /tasks/test/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | test_icon_1024 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /tasks/test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const task = require("azure-pipelines-task-lib/task"); 13 | const path = require("path"); 14 | const xml2js = require("xml2js"); 15 | const FLUTTER_TOOL_PATH_ENV_VAR = 'FlutterToolPath'; 16 | function main() { 17 | return __awaiter(this, void 0, void 0, function* () { 18 | // 1. Check flutter environment 19 | var flutterPath = task.getVariable(FLUTTER_TOOL_PATH_ENV_VAR) || process.env[FLUTTER_TOOL_PATH_ENV_VAR]; 20 | flutterPath = path.join(flutterPath, "flutter"); 21 | if (!flutterPath) { 22 | throw new Error(`The '${FLUTTER_TOOL_PATH_ENV_VAR}' environment variable must be set before using this task (you can use 'flutterinstall' task).`); 23 | } 24 | // 3. Move current working directory to project 25 | let projectDirectory = task.getPathInput('projectDirectory', false, false); 26 | if (projectDirectory) { 27 | task.debug(`Moving to ${projectDirectory}`); 28 | task.cd(projectDirectory); 29 | } 30 | // 4. Get inputs 31 | let testName = task.getInput('testName', false); 32 | let testPlainName = task.getInput('testPlainName', false); 33 | let updateGoldens = task.getBoolInput('updateGoldens', false); 34 | let concurrency = task.getInput('concurrency', false); 35 | let canPublishTests = task.getInput('publishTests', false); 36 | let canGenerateCodeCoverage = task.getBoolInput("generateCodeCoverageReport", false); 37 | let extraArgs = task.getDelimitedInput('extraArgs', ',', false); 38 | // 5. Running tests 39 | var results = yield runTests(flutterPath, (concurrency ? Number(concurrency) : null), updateGoldens, testName, testPlainName, canGenerateCodeCoverage, extraArgs); 40 | // 6. Publishing tests 41 | if (canPublishTests) { 42 | yield publishTests(results); 43 | } 44 | if (results.isSuccess) { 45 | task.setResult(task.TaskResult.Succeeded, `All tests passed`); 46 | } 47 | else { 48 | task.setResult(task.TaskResult.Failed, `Some tests failed`); 49 | } 50 | }); 51 | } 52 | function publishTests(results) { 53 | return __awaiter(this, void 0, void 0, function* () { 54 | var publisher = new task.TestPublisher("JUnit"); 55 | task.debug(`results: ` + JSON.stringify(results)); 56 | // 1. Generating Junit XML result file 57 | var junitResults = createJunitResults(results); 58 | var xmlBuilder = new xml2js.Builder(); 59 | var xml = xmlBuilder.buildObject(junitResults); 60 | var xmlPath = path.join(task.cwd(), "junit.xml"); 61 | task.writeFile(xmlPath, xml); 62 | // 2. Publishing to task 63 | publisher.publish([xmlPath], "false", "", "", "VSTS - Flutter", "true", ""); 64 | }); 65 | } 66 | function runTests(flutter, concurrency, updateGoldens, name, plainName, canGenerateCodeCoverage, extraArgs) { 67 | return __awaiter(this, void 0, void 0, function* () { 68 | let testRunner = task.tool(flutter); 69 | testRunner.arg(['test', '--pub']); 70 | if (updateGoldens) { 71 | testRunner.arg("--update-goldens"); 72 | } 73 | if (canGenerateCodeCoverage) { 74 | testRunner.arg("--coverage"); 75 | } 76 | if (name) { 77 | testRunner.arg("--name=" + name); 78 | } 79 | if (plainName) { 80 | testRunner.arg("--plain-name=" + plainName); 81 | } 82 | if (concurrency) { 83 | testRunner.arg("--concurrency=" + concurrency); 84 | } 85 | if (extraArgs && extraArgs.length > 0) { 86 | testRunner.arg(extraArgs); 87 | } 88 | var currentSuite = { 89 | title: undefined, 90 | isSuccess: false, 91 | succeeded: 0, 92 | failed: 0, 93 | cases: [] 94 | }; 95 | var allSuitesDict = {}; 96 | var results = { 97 | isSuccess: false, 98 | suites: [] 99 | }; 100 | let globalFailure = 0; 101 | testRunner.on('stdout', line => { 102 | let filename = undefined; 103 | const allTestRegex = /\s*\d\d:\d\d (\+\d+)?(\s+\-\d+)?:\s* \s*(.*\.dart)\s*/; 104 | const filenameRegex = /(\w+.dart)/; 105 | const allRegexResult = allTestRegex.exec(line); 106 | if (allRegexResult && allRegexResult[3]) { 107 | const filepath = allRegexResult[3]; 108 | if (filepath) { 109 | filename = filenameRegex.exec(filepath)[0]; 110 | } 111 | } 112 | var newSuite = { 113 | title: filename ? filename : undefined, 114 | isSuccess: false, 115 | succeeded: 0, 116 | failed: 0, 117 | cases: [] 118 | }; 119 | if (allSuitesDict[newSuite.title]) { 120 | currentSuite = allSuitesDict[newSuite.title]; 121 | } 122 | else { 123 | currentSuite = newSuite; 124 | } 125 | currentSuite = createTestCase(currentSuite, line, globalFailure); 126 | allSuitesDict[currentSuite.title] = currentSuite; 127 | }); 128 | try { 129 | yield testRunner.exec(); 130 | results.isSuccess = true; 131 | } 132 | catch (_a) { } 133 | var allKeys = Object.keys(allSuitesDict); 134 | allKeys.forEach((k) => { 135 | results.suites.push(allSuitesDict[k]); 136 | }); 137 | return results; 138 | }); 139 | } 140 | // TODO: remove in next version upgrade 141 | // function createTestCase(suite: any, output: string, globalFailure: number) { 142 | // const testRunRegex = /\s*\d\d:\d\d (\+\d+)?(\s+\-\d+)?:\s*(.*)/; 143 | // let match = testRunRegex.exec(output); 144 | // if (match) { 145 | // var tSplits = match[3].split(': '); 146 | // var title = tSplits[tSplits.length - 1]; 147 | // var successes = Number(match[1]); 148 | // var failures = match[2] ? -Number(match[2]) : suite.failed; 149 | // var newCase = { 150 | // title: title.trim(), 151 | // isSuccess: false, 152 | // started: new Date(), 153 | // ended: new Date, 154 | // }; 155 | // var hasNewCase = false; 156 | // if (suite.succeeded !== successes) { 157 | // newCase.isSuccess = true; 158 | // hasNewCase = true; 159 | // } 160 | // else if (suite.failed !== failures) { 161 | // newCase.isSuccess = false; 162 | // hasNewCase = true; 163 | // } 164 | // else if (suite.cases.length <= 0 165 | // && suite.succeeded === 0 166 | // && successes === 0 167 | // && suite.failed === 0 168 | // && failures === 0) { 169 | // // handles initial test, as it is always empty with everything as 0 or empty 170 | // // index in test starts with 0 171 | // // everything is 0 meaning it's a new case 172 | // newCase.isSuccess = true; 173 | // hasNewCase = true; 174 | // } else { 175 | // newCase.isSuccess = true; 176 | // hasNewCase = true; 177 | // } 178 | // if (hasNewCase) { 179 | // if (suite.cases.length > 0) { 180 | // suite.cases[suite.cases.length - 1].ended = newCase.started; 181 | // } 182 | // } 183 | // const caseExistsIdx = suite.cases.findIndex((i) => { 184 | // return i.title.toString().trim() === newCase.title.toString().trim(); 185 | // }); 186 | // if (caseExistsIdx < 0 || !suite.cases[caseExistsIdx].isSuccess) { 187 | // // only add cases if they are not added before 188 | // // checks if case doesn't exist or already added with success 189 | // suite.cases.push(newCase); 190 | // } 191 | // if (newCase.isSuccess) { 192 | // suite.succeeded += 1; 193 | // } else { 194 | // suite.failed += 1; 195 | // } 196 | // // console.log('\n', '\x1b[36m', 197 | // // 'Suite : ', suite, successes, failures, 198 | // // '\x1b[0m', '\n'); 199 | // } 200 | // return suite; 201 | // } 202 | function createTestCase(suite, output, globalFailure) { 203 | const testRunRegex = /\s*\d\d:\d\d (\+\d+)?(\s+\-\d+)?:\s*(.*)/; 204 | const match = testRunRegex.exec(output); 205 | if (!match || match.length < 4) { 206 | return suite; 207 | } 208 | const tSplits = match[3].split(': '); 209 | if (tSplits.length === 1) { 210 | // Log printed starting a new test suite - No test case handled here 211 | return suite; 212 | } 213 | // Sanitise test case name from other information 214 | let title = tSplits[tSplits.length - 1]; 215 | title = title.replace(' [E]', ''); 216 | const currentCase = suite.cases.find(testCase => testCase.title === title); 217 | if (!currentCase) { 218 | // Suite has to be marked as succeeded first, marking as failed can be done when there is a new row with [E] at the end 219 | suite.cases.push({ 220 | title: title.trim(), 221 | isSuccess: true, 222 | started: new Date(), 223 | ended: new Date(), 224 | }); 225 | suite.succeeded += 1; 226 | return suite; 227 | } 228 | if (match[3].endsWith(' [E]')) { 229 | currentCase.isSuccess = false; 230 | suite.failed += 1; 231 | suite.succeeded -= 1; 232 | } 233 | return suite; 234 | } 235 | function createJunitResults(results) { 236 | var testSuites = []; 237 | results.suites.forEach(suite => { 238 | var testCases = []; 239 | suite.cases.forEach(c => { 240 | var duration = (c.ended.getTime() - c.started.getTime()); 241 | var s = (duration / 1000); 242 | var testCase = { 243 | "$": { 244 | "name": c.title, 245 | "classname": c.title, 246 | "time": s, 247 | } 248 | }; 249 | if (!c.isSuccess) { 250 | testCase["failure"] = { 251 | "$": { 252 | "type": "FlutterError", 253 | } 254 | }; 255 | } 256 | testCases.push(testCase); 257 | }); 258 | var testSuite = { 259 | "$": { 260 | "name": suite.title, 261 | "timestamp": new Date().toISOString(), 262 | "errors": 0, // TODO 263 | "skipped": 0, // TODO 264 | "failures": suite.failed, 265 | "tests": (suite.failed + suite.succeeded) 266 | }, 267 | "testcase": testCases 268 | }; 269 | testSuites.push(testSuite); 270 | }); 271 | return { 272 | "testsuites": { 273 | "testsuite": testSuites 274 | } 275 | }; 276 | } 277 | main().catch(error => { 278 | task.setResult(task.TaskResult.Failed, error); 279 | }); 280 | -------------------------------------------------------------------------------- /tasks/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as task from "azure-pipelines-task-lib/task"; 2 | import * as path from "path"; 3 | import * as xml2js from "xml2js"; 4 | 5 | const FLUTTER_TOOL_PATH_ENV_VAR: string = 'FlutterToolPath'; 6 | 7 | async function main(): Promise { 8 | // 1. Check flutter environment 9 | var flutterPath = task.getVariable(FLUTTER_TOOL_PATH_ENV_VAR) || process.env[FLUTTER_TOOL_PATH_ENV_VAR]; 10 | flutterPath = path.join(flutterPath, "flutter") 11 | if (!flutterPath) { 12 | throw new Error(`The '${FLUTTER_TOOL_PATH_ENV_VAR}' environment variable must be set before using this task (you can use 'flutterinstall' task).`); 13 | } 14 | 15 | // 3. Move current working directory to project 16 | let projectDirectory = task.getPathInput('projectDirectory', false, false); 17 | if (projectDirectory) { 18 | task.debug(`Moving to ${projectDirectory}`); 19 | task.cd(projectDirectory); 20 | } 21 | 22 | // 4. Get inputs 23 | let testName = task.getInput('testName', false); 24 | let testPlainName = task.getInput('testPlainName', false); 25 | let updateGoldens = task.getBoolInput('updateGoldens', false); 26 | let concurrency = task.getInput('concurrency', false); 27 | let canPublishTests = task.getInput('publishTests', false); 28 | let canGenerateCodeCoverage = task.getBoolInput("generateCodeCoverageReport", false); 29 | let extraArgs = task.getDelimitedInput('extraArgs', ',', false); 30 | 31 | // 5. Running tests 32 | var results = await runTests( 33 | flutterPath, 34 | (concurrency ? Number(concurrency) : null), 35 | updateGoldens, 36 | testName, 37 | testPlainName, 38 | canGenerateCodeCoverage, 39 | extraArgs); 40 | 41 | // 6. Publishing tests 42 | if (canPublishTests) { 43 | await publishTests(results); 44 | } 45 | 46 | if (results.isSuccess) { 47 | task.setResult(task.TaskResult.Succeeded, `All tests passed`); 48 | } 49 | else { 50 | task.setResult(task.TaskResult.Failed, `Some tests failed`); 51 | } 52 | } 53 | 54 | async function publishTests(results: any) { 55 | var publisher = new task.TestPublisher("JUnit"); 56 | 57 | task.debug(`results: ` + JSON.stringify(results)); 58 | 59 | // 1. Generating Junit XML result file 60 | var junitResults = createJunitResults(results); 61 | var xmlBuilder = new xml2js.Builder(); 62 | var xml = xmlBuilder.buildObject(junitResults); 63 | var xmlPath = path.join(task.cwd(), "junit.xml"); 64 | task.writeFile(xmlPath, xml); 65 | 66 | // 2. Publishing to task 67 | publisher.publish([xmlPath], "false", "", "", "VSTS - Flutter", "true", ""); 68 | } 69 | 70 | async function runTests(flutter: string, 71 | concurrency?: number, 72 | updateGoldens?: boolean, 73 | name?: string, 74 | plainName?: string, 75 | canGenerateCodeCoverage?: boolean, 76 | extraArgs?: string[]) { 77 | let testRunner = task.tool(flutter); 78 | testRunner.arg(['test', '--pub']); 79 | 80 | if (updateGoldens) { 81 | testRunner.arg("--update-goldens"); 82 | } 83 | 84 | if (canGenerateCodeCoverage) { 85 | testRunner.arg("--coverage"); 86 | } 87 | 88 | if (name) { 89 | testRunner.arg("--name=" + name); 90 | } 91 | 92 | if (plainName) { 93 | testRunner.arg("--plain-name=" + plainName); 94 | } 95 | 96 | if (concurrency) { 97 | testRunner.arg("--concurrency=" + concurrency); 98 | } 99 | 100 | if (extraArgs && extraArgs.length > 0) { 101 | testRunner.arg(extraArgs); 102 | } 103 | 104 | var currentSuite: any = { 105 | title: undefined, 106 | isSuccess: false, 107 | succeeded: 0, 108 | failed: 0, 109 | cases: [] 110 | }; 111 | 112 | var allSuitesDict = {}; 113 | 114 | var results = { 115 | isSuccess: false, 116 | suites: [] 117 | }; 118 | 119 | let globalFailure = 0; 120 | 121 | testRunner.on('stdout', line => { 122 | let filename = undefined; 123 | 124 | const allTestRegex = /\s*\d\d:\d\d (\+\d+)?(\s+\-\d+)?:\s* \s*(.*\.dart)\s*/; 125 | const filenameRegex = /(\w+.dart)/; 126 | 127 | const allRegexResult = allTestRegex.exec(line); 128 | 129 | if (allRegexResult && allRegexResult[3]) { 130 | const filepath = allRegexResult[3]; 131 | 132 | if (filepath) { 133 | filename = filenameRegex.exec(filepath)[0]; 134 | } 135 | } 136 | 137 | var newSuite = { 138 | title: filename ? filename : undefined, 139 | isSuccess: false, 140 | succeeded: 0, 141 | failed: 0, 142 | cases: [] 143 | } 144 | 145 | if (allSuitesDict[newSuite.title]) { 146 | currentSuite = allSuitesDict[newSuite.title]; 147 | } else { 148 | currentSuite = newSuite; 149 | } 150 | 151 | currentSuite = createTestCase(currentSuite, line, globalFailure); 152 | 153 | allSuitesDict[currentSuite.title] = currentSuite; 154 | }); 155 | 156 | try { 157 | await testRunner.exec(); 158 | 159 | results.isSuccess = true; 160 | } 161 | catch { } 162 | 163 | var allKeys = Object.keys(allSuitesDict); 164 | allKeys.forEach((k) => { 165 | results.suites.push(allSuitesDict[k]); 166 | }); 167 | 168 | return results; 169 | } 170 | 171 | // TODO: remove in next version upgrade 172 | // function createTestCase(suite: any, output: string, globalFailure: number) { 173 | // const testRunRegex = /\s*\d\d:\d\d (\+\d+)?(\s+\-\d+)?:\s*(.*)/; 174 | // let match = testRunRegex.exec(output); 175 | // if (match) { 176 | // var tSplits = match[3].split(': '); 177 | // var title = tSplits[tSplits.length - 1]; 178 | // var successes = Number(match[1]); 179 | // var failures = match[2] ? -Number(match[2]) : suite.failed; 180 | 181 | // var newCase = { 182 | // title: title.trim(), 183 | // isSuccess: false, 184 | // started: new Date(), 185 | // ended: new Date, 186 | // }; 187 | 188 | // var hasNewCase = false; 189 | 190 | // if (suite.succeeded !== successes) { 191 | // newCase.isSuccess = true; 192 | // hasNewCase = true; 193 | // } 194 | // else if (suite.failed !== failures) { 195 | // newCase.isSuccess = false; 196 | // hasNewCase = true; 197 | // } 198 | // else if (suite.cases.length <= 0 199 | // && suite.succeeded === 0 200 | // && successes === 0 201 | // && suite.failed === 0 202 | // && failures === 0) { 203 | // // handles initial test, as it is always empty with everything as 0 or empty 204 | // // index in test starts with 0 205 | // // everything is 0 meaning it's a new case 206 | // newCase.isSuccess = true; 207 | // hasNewCase = true; 208 | // } else { 209 | // newCase.isSuccess = true; 210 | // hasNewCase = true; 211 | // } 212 | 213 | // if (hasNewCase) { 214 | // if (suite.cases.length > 0) { 215 | // suite.cases[suite.cases.length - 1].ended = newCase.started; 216 | // } 217 | // } 218 | 219 | // const caseExistsIdx = suite.cases.findIndex((i) => { 220 | // return i.title.toString().trim() === newCase.title.toString().trim(); 221 | // }); 222 | 223 | // if (caseExistsIdx < 0 || !suite.cases[caseExistsIdx].isSuccess) { 224 | // // only add cases if they are not added before 225 | // // checks if case doesn't exist or already added with success 226 | // suite.cases.push(newCase); 227 | // } 228 | 229 | // if (newCase.isSuccess) { 230 | // suite.succeeded += 1; 231 | // } else { 232 | // suite.failed += 1; 233 | // } 234 | 235 | // // console.log('\n', '\x1b[36m', 236 | // // 'Suite : ', suite, successes, failures, 237 | // // '\x1b[0m', '\n'); 238 | // } 239 | // return suite; 240 | // } 241 | 242 | function createTestCase(suite: any, output: string, globalFailure: number) { 243 | const testRunRegex = /\s*\d\d:\d\d (\+\d+)?(\s+\-\d+)?:\s*(.*)/; 244 | const match = testRunRegex.exec(output); 245 | if (!match || match.length < 4) { 246 | return suite; 247 | } 248 | const tSplits = match[3].split(': '); 249 | 250 | if (tSplits.length === 1) { 251 | // Log printed starting a new test suite - No test case handled here 252 | return suite; 253 | } 254 | 255 | // Sanitise test case name from other information 256 | let title = tSplits[tSplits.length - 1]; 257 | title = title.replace(' [E]', ''); 258 | const currentCase = suite.cases.find(testCase => testCase.title === title); 259 | 260 | if (!currentCase) { 261 | // Suite has to be marked as succeeded first, marking as failed can be done when there is a new row with [E] at the end 262 | suite.cases.push({ 263 | title: title.trim(), 264 | isSuccess: true, 265 | started: new Date(), 266 | ended: new Date(), 267 | }); 268 | suite.succeeded += 1; 269 | return suite; 270 | } 271 | 272 | if (match[3].endsWith(' [E]')) { 273 | currentCase.isSuccess = false; 274 | suite.failed += 1; 275 | suite.succeeded -= 1; 276 | } 277 | 278 | return suite; 279 | } 280 | 281 | function createJunitResults(results: any) { 282 | var testSuites = []; 283 | 284 | results.suites.forEach(suite => { 285 | var testCases = []; 286 | suite.cases.forEach(c => { 287 | var duration = (c.ended.getTime() - c.started.getTime()); 288 | var s = (duration / 1000); 289 | var testCase = { 290 | "$": { 291 | "name": c.title, 292 | "classname": c.title, 293 | "time": s, 294 | } 295 | }; 296 | 297 | if (!c.isSuccess) { 298 | testCase["failure"] = { 299 | "$": { 300 | "type": "FlutterError", 301 | } 302 | } 303 | } 304 | 305 | testCases.push(testCase); 306 | }); 307 | 308 | var testSuite = { 309 | "$": { 310 | "name": suite.title, 311 | "timestamp": new Date().toISOString(), 312 | "errors": 0, // TODO 313 | "skipped": 0, // TODO 314 | "failures": suite.failed, 315 | "tests": (suite.failed + suite.succeeded) 316 | }, 317 | "testcase": testCases 318 | }; 319 | testSuites.push(testSuite); 320 | }); 321 | 322 | return { 323 | "testsuites": { 324 | "testsuite": testSuites 325 | } 326 | }; 327 | } 328 | 329 | main().catch(error => { 330 | task.setResult(task.TaskResult.Failed, error); 331 | }); 332 | -------------------------------------------------------------------------------- /tasks/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluttertest", 3 | "version": "1.4.0", 4 | "description": "Flutter Test Task", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/hey24sheep/azure-flutter-tasks.git" 12 | }, 13 | "author": "Hey24sheep", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/hey24sheep/azure-flutter-tasks/issues" 17 | }, 18 | "homepage": "https://github.com/hey24sheep/azure-flutter-tasks#readme", 19 | "dependencies": { 20 | "@types/xml2js": "^0.4.14", 21 | "azure-pipelines-task-lib": "^4.17.2", 22 | "azure-pipelines-tool-lib": "^2.0.7", 23 | "xml2js": "^0.6.2" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^22.5.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tasks/test/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "6ca6cdf5-44cf-4600-9fe3-bd62340ddb1f", 3 | "name": "FlutterTest", 4 | "friendlyName": "Flutter Test Task", 5 | "description": "Executes all tests for a Flutter project.", 6 | "helpMarkdown": "For more information, take a look at the Flutter [documentation](https://flutter.io)", 7 | "category": "Test", 8 | "author": "Hey24sheep", 9 | "version": { 10 | "Major": 0, 11 | "Minor": 4, 12 | "Patch": 2 13 | }, 14 | "instanceNameFormat": "Flutter Test", 15 | "inputs": [ 16 | { 17 | "name": "projectDirectory", 18 | "type": "filePath", 19 | "label": "Path to the project folder", 20 | "defaultValue": "", 21 | "helpMarkDown": "The path to the project folder (which contains the `pubspec.yaml`)", 22 | "required": true 23 | }, 24 | { 25 | "name": "testName", 26 | "type": "string", 27 | "label": "Test name", 28 | "defaultValue": "", 29 | "required": false, 30 | "helpMarkDown": "A regular expression matching substrings of the names of tests to run." 31 | }, 32 | { 33 | "name": "testPlainName", 34 | "type": "string", 35 | "label": "Test plain name", 36 | "defaultValue": "", 37 | "required": false, 38 | "helpMarkDown": "A plain-text substring of the names of tests to run." 39 | }, 40 | { 41 | "name": "updateGoldens", 42 | "type": "boolean", 43 | "label": "Update goldens", 44 | "defaultValue": false, 45 | "required": false, 46 | "helpMarkDown": "Whether matchesGoldenFile() calls within your test methods should update the golden files rather than test for an existing match." 47 | }, 48 | { 49 | "name": "generateCodeCoverageReport", 50 | "type": "boolean", 51 | "label": "Generate code coverage report", 52 | "defaultValue": false, 53 | "required": false, 54 | "helpMarkDown": "Whether flutter should generate a code coverage report based on tests in project. File is generated at project source directory in coverage/lcov.info." 55 | }, 56 | { 57 | "name": "concurrency", 58 | "type": "string", 59 | "label": "Concurrency", 60 | "defaultValue": "", 61 | "required": false, 62 | "helpMarkDown": "The number of concurrent test processes to run. (defaults to '6')" 63 | }, 64 | { 65 | "name": "publishTests", 66 | "type": "boolean", 67 | "label": "Publish Test Results", 68 | "defaultValue": true, 69 | "required": false 70 | }, 71 | { 72 | "name": "extraArgs", 73 | "type": "string", 74 | "label": "Extra command arguments", 75 | "defaultValue": "", 76 | "required": false, 77 | "helpMarkDown": "Extra command arguments like '--no-sound-null-safety'. Comma Separated" 78 | } 79 | ], 80 | "execution": { 81 | "Node10": { 82 | "target": "index.js" 83 | }, 84 | "Node16": { 85 | "target": "index.js" 86 | }, 87 | "Node20_1": { 88 | "target": "index.js" 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /tasks/test/tests/basic.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const mr = require("azure-pipelines-task-lib/mock-run"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | const taskPath = path.join(__dirname, "../index.js"); 7 | var runner = new mr.TaskMockRunner(taskPath); 8 | function assertDirectory(path) { 9 | if (!fs.existsSync(path)) { 10 | fs.mkdirSync(path); 11 | } 12 | } 13 | // ENV 14 | const rootPath = path.join(__dirname, "..", "..", ".."); 15 | const tempPath = path.join(rootPath, "temp"); 16 | const agentPath = path.join(tempPath, "agent"); 17 | const dropPath = path.join(tempPath, "drop"); 18 | process.env["BUILD_BUILDNUMBER"] = "1"; 19 | assertDirectory(tempPath); 20 | assertDirectory(agentPath); 21 | assertDirectory(dropPath); 22 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home")); 23 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools")); 24 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp")); 25 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build")); 26 | // Run install tests 27 | process.env["FlutterToolPath"] = path.join(agentPath, "tools", "Flutter", "3.10.6", "windows", "flutter", "bin"); 28 | runner.setInput("projectDirectory", path.join(rootPath, "sample_project")); 29 | runner.setInput("publishTests", "true"); 30 | runner.run(true); 31 | -------------------------------------------------------------------------------- /tasks/test/tests/basic.ts: -------------------------------------------------------------------------------- 1 | import * as mr from 'azure-pipelines-task-lib/mock-run'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | 5 | const taskPath = path.join(__dirname, "../index.js"); 6 | var runner = new mr.TaskMockRunner(taskPath); 7 | 8 | function assertDirectory(path: string) { 9 | if (!fs.existsSync(path)) { 10 | fs.mkdirSync(path); 11 | } 12 | } 13 | 14 | // ENV 15 | const rootPath = path.join(__dirname, "..", "..", ".."); 16 | const tempPath = path.join(rootPath, "temp"); 17 | const agentPath = path.join(tempPath, "agent"); 18 | const dropPath = path.join(tempPath, "drop"); 19 | process.env["BUILD_BUILDNUMBER"] = "1"; 20 | assertDirectory(tempPath); 21 | assertDirectory(agentPath); 22 | assertDirectory(dropPath); 23 | assertDirectory(process.env["AGENT_HOMEDIRECTORY"] = path.join(agentPath, "home")); 24 | assertDirectory(process.env["AGENT_TOOLSDIRECTORY"] = path.join(agentPath, "tools")); 25 | assertDirectory(process.env["AGENT_TEMPDIRECTORY"] = path.join(agentPath, "temp")); 26 | assertDirectory(process.env["AGENT_BUILDDIRECTORY"] = path.join(agentPath, "build")); 27 | 28 | // Run install tests 29 | process.env["FlutterToolPath"] = path.join(agentPath, "tools", "Flutter", "3.10.6", "windows", "flutter", "bin"); 30 | 31 | runner.setInput("projectDirectory", path.join(rootPath, "sample_project")); 32 | runner.setInput("publishTests", "true"); 33 | runner.run(true); -------------------------------------------------------------------------------- /tasks/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | }, 6 | "exclude": [ 7 | "node_modules" 8 | ] 9 | } -------------------------------------------------------------------------------- /tasks/test/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:latest" 4 | ], 5 | "rules": { 6 | "member-ordering": [ false ], 7 | "interface-name": [ false ], 8 | "object-literal-sort-keys": false, 9 | 10 | "whitespace": [ 11 | "check-branch", 12 | "check-decl", 13 | "check-operator", 14 | "check-module", 15 | "check-separator", 16 | "check-type" 17 | ], 18 | 19 | "one-line": [ 20 | "check-open-brace", 21 | "check-whitespace" 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /tsc-build-with-npm.sh: -------------------------------------------------------------------------------- 1 | # Build TS 2 | npm install -g typescript 3 | cd ./tasks/test 4 | npm install 5 | tsc 6 | cd ../install 7 | npm install 8 | tsc 9 | cd ../build 10 | npm install 11 | tsc 12 | cd ../analyze 13 | npm install 14 | tsc 15 | cd ../command 16 | npm install 17 | tsc 18 | cd ../env 19 | npm install 20 | tsc 21 | cd ../../ 22 | -------------------------------------------------------------------------------- /vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": 1, 3 | "id": "flutter", 4 | "version": "0.4.4", 5 | "name": "Flutter Tasks", 6 | "description": "Flutter extension for Azure DevOps. Install, build, analyze, command and env tasks for easier Flutter DevOps.", 7 | "publisher": "hey24sheep", 8 | "public": true, 9 | "targets": [ 10 | { 11 | "id": "Microsoft.VisualStudio.Services" 12 | } 13 | ], 14 | "categories": [ 15 | "Azure Pipelines" 16 | ], 17 | "icons": { 18 | "default": "icon.png" 19 | }, 20 | "screenshots": [], 21 | "content": { 22 | "details": { 23 | "path": "README.md" 24 | }, 25 | "license": { 26 | "path": "LICENSE" 27 | }, 28 | "privacy": { 29 | "path": "PRIVACY.md" 30 | } 31 | }, 32 | "links": { 33 | "getstarted": { 34 | "uri": "https://github.com/hey24sheep/azure-flutter-tasks" 35 | }, 36 | "support": { 37 | "uri": "https://github.com/hey24sheep/azure-flutter-tasks/issues" 38 | }, 39 | "license": { 40 | "uri": "https://github.com/hey24sheep/azure-flutter-tasks/LICENSE.md" 41 | }, 42 | "privacypolicy": { 43 | "uri": "https://github.com/hey24sheep/azure-flutter-tasks/PRIVACY.md" 44 | } 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "uri": "https://github.com/hey24sheep/azure-flutter-tasks" 49 | }, 50 | "tags": [ 51 | "flutter", 52 | "mobile", 53 | "ios", 54 | "android", 55 | "dart" 56 | ], 57 | "files": [ 58 | { 59 | "path": "tasks/install" 60 | }, 61 | { 62 | "path": "tasks/build" 63 | }, 64 | { 65 | "path": "tasks/test" 66 | }, 67 | { 68 | "path": "tasks/analyze" 69 | }, 70 | { 71 | "path": "tasks/command" 72 | }, 73 | { 74 | "path": "tasks/env" 75 | }, 76 | { 77 | "path": "images", 78 | "addressable": true 79 | } 80 | ], 81 | "contributions": [ 82 | { 83 | "id": "flutter-install", 84 | "type": "ms.vss-distributed-task.task", 85 | "targets": [ 86 | "ms.vss-distributed-task.tasks" 87 | ], 88 | "properties": { 89 | "name": "tasks/install" 90 | } 91 | }, 92 | { 93 | "id": "flutter-build", 94 | "type": "ms.vss-distributed-task.task", 95 | "targets": [ 96 | "ms.vss-distributed-task.tasks" 97 | ], 98 | "properties": { 99 | "name": "tasks/build" 100 | } 101 | }, 102 | { 103 | "id": "flutter-test", 104 | "type": "ms.vss-distributed-task.task", 105 | "targets": [ 106 | "ms.vss-distributed-task.tasks" 107 | ], 108 | "properties": { 109 | "name": "tasks/test" 110 | } 111 | }, 112 | { 113 | "id": "flutter-analyze", 114 | "type": "ms.vss-distributed-task.task", 115 | "targets": [ 116 | "ms.vss-distributed-task.tasks" 117 | ], 118 | "properties": { 119 | "name": "tasks/analyze" 120 | } 121 | }, 122 | { 123 | "id": "flutter-command", 124 | "type": "ms.vss-distributed-task.task", 125 | "targets": [ 126 | "ms.vss-distributed-task.tasks" 127 | ], 128 | "properties": { 129 | "name": "tasks/command" 130 | } 131 | }, 132 | { 133 | "id": "flutter-env", 134 | "type": "ms.vss-distributed-task.task", 135 | "targets": [ 136 | "ms.vss-distributed-task.tasks" 137 | ], 138 | "properties": { 139 | "name": "tasks/env" 140 | } 141 | } 142 | ] 143 | } --------------------------------------------------------------------------------