Skip to content

Commit 2278e1b

Browse files
Support deploying to firebase.
Support deploying to firebase.
2 parents 46e033e + b414319 commit 2278e1b

File tree

3 files changed

+167
-15
lines changed

3 files changed

+167
-15
lines changed

README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ API access is available only to users with active subscriptions. Visit https://a
1010

1111
`dart pub global activate flutterflow_cli`
1212

13-
## Usage
13+
## Export Code
14+
15+
### Usage
1416

1517
`flutterflow export-code --project <project id> --dest <output folder> --[no-]include-assets --token <token> --[no-]fix --[no-]parent-folder --[no-]as-module`
1618

1719
* Instead of passing `--token` you can set `FLUTTERFLOW_API_TOKEN` environment variable.
1820
* Instead of passing `--project` you can set `FLUTTERFLOW_PROJECT` environment variable.
1921

20-
## Flags
22+
### Flags
2123

2224
| Flag | Abbreviation | Usage |
2325
| ----------- | ----------- | ----------- |
@@ -30,6 +32,27 @@ API access is available only to users with active subscriptions. Visit https://a
3032
| `--[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`. |
3133
| `--[no-]as-module` | None | [Optional] Whether to generate the project as a Flutter module |
3234

35+
## Deploy Firebase
36+
37+
### Prerequisites
38+
39+
`npm` and `firebase-tools` must be installed in order to deploy to Firebase. You can follow the instructions at https://firebase.google.com/docs/cli#install_the_firebase_cli.
40+
41+
### Usage
42+
43+
`flutterflow deploy-firebase --project <project id> --[no]-append-rules --token <token>`
44+
45+
* Instead of passing `--token` you can set `FLUTTERFLOW_API_TOKEN` environment variable.
46+
* Instead of passing `--project` you can set `FLUTTERFLOW_PROJECT` environment variable.
47+
48+
### Flags
49+
50+
| Flag | Abbreviation | Usage |
51+
| ----------- | ----------- | ----------- |
52+
| `--project` | `-p` | [Required or environment variable] Project ID. |
53+
| `--token` | `-t` | [Required or environment variable] API Token. |
54+
| `--append-rules` | `-a` | Whether to append to existing Firestore rules, instead of overwriting them. |
55+
3356
## Issues
3457

