Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ API access is available only to users with active subscriptions. Visit https://a

### Usage

`flutterflow export-code --project <project id> --dest <output folder> --[no-]include-assets --token <token> --[no-]fix --[no-]parent-folder --[no-]as-module --[no-]as-debug`
`flutterflow export-code --project <project id> --dest <output folder> --[no-]include-assets --token <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.
Expand All @@ -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`. |
Expand Down
18 changes: 15 additions & 3 deletions bin/flutterflow_cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,23 @@ Future<void> appMain(List<String> 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);
}

Expand Down Expand Up @@ -60,6 +63,8 @@ Future<void> appMain(List<String> 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(
Expand Down Expand Up @@ -114,6 +119,13 @@ ArgResults _parseArgs(List<String> 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,
Expand Down
44 changes: 31 additions & 13 deletions lib/src/flutterflow_api_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -46,6 +48,7 @@ class FlutterFlowApi {
bool exportAsModule = false,
bool format = true,
bool exportAsDebug = false,
bool includeExportManifest = false,
}) =>
exportCode(
token: token,
Expand All @@ -60,6 +63,7 @@ class FlutterFlowApi {
exportAsModule: exportAsModule,
format: format,
exportAsDebug: exportAsDebug,
includeExportManifest: includeExportManifest,
);
}

Expand All @@ -77,11 +81,13 @@ Future<String?> 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.';
}
Expand All @@ -101,6 +107,7 @@ Future<String?> exportCode({
includeAssets: includeAssets,
format: format,
exportAsDebug: exportAsDebug,
includeExportManifest: includeExportManifest,
);
// Download actual code
final List<int> projectZipBytes;
Expand Down Expand Up @@ -149,13 +156,17 @@ Future<String?> 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)) {
Expand All @@ -165,7 +176,9 @@ void extractArchiveTo(

// Remove the `<project>` 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);
Expand All @@ -186,6 +199,7 @@ Future<dynamic> _callExport({
required bool includeAssets,
required bool format,
required bool exportAsDebug,
required bool includeExportManifest,
}) async {
final body = jsonEncode({
'project_id': projectId,
Expand All @@ -196,6 +210,7 @@ Future<dynamic> _callExport({
'include_assets_map': includeAssets,
'format': format,
'export_as_debug': exportAsDebug,
'includeExportManifest': includeExportManifest,
});
return await _callEndpoint(
client: client,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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,
);

Expand All @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand Down
66 changes: 50 additions & 16 deletions test/integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ String kProjectId = 'app-with-assets-and-custom-fonts-qxwg6o';
String kToken = Platform.environment['FF_TESTER_TOKEN'] ?? 'not-set';

Future<bool> 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);
Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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)));
}
Loading