diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e32ad..d159ff9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.31 + +- Add `--include-export-manifest` to `flutterflow export-code` and forward `includeExportManifest` to the export API so the downloaded archive can include `.flutterflow/export_manifest.json`. + ## 0.0.30 - Occasionally suggest using `--fix` flag when it's not passed. diff --git a/README.md b/README.md index d97574f..9464e16 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ API access is available only to users with active subscriptions. Visit https://a ### Usage -`flutterflow export-code --project --dest --[no-]include-assets --token --[no-]fix --[no-]parent-folder --[no-]as-module --[no-]as-debug` +`flutterflow export-code --project --dest --[no-]include-assets --token --[no-]fix --[no-]parent-folder --[no-]as-module --[no-]as-debug --[no-]include-export-manifest` * Instead of passing `--token` you can set `FLUTTERFLOW_API_TOKEN` environment variable. * Instead of passing `--project` you can set `FLUTTERFLOW_PROJECT` environment variable. @@ -30,6 +30,7 @@ with a list of files to be ignored using [globbing syntax](https://pub.dev/packa | `--token` | `-t` | [Required or environment variable] API Token. | | `--dest` | `-d` | [Optional] Output folder. Defaults to the current directory if none is specified. | | `--[no-]include-assets` | None | [Optional] Whether to include media assets. Defaults to `false`. | +| `--[no-]include-export-manifest` | None | [Optional] Whether to include `.flutterflow/export_manifest.json` so tools can map FlutterFlow entities to generated files. Defaults to `false`. | | `--branch-name` | `-b` | [Optional] Which branch to download. Defaults to `main`. | | `--[no-]fix` | None | [Optional] Whether to run `dart fix` on the downloaded code. Defaults to `false`. | | `--[no-]parent-folder` | None | [Optional] Whether to download code into a project-named sub-folder. If true, downloads all project files directly to the specified directory. Defaults to `true`. | diff --git a/bin/flutterflow_cli.dart b/bin/flutterflow_cli.dart index b255560..fd2c3a6 100644 --- a/bin/flutterflow_cli.dart +++ b/bin/flutterflow_cli.dart @@ -17,20 +17,23 @@ Future appMain(List args) async { if (project == null || project.isEmpty) { stderr.write( - 'Either --project option or FLUTTERFLOW_PROJECT environment variable must be set.\n'); + 'Either --project option or FLUTTERFLOW_PROJECT environment variable must be set.\n', + ); exit(1); } if (parsedArguments['endpoint'] != null && parsedArguments['environment'] != null) { stderr.write( - 'Only one of --endpoint and --environment options can be set.\n'); + 'Only one of --endpoint and --environment options can be set.\n', + ); exit(1); } if (token?.isEmpty ?? true) { stderr.write( - 'Either --token option or FLUTTERFLOW_API_TOKEN environment variable must be set.\n'); + 'Either --token option or FLUTTERFLOW_API_TOKEN environment variable must be set.\n', + ); exit(1); } @@ -60,6 +63,8 @@ Future appMain(List args) async { exportAsModule: parsedArguments.command!['as-module'], exportAsDebug: parsedArguments.command!['as-debug'], environmentName: parsedArguments.command!['project-environment'], + includeExportManifest: + parsedArguments.command!['include-export-manifest'], ); if (!parsedArguments.command!['fix'] && Random().nextDouble() < 0.2) { print( @@ -114,6 +119,13 @@ ArgResults _parseArgs(List args) { 'Downloading code without assets is typically much faster.', defaultsTo: false, ) + ..addFlag( + 'include-export-manifest', + negatable: true, + help: 'Request .flutterflow/export_manifest.json in the downloaded ' + 'export so tools can map FlutterFlow entities to generated files.', + defaultsTo: false, + ) ..addFlag( 'fix', negatable: true, diff --git a/lib/src/flutterflow_api_client.dart b/lib/src/flutterflow_api_client.dart index cfc52c0..bb98da7 100644 --- a/lib/src/flutterflow_api_client.dart +++ b/lib/src/flutterflow_api_client.dart @@ -29,6 +29,8 @@ class FlutterFlowApi { /// * [exportAsDebug] flag indicates whether to export the code as debug for /// local run. /// * [environmentName] is the name of the environment to export the code for. + /// * [includeExportManifest] flag indicates whether to include + /// `.flutterflow/export_manifest.json` in the export archive. /// /// Returns a [Future] that completes with the path to the exported code, or /// throws an error if the export fails. @@ -46,6 +48,7 @@ class FlutterFlowApi { bool exportAsModule = false, bool format = true, bool exportAsDebug = false, + bool includeExportManifest = false, }) => exportCode( token: token, @@ -60,6 +63,7 @@ class FlutterFlowApi { exportAsModule: exportAsModule, format: format, exportAsDebug: exportAsDebug, + includeExportManifest: includeExportManifest, ); } @@ -77,11 +81,13 @@ Future exportCode({ String? environmentName, String? commitHash, bool exportAsDebug = false, + bool includeExportManifest = false, }) async { stderr.write('Downloading code with the FlutterFlow CLI...\n'); stderr.write('You are exporting project $projectId.\n'); stderr.write( - '${branchName != null ? 'Branch: $branchName ' : ''}${environmentName != null ? 'Environment: $environmentName ' : ''}${commitHash != null ? 'Commit: $commitHash' : ''}\n'); + '${branchName != null ? 'Branch: $branchName ' : ''}${environmentName != null ? 'Environment: $environmentName ' : ''}${commitHash != null ? 'Commit: $commitHash' : ''}\n', + ); if (exportAsDebug && exportAsModule) { throw 'Cannot export as module and debug at the same time.'; } @@ -101,6 +107,7 @@ Future exportCode({ includeAssets: includeAssets, format: format, exportAsDebug: exportAsDebug, + includeExportManifest: includeExportManifest, ); // Download actual code final List projectZipBytes; @@ -149,13 +156,17 @@ Future exportCode({ // Extract files to the specified directory without a project-named // parent folder. void extractArchiveTo( - Archive projectFolder, String destinationPath, bool unzipToParentFolder) { + Archive projectFolder, + String destinationPath, + bool unzipToParentFolder, +) { final ignore = FlutterFlowIgnore(path: destinationPath); for (final file in projectFolder.files) { if (file.isFile) { - final relativeFilename = - path_util.joinAll(path_util.split(file.name).sublist(1)); + final relativeFilename = path_util.joinAll( + path_util.split(file.name).sublist(1), + ); // Found on .flutterflowignore, move on. if (ignore.matches(unzipToParentFolder ? file.name : relativeFilename)) { @@ -165,7 +176,9 @@ void extractArchiveTo( // Remove the `` prefix from paths if needed. final path = path_util.join( - destinationPath, unzipToParentFolder ? file.name : relativeFilename); + destinationPath, + unzipToParentFolder ? file.name : relativeFilename, + ); final fileOut = File(path); fileOut.createSync(recursive: true); @@ -186,6 +199,7 @@ Future _callExport({ required bool includeAssets, required bool format, required bool exportAsDebug, + required bool includeExportManifest, }) async { final body = jsonEncode({ 'project_id': projectId, @@ -196,6 +210,7 @@ Future _callExport({ 'include_assets_map': includeAssets, 'format': format, 'export_as_debug': exportAsDebug, + 'includeExportManifest': includeExportManifest, }); return await _callEndpoint( client: client, @@ -258,9 +273,7 @@ Future _downloadAssets({ String path = assetDescription['path']; if (!unzipToParentFolder) { - path = path_util.joinAll( - path_util.split(path).sublist(1), - ); + path = path_util.joinAll(path_util.split(path).sublist(1)); } final url = assetDescription['url']; final fileDest = path_util.join(destinationPath, path); @@ -307,7 +320,8 @@ Future _runFix({ ); if (pubGetResult.exitCode != 0) { stderr.write( - '"flutter pub get" failed with code ${pubGetResult.exitCode}, stderr:\n${pubGetResult.stderr}\n'); + '"flutter pub get" failed with code ${pubGetResult.exitCode}, stderr:\n${pubGetResult.stderr}\n', + ); return; } stderr.write('Running dart fix...\n'); @@ -322,7 +336,8 @@ Future _runFix({ ); if (dartFixResult.exitCode != 0) { stderr.write( - '"dart fix" failed with code ${dartFixResult.exitCode}, stderr:\n${dartFixResult.stderr}\n'); + '"dart fix" failed with code ${dartFixResult.exitCode}, stderr:\n${dartFixResult.stderr}\n', + ); } } catch (e) { stderr.write('Error running "dart fix": $e\n'); @@ -344,7 +359,9 @@ Future firebaseDeploy({ client: http.Client(), token: token, url: Uri.https( - endpointUrl.host, '${endpointUrl.path}/exportFirebaseDeployCode'), + endpointUrl.host, + '${endpointUrl.path}/exportFirebaseDeployCode', + ), body: body, ); @@ -355,8 +372,9 @@ Future firebaseDeploy({ Directory? tmpFolder; try { - tmpFolder = - Directory.systemTemp.createTempSync('${projectId}_$firebaseProjectId'); + tmpFolder = Directory.systemTemp.createTempSync( + '${projectId}_$firebaseProjectId', + ); extractArchiveTo(projectFolder, tmpFolder.path, false); final firebaseDir = '${tmpFolder.path}/firebase'; diff --git a/pubspec.yaml b/pubspec.yaml index c6de086..dcdf85c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: flutterflow_cli description: >- Command-line client for FlutterFlow. Export code from FlutterFlow projects. -version: 0.0.30 +version: 0.0.31 homepage: https://github.com/FlutterFlow/flutterflow-cli issue_tracker: https://github.com/flutterflow/flutterflow-issues diff --git a/test/integration_test.dart b/test/integration_test.dart index b20a1d8..997ea38 100644 --- a/test/integration_test.dart +++ b/test/integration_test.dart @@ -9,8 +9,12 @@ 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); + var result = await Process.run( + 'flutter', + ['build', 'web'], + workingDirectory: p.normalize(project), + runInShell: true, + ); if (result.exitCode != 0) { stderr.writeln(result.stderr); @@ -108,9 +112,12 @@ void main() { // Fix will add 'const' to a lot of stuff :-) expect( - fileContains( - '$project/lib/main.dart', 'localizationsDelegates: const ['), - true); + fileContains( + '$project/lib/main.dart', + 'localizationsDelegates: const [', + ), + true, + ); expect(checkAssets(project), true); @@ -136,9 +143,11 @@ void main() { ]); expect( - fileExists( - '$project/lib/pages/page_only_on_this_branch/page_only_on_this_branch_widget.dart'), - true); + fileExists( + '$project/lib/pages/page_only_on_this_branch/page_only_on_this_branch_widget.dart', + ), + true, + ); expect(checkAssets(project), true); @@ -164,9 +173,11 @@ void main() { ]); expect( - fileExists( - '$project/lib/pages/page_only_on_this_commit/page_only_on_this_commit_widget.dart'), - true); + fileExists( + '$project/lib/pages/page_only_on_this_commit/page_only_on_this_commit_widget.dart', + ), + true, + ); expect(checkAssets(project), true); @@ -191,8 +202,10 @@ void main() { ]); // Debug instrumentation added by the flag - expect(fileContains('$project/lib/main.dart', 'debugLogGlobalProperty'), - true); + expect( + fileContains('$project/lib/main.dart', 'debugLogGlobalProperty'), + true, + ); expect(checkAssets(project), true); @@ -242,14 +255,35 @@ void main() { ]); expect( - fileContains('$project/assets/environment_values/environment.json', - '"foobar": "barfoo"'), - true); + fileContains( + '$project/assets/environment_values/environment.json', + '"foobar": "barfoo"', + ), + true, + ); expect(checkAssets(project), true); final buildResult = await buildProject(project); expect(buildResult, true); }); + + test('export manifest', () async { + final project = 'export/export_manifest'; + + await appMain([ + 'export-code', + '--no-parent-folder', + '--project', + kProjectId, + '--token', + kToken, + '-d', + p.normalize(project), + '--include-export-manifest', + ]); + + expect(fileExists('$project/.flutterflow/export_manifest.json'), true); + }); }, timeout: Timeout(Duration(minutes: 30))); }