├── .github ├── FUNDING.yml └── workflows │ └── nightly.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── flutterw ├── install.sh ├── test ├── .gitignore ├── analysis_options.yaml ├── pubspec.yaml └── test │ ├── install_test.dart │ ├── multi_packages_test.dart │ ├── submodule_test.dart │ └── update_test.dart ├── uninstall.sh └── version /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [passsy] 2 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yaml: -------------------------------------------------------------------------------- 1 | name: nightly 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | schedule: 10 | # Every night at 03:00 11 | - cron: '0 3 * * *' 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | 17 | container: 18 | image: passsy/flutterw:base-0.4.1 19 | 20 | steps: 21 | - uses: actions/checkout@v2.4.0 22 | - name: Preload Flutter 23 | run: | 24 | mkdir -p /flutter 25 | git clone https://github.com/flutter/flutter.git -b stable /flutter 26 | export PATH="$PATH:/flutter/bin" 27 | echo "/flutter/bin" >> $GITHUB_PATH 28 | cat /flutter/.git/config 29 | # TODO fetch this in flutterw script 30 | git -C /flutter fetch origin 31 | git -C /flutter fetch --all 32 | flutter precache --linux --web 33 | 34 | git config --global user.email "dash@flutter.io" 35 | git config --global user.name "Dash" 36 | - name: Get dependencies 37 | run: | 38 | cd test 39 | dart pub get 40 | 41 | - name: Run tests 42 | run: | 43 | cd test 44 | dart run test -j 1 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/.idea 2 | *.iml 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.3.1 4 | 5 | - Allow flutterw update when placed in repo subfolder [#34](https://github.com/passsy/flutter_wrapper/pull/34) 6 | 7 | ## 1.3.0 8 | 9 | - Don't clear .gitmodules when calling `flutter channel` without a channel [#26](https://github.com/passsy/flutter_wrapper/pull/26) 10 | - Move echos to `stderr`, so that the output of `stdout` is not changed for scripts consuming it [#24](https://github.com/passsy/flutter_wrapper/pull/24) 11 | - Don't error `flutter upgrade` when `pubspec.lock` doesn't exist [#28](https://github.com/passsy/flutter_wrapper/pull/28) 12 | - This project is now tested! This gives me personally confidence to change things without breaking your project 13 | 14 | ## 1.2.0 15 | 16 | - Fix submodule initialization detection #22 17 | - Support for multi-package repositories #18 18 | - Support placing flutterw in a sub folder #17 19 | - Added tests so I don't accidentally break your project 20 | 21 | ## 1.1.2 22 | 23 | - Always switch to defined channel to fix `./flutterw upgrade` #19 24 | - Escape all git arguments 25 | 26 | ## 1.1.1 27 | 28 | - Escape arguments in post flutterw section 7701728ffab0053db5a5f5b647c2c38ea0e0b27e 29 | - Fix `./flutterw upgrade` when branch (`stable`) is not fetched ba5729987c10d62cd860b3b81ebc8ae62484b86c 30 | 31 | ## 1.1.0 32 | 33 | - `./flutterw upgrade` and `./flutterw channel X` now works without manual adjustments inside the submodule #15 34 | - The channel (master|dev|beta|stable) is now synced with what's defined in `.gitmodules` 35 | 36 | ## 1.0.3 37 | 38 | - Fix submodule matching in install script 605854d1db5053fd36814d4a9733e9d1b182bcd3 39 | 40 | ## 1.0.2 41 | 42 | - Improve `.flutter` submodule matching 5008757479b29c7296fb9143e6adb859a392ba7e a1c7c7fa8903c5bf0b3344e921dfbd6c45cedcc7 43 | 44 | ## 1.0.1 45 | 46 | - Fix `.flutter` submodule existence check, allow other submodules containing `flutter` as name #7 47 | 48 | ## 1.0.0 49 | 50 | - Use flutters stable branch as default channel 51 | 52 | ## 0.8.0 53 | 54 | - #4 Use `https` instead of `ssh` to clone flutter 55 | - fc7d105 fail fast on download error 56 | 57 | ## 0.7.1 58 | 59 | - Fix: Download flutterw from latest tag defined in `version` file 053fc8a4aeb219d70c6793fb71bd334e1d860f37 60 | 61 | ## 0.7.0 62 | 63 | - Linux support (Hello PixelBook 😁) #3 64 | 65 | ## 0.6.1 66 | 67 | - Fix: Abort install script when git clone fails 8ec7c79748e11ab8e04356e5ca574ad9de3b4140 68 | - Fix: '.flutter' already exists in the index during update d02f505be2cf2648d67fc260c812a387d5e05cf1 69 | 70 | ## 0.6.0 71 | 72 | - Download version which is defined in `version` on master branch. Prevents downloading of preview versions 73 | - Adds version and date to `flutterw` header 74 | - `install.sh` does now support the argument `--tag|-t` for the git tag to download 75 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2018 Pascal Welsch 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Wrapper 2 | 3 | `flutterw` is a tiny, open source shell script which downloads and executes the Flutter SDK with the exact version defined in your project respository. 4 | It encourages the idea that upgrading Flutter should happen per project, not per developer. 5 | Thus upgrading Flutter with `flutterw` automatically upgrades Flutter for your co-workers and on the CI servers. 6 | 7 | The Flutter Wrapper will add the Flutter SDK as a git submodule to your project. 8 | It pins the version and the channel. 9 | 10 | This project is inspired by the gradle wrapper. 11 | 12 | Read more on [Medium](https://medium.com/grandcentrix/flutter-wrapper-bind-your-project-to-an-explicit-flutter-release-4062cfe6dcaf) 13 | 14 | 15 | # Install flutterw 16 | 17 | ```bash 18 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/passsy/flutter_wrapper/master/install.sh)" 19 | ``` 20 | _Open the Terminal, navigate to your project root and execute the line above._ 21 | 22 | From now on use `./flutterw` instead of `flutter` 23 | 24 | ![flutterw terminal demo](https://user-images.githubusercontent.com/1096485/64660427-840dc080-d440-11e9-97a2-a9e2bef203bd.gif) 25 | 26 | ## IDE Setup 27 | ### Use with VScode 28 | 29 | If you're a VScode user link the new Flutter SDK path in your settings 30 | `$projectRoot/.vscode/settings.json` (create if it doesn't exists yet) 31 | 32 | ```json 33 | { 34 | "dart.flutterSdkPath": ".flutter", 35 | } 36 | ``` 37 | 38 | Commit this file to your git repo and your coworkers will automatically use `flutterw` from now on 39 | 40 | ### Use with IntelliJ / Android Studio 41 | 42 | Go to `File > Settings > Languages & Frameworks > Flutter` and set the Flutter SDK path to `$projectRoot/.flutter` 43 | 44 | IntelliJ Settings 45 | 46 | Add this step to the onboarding guidelines of your projects because this has to be done for every developer for every project using `flutterw`. 47 | 48 | 49 | ## Tips and Tricks 50 | ### Upgrading Flutter 51 | 52 | Flutter Wrapper doesn't require any special command to update Flutter. 53 | Run `./flutterw channel ` to change the channel or update to the lastest version of a given channel. 54 | 55 | ``` 56 | ./flutterw channel beta 57 | ./flutterw upgrade 58 | ``` 59 | 60 | Don't forget to commit the submodule changes. 61 | 62 | 63 | ### Updating flutterw 64 | 65 | To update the flutter wrapper to the latest version run the install command again: 66 | 67 | ```bash 68 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/passsy/flutter_wrapper/master/install.sh)" 69 | ``` 70 | 71 | To update the flutter wrapper to a specific verssion, use the `-t ` (i.e. `v1.0.0`) 72 | 73 | ```bash 74 | sh -c "curl -fsSL https://raw.githubusercontent.com/passsy/flutter_wrapper/master/install.sh" | bash /dev/stdin -t v1.0.0 75 | ``` 76 | 77 | 78 | ### Uninstall flutterw 79 | 80 | Sorry to let you go! 81 | Removing submodules is hard, that's why I did the hard work for you. 82 | Simply run this command from the root of your flutter project and the uninstall script will cleanup everything. 83 | 84 | ```bash 85 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/passsy/flutter_wrapper/master/uninstall.sh)" 86 | ``` 87 | 88 | Bye :wave: 89 | 90 | ### Create a new project using the flutter wrapper 91 | 92 | You can create a new Flutter project without installing Flutter globally on your machine. 93 | 94 | ```bash 95 | # 1. Create an empty git repo 96 | mkdir flutter_wrapper_project && cd "$_" 97 | git init 98 | 99 | # 2. Install flutterw 100 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/passsy/flutter_wrapper/master/install.sh)" 101 | 102 | # 3. Create Flutter project 103 | ./flutterw create . 104 | ./flutterw run 105 | ``` 106 | 107 | ### Using flutterw on CI 108 | 109 | Here is an example of using flutterw on a CI server. 110 | 111 | ```bash 112 | jobs: 113 | linux: 114 | runs-on: ubuntu-latest 115 | 116 | container: 117 | # Use container with pre-installed Flutter dependencies like unzip, libglu1-mesa 118 | image: passsy/flutterw:base-0.4.1 119 | 120 | steps: 121 | - uses: actions/checkout@v2.4.0 122 | - run: ./flutterw doctor -v 123 | - run: ./flutterw pub get 124 | - run: ./flutterw test 125 | - run: ./flutterw analyze --fatal-infos --fatal-warnings . 126 | 127 | macos: 128 | runs-on: macos-latest 129 | steps: 130 | - uses: actions/checkout@v2.4.0 131 | - run: ./flutterw doctor -v 132 | - run: ./flutterw pub get 133 | - run: ./flutterw test 134 | - run: ./flutterw analyze --fatal-infos --fatal-warnings . 135 | ``` 136 | 137 | ## License 138 | 139 | ``` 140 | Copyright 2019 Pascal Welsch 141 | 142 | Licensed under the Apache License, Version 2.0 (the "License"); 143 | you may not use this file except in compliance with the License. 144 | You may obtain a copy of the License at 145 | 146 | http://www.apache.org/licenses/LICENSE-2.0 147 | 148 | Unless required by applicable law or agreed to in writing, software 149 | distributed under the License is distributed on an "AS IS" BASIS, 150 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 151 | See the License for the specific language governing permissions and 152 | limitations under the License. 153 | ``` 154 | -------------------------------------------------------------------------------- /flutterw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Flutter start up script for UN*X 6 | ## Version: VERSION_PLACEHOLDER 7 | ## Date: DATE_PLACEHOLDER 8 | ## 9 | ## Use this flutter wrapper to bundle Flutter within your project to make 10 | ## sure everybody builds with the same version. 11 | ## 12 | ## Read about the install and uninstall process in the README on GitHub 13 | ## https://github.com/passsy/flutter_wrapper 14 | ## 15 | ## Inspired by gradle-wrapper. 16 | ## 17 | ############################################################################## 18 | 19 | echoerr() { echo "$@" 1>&2; } 20 | 21 | # Attempt to set APP_HOME 22 | # Resolve links: $0 may be a link 23 | PRG="$0" 24 | # Need this for relative symlinks. 25 | while [ -h "$PRG" ]; do 26 | ls=$(ls -ld "$PRG") 27 | link=$(expr "$ls" : '.*-> \(.*\)$') 28 | if expr "$link" : '/.*' >/dev/null; then 29 | PRG="$link" 30 | else 31 | PRG=$(dirname "$PRG")"/$link" 32 | fi 33 | done 34 | SAVED="$(pwd)" 35 | cd "$(dirname "$PRG")/" >/dev/null 36 | APP_HOME="$(pwd -P)" 37 | cd "$SAVED" >/dev/null 38 | 39 | FLUTTER_SUBMODULE_NAME='.flutter' 40 | GIT_HOME=$(git -C "${APP_HOME}" rev-parse --show-toplevel) 41 | FLUTTER_DIR="${GIT_HOME}/${FLUTTER_SUBMODULE_NAME}" 42 | 43 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 44 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 45 | cd "$(dirname "$0")" 46 | fi 47 | 48 | # Fix not initialized flutter submodule 49 | if [ ! -f "${FLUTTER_DIR}/bin/flutter" ]; then 50 | echoerr "$FLUTTER_SUBMODULE_NAME submodule not initialized. Initializing..." 51 | git submodule update --init "${FLUTTER_DIR}" 52 | fi 53 | 54 | # Detect detach HEAD and fix it. commands like upgrade expect a valid branch, not a detached HEAD 55 | FLUTTER_SYMBOLIC_REF=$(git -C "${FLUTTER_DIR}" symbolic-ref -q HEAD) 56 | if [ -z "${FLUTTER_SYMBOLIC_REF}" ]; then 57 | FLUTTER_REV=$(git -C "${FLUTTER_DIR}" rev-parse HEAD) 58 | FLUTTER_CHANNEL=$(git -C "${GIT_HOME}" config -f .gitmodules submodule.${FLUTTER_SUBMODULE_NAME}.branch) 59 | 60 | if [ -z "${FLUTTER_CHANNEL}" ]; then 61 | echoerr "Warning: Submodule '$FLUTTER_SUBMODULE_NAME' doesn't point to an official Flutter channel \ 62 | (one of stable|beta|dev|master). './flutterw upgrade' will fail without a channel." 63 | echoerr "Fix this by adding a specific channel with:" 64 | echoerr " - './flutterw channel ' or" 65 | echoerr " - Add 'branch = ' to '$FLUTTER_SUBMODULE_NAME' submodule in .gitmodules" 66 | else 67 | echoerr "Fixing detached HEAD: '$FLUTTER_SUBMODULE_NAME' submodule points to a specific commit $FLUTTER_REV, not channel '$FLUTTER_CHANNEL' (as defined in .gitmodules)." 68 | # Make sure channel is fetched 69 | # Remove old channel branch because it might be moved to an unrelated commit where fast-forward pull isn't possible 70 | git -C "${FLUTTER_DIR}" branch -q -D "${FLUTTER_CHANNEL}" 2> /dev/null || true 71 | git -C "${FLUTTER_DIR}" fetch -q origin 72 | 73 | # bind current HEAD to channel defined in .gitmodules 74 | git -C "${FLUTTER_DIR}" checkout -q -b "${FLUTTER_CHANNEL}" "${FLUTTER_REV}" 75 | git -C "${FLUTTER_DIR}" branch -q -u "origin/${FLUTTER_CHANNEL}" "${FLUTTER_CHANNEL}" 76 | echoerr "Fixed! Migrated to channel '$FLUTTER_CHANNEL' while staying at commit $FLUTTER_REV. './flutterw upgrade' now works without problems!" 77 | git -C "${FLUTTER_DIR}" status -bs 78 | fi 79 | fi 80 | 81 | # Wrapper tasks done, call flutter binary with all args 82 | set -e 83 | "$FLUTTER_DIR/bin/flutter" "$@" 84 | set +e 85 | 86 | # Post flutterw tasks. exit code from /bin/flutterw will be used as final exit 87 | FLUTTER_EXIT_STATUS=$? 88 | if [ ${FLUTTER_EXIT_STATUS} -eq 0 ]; then 89 | 90 | # ./flutterw channel CHANNEL 91 | if echo "$@" | grep -q "channel"; then 92 | if [ -n "$2" ]; then 93 | # make sure .gitmodules is updated as well 94 | CHANNEL=${2} # second arg 95 | git config -f "${GIT_HOME}/.gitmodules" "submodule.${FLUTTER_SUBMODULE_NAME}.branch" "${CHANNEL}" 96 | # makes sure nobody forgets to do commit all changed files 97 | git add "${GIT_HOME}/.gitmodules" 98 | git add "${FLUTTER_DIR}" 99 | fi 100 | fi 101 | 102 | # ./flutterw upgrade 103 | if echo "$@" | grep -q "upgrade"; then 104 | # makes sure nobody forgets to do commit the changed submodule 105 | git add "${FLUTTER_DIR}" 106 | # flutter packages get runs automatically. Stage those changes as well 107 | if [ -f pubspec.lock ]; then 108 | git add pubspec.lock 109 | fi 110 | fi 111 | fi 112 | 113 | exit ${FLUTTER_EXIT_STATUS} 114 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ### 4 | # Check preconditions 5 | ### 6 | 7 | # Verify flutter project is a git repo 8 | inside_git_repo="$(git rev-parse --is-inside-work-tree 2>/dev/null)" 9 | if ! [ "$inside_git_repo" ]; then 10 | printf "Error: Not a git repository, to fix this run: git init\n" 11 | exit 1 12 | fi 13 | 14 | # Make sure this is the root of the flutter dir (search for pubspec.yaml) 15 | # flutterw should be placed next to 16 | if ! [ -f pubspec.yaml ]; then 17 | printf "Warning: Not executed in flutter root. Couldn't find pubspec.yaml.\n" 18 | printf "Continuing in case this flutter wrapper is used to create a new project. If so continue with './flutterw create .'\n\n" 19 | fi 20 | 21 | ### 22 | # Parse arguments 23 | ### 24 | 25 | # Parse arguments 26 | while [ "$1" != "" ]; do 27 | case $1 in 28 | 29 | # version tag which should be used for downloading 30 | -t | --tag) 31 | shift 32 | VERSION_TAG="$1" 33 | ;; 34 | 35 | *) 36 | echo "Unknown option '$key'" 37 | ;; 38 | esac 39 | shift 40 | done 41 | 42 | if [ -z "$VERSION_TAG" ]; then 43 | # Get latest version from master in git 44 | VERSION_TAG=$(curl -s "https://raw.githubusercontent.com/passsy/flutter_wrapper/master/version") 45 | 46 | starts_with_v=$(echo "$VERSION_TAG" | cut -c 1) 47 | if [ "$starts_with_v" != "v" ]; then 48 | # add v prefix for tag if not present 49 | VERSION_TAG="v$VERSION_TAG" 50 | fi 51 | fi 52 | 53 | printf "Installing Flutter Wrapper %s\n" "$VERSION_TAG" 54 | 55 | ### 56 | # Add .flutter submodule 57 | ### 58 | FLUTTER_SUBMODULE_NAME='.flutter' 59 | GIT_HOME=$(git rev-parse --show-toplevel) 60 | 61 | # Check if submodule already exists (when updating flutter wrapper) 62 | HAS_SUBMODULE=$(git -C "${GIT_HOME}" submodule | grep "\ ${FLUTTER_SUBMODULE_NAME}") 63 | if [ -z "${HAS_SUBMODULE}" ]; then 64 | printf "adding '%s' submodule\n" "${FLUTTER_SUBMODULE_NAME}" 65 | UPDATED=false 66 | 67 | # create relative to /.flutter 68 | source=$PWD 69 | target=${GIT_HOME}/${FLUTTER_SUBMODULE_NAME} 70 | common_part=$source 71 | back= 72 | while [ "${target#$common_part}" = "${target}" ]; do 73 | common_part=$(dirname "$common_part") 74 | back="../${back}" 75 | done 76 | CLONE_TO=${back}${target#$common_part/} 77 | 78 | # add the flutter submodule 79 | git submodule add -b stable https://github.com/flutter/flutter.git "${CLONE_TO}" 80 | 81 | # When submodule failed, abort 82 | if [ ! $? -eq 0 ]; then 83 | echo "Abort installation of flutterw, couldn't clone flutter" >&2 84 | exit 1 85 | fi 86 | else 87 | # update url to https 88 | printf "Upgrading existing flutter wrapper\n" 89 | UPDATED=true 90 | 91 | # Update old ssh url to https 92 | SUBMODULE_PATH=$(git config -f "${GIT_HOME}/.gitmodules" "submodule.$FLUTTER_SUBMODULE_NAME.path" ) 93 | 94 | USES_SSH=$(git config -f "${GIT_HOME}/.gitmodules" "submodule.${SUBMODULE_PATH}.url" | cut -c 1-4) 95 | if [ "$USES_SSH" = "git@" ]; then 96 | printf "Update %s submodule url to https\n" "$FLUTTER_SUBMODULE_NAME" 97 | git config -f "${GIT_HOME}/.gitmodules submodule.${SUBMODULE_PATH}.url" https://github.com/flutter/flutter.git 98 | git add "${GIT_HOME}/.gitmodules" 99 | git submodule sync "${FLUTTER_SUBMODULE_NAME}" 100 | fi 101 | fi 102 | 103 | ### 104 | # Download flutterw executable 105 | ### 106 | printf "Downloading new flutterw\n" 107 | # Download latest flutterw version 108 | FLUTTERW_URL="https://raw.githubusercontent.com/passsy/flutter_wrapper/$VERSION_TAG/flutterw" 109 | curl -sfO "$FLUTTERW_URL" 110 | if [ "$?" != "0" ]; then 111 | printf "Couldn't download flutterw from '%s'\n" "$FLUTTERW_URL" 112 | exit 1 113 | fi 114 | 115 | # make it executable 116 | chmod 755 flutterw 117 | 118 | # Replace version string in wrapper 119 | sed -i.bak "s/VERSION_PLACEHOLDER/$VERSION_TAG/g" flutterw && rm flutterw.bak 120 | 121 | # Replace date placeholder in wrapper 122 | DATE=$(date '+%Y-%m-%d %H:%M:%S') 123 | sed -i.bak "s/DATE_PLACEHOLDER/$DATE/g" flutterw && rm flutterw.bak 124 | 125 | # add it to git 126 | git add flutterw 127 | 128 | ### 129 | # Run flutterw 130 | ### 131 | 132 | # bind this flutter instance to the project (update .packages file) 133 | if [ -f pubspec.yaml ]; then 134 | ./flutterw packages get 135 | fi 136 | 137 | if $UPDATED; then 138 | printf "\nFlutter Wrapper updated to version %s\n\n" "$VERSION_TAG" 139 | else 140 | printf "\nFlutter Wrapper installed (version %s), initialized with channel stable.\n\n" "$VERSION_TAG" 141 | fi 142 | printf "Run your app with: ./flutterw run\n" 143 | printf "Switch channel: ./flutterw channel beta\n" 144 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | # Remove the following pattern if you wish to check in your lock file 5 | pubspec.lock 6 | 7 | # Conventional directory for build outputs 8 | build/ 9 | 10 | # Directory created by dartdoc 11 | doc/api/ 12 | -------------------------------------------------------------------------------- /test/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lint/analysis_options.yaml -------------------------------------------------------------------------------- /test/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_wrapper_test 2 | 3 | environment: 4 | sdk: '>=3.0.0 <4.0.0' 5 | 6 | dependencies: 7 | cli_script: ^1.0.0 8 | file: ^7.0.0 9 | 10 | dev_dependencies: 11 | lint: ^2.0.0 12 | synchronized: ^3.1.0 13 | test: ^1.24.2 14 | -------------------------------------------------------------------------------- /test/test/install_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io' as io; 3 | 4 | import 'package:cli_script/cli_script.dart'; 5 | import 'package:file/file.dart'; 6 | import 'package:file/local.dart'; 7 | import 'package:synchronized/synchronized.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | group('install', () { 12 | test( 13 | 'report missing git', 14 | () async { 15 | final dir = io.Directory.systemTemp.createTempSync('root'); 16 | addTearDown(() { 17 | dir.deleteSync(recursive: true); 18 | }); 19 | 20 | final script = Script.capture((_) async { 21 | await runInstallScript( 22 | appDir: dir.absolute.path, gitRootDir: dir.absolute.path); 23 | }); 24 | // access fields before accessing them or they crash 25 | final exitCodeFuture = script.exitCode; 26 | final outFuture = script.stdout.text; 27 | 28 | final err = await outFuture; 29 | expect( 30 | err, contains("Not a git repository, to fix this run: git init")); 31 | final code = await exitCodeFuture; 32 | expect(code, 1); 33 | }, 34 | timeout: const Timeout(Duration(minutes: 5)), 35 | ); 36 | 37 | group("install in git root", () { 38 | late Directory gitRootDir; 39 | late Directory appDir; 40 | 41 | tearDownAll(() { 42 | appDir.parent.deleteSync(recursive: true); 43 | }); 44 | setUpAll(() async { 45 | final dir = 46 | const LocalFileSystem().systemTempDirectory.createTempSync('root'); 47 | gitRootDir = appDir = dir.childDirectory('myApp'); 48 | assert(gitRootDir == appDir); 49 | 50 | // create git in appDir 51 | appDir.createSync(); 52 | await run("git init", workingDirectory: appDir.absolute.path); 53 | 54 | await runInstallScript( 55 | appDir: appDir.absolute.path, gitRootDir: gitRootDir.absolute.path); 56 | }); 57 | 58 | test('flutterw was downloaded', () async { 59 | expect(appDir.childFile('flutterw').existsSync(), isTrue); 60 | }); 61 | 62 | test('flutterw is executable', () async { 63 | final flutterw = appDir.childFile('flutterw'); 64 | final script = 65 | Script.capture((_) async => run("stat ${flutterw.absolute.path}")); 66 | expect(await script.stdout.text, contains("-rwxr-xr-x")); 67 | }); 68 | 69 | test('created .flutter submodule in appDir', () async { 70 | final flutterw = appDir.childFile('flutterw'); 71 | expect(flutterw.existsSync(), isTrue); 72 | }); 73 | 74 | test('downloaded dart tools', () async { 75 | expect( 76 | gitRootDir 77 | .childFile(".flutter/bin/cache/dart-sdk/bin/dart") 78 | .existsSync(), 79 | isTrue); 80 | }); 81 | 82 | test('flutterw contains version', () async { 83 | final flutterw = appDir.childFile('flutterw'); 84 | final text = flutterw.readAsStringSync(); 85 | expect(text, isNot(contains("VERSION_PLACEHOLDER"))); 86 | expect(text, isNot(contains("DATE_PLACEHOLDER"))); 87 | }); 88 | }); 89 | 90 | group("install in subdir", () { 91 | late Directory gitRootDir; 92 | late Directory appDir; 93 | 94 | tearDownAll(() { 95 | gitRootDir.deleteSync(recursive: true); 96 | }); 97 | 98 | setUpAll(() async { 99 | gitRootDir = 100 | const LocalFileSystem().systemTempDirectory.createTempSync('root'); 101 | // git repo in root, flutterw in appDir 102 | appDir = gitRootDir.childDirectory('myApp')..createSync(); 103 | 104 | await run("git init", workingDirectory: gitRootDir.absolute.path); 105 | await runInstallScript( 106 | appDir: appDir.absolute.path, gitRootDir: gitRootDir.absolute.path); 107 | }); 108 | 109 | test('subdir flutterw was downloaded', () async { 110 | final flutterw = appDir.childFile('flutterw'); 111 | expect(flutterw.existsSync(), isTrue); 112 | }); 113 | 114 | test('subdir flutterw is executable', () async { 115 | final flutterw = appDir.childFile('flutterw'); 116 | final script = 117 | Script.capture((_) async => run("stat ${flutterw.absolute.path}")); 118 | expect(await script.stdout.text, contains("-rwxr-xr-x")); 119 | }); 120 | 121 | test('subdir created .flutter submodule', () async { 122 | final flutterDir = gitRootDir.childDirectory('.flutter'); 123 | expect(flutterDir.existsSync(), isTrue); 124 | }); 125 | 126 | test('subdir downloaded dart tools', () async { 127 | final dartBinary = 128 | gitRootDir.childFile(".flutter/bin/cache/dart-sdk/bin/dart"); 129 | expect(dartBinary.existsSync(), isTrue); 130 | }); 131 | 132 | test('subdir flutterw contains version', () async { 133 | final flutterw = appDir.childFile('flutterw'); 134 | final text = flutterw.readAsStringSync(); 135 | expect(text, isNot(contains("VERSION_PLACEHOLDER"))); 136 | expect(text, isNot(contains("DATE_PLACEHOLDER"))); 137 | }); 138 | }); 139 | }); 140 | } 141 | 142 | bool _precached = false; 143 | Lock precacheLock = Lock(); 144 | 145 | /// Allow to clone from file system to speed up tests 146 | Future allowCloneFromFile() async { 147 | // get current allow setting 148 | String? allowValue; 149 | try { 150 | allowValue = await output('git config --global protocol.file.allow'); 151 | } catch (e) { 152 | // ignore, not set 153 | } 154 | if (allowValue?.trim() != 'always') { 155 | await run('git config --global protocol.file.allow always'); 156 | 157 | // always restore to previous value 158 | addTearDown(() async { 159 | if (allowValue == null) { 160 | await run('git config --global --unset protocol.file.allow'); 161 | } else { 162 | await run('git config --global protocol.file.allow $allowValue'); 163 | } 164 | }); 165 | } 166 | } 167 | 168 | Future runInstallScript({ 169 | required String appDir, 170 | required String gitRootDir, 171 | }) async { 172 | await allowCloneFromFile(); 173 | const fs = LocalFileSystem(); 174 | final repoRoot = fs.currentDirectory.parent; 175 | // Get path from line 176 | // • Flutter version 2.2.0-10.1.pre at /usr/local/Caskroom/flutter/latest/flutter 177 | final doctor = Script.capture((_) async => run('flutter doctor -v')); 178 | final lines = await doctor.stdout.lines.toList(); 179 | final flutterRepoPath = lines 180 | .firstWhere((line) => line.contains("Flutter version")) 181 | .split(" ") 182 | .last; 183 | final localFlutterExists = fs.directory(flutterRepoPath).existsSync(); 184 | if (!localFlutterExists) { 185 | throw "Did not find a flutter repo on your system"; 186 | } 187 | 188 | await precacheLock.synchronized(() async { 189 | if (!_precached) { 190 | // make sure remote is setup correctly 191 | final remotes = 192 | await output('git remote', workingDirectory: flutterRepoPath); 193 | if (!remotes.contains('origin')) { 194 | await run( 195 | 'git remote add origin https://github.com/flutter/flutter.git', 196 | workingDirectory: flutterRepoPath); 197 | } 198 | // make sure all branches are available as in the remote 199 | await run('git fetch --all', workingDirectory: flutterRepoPath); 200 | await run('git checkout beta', workingDirectory: flutterRepoPath); 201 | await run('git checkout master', workingDirectory: flutterRepoPath); 202 | await run('git checkout stable', workingDirectory: flutterRepoPath); 203 | 204 | await run('flutter channel stable'); 205 | await run('flutter upgrade'); 206 | await run('flutter precache'); 207 | _precached = true; 208 | } 209 | }); 210 | 211 | final buildDir = fs.directory(appDir).childDirectory('build') 212 | ..createSync(recursive: true); 213 | 214 | final File testableInstall = repoRoot 215 | .childFile('install.sh') 216 | .copySync(buildDir.childFile('testable_install.sh').path); 217 | 218 | await run('chmod 755 ${testableInstall.path}'); 219 | { 220 | // modify install script to use local dependencies 221 | var modified = testableInstall.readAsStringSync(); 222 | 223 | // Close local repo instead of remote 224 | modified = modified.replaceFirst( 225 | 'https://github.com/flutter/flutter.git', flutterRepoPath); 226 | 227 | // Instead of getting dependencies, preload flutter dependencies 228 | modified = modified.replaceFirst( 229 | '''if [ -f pubspec.yaml ]; then 230 | ./flutterw packages get 231 | fi''', 232 | // copy over precached files 233 | 'mkdir -p $appDir/.flutter/bin/cache/ \n' 234 | 'cp -R -L -f "$flutterRepoPath/bin/" "$gitRootDir/.flutter/bin/" \n' 235 | 'cp -R -L -f "$flutterRepoPath/packages/flutter_tools/" "$gitRootDir/.flutter/packages/flutter_tools/" \n' 236 | './flutterw \n', 237 | ); 238 | 239 | // Don't load version from repo 240 | modified = modified.replaceFirst( 241 | 'VERSION_TAG=\$(curl -s "https://raw.githubusercontent.com/passsy/flutter_wrapper/master/version")', 242 | 'VERSION_TAG=T.E.S.T', 243 | ); 244 | 245 | // Local local flutterw file 246 | modified = modified.replaceFirst( 247 | 'https://raw.githubusercontent.com/passsy/flutter_wrapper/\$VERSION_TAG/flutterw', 248 | 'file://${repoRoot.childFile('flutterw').path}', 249 | ); 250 | 251 | // add set -x for debugging 252 | modified = modified.replaceFirst( 253 | '#!/usr/bin/env sh', 254 | '#!/usr/bin/env sh\nset -x', 255 | ); 256 | 257 | testableInstall.writeAsStringSync(modified); 258 | } 259 | 260 | final script = Script( 261 | "${testableInstall.absolute.path}", 262 | name: 'install.sh (testable)', 263 | workingDirectory: appDir, 264 | ); 265 | await script.done; 266 | } 267 | -------------------------------------------------------------------------------- /test/test/multi_packages_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_print 2 | 3 | import 'package:cli_script/cli_script.dart'; 4 | import 'package:file/local.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | import 'install_test.dart'; 8 | 9 | void main() { 10 | test( 11 | 'Calling ./../../fluttew from packages/xyz/', 12 | () async { 13 | // setup repo 14 | final repo = 15 | const LocalFileSystem().systemTempDirectory.createTempSync('repo'); 16 | addTearDown(() { 17 | repo.deleteSync(recursive: true); 18 | }); 19 | await run('git init', workingDirectory: repo.absolute.path); 20 | await runInstallScript( 21 | appDir: repo.absolute.path, gitRootDir: repo.absolute.path); 22 | await run('git commit -a -m "initial commit"', 23 | workingDirectory: repo.absolute.path); 24 | 25 | // create package 26 | final package = repo.childDirectory('packages/xyz') 27 | ..createSync(recursive: true); 28 | await run('./flutterw create packages/xyz', 29 | workingDirectory: repo.absolute.path); 30 | 31 | // Make sure flutterw can be executed in package 32 | final script = Script.capture((_) async => 33 | run('./../../flutterw', workingDirectory: package.absolute.path)); 34 | final output = await script.combineOutput().text; 35 | print(output); 36 | expect( 37 | output, 38 | isNot(contains( 39 | 'fatal: cannot change to \'\.flutter\': No such file or directory'))); 40 | }, 41 | timeout: const Timeout(Duration(minutes: 5)), 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /test/test/submodule_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:cli_script/cli_script.dart'; 2 | import 'package:file/local.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import 'install_test.dart'; 6 | 7 | void main() { 8 | test( 9 | 'populate submodule when uninitialized', 10 | () async { 11 | final origin = 12 | const LocalFileSystem().systemTempDirectory.createTempSync('origin'); 13 | addTearDown(() { 14 | origin.deleteSync(recursive: true); 15 | }); 16 | await run('git init', workingDirectory: origin.absolute.path); 17 | await runInstallScript( 18 | appDir: origin.absolute.path, gitRootDir: origin.absolute.path); 19 | await run('git commit -a -m "initial commit"', 20 | workingDirectory: origin.absolute.path); 21 | 22 | final clone = 23 | const LocalFileSystem().systemTempDirectory.createTempSync('clone'); 24 | addTearDown(() { 25 | clone.deleteSync(recursive: true); 26 | }); 27 | await run('git clone ${origin.absolute.path} ${clone.absolute.path}'); 28 | // calling flutterw should now automatically initialize the submodule and build the flutter tool 29 | await run('./flutterw', workingDirectory: clone.absolute.path); 30 | 31 | expect(clone.childFile('.flutter/bin/flutter').existsSync(), isTrue); 32 | expect( 33 | clone.childFile('.flutter/bin/cache/dart-sdk/bin/dart').existsSync(), 34 | isTrue); 35 | }, 36 | timeout: const Timeout(Duration(minutes: 5)), 37 | ); 38 | 39 | group('gitmodules update', () { 40 | test( 41 | 'flutter channel updates gitmodules (single module)', 42 | () async { 43 | final repo = 44 | const LocalFileSystem().systemTempDirectory.createTempSync('repo'); 45 | addTearDown(() { 46 | repo.deleteSync(recursive: true); 47 | }); 48 | await run('git init', workingDirectory: repo.absolute.path); 49 | await runInstallScript( 50 | appDir: repo.absolute.path, gitRootDir: repo.absolute.path); 51 | await run('git commit -a -m "initial commit"', 52 | workingDirectory: repo.absolute.path); 53 | expect(repo.childFile('.gitmodules').readAsStringSync(), 54 | contains('branch = stable')); 55 | 56 | await run('./flutterw channel beta', 57 | workingDirectory: repo.absolute.path); 58 | expect(repo.childFile('.gitmodules').readAsStringSync(), 59 | contains('branch = beta')); 60 | }, 61 | timeout: const Timeout(Duration(minutes: 5)), 62 | ); 63 | 64 | test( 65 | 'flutter channel without second arg does not update gitmodules (single module)', 66 | () async { 67 | final repo = 68 | const LocalFileSystem().systemTempDirectory.createTempSync('repo'); 69 | addTearDown(() { 70 | repo.deleteSync(recursive: true); 71 | }); 72 | await run('git init', workingDirectory: repo.absolute.path); 73 | await runInstallScript( 74 | appDir: repo.absolute.path, gitRootDir: repo.absolute.path); 75 | await run('git commit -a -m "initial commit"', 76 | workingDirectory: repo.absolute.path); 77 | expect(repo.childFile('.gitmodules').readAsStringSync(), 78 | contains('branch = stable')); 79 | 80 | await run('./flutterw channel', workingDirectory: repo.absolute.path); 81 | expect(repo.childFile('.gitmodules').readAsStringSync(), 82 | contains('branch = stable')); 83 | }, 84 | timeout: const Timeout(Duration(minutes: 5)), 85 | ); 86 | 87 | test( 88 | 'flutter channel called from package updates gitmodules in root', 89 | () async { 90 | final repo = 91 | const LocalFileSystem().systemTempDirectory.createTempSync('repo'); 92 | addTearDown(() { 93 | repo.deleteSync(recursive: true); 94 | }); 95 | await run('git init', workingDirectory: repo.absolute.path); 96 | await runInstallScript( 97 | appDir: repo.absolute.path, gitRootDir: repo.absolute.path); 98 | await run('git commit -a -m "initial commit"', 99 | workingDirectory: repo.absolute.path); 100 | expect(repo.childFile('.gitmodules').readAsStringSync(), 101 | contains('branch = stable')); 102 | 103 | // create package 104 | final package = repo.childDirectory('packages/xyz') 105 | ..createSync(recursive: true); 106 | 107 | await run('./../../flutterw channel beta', 108 | workingDirectory: package.absolute.path); 109 | // doesn't accidentally create a .gitmodules file in package 110 | expect(package.childFile('.gitmodules').existsSync(), isFalse); 111 | // updates .gitmodules in root 112 | expect(repo.childFile('.gitmodules').readAsStringSync(), 113 | contains('branch = beta')); 114 | }, 115 | timeout: const Timeout(Duration(minutes: 5)), 116 | ); 117 | 118 | test( 119 | 'flutter channel without second arg called from package does not update gitmodules in root', 120 | () async { 121 | final repo = 122 | const LocalFileSystem().systemTempDirectory.createTempSync('repo'); 123 | addTearDown(() { 124 | repo.deleteSync(recursive: true); 125 | }); 126 | await run('git init', workingDirectory: repo.absolute.path); 127 | await runInstallScript( 128 | appDir: repo.absolute.path, gitRootDir: repo.absolute.path); 129 | await run('git commit -a -m "initial commit"', 130 | workingDirectory: repo.absolute.path); 131 | expect(repo.childFile('.gitmodules').readAsStringSync(), 132 | contains('branch = stable')); 133 | 134 | // create package 135 | final package = repo.childDirectory('packages/xyz') 136 | ..createSync(recursive: true); 137 | 138 | await run('./../../flutterw channel', 139 | workingDirectory: package.absolute.path); 140 | // doesn't accidentally create a .gitmodules file in package 141 | expect(package.childFile('.gitmodules').existsSync(), isFalse); 142 | // Doesn't update .gitmodules in root 143 | expect(repo.childFile('.gitmodules').readAsStringSync(), 144 | contains('branch = stable')); 145 | }, 146 | timeout: const Timeout(Duration(minutes: 5)), 147 | ); 148 | }); 149 | } 150 | -------------------------------------------------------------------------------- /test/test/update_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:cli_script/cli_script.dart'; 2 | import 'package:file/file.dart'; 3 | import 'package:file/local.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | import 'install_test.dart'; 7 | 8 | void main() { 9 | group('update', () { 10 | group("Update flutterw in git root", () { 11 | late Directory gitRootDir; 12 | late Directory appDir; 13 | 14 | tearDownAll(() { 15 | appDir.parent.deleteSync(recursive: true); 16 | }); 17 | setUpAll(() async { 18 | final dir = 19 | const LocalFileSystem().systemTempDirectory.createTempSync('root'); 20 | gitRootDir = appDir = dir.childDirectory('myApp'); 21 | assert(gitRootDir == appDir); 22 | 23 | // create git in appDir 24 | appDir.createSync(); 25 | await run("git init", workingDirectory: appDir.absolute.path); 26 | 27 | await runInstallScript( 28 | appDir: appDir.absolute.path, gitRootDir: gitRootDir.absolute.path); 29 | }); 30 | 31 | test('updates flutterw', () async { 32 | final flutterw = appDir.childFile('flutterw'); 33 | final content = flutterw.readAsStringSync(); 34 | await runInstallScript( 35 | appDir: appDir.absolute.path, gitRootDir: gitRootDir.absolute.path); 36 | 37 | final update = flutterw.readAsStringSync(); 38 | expect(update, isNot(content)); 39 | }); 40 | }); 41 | 42 | group("update in subdir", () { 43 | late Directory gitRootDir; 44 | late Directory appDir; 45 | 46 | tearDownAll(() { 47 | gitRootDir.deleteSync(recursive: true); 48 | }); 49 | 50 | setUpAll(() async { 51 | gitRootDir = 52 | const LocalFileSystem().systemTempDirectory.createTempSync('root'); 53 | // git repo in root, flutterw in appDir 54 | appDir = gitRootDir.childDirectory('myApp')..createSync(); 55 | 56 | await run("git init", workingDirectory: gitRootDir.absolute.path); 57 | await runInstallScript( 58 | appDir: appDir.absolute.path, gitRootDir: gitRootDir.absolute.path); 59 | }); 60 | 61 | test('updates flutterw', () async { 62 | final flutterw = appDir.childFile('flutterw'); 63 | final content = flutterw.readAsStringSync(); 64 | await runInstallScript( 65 | appDir: appDir.absolute.path, gitRootDir: gitRootDir.absolute.path); 66 | 67 | final update = flutterw.readAsStringSync(); 68 | expect(update, isNot(content)); 69 | }); 70 | }); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | echo "Uninstalling Flutter Wrapper..." 4 | 5 | FLUTTER_DIR_NAME='.flutter' 6 | 7 | # remove wrapper executable via git or fallback just the wrapper file when not 8 | # known to git 9 | git rm -f flutterw >>/dev/null 2>&1 || rm flutterw 10 | 11 | # remove submodule 12 | git submodule deinit -f $FLUTTER_DIR_NAME 13 | 14 | # remove submodule directory 15 | git rm -rf $FLUTTER_DIR_NAME 16 | 17 | # remove submodule history 18 | rm -rf .git/modules/$FLUTTER_DIR_NAME 19 | 20 | # remove empty .gitmodules file 21 | if ! [ -s .gitmodules ]; then 22 | # try via git first, fallback to just rm when not added to git 23 | git rm -f .gitmodules >>/dev/null 2>&1 || rm .gitmodules 24 | fi 25 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 1.3.1 2 | --------------------------------------------------------------------------------