diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9c8b630..2df6d79 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,8 @@ on: push env: # Keep this in sync with the version used by FlutterFlow. - DART_VERSION: 3.4.3 + DART_VERSION: 3.5.2 + FLUTTER_VERSION: 3.24.2 jobs: check: @@ -21,7 +22,7 @@ jobs: - name: Install dependencies run: | - dart pub get + dart pub get --enforce-lockfile - name: Analyze code run: | @@ -34,24 +35,28 @@ jobs: build_and_test: runs-on: ${{ matrix.os }} needs: check - timeout-minutes: 10 + timeout-minutes: 30 strategy: + # Can't run in parallel otherwise we get rate limted + max-parallel: 1 matrix: - os: [ubuntu-24.04, windows-2022, macos-14] + os: [ubuntu-24.04, windows-2025, windows-2022, macos-14] steps: - name: Clone repository uses: actions/checkout@v4 - - name: Setup Dart - uses: dart-lang/setup-dart@v1 + - name: Setup Flutter + uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # v2 with: - sdk: ${{ env.DART_VERSION }} + channel: master + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true - name: Install dependencies run: | - dart pub get + dart pub get --enforce-lockfile - name: Build run: | @@ -62,5 +67,7 @@ jobs: dart run bin/flutterflow_cli.dart -h - name: Test + env: + FF_TESTER_TOKEN: ${{ secrets.FF_TESTER_TOKEN }} run: | dart test diff --git a/.gitignore b/.gitignore index 05d0295..e5b6e19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +bin/flutterflow .dart_tool/ -pubspec.lock +export/ diff --git a/bin/flutterflow_cli.dart b/bin/flutterflow_cli.dart index 9e44c57..5eb543f 100644 --- a/bin/flutterflow_cli.dart +++ b/bin/flutterflow_cli.dart @@ -5,7 +5,7 @@ import 'package:flutterflow_cli/src/flutterflow_api_client.dart'; const kDefaultEndpoint = 'https://api.flutterflow.io/v1'; -void main(List args) async { +Future appMain(List args) async { final parsedArguments = _parseArgs(args); final token = @@ -71,7 +71,8 @@ void main(List args) async { break; default: } - } catch (_) { + } catch (e) { + stderr.write('Error running the application: $e\n'); exit(1); } } @@ -181,3 +182,7 @@ ArgResults _parseArgs(List args) { return parsed; } + +void main(List args) async { + await appMain(args); +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..a5887d9 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,434 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" + url: "https://pub.dev" + source: hosted + version: "73.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" + url: "https://pub.dev" + source: hosted + version: "6.8.0" + archive: + dependency: "direct main" + description: + name: archive + sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + args: + dependency: "direct main" + description: + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.dev" + source: hosted + version: "2.12.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + url: "https://pub.dev" + source: hosted + version: "1.11.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: "direct main" + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + http: + dependency: "direct main" + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + lints: + dependency: "direct dev" + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "8391fbe68d520daf2314121764d38e37f934c02fd7301ad18307bd93bd6b725d" + url: "https://pub.dev" + source: hosted + version: "1.25.14" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + test_core: + dependency: transitive + description: + name: test_core + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + url: "https://pub.dev" + source: hosted + version: "0.6.8" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.5.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index df06688..9cdbf36 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,11 +9,11 @@ environment: sdk: ">=2.17.0 <4.0.0" dependencies: - archive: ^3.3.5 - args: ^2.3.0 - glob: ^2.1.0 - http: ^1.1.0 - path: ^1.8.0 + archive: ^4.0.2 + args: ^2.6.0 + glob: ^2.1.3 + http: ^1.3.0 + path: ^1.9.1 dev_dependencies: lints: ^3.0.0 diff --git a/test/integration_test.dart b/test/integration_test.dart new file mode 100644 index 0000000..46075a0 --- /dev/null +++ b/test/integration_test.dart @@ -0,0 +1,251 @@ +import '../bin/flutterflow_cli.dart'; + +import 'package:test/test.dart'; +import 'package:path/path.dart' as p; + +import 'dart:io'; + +String kProjectId = 'app-with-assets-and-custom-fonts-qxwg6o'; +String kToken = Platform.environment['FF_TESTER_TOKEN'] ?? 'not-set'; + +Future buildProject(String project) async { + var result = await Process.run('flutter', ['build', 'web'], + workingDirectory: p.normalize(project), runInShell: true); + + return result.exitCode == 0; +} + +bool checkAssets(String project) { + var assets = [ + 'assets/fonts/JetBrainsMonoNerdFont-Bold.ttf', + 'assets/fonts/JetBrainsMonoNerdFont-Italic.ttf', + 'assets/fonts/JetBrainsMonoNerdFont-Regular.ttf', + 'assets/fonts/MartianMonoNerdFont-Bold.ttf', + 'assets/fonts/MartianMonoNerdFont-Medium.ttf', + 'assets/fonts/MartianMonoNerdFont-Regular.ttf', + 'assets/fonts/ProFontIIxNerdFont-Regular.ttf', + 'assets/fonts/ProFontIIxNerdFontMono-Regular.ttf', + 'assets/fonts/ProFontIIxNerdFontPropo-Regular.ttf', + 'assets/images/6740ae761553efad6aa2a5d4.png', + 'assets/images/6740ae76c0adf77347645294.png', + 'assets/images/6740af8ffbd4c3414fcf5728.png', + 'assets/images/6740aff03c7e45b220ed9775.png', + 'assets/images/6740aff0d10e0295e5fe33e6.png', + 'assets/images/6740b3cc6d3624484183520b.png', + 'assets/images/6740b3cca8e014dee9325b5d.png', + 'assets/images/6740b4b494d7239248fa491e.png', + 'assets/images/6740b4b5c0adf773476a5452.png', + 'assets/images/6740b4b5cf7b4fdf95795e2c.png', + 'assets/images/6740bca9c28f22a68495d368.png', + 'assets/images/6740bca9ed26c9a34b1ab1ce.png', + 'assets/images/6744ab4d50d5a3dad758fa39.png', + 'assets/images/6785366c215b774f00c041a3.png', + 'assets/images/6785366c3e83b0072fdc8ef4.png', + 'assets/images/6785366c77c17f02779e160c.png', + 'assets/images/67895d616be6f220ee4ec9c3.png', + 'assets/images/67895d6177fc072b5e166fd1.png', + 'assets/images/67895d61a7af8d11cb9aa957.png', + ]; + + for (var asset in assets) { + if (fileExists('$project/$asset') == false) { + return false; + } + } + + return true; +} + +bool fileExists(path) { + return File(p.normalize(path)).existsSync(); +} + +bool fileContains(path, data) { + return File(p.normalize(path)).readAsStringSync().contains(data); +} + +void main() { + group('export-code', () { + test('default parameters', () async { + final project = 'export/app_with_assets_and_custom_fonts'; + + await appMain([ + 'export-code', + '--project', + kProjectId, + '--token', + kToken, + '-d', + 'export', + ]); + + // Missing assets + expect(checkAssets(project), false); + + final buildResult = await buildProject(project); + expect(buildResult, false); + }); + + test('fix code', () async { + final project = 'export/fix_code'; + + await appMain([ + 'export-code', + '--no-parent-folder', + '--include-assets', + '--project', + kProjectId, + '--token', + kToken, + '-d', + p.normalize(project), + '--fix', + ]); + + // Fix will add 'const' to a lot of stuff :-) + expect( + fileContains( + '$project/lib/main.dart', 'localizationsDelegates: const ['), + true); + + expect(checkAssets(project), true); + + final buildResult = await buildProject(project); + expect(buildResult, true); + }); + + test('branch', () async { + final project = 'export/branch'; + + await appMain([ + 'export-code', + '--no-parent-folder', + '--include-assets', + '--project', + kProjectId, + '--token', + kToken, + '-d', + p.normalize(project), + '--branch-name', + 'TestBranch', + ]); + + expect( + fileExists( + '$project/lib/pages/page_only_on_this_branch/page_only_on_this_branch_widget.dart'), + true); + + expect(checkAssets(project), true); + + final buildResult = await buildProject(project); + expect(buildResult, true); + }); + + test('commit', () async { + final project = 'export/commit'; + + await appMain([ + 'export-code', + '--no-parent-folder', + '--include-assets', + '--project', + kProjectId, + '--token', + kToken, + '-d', + p.normalize(project), + '--commit-hash', + '0jfsCktnCmIcNp02q3yW', + ]); + + expect( + fileExists( + '$project/lib/pages/page_only_on_this_commit/page_only_on_this_commit_widget.dart'), + true); + + expect(checkAssets(project), true); + + final buildResult = await buildProject(project); + expect(buildResult, true); + }); + + test('debug', () async { + final project = 'export/debug'; + + await appMain([ + 'export-code', + '--no-parent-folder', + '--include-assets', + '--project', + kProjectId, + '--token', + kToken, + '-d', + p.normalize(project), + '--as-debug', + ]); + + // Debug instrumentation added by the flag + expect(fileContains('$project/lib/main.dart', 'debugLogGlobalProperty'), + true); + + expect(checkAssets(project), true); + + final buildResult = await buildProject(project); + expect(buildResult, true); + }); + + test('module', () async { + final project = 'export/module'; + + await appMain([ + 'export-code', + '--no-parent-folder', + '--include-assets', + '--project', + kProjectId, + '--token', + kToken, + '-d', + p.normalize(project), + '--as-module', + ]); + + expect(fileContains('$project/pubspec.yaml', 'module:'), true); + + expect(checkAssets(project), true); + + final buildResult = await buildProject(project); + expect(buildResult, true); + }); + + test('environment', () async { + final project = 'export/environment'; + + await appMain([ + 'export-code', + '--no-parent-folder', + '--include-assets', + '--project', + kProjectId, + '--token', + kToken, + '-d', + p.normalize(project), + '--project-environment', + 'Development', + ]); + + expect( + fileContains('$project/assets/environment_values/environment.json', + '"foobar": "barfoo"'), + true); + + expect(checkAssets(project), true); + + final buildResult = await buildProject(project); + expect(buildResult, true); + }); + }, timeout: Timeout(Duration(minutes: 30))); +}