3558
Please file any issues in [this repository](https://github.com/flutterflow/flutterflow-issues).

bin/flutterflow_cli.dart

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,30 @@ void main(List<String> args) async {
4444
}
4545

4646
try {
47-
await exportCode(
48-
token: token,
49-
endpoint: endpoint,
50-
projectId: project,
51-
destinationPath: parsedArguments.command!['dest'],
52-
includeAssets: parsedArguments.command!['include-assets'],
53-
branchName: parsedArguments.command!['branch-name'],
54-
unzipToParentFolder: parsedArguments.command!['parent-folder'],
55-
fix: parsedArguments.command!['fix'],
56-
exportAsModule: parsedArguments.command!['as-module'],
57-
);
47+
switch (parsedArguments.command?.name) {
48+
case 'export-code':
49+
await exportCode(
50+
token: token,
51+
endpoint: endpoint,
52+
projectId: project,
53+
destinationPath: parsedArguments.command!['dest'],
54+
includeAssets: parsedArguments.command!['include-assets'],
55+
branchName: parsedArguments.command!['branch-name'],
56+
unzipToParentFolder: parsedArguments.command!['parent-folder'],
57+
fix: parsedArguments.command!['fix'],
58+
exportAsModule: parsedArguments.command!['as-module'],
59+
);
60+
break;
61+
case 'deploy-firebase':
62+
await firebaseDeploy(
63+
token: token,
64+
projectId: project,
65+
appendRules: parsedArguments.command!['append-rules'],
66+
endpoint: endpoint,
67+
);
68+
break;
69+
default:
70+
}
5871
} catch (_) {
5972
exit(1);
6073
}
@@ -98,12 +111,22 @@ ArgResults _parseArgs(List<String> args) {
98111
defaultsTo: false,
99112
);
100113

114+
final firebaseDeployCommandParser = ArgParser()
115+
..addOption('project', abbr: 'p', help: 'Project id')
116+
..addFlag(
117+
'append-rules',
118+
abbr: 'a',
119+
help: 'Append to rules, instead of overwriting them.',
120+
defaultsTo: false,
121+
);
122+
101123
final parser = ArgParser()
102124
..addOption('endpoint', abbr: 'e', help: 'Endpoint', hide: true)
103125
..addOption('environment', help: 'Environment', hide: true)
104126
..addOption('token', abbr: 't', help: 'API Token')
105127
..addFlag('help', negatable: false, abbr: 'h', help: 'Help')
106-
..addCommand('export-code', exportCodeCommandParser);
128+
..addCommand('export-code', exportCodeCommandParser)
129+
..addCommand('deploy-firebase', firebaseDeployCommandParser);
107130

108131
late ArgResults parsed;
109132
try {

lib/src/flutterflow_api_client.dart

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,22 @@ Future<dynamic> _callExport({
160160
'include_assets_map': includeAssets,
161161
'format': format,
162162
});
163+
return await _callEndpoint(
164+
client: client,
165+
token: token,
166+
url: Uri.https(endpoint.host, '${endpoint.path}/exportCode'),
167+
body: body,
168+
);
169+
}
170+
171+
Future<dynamic> _callEndpoint({
172+
required final http.Client client,
173+
required String token,
174+
required Uri url,
175+
required String body,
176+
}) async {
163177
final response = await client.post(
164-
Uri.https(endpoint.host, '${endpoint.path}/exportCode'),
178+
url,
165179
body: body,
166180
headers: {
167181
'Content-Type': 'application/json',
@@ -275,3 +289,95 @@ Future _runFix({
275289
stderr.write('Error running "dart fix": $e\n');
276290
}
277291
}
292+
293+
Future firebaseDeploy({
294+
required String token,
295+
required String projectId,
296+
bool appendRules = false,
297+
String endpoint = kDefaultEndpoint,
298+
}) async {
299+
final endpointUrl = Uri.parse(endpoint);
300+
final body = jsonEncode({
301+
'project': {'path': 'projects/$projectId'},
302+
'append_rules': appendRules,
303+
});
304+
final result = await _callEndpoint(
305+
client: http.Client(),
306+
token: token,
307+
url: Uri.https(
308+
endpointUrl.host, '${endpointUrl.path}/exportFirebaseDeployCode'),
309+
body: body,
310+
);
311+
312+
// Download actual code
313+
final projectZipBytes = base64Decode(result['firebase_zip']);
314+
final firebaseProjectId = result['firebase_project_id'];
315+
final projectFolder = ZipDecoder().decodeBytes(projectZipBytes);
316+
Directory? tmpFolder;
317+
318+
try {
319+
tmpFolder =
320+
Directory.systemTemp.createTempSync('${projectId}_$firebaseProjectId');
321+
extractArchiveToCurrentDirectory(projectFolder, tmpFolder.path);
322+
final firebaseDir = '${tmpFolder.path}/firebase';
323+
324+
// Install required modules for deployment.
325+
await Process.run(
326+
'npm',
327+
['install'],
328+
workingDirectory: '$firebaseDir/functions',
329+
runInShell: true,
330+
stdoutEncoding: utf8,
331+
stderrEncoding: utf8,
332+
);
333+
334+
// This directory only exists if there were custom cloud functions.
335+
if (Directory('$firebaseDir/custom_cloud_functions').existsSync()) {
336+
await Process.run(
337+
'npm',
338+
['install'],
339+
workingDirectory: '$firebaseDir/custom_cloud_functions',
340+
runInShell: true,
341+
stdoutEncoding: utf8,
342+
stderrEncoding: utf8,
343+
);
344+
}
345+
346+
stdout.write('Initializing firebase...\n');
347+
await Process.run(
348+
'firebase',
349+
['use', firebaseProjectId],
350+
workingDirectory: firebaseDir,
351+
runInShell: true,
352+
);
353+
final initHostingProcess = await Process.start(
354+
'firebase',
355+
['init', 'hosting'],
356+
workingDirectory: firebaseDir,
357+
runInShell: true,
358+
);
359+
final initHostingInputStream = Stream.periodic(
360+
Duration(milliseconds: 100),
361+
(count) => utf8.encode('\n'),
362+
);
363+
initHostingProcess.stdin.addStream(initHostingInputStream);
364+
// Make sure hosting is initialized before deploying.
365+
await initHostingProcess.exitCode;
366+
367+
final deployProcess = await Process.start(
368+
'firebase',
369+
['deploy', '--project', firebaseProjectId],
370+
workingDirectory: firebaseDir,
371+
runInShell: true,
372+
);
373+
// There may be a need for the user to interactively provide inputs.
374+
deployProcess.stdout.transform(utf8.decoder).forEach(print);
375+
deployProcess.stdin.addStream(stdin);
376+
final exitCode = await deployProcess.exitCode;
377+
if (exitCode != 0) {
378+
stderr.write('Failed to deploy to Firebase.\n');
379+
}
380+
} finally {
381+
tmpFolder?.deleteSync(recursive: true);
382+
}
383+
}

0 commit comments

Comments
 (0)