Browse Source

1001 update

dienianindya 1 year ago
parent
commit
d731c0e823
100 changed files with 20876 additions and 447 deletions
  1. 6122
    0
      assets/animation/animation_no_internet.json
  2. BIN
      assets/images/declaration.png
  3. BIN
      assets/images/extend_tanggal.png
  4. BIN
      assets/images/extra_money.png
  5. BIN
      assets/images/submit_st.png
  6. 100
    0
      lib/Screens/CheckInternetConnection/CheckInternetConnection.dart
  7. 21
    0
      lib/Screens/CheckInternetConnection/ConnectivityProvider.dart
  8. 40
    0
      lib/Screens/CheckInternetConnection/NoInternetConnection.dart
  9. 0
    1
      lib/Screens/ForgotPassword/forgotPassword_screen.dart
  10. 11
    4
      lib/Screens/Home/home_screen.dart
  11. 4
    6
      lib/Screens/Menu/Absensi/absensi_screen.dart
  12. 35
    19
      lib/Screens/Menu/AjukanCuti/ajukancuti_screen.dart
  13. 2
    2
      lib/Screens/Menu/AjukanCuti/history_cuti.dart
  14. 0
    9
      lib/Screens/Menu/AjukanCuti/model.dart
  15. 39
    0
      lib/Screens/Menu/Reimburse/RequestHttp/categoryReimburse_post.dart
  16. 34
    0
      lib/Screens/Menu/Reimburse/RequestHttp/historyReimburse_post.dart
  17. 47
    0
      lib/Screens/Menu/Reimburse/RequestHttp/pengajuanReimburse_post.dart
  18. 296
    0
      lib/Screens/Menu/Reimburse/history_reimburse.dart
  19. 833
    374
      lib/Screens/Menu/Reimburse/reimburse_screen.dart
  20. 2
    2
      lib/Screens/Menu/SlipGaji/slipgaji_screen.dart
  21. 0
    0
      lib/Screens/Menu/SuratTugas/deklarasi_st.dart
  22. 0
    0
      lib/Screens/Menu/SuratTugas/history_st.dart
  23. 0
    0
      lib/Screens/Menu/SuratTugas/pengajuan_extendTanggalKembali.dart
  24. 811
    0
      lib/Screens/Menu/SuratTugas/pengajuan_st.dart
  25. 0
    0
      lib/Screens/Menu/SuratTugas/pengajuan_uangMukaTambahan.dart
  26. 136
    8
      lib/Screens/Menu/SuratTugas/surattugas_screen.dart
  27. 10
    22
      lib/Screens/Settings/settings_screen.dart
  28. 2
    0
      macos/Flutter/GeneratedPluginRegistrant.swift
  29. 12
    0
      node_modules/.bin/firebase
  30. 17
    0
      node_modules/.bin/firebase.cmd
  31. 28
    0
      node_modules/.bin/firebase.ps1
  32. 6743
    0
      node_modules/.package-lock.json
  33. 21
    0
      node_modules/firebase-tools/LICENSE
  34. 283
    0
      node_modules/firebase-tools/README.md
  35. 205
    0
      node_modules/firebase-tools/lib/accountExporter.js
  36. 305
    0
      node_modules/firebase-tools/lib/accountImporter.js
  37. 81
    0
      node_modules/firebase-tools/lib/api.js
  38. 356
    0
      node_modules/firebase-tools/lib/apiv2.js
  39. 141
    0
      node_modules/firebase-tools/lib/appdistribution/client.js
  40. 51
    0
      node_modules/firebase-tools/lib/appdistribution/distribution.js
  41. 51
    0
      node_modules/firebase-tools/lib/appdistribution/options-parser-util.js
  42. 120
    0
      node_modules/firebase-tools/lib/archiveDirectory.js
  43. 535
    0
      node_modules/firebase-tools/lib/auth.js
  44. 132
    0
      node_modules/firebase-tools/lib/bin/firebase.js
  45. 14
    0
      node_modules/firebase-tools/lib/checkMinRequiredVersion.js
  46. 46
    0
      node_modules/firebase-tools/lib/checkValidTargetFilters.js
  47. 235
    0
      node_modules/firebase-tools/lib/command.js
  48. 118
    0
      node_modules/firebase-tools/lib/commands/appdistribution-distribute.js
  49. 19
    0
      node_modules/firebase-tools/lib/commands/appdistribution-testers-add.js
  50. 33
    0
      node_modules/firebase-tools/lib/commands/appdistribution-testers-remove.js
  51. 29
    0
      node_modules/firebase-tools/lib/commands/apps-android-sha-create.js
  52. 16
    0
      node_modules/firebase-tools/lib/commands/apps-android-sha-delete.js
  53. 42
    0
      node_modules/firebase-tools/lib/commands/apps-android-sha-list.js
  54. 166
    0
      node_modules/firebase-tools/lib/commands/apps-create.js
  55. 53
    0
      node_modules/firebase-tools/lib/commands/apps-list.js
  56. 103
    0
      node_modules/firebase-tools/lib/commands/apps-sdkconfig.js
  57. 44
    0
      node_modules/firebase-tools/lib/commands/auth-export.js
  58. 114
    0
      node_modules/firebase-tools/lib/commands/auth-import.js
  59. 26
    0
      node_modules/firebase-tools/lib/commands/crashlytics-mappingfile-generateid.js
  60. 46
    0
      node_modules/firebase-tools/lib/commands/crashlytics-mappingfile-upload.js
  61. 78
    0
      node_modules/firebase-tools/lib/commands/crashlytics-symbols-upload.js
  62. 122
    0
      node_modules/firebase-tools/lib/commands/database-get.js
  63. 29
    0
      node_modules/firebase-tools/lib/commands/database-instances-create.js
  64. 76
    0
      node_modules/firebase-tools/lib/commands/database-instances-list.js
  65. 46
    0
      node_modules/firebase-tools/lib/commands/database-profile.js
  66. 63
    0
      node_modules/firebase-tools/lib/commands/database-push.js
  67. 42
    0
      node_modules/firebase-tools/lib/commands/database-remove.js
  68. 24
    0
      node_modules/firebase-tools/lib/commands/database-rules-canary.js
  69. 22
    0
      node_modules/firebase-tools/lib/commands/database-rules-get.js
  70. 36
    0
      node_modules/firebase-tools/lib/commands/database-rules-list.js
  71. 24
    0
      node_modules/firebase-tools/lib/commands/database-rules-release.js
  72. 26
    0
      node_modules/firebase-tools/lib/commands/database-rules-stage.js
  73. 68
    0
      node_modules/firebase-tools/lib/commands/database-set.js
  74. 44
    0
      node_modules/firebase-tools/lib/commands/database-settings-get.js
  75. 46
    0
      node_modules/firebase-tools/lib/commands/database-settings-set.js
  76. 69
    0
      node_modules/firebase-tools/lib/commands/database-update.js
  77. 80
    0
      node_modules/firebase-tools/lib/commands/deploy.js
  78. 18
    0
      node_modules/firebase-tools/lib/commands/emulators-exec.js
  79. 14
    0
      node_modules/firebase-tools/lib/commands/emulators-export.js
  80. 114
    0
      node_modules/firebase-tools/lib/commands/emulators-start.js
  81. 13
    0
      node_modules/firebase-tools/lib/commands/experimental-functions-shell.js
  82. 31
    0
      node_modules/firebase-tools/lib/commands/experiments-describe.js
  83. 28
    0
      node_modules/firebase-tools/lib/commands/experiments-disable.js
  84. 28
    0
      node_modules/firebase-tools/lib/commands/experiments-enable.js
  85. 27
    0
      node_modules/firebase-tools/lib/commands/experiments-list.js
  86. 108
    0
      node_modules/firebase-tools/lib/commands/ext-configure.js
  87. 64
    0
      node_modules/firebase-tools/lib/commands/ext-dev-deprecate.js
  88. 27
    0
      node_modules/firebase-tools/lib/commands/ext-dev-emulators-exec.js
  89. 24
    0
      node_modules/firebase-tools/lib/commands/ext-dev-emulators-start.js
  90. 45
    0
      node_modules/firebase-tools/lib/commands/ext-dev-extension-delete.js
  91. 136
    0
      node_modules/firebase-tools/lib/commands/ext-dev-init.js
  92. 46
    0
      node_modules/firebase-tools/lib/commands/ext-dev-list.js
  93. 48
    0
      node_modules/firebase-tools/lib/commands/ext-dev-publish.js
  94. 47
    0
      node_modules/firebase-tools/lib/commands/ext-dev-register.js
  95. 57
    0
      node_modules/firebase-tools/lib/commands/ext-dev-undeprecate.js
  96. 49
    0
      node_modules/firebase-tools/lib/commands/ext-dev-unpublish.js
  97. 141
    0
      node_modules/firebase-tools/lib/commands/ext-dev-usage.js
  98. 68
    0
      node_modules/firebase-tools/lib/commands/ext-export.js
  99. 118
    0
      node_modules/firebase-tools/lib/commands/ext-info.js
  100. 0
    0
      node_modules/firebase-tools/lib/commands/ext-install.js

+ 6122
- 0
assets/animation/animation_no_internet.json
File diff suppressed because it is too large
View File


BIN
assets/images/declaration.png View File


BIN
assets/images/extend_tanggal.png View File


BIN
assets/images/extra_money.png View File


BIN
assets/images/submit_st.png View File


+ 100
- 0
lib/Screens/CheckInternetConnection/CheckInternetConnection.dart View File

@@ -0,0 +1,100 @@
1
+import 'dart:async';
2
+import 'dart:developer' as developer;
3
+
4
+import 'package:connectivity_plus/connectivity_plus.dart';
5
+import 'package:flutter/material.dart';
6
+import 'package:flutter/services.dart';
7
+import 'package:google_fonts/google_fonts.dart';
8
+import 'package:lottie/lottie.dart';
9
+
10
+class MyHomePage extends StatefulWidget {
11
+  @override
12
+  State<MyHomePage> createState() => _MyHomePageState();
13
+}
14
+
15
+class _MyHomePageState extends State<MyHomePage> {
16
+  ConnectivityResult _connectionStatus = ConnectivityResult.none;
17
+  final Connectivity _connectivity = Connectivity();
18
+  late StreamSubscription<ConnectivityResult> _connectivitySubscription;
19
+
20
+  @override
21
+  void initState() {
22
+    super.initState();
23
+    initConnectivity();
24
+
25
+    _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
26
+  }
27
+
28
+  @override
29
+  void dispose() {
30
+    _connectivitySubscription.cancel();
31
+    super.dispose();
32
+  }
33
+
34
+  // Platform messages are asynchronous, so we initialize in an async method.
35
+  Future<void> initConnectivity() async {
36
+    late ConnectivityResult result;
37
+    // Platform messages may fail, so we use a try/catch PlatformException.
38
+    try {
39
+      result = await _connectivity.checkConnectivity();
40
+    } on PlatformException catch (e) {
41
+      developer.log('Couldn\'t check connectivity status', error: e);
42
+      return;
43
+    }
44
+
45
+    // If the widget was removed from the tree while the asynchronous platform
46
+    // message was in flight, we want to discard the reply rather than calling
47
+    // setState to update our non-existent appearance.
48
+    if (!mounted) {
49
+      return Future.value(null);
50
+    }
51
+
52
+    return _updateConnectionStatus(result);
53
+  }
54
+
55
+  Future<void> _updateConnectionStatus(ConnectivityResult result) async {
56
+    setState(() {
57
+      _connectionStatus = result;
58
+    });
59
+  }
60
+
61
+  @override
62
+  Widget build(BuildContext context) {
63
+    return Container(
64
+      child: Column(
65
+        children: [
66
+          SizedBox(
67
+            width: 250,
68
+            height: 250,
69
+            child: LottieBuilder.asset(
70
+                'assets/animation/animation_no_internet.json',
71
+                repeat: true),
72
+          ),
73
+          Text(
74
+            "No Internet Connection",
75
+            style: GoogleFonts.josefinSans(fontSize: 18),
76
+          ),
77
+          Padding(
78
+            padding: EdgeInsets.all(15),
79
+            child: Text(
80
+              'You Are Not Connected to The Internet. Please check your Wifi or Mobile Data!',
81
+              style: GoogleFonts.zillaSlab(fontSize: 16),
82
+            ),
83
+          ),
84
+          ElevatedButton(
85
+              onPressed: (){
86
+
87
+              },
88
+              child: Text("Retry",
89
+                  textAlign: TextAlign.center,
90
+                  style: TextStyle(
91
+                      color: Colors.white,
92
+                      fontSize: 17,
93
+                      fontWeight: FontWeight.w500)
94
+              )
95
+          )
96
+        ],
97
+      ),
98
+    );
99
+  }
100
+}

+ 21
- 0
lib/Screens/CheckInternetConnection/ConnectivityProvider.dart View File

@@ -0,0 +1,21 @@
1
+import 'package:flutter/material.dart';
2
+
3
+class ConnectivityProvider with ChangeNotifier{
4
+  /*Connectivity
5
+  ConnectivityProvider _connectivityProvider = new ConnectivityProvider();
6
+
7
+  late bool _isOnline;
8
+  bool get isOnline => _isOnline;
9
+
10
+  startMonitoring() async {
11
+
12
+  }
13
+
14
+  Future<void> initConnectivity() async {
15
+    try {
16
+      var connectivityResult = await (Connectivity().checkConnectivity());
17
+    } catch {
18
+
19
+    }
20
+  }*/
21
+}

+ 40
- 0
lib/Screens/CheckInternetConnection/NoInternetConnection.dart View File

@@ -0,0 +1,40 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:google_fonts/google_fonts.dart';
3
+import 'package:lottie/lottie.dart';
4
+
5
+class NoInternetConnection extends StatefulWidget {
6
+  const NoInternetConnection({Key? key}) : super(key: key);
7
+
8
+  @override
9
+  State<NoInternetConnection> createState() => _NoInternetConnectionState();
10
+}
11
+
12
+class _NoInternetConnectionState extends State<NoInternetConnection> {
13
+  @override
14
+  Widget build(BuildContext context) {
15
+    return Container(
16
+      child: Column(
17
+        children: [
18
+          SizedBox(
19
+            width: 250,
20
+            height: 250,
21
+            child: LottieBuilder.asset(
22
+                'assets/animation/animation_no_internet.json',
23
+                repeat: true),
24
+          ),
25
+          Text(
26
+            "No Internet Connection",
27
+            style: GoogleFonts.josefinSans(fontSize: 18),
28
+          ),
29
+          Padding(
30
+            padding: EdgeInsets.all(15),
31
+            child: Text(
32
+              'You Are Not Connected to The Internet. Please check your Wifi or Mobile Data!',
33
+              style: GoogleFonts.zillaSlab(fontSize: 16),
34
+            ),
35
+          ),
36
+        ],
37
+      ),
38
+    );
39
+  }
40
+}

+ 0
- 1
lib/Screens/ForgotPassword/forgotPassword_screen.dart View File

@@ -150,7 +150,6 @@ class ForgotPasswordScreen extends StatelessWidget {
150 150
                                           okButton,
151 151
                                         ],
152 152
                                       );
153
-
154 153
                                       // show the dialog
155 154
                                       showDialog(
156 155
                                         context: context,

+ 11
- 4
lib/Screens/Home/home_screen.dart View File

@@ -332,8 +332,9 @@ class HomeScreen extends StatelessWidget {
332 332
                                 ),
333 333
                                 Container(
334 334
                                   margin: EdgeInsets.only(top: 10),
335
+                                  padding: EdgeInsets.all(5),
335 336
                                   child: Text(
336
-                                    'Absensi',
337
+                                    'Attendance',
337 338
                                     textAlign: TextAlign.center,
338 339
                                     style: GoogleFonts.acme(
339 340
                                         fontSize: 18, color: Colors.black),
@@ -367,8 +368,9 @@ class HomeScreen extends StatelessWidget {
367 368
                               ),
368 369
                               Container(
369 370
                                 margin: EdgeInsets.only(top: 10),
371
+                                padding: EdgeInsets.all(5),
370 372
                                 child: Text(
371
-                                  'Slip Gaji',
373
+                                  'Salary Slip',
372 374
                                   textAlign: TextAlign.center,
373 375
                                   style: GoogleFonts.acme(
374 376
                                       fontSize: 18, color: Colors.black),
@@ -403,8 +405,9 @@ class HomeScreen extends StatelessWidget {
403 405
                               ),
404 406
                               Container(
405 407
                                 margin: EdgeInsets.only(top: 10),
408
+                                padding: EdgeInsets.all(5),
406 409
                                 child: Text(
407
-                                  'Ajukan Cuti',
410
+                                  'Time Off Submission',
408 411
                                   textAlign: TextAlign.center,
409 412
                                   style: GoogleFonts.acme(
410 413
                                       fontSize: 18, color: Colors.black),
@@ -437,6 +440,7 @@ class HomeScreen extends StatelessWidget {
437 440
                               ),
438 441
                               Container(
439 442
                                 margin: EdgeInsets.only(top: 10),
443
+                                padding: EdgeInsets.all(5),
440 444
                                 child: Text(
441 445
                                   'Berita',
442 446
                                   textAlign: TextAlign.center,
@@ -474,8 +478,9 @@ class HomeScreen extends StatelessWidget {
474 478
                               ),
475 479
                               Container(
476 480
                                 margin: EdgeInsets.only(top: 10),
481
+                                padding: EdgeInsets.all(5),
477 482
                                 child: Text(
478
-                                  'Surat Tugas',
483
+                                  'Assignment Letter',
479 484
                                   textAlign: TextAlign.center,
480 485
                                   style: GoogleFonts.acme(
481 486
                                       fontSize: 18, color: Colors.black),
@@ -510,6 +515,7 @@ class HomeScreen extends StatelessWidget {
510 515
                               ),
511 516
                               Container(
512 517
                                 margin: EdgeInsets.only(top: 10),
518
+                                padding: EdgeInsets.all(5),
513 519
                                 child: Text(
514 520
                                   'Reimburse',
515 521
                                   textAlign: TextAlign.center,
@@ -546,6 +552,7 @@ class HomeScreen extends StatelessWidget {
546 552
                               ),
547 553
                               Container(
548 554
                                 margin: EdgeInsets.only(top: 10),
555
+                                padding: EdgeInsets.all(5),
549 556
                                 child: Text(
550 557
                                   'About',
551 558
                                   textAlign: TextAlign.center,

+ 4
- 6
lib/Screens/Menu/Absensi/absensi_screen.dart View File

@@ -1,9 +1,8 @@
1 1
 import 'package:flutter/material.dart';
2 2
 import 'package:flutter_map/plugin_api.dart';
3 3
 import 'package:geolocator/geolocator.dart';
4
-import 'package:google_maps_flutter/google_maps_flutter.dart';
5 4
 import 'package:hris_selfservice_mobile/Screens/Menu/Absensi/absensi_history_screen.dart';
6
-import 'package:latlong/latlong.dart' as latlong;
5
+//import 'package:latlong/latlong.dart' as latlong;
7 6
 import 'package:latlong2/latlong.dart';
8 7
 
9 8
 
@@ -17,7 +16,7 @@ class AbsensiScreen extends StatefulWidget {
17 16
 class _AbsensiScreenState extends State<AbsensiScreen> {
18 17
   double long = 49.5;
19 18
   double lat = -0.09;
20
-  latlong.LatLng point = latlong.LatLng(-6.186729296979901, 106.93023205185953);
19
+  //latlong.LatLng point = latlong.LatLng(-6.186729296979901, 106.93023205185953);
21 20
   var location = [];
22 21
 
23 22
   @override
@@ -30,11 +29,10 @@ class _AbsensiScreenState extends State<AbsensiScreen> {
30 29
           children: [
31 30
             Container(
32 31
               //Ganti yg ini untuk maps
33
-              height: size.height * 0.3,
32
+              height: size.height * 0.5,
34 33
               child: FlutterMap(
35 34
                 options: MapOptions(
36
-                  center: point,
37
-                    zoom: 10.0,
35
+
38 36
                 ),
39 37
                 children: [
40 38
                   TileLayer(

+ 35
- 19
lib/Screens/Menu/AjukanCuti/ajukancuti_screen.dart View File

@@ -3,6 +3,7 @@ import 'dart:io';
3 3
 import 'package:file_picker/file_picker.dart';
4 4
 import 'package:flutter/cupertino.dart';
5 5
 import 'package:flutter/material.dart';
6
+import 'package:flutter/rendering.dart';
6 7
 import 'package:fluttertoast/fluttertoast.dart';
7 8
 import 'package:google_fonts/google_fonts.dart';
8 9
 import 'package:hris_selfservice_mobile/Screens/Menu/AjukanCuti/RequestHttp/jenisCuti_post.dart';
@@ -47,6 +48,13 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
47 48
     deskripsiTeksController.clear();
48 49
     cutiType = [""];
49 50
     idCutiType = [""];
51
+
52
+    formattedDateFrom = "";
53
+    visibleDateFrom = false;
54
+    formattedDateTo = "";
55
+    visibleDateTo = false;
56
+    deskripsiTeksController.clear();
57
+
50 58
     WidgetsBinding.instance.addPostFrameCallback((_) {
51 59
       cutiType = getJenisCuti();
52 60
     });
@@ -133,7 +141,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
133 141
                     crossAxisAlignment: CrossAxisAlignment.end,
134 142
                     children: [
135 143
                       Text(
136
-                        'Ajukan Cuti\t\t',
144
+                        'Time Off\t\t',
137 145
                         maxLines: 1,
138 146
                         style: GoogleFonts.luckiestGuy(
139 147
                           fontSize: 28,
@@ -174,7 +182,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
174 182
                             child: Row(
175 183
                               mainAxisAlignment: MainAxisAlignment.center,
176 184
                               children: [
177
-                                Text('Lihat Riwayat Cuti\t\t',
185
+                                Text(' See Time Off History\t\t',
178 186
                                     textAlign: TextAlign.center,
179 187
                                     style: TextStyle(
180 188
                                         color: Colors.white,
@@ -188,6 +196,9 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
188 196
                               ],
189 197
                             )),
190 198
                         onTap: () {
199
+                          deskripsiTeksController.clear();
200
+                          visibleDateFrom = !visibleDateFrom;
201
+                          visibleDateTo = !visibleDateTo;
191 202
                           Navigator.push(context, MaterialPageRoute(
192 203
                               builder: (context) => HistoryCuti()));
193 204
                         },
@@ -201,6 +212,13 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
201 212
                                 borderRadius: BorderRadius.circular(10)),
202 213
                             child: Column(
203 214
                               children: [
215
+                                Container(
216
+                                  margin: EdgeInsets.only(
217
+                                      left: 10, right: 10, top: 15, bottom: 10),
218
+                                  child: Text('Time Off Submission', style:
219
+                                  GoogleFonts.josefinSans(fontSize: 18, fontWeight: FontWeight.bold, decoration: TextDecoration.underline,
220
+                                  decorationStyle: TextDecorationStyle.dashed),),
221
+                                ),
204 222
                                 Container(
205 223
                                   margin: EdgeInsets.only(
206 224
                                       left: 10, right: 10, top: 10, bottom: 10),
@@ -209,7 +227,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
209 227
                                       Expanded(
210 228
                                           flex: 2,
211 229
                                           child: Text(
212
-                                            'Tipe',
230
+                                            'Type',
213 231
                                             style: GoogleFonts.inconsolata(
214 232
                                                 fontSize: 17),
215 233
                                           )),
@@ -226,7 +244,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
226 244
                                               isExpanded: true,
227 245
                                               underline: SizedBox(),
228 246
                                               hint: Text(
229
-                                                '\t\t\tPilih Tipe Cuti',
247
+                                                '\t\t\tChoose Time Off Type',
230 248
                                                 style: TextStyle(
231 249
                                                     color: Colors.black54),
232 250
                                               ),
@@ -240,7 +258,6 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
240 258
                                                       idCuti = idCutiType.elementAt(i);
241 259
                                                     }
242 260
                                                   }
243
-                                                  logDev.log(idCuti, name: "ID CUTINYA APA");
244 261
                                                 });
245 262
                                               },
246 263
                                               items: cutiType
@@ -266,7 +283,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
266 283
                                                 CrossAxisAlignment.start,
267 284
                                             children: [
268 285
                                               Text(
269
-                                                'Mulai',
286
+                                                'From',
270 287
                                                 style: GoogleFonts.inconsolata(
271 288
                                                     fontSize: 17),
272 289
                                               ),
@@ -311,7 +328,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
311 328
                                               child: Container(
312 329
                                                 width: double.infinity,
313 330
                                                 child: Text(
314
-                                                  "Pilih Tanggal",
331
+                                                  "Choose Date",
315 332
                                                   style: TextStyle(
316 333
                                                       color: Colors.white,
317 334
                                                       fontSize: 16,
@@ -355,7 +372,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
355 372
                                                 CrossAxisAlignment.start,
356 373
                                             children: [
357 374
                                               Text(
358
-                                                'Sampai',
375
+                                                'To',
359 376
                                                 style: GoogleFonts.inconsolata(
360 377
                                                     fontSize: 17),
361 378
                                               ),
@@ -399,7 +416,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
399 416
                                               child: Container(
400 417
                                                 width: double.infinity,
401 418
                                                 child: Text(
402
-                                                  "Pilih Tanggal",
419
+                                                  "Choose Date",
403 420
                                                   style: TextStyle(
404 421
                                                       color: Colors.white,
405 422
                                                       fontSize: 16,
@@ -439,7 +456,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
439 456
                                       Expanded(
440 457
                                           flex: 3,
441 458
                                           child: Text(
442
-                                            'Deskripsi',
459
+                                            'Description',
443 460
                                             style: GoogleFonts.inconsolata(
444 461
                                                 fontSize: 17),
445 462
                                           )),
@@ -466,7 +483,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
466 483
                                                     decoration: InputDecoration(
467 484
                                                         border:
468 485
                                                             InputBorder.none,
469
-                                                        hintText: "Deskripsi")),
486
+                                                        hintText: "description")),
470 487
                                               ))),
471 488
                                     ],
472 489
                                   ),
@@ -481,7 +498,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
481 498
                                           Expanded(
482 499
                                             flex: 3,
483 500
                                             child: Text(
484
-                                              'Lampiran',
501
+                                              'Attachment',
485 502
                                               style: GoogleFonts.inconsolata(
486 503
                                                   fontSize: 17),
487 504
                                             ),
@@ -499,7 +516,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
499 516
                                                             .systemGrey2),*/
500 517
                                                 child: Container(
501 518
                                                   width: double.infinity,
502
-                                                  child: Text("Pilih File",
519
+                                                  child: Text("Choose File",
503 520
                                                       style: TextStyle(
504 521
                                                           color: Colors.white,
505 522
                                                           fontSize: 16,
@@ -553,7 +570,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
553 570
                                                 Color(0xFFFF9945),
554 571
                                                 Color(0xFFFc6076)
555 572
                                               ])),
556
-                                          child: Text('Ajukan',
573
+                                          child: Text('Submit',
557 574
                                               textAlign: TextAlign.center,
558 575
                                               style: TextStyle(
559 576
                                                   color: Colors.white,
@@ -611,7 +628,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
611 628
                                                 // set up the AlertDialog
612 629
                                                 AlertDialog alert = AlertDialog(
613 630
                                                   title: Text("Employee Self Service"),
614
-                                                  content: Text("Berhasil Mengajukan Cuti"),
631
+                                                  content: Text("Success Submit Time Off"),
615 632
                                                   actions: [
616 633
                                                     okButton,
617 634
                                                   ],
@@ -698,7 +715,6 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
698 715
         fileAttach.add(toBase64);
699 716
       }
700 717
       fileAttach.removeAt(0);
701
-      /*logDev.log(fileAttach.toString(), name: "File Attach Base64");*/
702 718
       logDev.log(fileAttach.length.toString(), name: "Length File Attach");
703 719
       logDev.log(files.toString(), name: "Files Picked");
704 720
 
@@ -715,7 +731,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
715 731
     bool result = true;
716 732
     if (selectedType == null) {
717 733
       Fluttertoast.showToast(
718
-          msg: "Tipe Cuti Belum Dipilih",
734
+          msg: "Time Off Type Not Selected",
719 735
           toastLength: Toast.LENGTH_SHORT,
720 736
           gravity: ToastGravity.CENTER,
721 737
           timeInSecForIosWeb: 1,
@@ -724,7 +740,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
724 740
       result = false;
725 741
     } else if  (formattedDateFrom == "" || formattedDateTo == "") {
726 742
       Fluttertoast.showToast(
727
-          msg: "Tanggal Cuti Belum Dipilih",
743
+          msg: "Time Off Date Not Selected",
728 744
           toastLength: Toast.LENGTH_SHORT,
729 745
           gravity: ToastGravity.CENTER,
730 746
           timeInSecForIosWeb: 1,
@@ -733,7 +749,7 @@ class _AjukanCutiScreenState extends State<AjukanCutiScreen> {
733 749
       result = false;
734 750
     } else if (deskripsiTeksController.text.toString().isEmpty) {
735 751
       Fluttertoast.showToast(
736
-          msg: "Deskripsi Harus Diisi",
752
+          msg: "Description Required!",
737 753
           toastLength: Toast.LENGTH_SHORT,
738 754
           gravity: ToastGravity.CENTER,
739 755
           timeInSecForIosWeb: 1,

+ 2
- 2
lib/Screens/Menu/AjukanCuti/history_cuti.dart View File

@@ -175,7 +175,7 @@ class _HistoryCutiState extends State<HistoryCuti> {
175 175
                           crossAxisAlignment: CrossAxisAlignment.end,
176 176
                           children: [
177 177
                             Text(
178
-                              'Riwayat Cuti\t\t',
178
+                              'Time Off History\t\t',
179 179
                               maxLines: 1,
180 180
                               style: GoogleFonts.luckiestGuy(
181 181
                                 fontSize: 28,
@@ -220,7 +220,7 @@ class _HistoryCutiState extends State<HistoryCuti> {
220 220
                                         children: [
221 221
                                           Text(type_cuti_List[i], style: GoogleFonts.rubikBubbles(fontSize: 16)),
222 222
                                           Text(start_date_List[i] + " - " + end_date_List[i] + " (" + duration_List[i] + ") ", style: GoogleFonts.nunito(fontSize: 15)),
223
-                                          Text("\nKeterangan : " + detail_List[i], style: GoogleFonts.nunito(fontSize: 15)),
223
+                                          Text("\nDescription : " + detail_List[i], style: GoogleFonts.nunito(fontSize: 15)),
224 224
                                           Text("\nCreated : " + created_on_List[i], style: GoogleFonts.yeonSung(fontSize: 14, fontStyle: FontStyle.italic)),
225 225
                                         ],
226 226
                                       ),

+ 0
- 9
lib/Screens/Menu/AjukanCuti/model.dart View File

@@ -1,9 +0,0 @@
1
-class cuti_model{
2
-  late final String jenisCuti;
3
-  late final String dateFrom;
4
-  late final String dateTo;
5
-  late final String deskripsi;
6
-
7
-  cuti_model(this.jenisCuti, this.dateFrom, this.dateTo, this.deskripsi);
8
-
9
-}

+ 39
- 0
lib/Screens/Menu/Reimburse/RequestHttp/categoryReimburse_post.dart View File

@@ -0,0 +1,39 @@
1
+import 'dart:convert';
2
+import 'dart:core';
3
+import 'package:http/http.dart' as http;
4
+import 'package:hris_selfservice_mobile/constants.dart';
5
+import 'dart:developer' as developer;
6
+
7
+import 'package:shared_preferences/shared_preferences.dart';
8
+
9
+class CategoryReimburse_Post {
10
+  late String session;
11
+
12
+  CategoryReimburse_Post({required this.session});
13
+
14
+  static Future<String> connectToAPI() async {
15
+    String URL = baseURL + "/api/v1/kategori_reimburse";
16
+
17
+    final SharedPreferences prefs = await SharedPreferences.getInstance();
18
+    final session = prefs.getString('session');
19
+
20
+    var sendData = await http.post(Uri.parse(URL), body: jsonEncode({
21
+      "data": [
22
+        {
23
+          "session": session
24
+        }
25
+      ]
26
+    }), headers: {
27
+      "Content-Type": "application/json",
28
+      "Api-key": apiKey
29
+    });
30
+
31
+    //developer.log(sendData.body, name: "Get Jenis Cuti Result");
32
+    return sendData.body;
33
+
34
+    /*var jsonObject = json.decode(sendData.body);
35
+    developer.log(jsonObject.toString(), name: 'Log');*/
36
+    // return jsonObject;
37
+    // return LoginPostResult.createPostResult(jsonObject);
38
+  }
39
+}

+ 34
- 0
lib/Screens/Menu/Reimburse/RequestHttp/historyReimburse_post.dart View File

@@ -0,0 +1,34 @@
1
+import 'dart:convert';
2
+import 'dart:core';
3
+import 'package:http/http.dart' as http;
4
+import 'package:hris_selfservice_mobile/constants.dart';
5
+import 'dart:developer' as developer;
6
+
7
+import 'package:shared_preferences/shared_preferences.dart';
8
+
9
+class HistoryReimburse_Post {
10
+  late String session;
11
+
12
+  HistoryReimburse_Post({required this.session});
13
+
14
+  static Future<String> connectToAPI() async {
15
+    String URL = baseURL + "/api/v1/history_reimburse";
16
+
17
+    final SharedPreferences prefs = await SharedPreferences.getInstance();
18
+    final session = prefs.getString('session');
19
+
20
+    var sendData = await http.post(Uri.parse(URL), body: jsonEncode({
21
+      "data": [
22
+        {
23
+          "session": "2113213123211321123112123123"
24
+        }
25
+      ]
26
+    }), headers: {
27
+      "Content-Type": "application/json",
28
+      "Api-key": apiKey
29
+    });
30
+
31
+    developer.log(sendData.body, name: "Get History Reimburse Result");
32
+    return sendData.body;
33
+  }
34
+}

+ 47
- 0
lib/Screens/Menu/Reimburse/RequestHttp/pengajuanReimburse_post.dart View File

@@ -0,0 +1,47 @@
1
+import 'dart:convert';
2
+import 'dart:core';
3
+import 'package:http/http.dart' as http;
4
+import 'package:hris_selfservice_mobile/constants.dart';
5
+import 'dart:developer' as developer;
6
+
7
+import 'package:shared_preferences/shared_preferences.dart';
8
+
9
+class PengajuanReimburse_Post {
10
+  late String name;
11
+  late String product;
12
+  late String date;
13
+  late String total;
14
+  late String payment_mode;
15
+  late String description;
16
+  late List<String> file;
17
+  late String session;
18
+
19
+  PengajuanReimburse_Post({required this.session});
20
+
21
+  static Future<String> connectToAPI(String name, String product, String date, String total, String payment_mode, String description) async {
22
+    String URL = baseURL + "/api/v1/pengajuan_reimburse";
23
+
24
+    final SharedPreferences prefs = await SharedPreferences.getInstance();
25
+    final session = prefs.getString('session');
26
+
27
+    var sendData = await http.post(Uri.parse(URL), body: jsonEncode({
28
+      "data": [
29
+        {
30
+          "name" : name,
31
+          "product" : product,
32
+          "date" : date,
33
+          "total" : total,
34
+          "payment_mode" : payment_mode,
35
+          "description" : description,
36
+          "session": session
37
+        }
38
+      ]
39
+    }), headers: {
40
+      "Content-Type": "application/json",
41
+      "Api-key": apiKey
42
+    });
43
+
44
+    //developer.log(sendData.body, name: "Get Jenis Cuti Result");
45
+    return sendData.body;
46
+  }
47
+}

+ 296
- 0
lib/Screens/Menu/Reimburse/history_reimburse.dart View File

@@ -0,0 +1,296 @@
1
+import 'dart:convert';
2
+
3
+import 'package:flutter/material.dart';
4
+import 'package:fluttertoast/fluttertoast.dart';
5
+import 'package:google_fonts/google_fonts.dart';
6
+import 'package:progress_dialog_null_safe/progress_dialog_null_safe.dart';
7
+import 'dart:developer' as logDev;
8
+
9
+import '../AjukanCuti/backgroundHistory.dart';
10
+import 'RequestHttp/historyReimburse_post.dart';
11
+
12
+class HistoryReimburse extends StatefulWidget {
13
+  const HistoryReimburse({Key? key}) : super(key: key);
14
+
15
+  @override
16
+  State<HistoryReimburse> createState() => _HistoryReimburse();
17
+}
18
+
19
+class _HistoryReimburse extends State<HistoryReimburse> {
20
+  late List <String> date_List;
21
+  late List <String> name_List;
22
+  late List <String> employee_List;
23
+  late List <String> payment_List;
24
+  late List <String> activity_List;
25
+  late List <String> total_List;
26
+  late List <String> status_List;
27
+
28
+  late List <Color> statusColor;
29
+  late List <bool> visible;
30
+
31
+  int HistoryLength = 0;
32
+
33
+  @override
34
+  initState() {
35
+    super.initState();
36
+    date_List = [""];
37
+    name_List = [""];
38
+    employee_List = [""];
39
+    payment_List = [""];
40
+    activity_List = [""];
41
+    total_List = [""];
42
+    status_List = [""];
43
+
44
+    statusColor = [Colors.black54];
45
+    visible = [false];
46
+
47
+    WidgetsBinding.instance.addPostFrameCallback((_) async {
48
+      getHistoryData();
49
+    });
50
+    logDev.log(HistoryLength.toString(), name: "Banyak History");
51
+  }
52
+
53
+  getHistoryData() async {
54
+    ProgressDialog loading = ProgressDialog(context);
55
+    loading = ProgressDialog(context,
56
+        type: ProgressDialogType.normal, isDismissible: false, showLogs: true);
57
+    loading.style(
58
+        message: 'Please Wait .....',
59
+        borderRadius: 3,
60
+        backgroundColor: Colors.white,
61
+        progressWidget: CircularProgressIndicator(),
62
+        elevation: 10.0,
63
+        padding: EdgeInsets.all(10),
64
+        insetAnimCurve: Curves.easeInOut,
65
+        progress: 0.0,
66
+        maxProgress: 100.0,
67
+        progressTextStyle: TextStyle(
68
+            color: Colors.black, fontSize: 10.0, fontWeight: FontWeight.w400),
69
+        messageTextStyle: TextStyle(
70
+            color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.w600));
71
+
72
+    loading.show();
73
+    HistoryReimburse_Post.connectToAPI().then((valueResult) async {
74
+      Map<String, dynamic> object = jsonDecode(valueResult);
75
+      if (object.containsKey("result").toString() == "true") {
76
+        String result = object['result'].toString();
77
+        if (result.contains("failed")) {
78
+          loading.hide();
79
+          alertDialogFailedRetrievedData(context);
80
+        } else {
81
+          List <dynamic> historyReimburse = object['result'];
82
+          setState(() {
83
+            for (int i = 0; i < historyReimburse.length; i++){
84
+              String date = historyReimburse[i]['date'].toString();
85
+              String name = historyReimburse[i]['name'].toString();
86
+              String employee = historyReimburse[i]['employee'].toString();
87
+              String payment = historyReimburse[i]['payment'].toString();
88
+              String activity = historyReimburse[i]['activity'].toString();
89
+              String total = historyReimburse[i]['total'].toString();
90
+              String status = historyReimburse[i]['status'].toString();
91
+
92
+              /*if (detail == "false"){
93
+                detail = "-";
94
+              }*/
95
+
96
+              date_List.add(date);
97
+              name_List.add(name);
98
+              employee_List.add(employee);
99
+              payment_List.add(payment);
100
+              activity_List.add(activity);
101
+              total_List.add(total);
102
+              status_List.add(status);
103
+              visible.add(false);
104
+
105
+              var statColor;
106
+              if (status == "draft"){
107
+                statColor = Colors.red;
108
+              } else if (status == "approved"){
109
+                statColor = Colors.green;
110
+              } else if (status == "done"){
111
+                statColor = Colors.blueAccent;
112
+              }
113
+              statusColor.add(statColor);
114
+            }
115
+
116
+            date_List.removeAt(0);
117
+            name_List.removeAt(0);
118
+            employee_List.removeAt(0);
119
+            payment_List.removeAt(0);
120
+            activity_List.removeAt(0);
121
+            total_List.removeAt(0);
122
+            status_List.removeAt(0);
123
+            statusColor.removeAt(0);
124
+            visible.removeAt(0);
125
+
126
+            HistoryLength = historyReimburse.length;
127
+          });
128
+          loading.hide();
129
+        }
130
+      } else {
131
+        Fluttertoast.showToast(
132
+            msg: "Server Response Error",
133
+            toastLength: Toast.LENGTH_SHORT,
134
+            gravity: ToastGravity.CENTER,
135
+            timeInSecForIosWeb: 1,
136
+            textColor: Colors.white,
137
+            fontSize: 16.0);
138
+      }
139
+    });
140
+    loading.hide();
141
+  }
142
+
143
+  @override
144
+  Widget build(BuildContext context) {
145
+    var size = MediaQuery.of(context).size;
146
+    return Scaffold(
147
+      body: Stack(
148
+          children: [
149
+            Column(
150
+              children: <Widget>[
151
+                Stack(
152
+                  children: [
153
+                    WavyHeader(),
154
+                    Container(
155
+                        margin: EdgeInsets.only(
156
+                            top: (size.height / 6) - 20),
157
+                        padding: EdgeInsets.fromLTRB(0, 5, 25, 5),
158
+                        child: Row(
159
+                          mainAxisAlignment: MainAxisAlignment.end,
160
+                          crossAxisAlignment: CrossAxisAlignment.end,
161
+                          children: [
162
+                            Text(
163
+                              'Reimburse History\t\t',
164
+                              maxLines: 1,
165
+                              style: GoogleFonts.luckiestGuy(
166
+                                fontSize: 28,
167
+                                color: Colors.red,
168
+                                fontStyle: FontStyle.italic,
169
+                              ),
170
+                            ),
171
+                            Image.asset(
172
+                              'assets/images/ic_history.png',
173
+                              width: 40,
174
+                              height: 40,
175
+                            ),
176
+                          ],
177
+                        )
178
+                    ),
179
+                  ],
180
+                ),
181
+              ],
182
+            ),
183
+            Container(
184
+              margin: EdgeInsets.only(top: (MediaQuery.of(context).size.height / 6) + 40, left: 5, right: 5, bottom: 10),
185
+              child: ListView.builder(
186
+                scrollDirection: Axis.vertical,
187
+                shrinkWrap: true,
188
+                itemCount: HistoryLength,
189
+                itemBuilder: (context, int i) {
190
+                  return Container(
191
+                    child: InkWell(
192
+                      child: Card(
193
+                        elevation: 10,
194
+                        child: Column(
195
+                          children: [
196
+                            Row(
197
+                              children: [
198
+                                Expanded(
199
+                                    flex: 8,
200
+                                    child: Padding(
201
+                                      padding: EdgeInsets.fromLTRB(10, 5, 5, 5),
202
+                                      child: Column(
203
+                                        mainAxisAlignment: MainAxisAlignment.center,
204
+                                        crossAxisAlignment: CrossAxisAlignment.start,
205
+                                        children: [
206
+                                          Text(name_List[i], style: GoogleFonts.rubikBubbles(fontSize: 16)),
207
+                                          Text(activity_List[i], style: GoogleFonts.nunito(fontSize: 15)),
208
+                                          Text(date_List[i], style: GoogleFonts.nunito(fontSize: 15)),
209
+                                          Text("\Total : " + total_List[i], style: GoogleFonts.nunito(fontSize: 15)),
210
+                                          Text("Payment : " + payment_List[i], style: GoogleFonts.yeonSung(fontSize: 14, fontStyle: FontStyle.italic)),
211
+                                        ],
212
+                                      ),
213
+                                    )
214
+                                ),
215
+                                Expanded(
216
+                                    flex: 2,
217
+                                    child: Padding(
218
+                                      padding: EdgeInsets.fromLTRB(5, 5, 5, 5),
219
+                                      child: Text(status_List[i], style: GoogleFonts.lilitaOne(color: statusColor[i], fontSize: 17),
220
+                                      ),
221
+                                    )
222
+                                ),
223
+                              ],
224
+                            ),
225
+                            Align(
226
+                              alignment: Alignment.centerLeft,
227
+                              child: Visibility(
228
+                                  visible: visible[i],
229
+                                  child: Padding(
230
+                                    padding: EdgeInsets.fromLTRB(10, 5, 5, 5),
231
+                                    child: Column(
232
+                                      mainAxisAlignment: MainAxisAlignment.start,
233
+                                      crossAxisAlignment: CrossAxisAlignment.start,
234
+                                      children: [
235
+                                        Text("\nAttachment : ", style: GoogleFonts.josefinSans(fontSize: 15, fontWeight: FontWeight.bold)),
236
+                                        Text("\Total : " + total_List[i], style: GoogleFonts.nunito(fontSize: 15)),
237
+                                        Text("Payment : " + payment_List[i], style: GoogleFonts.yeonSung(fontSize: 14, fontStyle: FontStyle.italic)),
238
+                                      ],
239
+                                    ),
240
+                                  )
241
+                              ),
242
+                            )
243
+                          ],
244
+                        ),
245
+                      ),
246
+                      onTap: (){
247
+                        setState(() {
248
+                          visible[i] = !visible[i];
249
+                        });
250
+                      },
251
+                    ),
252
+                  );
253
+                },
254
+              ),
255
+            )
256
+          ]),
257
+    );
258
+  }
259
+}
260
+
261
+alertDialogFailedRetrievedData(BuildContext context) {
262
+  Widget okButton = TextButton(
263
+    child: Text("Refresh"),
264
+    onPressed: () {
265
+      Navigator.of(context, rootNavigator: true).pop();
266
+      Navigator.pushReplacement(
267
+          context, MaterialPageRoute(builder: (context) => HistoryReimburse()));
268
+    },
269
+  );
270
+
271
+  Widget noButton = TextButton(
272
+    child: Text("Back"),
273
+    onPressed: () {
274
+      Navigator.of(context, rootNavigator: true).pop();
275
+      Navigator.pop(context);
276
+    },
277
+  );
278
+
279
+  // set up the AlertDialog
280
+  AlertDialog alert = AlertDialog(
281
+    title: Text("Employee Self Service"),
282
+    content: Text("Failed to Retrieve Data"),
283
+    actions: [
284
+      noButton,
285
+      okButton,
286
+    ],
287
+  );
288
+
289
+  // show the dialog
290
+  showDialog(
291
+    context: context,
292
+    builder: (BuildContext context) {
293
+      return alert;
294
+    },
295
+  );
296
+}

+ 833
- 374
lib/Screens/Menu/Reimburse/reimburse_screen.dart
File diff suppressed because it is too large
View File


+ 2
- 2
lib/Screens/Menu/SlipGaji/slipgaji_screen.dart View File

@@ -31,7 +31,7 @@ class _SlipGajiScreenState extends State<SlipGajiScreen> {
31 31
                   crossAxisAlignment: CrossAxisAlignment.end,
32 32
                   children: [
33 33
                     Text(
34
-                      'Slip Gaji\t\t',
34
+                      'Salary Slip\t\t',
35 35
                       maxLines: 1,
36 36
                       style: GoogleFonts.luckiestGuy(
37 37
                         fontSize: 28,
@@ -103,7 +103,7 @@ class _SlipGajiScreenState extends State<SlipGajiScreen> {
103 103
                     Visibility(
104 104
                       visible: visible,
105 105
                       child: Text(
106
-                        'Detail Slip Gaji Bulan ' + selectedDate.toString(),
106
+                        'Salary Slip ' + selectedDate.toString(),
107 107
                         style: GoogleFonts.viga(
108 108
                           fontSize: 15,
109 109
                           fontWeight: FontWeight.bold,

+ 0
- 0
lib/Screens/Menu/SuratTugas/deklarasi_st.dart View File


+ 0
- 0
lib/Screens/Menu/SuratTugas/history_st.dart View File


+ 0
- 0
lib/Screens/Menu/SuratTugas/pengajuan_extendTanggalKembali.dart View File


+ 811
- 0
lib/Screens/Menu/SuratTugas/pengajuan_st.dart View File

@@ -0,0 +1,811 @@
1
+import 'dart:convert';
2
+import 'dart:io';
3
+import 'package:file_picker/file_picker.dart';
4
+import 'package:flutter/cupertino.dart';
5
+import 'package:flutter/material.dart';
6
+import 'package:fluttertoast/fluttertoast.dart';
7
+import 'package:google_fonts/google_fonts.dart';
8
+import 'package:hris_selfservice_mobile/Screens/Menu/AjukanCuti/RequestHttp/jenisCuti_post.dart';
9
+import 'package:intl/intl.dart';
10
+import 'package:progress_dialog_null_safe/progress_dialog_null_safe.dart';
11
+
12
+import 'dart:developer' as logDev;
13
+import '../AjukanCuti/RequestHttp/pengajuanCuti_post.dart';
14
+import '../SlipGaji/background.dart';
15
+
16
+List<String> fileAttach = [""];
17
+TextEditingController deskripsiTeksController = TextEditingController();
18
+
19
+
20
+class PengajuanST_Screen extends StatefulWidget {
21
+  const PengajuanST_Screen({Key? key}) : super(key: key);
22
+
23
+  @override
24
+  State<PengajuanST_Screen> createState() => _PengajuanST_Screen_State();
25
+}
26
+
27
+class _PengajuanST_Screen_State extends State<PengajuanST_Screen> {
28
+  var selectedType;
29
+
30
+  String _fileText = "";
31
+  String _totalFile= "";
32
+
33
+  bool visibleDateFrom = false;
34
+  bool visibleDateTo = false;
35
+
36
+  late List <String> idCutiType;
37
+  late List <String> cutiType;
38
+
39
+  DateTime dateFrom = DateTime.now();
40
+
41
+  late String formattedDateFrom = "";
42
+  late String idCuti;
43
+
44
+  var selectedCategory;
45
+  var selectedDate;
46
+
47
+  int _value = 0;
48
+
49
+  initState(){
50
+    deskripsiTeksController.clear();
51
+    cutiType = [""];
52
+    idCutiType = [""];
53
+    WidgetsBinding.instance.addPostFrameCallback((_) {
54
+      cutiType = getJenisCuti();
55
+    });
56
+    super.initState();
57
+  }
58
+
59
+  getJenisCuti () {
60
+    ProgressDialog loading = ProgressDialog(context);
61
+    loading = ProgressDialog(context,
62
+        type: ProgressDialogType.normal, isDismissible: false, showLogs: true);
63
+    loading.style(
64
+        message: 'Please Wait .....',
65
+        borderRadius: 3,
66
+        backgroundColor: Colors.white,
67
+        progressWidget: CircularProgressIndicator(),
68
+        elevation: 10.0,
69
+        padding: EdgeInsets.all(10),
70
+        insetAnimCurve: Curves.easeInOut,
71
+        progress: 0.0,
72
+        maxProgress: 100.0,
73
+        progressTextStyle: TextStyle(
74
+            color: Colors.black, fontSize: 10.0, fontWeight: FontWeight.w400),
75
+        messageTextStyle: TextStyle(
76
+            color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.w600));
77
+
78
+    loading.show();
79
+    JenisCuti_Post.connectToAPI().then((valueResult) async {
80
+      Map<String, dynamic> object = json.decode(valueResult);
81
+      if (object.containsKey("result").toString() == "true"){
82
+        String result = object['result'].toString();
83
+        logDev.log(result, name: "Jenis Cuti Result");
84
+        if (result.contains("Failed")) {
85
+          loading.hide();
86
+          setState(() {
87
+            cutiType = [""];
88
+            alertDialogFailedRetrievedData(context);
89
+          });
90
+        } else {
91
+          List <dynamic> jenis = object['result']['jenis'];
92
+          setState(() {
93
+            idCutiType.removeAt(0);
94
+            cutiType.removeAt(0);
95
+            for (int i = 0; i < jenis.length; i++){
96
+              String id = jenis[i]['id'].toString();
97
+              String text = jenis[i]['text'].toString();
98
+              idCutiType.add(id);
99
+              cutiType.add(text);
100
+            }
101
+          });
102
+          loading.hide();
103
+        }
104
+      } else {
105
+        setState((){
106
+          cutiType = [""];
107
+          alertDialogFailedResponse(context);
108
+          /*Fluttertoast.showToast(
109
+              msg: "Server Response Error",
110
+              toastLength: Toast.LENGTH_SHORT,
111
+              gravity: ToastGravity.CENTER,
112
+              timeInSecForIosWeb: 1,
113
+              textColor: Colors.white,
114
+              fontSize: 16.0);*/
115
+        });
116
+        loading.hide();
117
+      }
118
+    });
119
+    return cutiType;
120
+  }
121
+
122
+  @override
123
+  Widget build(BuildContext context) {
124
+    return Scaffold(
125
+      body: SingleChildScrollView(
126
+          child: Column(
127
+            children: <Widget>[
128
+              Stack(
129
+                children: [
130
+                  WavyHeader(),
131
+                  Container(
132
+                      margin: EdgeInsets.only(top: 90),
133
+                      padding: EdgeInsets.fromLTRB(20, 5, 25, 5),
134
+                      child: Row(
135
+                        mainAxisAlignment: MainAxisAlignment.end,
136
+                        crossAxisAlignment: CrossAxisAlignment.end,
137
+                        children: [
138
+                          Text(
139
+                            'Submission\t\t',
140
+                            maxLines: 2,
141
+                            style: GoogleFonts.luckiestGuy(
142
+                              fontSize: 28,
143
+                              color: Colors.red,
144
+                              fontStyle: FontStyle.italic,
145
+                            ),
146
+                          ),
147
+                          Image.asset('assets/images/submit_st.png',
148
+                            width: 40,
149
+                            height: 40,
150
+                          ),
151
+                        ],
152
+                      )),
153
+                  SafeArea(
154
+                    child: Container(
155
+                      width: MediaQuery.of(context).size.width,
156
+                      margin: EdgeInsets.only(
157
+                        top: MediaQuery.of(context).size.height / 5,
158
+                        left: 10,
159
+                        right: 10,
160
+                      ),
161
+                      child: Column(
162
+                        children: [
163
+                          Container(
164
+                        child: Card(
165
+                          elevation: 10,
166
+                          child: Container(
167
+                            decoration: BoxDecoration(
168
+                                color: Color(0XFFFAF7EE),
169
+                                borderRadius: BorderRadius.circular(10)),
170
+                            child: Column(
171
+                              children: [
172
+                                Container(
173
+                                  margin: EdgeInsets.only(
174
+                                      left: 10, right: 10, top: 10, bottom: 10),
175
+                                  child: Row(
176
+                                    children: [
177
+                                      Expanded(
178
+                                          flex: 3,
179
+                                          child: Text(
180
+                                            'Category',
181
+                                            style: GoogleFonts.inconsolata(
182
+                                                fontSize: 17),
183
+                                          )),
184
+                                      Expanded(
185
+                                          flex: 7,
186
+                                          child: Container(
187
+                                            decoration: BoxDecoration(
188
+                                                color:
189
+                                                    CupertinoColors.systemGrey2,
190
+                                                borderRadius:
191
+                                                    BorderRadius.circular(5)),
192
+                                            child: DropdownButton(
193
+                                              value: this.selectedCategory,
194
+                                              isExpanded: true,
195
+                                              underline: SizedBox(),
196
+                                              hint: Text(
197
+                                                '\t\tChoose Category',
198
+                                                style: TextStyle(
199
+                                                    color: Colors.black54),
200
+                                              ),
201
+                                              onChanged: (value) {
202
+                                                print(value);
203
+                                                setState(() {
204
+                                                  selectedCategory = value!;
205
+                                                });
206
+                                              },
207
+                                              items: reimburseCategory
208
+                                                  .map(
209
+                                                    (e) => DropdownMenuItem(
210
+                                                        value: e,
211
+                                                        child:
212
+                                                            Text("\t\t\t" + e)),
213
+                                                  )
214
+                                                  .toList(),
215
+                                            ),
216
+                                          )),
217
+                                    ],
218
+                                  ),
219
+                                ),
220
+                                Container(
221
+                                  margin: EdgeInsets.only(
222
+                                      left: 10, right: 10, top: 10, bottom: 10),
223
+                                  child: Column(
224
+                                    children: [
225
+                                      Row(
226
+                                        children: [
227
+                                          Expanded(
228
+                                              flex: 3,
229
+                                              child: Text(
230
+                                                'Date',
231
+                                                style: GoogleFonts.inconsolata(
232
+                                                    fontSize: 17),
233
+                                              )),
234
+                                          Expanded(
235
+                                              flex: 7,
236
+                                              child: Column(
237
+                                                children: [
238
+                                                  ElevatedButton(
239
+                                                    onPressed: () async {
240
+                                                      DateTime? newDate =
241
+                                                      await showDatePicker(
242
+                                                          context: context,
243
+                                                          initialDate: dateFrom,
244
+                                                          firstDate:
245
+                                                          DateTime(1900),
246
+                                                          lastDate:
247
+                                                          DateTime(2100));
248
+                                                      final DateFormat formatter = DateFormat('yyyy-MM-dd');
249
+                                                      if (newDate == null) {
250
+                                                        return;
251
+                                                      } else {
252
+                                                        setState(() {
253
+                                                          if (visibleDateFrom == false){
254
+                                                            visibleDateFrom = !visibleDateFrom;
255
+                                                          }
256
+                                                          formattedDateFrom = formatter.format(newDate);
257
+                                                          //dateFrom = formattedFrom as DateTime;
258
+                                                        });
259
+                                                      }
260
+                                                    },
261
+                                                    /*style: ElevatedButton.styleFrom(
262
+                                                  backgroundColor:
263
+                                                      CupertinoColors
264
+                                                          .systemGrey2),*/
265
+                                                    child: Container(
266
+                                                      width: double.infinity,
267
+                                                      child: Text(
268
+                                                        "Choose Date",
269
+                                                        style: TextStyle(
270
+                                                            color: Colors.white,
271
+                                                            fontSize: 16,
272
+                                                            fontWeight:
273
+                                                            FontWeight.w400),
274
+                                                      ),
275
+                                                    ),
276
+                                                  ),
277
+                                                ],
278
+                                              ),
279
+                                          ),
280
+                                        ],
281
+                                      ),Row(
282
+                                        children: [
283
+                                          Expanded(
284
+                                              flex: 3,
285
+                                              child: Text(
286
+                                                '',
287
+                                                style: GoogleFonts.inconsolata(
288
+                                                    fontSize: 17),
289
+                                              )),
290
+                                          Expanded(
291
+                                            flex: 7,
292
+                                            child: Column(
293
+                                              children: [
294
+                                                Visibility(
295
+                                                  visible: visibleDateFrom,
296
+                                                  child: Container(
297
+                                                    alignment: Alignment.centerLeft,
298
+                                                    margin: EdgeInsets.only(
299
+                                                        left: 15,
300
+                                                        right: 15,
301
+                                                        bottom: 5),
302
+                                                    child: Text(formattedDateFrom,
303
+                                                      overflow:
304
+                                                      TextOverflow.ellipsis,
305
+                                                      maxLines: 1,
306
+                                                      style: TextStyle(
307
+                                                          color: Colors.black54),
308
+                                                    ),
309
+                                                  ),
310
+                                                )
311
+                                              ],
312
+                                            ),
313
+                                          ),
314
+                                        ],
315
+                                      ),
316
+                                    ],
317
+                                  ),
318
+                                ),
319
+                                Container(
320
+                                  margin: EdgeInsets.only(
321
+                                      left: 10, right: 10, top: 10, bottom: 10),
322
+                                  child: Row(
323
+                                    children: [
324
+                                      Expanded(
325
+                                          flex: 3,
326
+                                          child: Text(
327
+                                            'Total Amount',
328
+                                            style: GoogleFonts.inconsolata(
329
+                                                fontSize: 17),
330
+                                          )),
331
+                                      Expanded(
332
+                                          flex: 7,
333
+                                          child: Container(
334
+                                              decoration: BoxDecoration(
335
+                                                  color: Colors.white,
336
+                                                  borderRadius:
337
+                                                      BorderRadius.circular(5)),
338
+                                              child: Padding(
339
+                                                padding: EdgeInsets.only(
340
+                                                    left: 10,
341
+                                                    right: 10,
342
+                                                    top: 5,
343
+                                                    bottom: 5),
344
+                                                child: TextFormField(
345
+                                                    keyboardType:
346
+                                                        TextInputType.number,
347
+                                                    maxLines: 1,
348
+                                                    textInputAction:
349
+                                                        TextInputAction.next,
350
+                                                    decoration: InputDecoration(
351
+                                                        border:
352
+                                                            InputBorder.none,
353
+                                                        hintText: "ex. 3000000")),
354
+                                              ))),
355
+                                    ],
356
+                                  ),
357
+                                ),
358
+                                Container(
359
+                                  margin: EdgeInsets.only(
360
+                                      left: 10, right: 10, top: 10, bottom: 10),
361
+                                  child: Row(
362
+                                    children: [
363
+                                      Expanded(
364
+                                          flex: 3,
365
+                                          child: Text(
366
+                                            'Description',
367
+                                            style: GoogleFonts.inconsolata(
368
+                                                fontSize: 17),
369
+                                          )),
370
+                                      Expanded(
371
+                                          flex: 7,
372
+                                          child: Container(
373
+                                              decoration: BoxDecoration(
374
+                                                  color: Colors.white,
375
+                                                  borderRadius:
376
+                                                      BorderRadius.circular(5)),
377
+                                              child: Padding(
378
+                                                padding: EdgeInsets.only(
379
+                                                    left: 10,
380
+                                                    right: 10,
381
+                                                    top: 5,
382
+                                                    bottom: 5),
383
+                                                child: TextFormField(
384
+                                                    keyboardType:
385
+                                                        TextInputType.multiline,
386
+                                                    maxLines: null,
387
+                                                    textInputAction:
388
+                                                        TextInputAction.done,
389
+                                                    decoration: InputDecoration(
390
+                                                        border:
391
+                                                            InputBorder.none,
392
+                                                        hintText: "description")),
393
+                                              ))),
394
+                                    ],
395
+                                  ),
396
+                                ),
397
+                                Container(
398
+                                  margin: EdgeInsets.only(
399
+                                      left: 10, right: 10, top: 10, bottom: 10),
400
+                                  child: Column(
401
+                                    children: [
402
+                                      Row(
403
+                                        children: [
404
+                                          Expanded(
405
+                                              flex: 3,
406
+                                              child: Text(
407
+                                                'Payment',
408
+                                                style: GoogleFonts.inconsolata(
409
+                                                    fontSize: 17),
410
+                                              )),
411
+                                          Expanded(
412
+                                              flex: 7,
413
+                                              child: Container(
414
+                                                child: Column(
415
+                                                  children: [
416
+                                                    Row(
417
+                                                      children: [
418
+                                                        Radio(
419
+                                                          value: 1,
420
+                                                          groupValue: _value,
421
+                                                          onChanged: (value) {
422
+                                                            setState(() {
423
+                                                              _value = value!;
424
+                                                            });
425
+                                                          },
426
+                                                        ),
427
+                                                        Flexible(
428
+                                                            child: Text(
429
+                                                                "Own (Need Reimburse)",
430
+                                                                style:
431
+                                                                    TextStyle(
432
+                                                                  color: Colors
433
+                                                                      .black54,
434
+                                                                ))),
435
+                                                      ],
436
+                                                    ),
437
+                                                    Row(
438
+                                                      children: [
439
+                                                        Radio(
440
+                                                          value: 2,
441
+                                                          groupValue: _value,
442
+                                                          onChanged: (value) {
443
+                                                            setState(() {
444
+                                                              _value = value!;
445
+                                                            });
446
+                                                          },
447
+                                                        ),
448
+                                                        Flexible(
449
+                                                            child: Text(
450
+                                                                "Company",
451
+                                                                style: TextStyle(
452
+                                                                    color: Colors
453
+                                                                        .black54)))
454
+                                                      ],
455
+                                                    ),
456
+                                                  ],
457
+                                                ),
458
+                                              )),
459
+                                        ],
460
+                                      ),
461
+                                    ],
462
+                                  ),
463
+                                ),
464
+                                Container(
465
+                                  margin: EdgeInsets.only(
466
+                                      left: 10, right: 10, top: 10, bottom: 10),
467
+                                  child: Column(
468
+                                    children: [
469
+                                      Row(
470
+                                        children: [
471
+                                          Expanded(
472
+                                            flex: 3,
473
+                                            child: Text(
474
+                                              'Attachment',
475
+                                              style: GoogleFonts.inconsolata(
476
+                                                  fontSize: 17),
477
+                                            ),
478
+                                          ),
479
+                                          Expanded(
480
+                                            flex: 7,
481
+                                            child: Container(
482
+                                              child: ElevatedButton(
483
+                                                onPressed: () {
484
+                                                  _pickMultipleFiles();
485
+                                                },
486
+                                                /*style: ElevatedButton.styleFrom(
487
+                                                    backgroundColor:
488
+                                                        CupertinoColors
489
+                                                            .systemGrey2),*/
490
+                                                child: Container(
491
+                                                  width: double.infinity,
492
+                                                  child: Text("Choose File",
493
+                                                      style: TextStyle(
494
+                                                          color: Colors.white,
495
+                                                          fontSize: 16,
496
+                                                          fontWeight:
497
+                                                          FontWeight.w400)),
498
+                                                ),
499
+                                              ),
500
+                                            ),
501
+                                          )
502
+                                        ],
503
+                                      ),
504
+                                      Row(
505
+                                        children: [
506
+                                          Expanded(
507
+                                            flex: 3,
508
+                                            child: Text(
509
+                                              '',
510
+                                              style: GoogleFonts.inconsolata(
511
+                                                  fontSize: 17),
512
+                                            ),
513
+                                          ),
514
+                                          Expanded(
515
+                                            flex: 7,
516
+                                            child:  Container(
517
+                                              alignment:
518
+                                              Alignment.centerLeft,
519
+                                              margin: EdgeInsets.only(
520
+                                                  left: 15,
521
+                                                  right: 15,
522
+                                                  bottom: 10),
523
+                                              child: Text(_totalFile + _fileText,
524
+                                                overflow:
525
+                                                TextOverflow.ellipsis,
526
+                                                /*maxLines: 7,*/
527
+                                                style: TextStyle(
528
+                                                    color: Colors.black54),
529
+                                              ),
530
+                                            ),
531
+                                          )
532
+                                        ],
533
+                                      ),
534
+                                      InkWell(
535
+                                        child: Container(
536
+                                          padding: EdgeInsets.fromLTRB(
537
+                                              10, 10, 10, 10),
538
+                                          width: double.infinity,
539
+                                          decoration: BoxDecoration(
540
+                                              borderRadius:
541
+                                              BorderRadius.circular(5),
542
+                                              gradient: LinearGradient(colors: [
543
+                                                Color(0xFFFF9945),
544
+                                                Color(0xFFFc6076)
545
+                                              ])),
546
+                                          child: Text('Submit',
547
+                                              textAlign: TextAlign.center,
548
+                                              style: TextStyle(
549
+                                                  color: Colors.white,
550
+                                                  fontSize: 17,
551
+                                                  fontWeight: FontWeight.w500)),
552
+                                        ),
553
+                                        onTap: () {
554
+                                          ProgressDialog loading = ProgressDialog(context);
555
+                                          loading = ProgressDialog(context,
556
+                                              type: ProgressDialogType.normal, isDismissible: false, showLogs: true);
557
+                                          loading.style(
558
+                                              message: 'Please Wait .....',
559
+                                              borderRadius: 3,
560
+                                              backgroundColor: Colors.white,
561
+                                              progressWidget: CircularProgressIndicator(),
562
+                                              elevation: 10.0,
563
+                                              padding: EdgeInsets.all(10),
564
+                                              insetAnimCurve: Curves.easeInOut,
565
+                                              progress: 0.0,
566
+                                              maxProgress: 100.0,
567
+                                              progressTextStyle: TextStyle(
568
+                                                  color: Colors.black, fontSize: 10.0, fontWeight: FontWeight.w400),
569
+                                              messageTextStyle: TextStyle(
570
+                                                  color: Colors.black, fontSize: 15.0, fontWeight: FontWeight.w600));
571
+
572
+                                          //loading.show();
573
+
574
+                                          if (!validateFormCuti(context)){
575
+                                            return;
576
+                                          } else if (validateFormCuti(context)){
577
+                                            /*loading.show();
578
+                                            PengajuanCuti_Post.connectToAPI(idCuti, formattedDateFrom,
579
+                                                formattedDateTo, deskripsiTeksController.text.toString(), fileAttach)
580
+                                                .then((valueResult) async {
581
+                                              Map<String, dynamic> object = json.decode(valueResult);
582
+                                              logDev.log(fileAttach.toString(), name: "Attachment File Upload");
583
+                                              if (object.containsKey("result").toString() == "true") {
584
+                                                *//*String employee = object['result']['employee'].toString();
585
+                                              String tipe = object['result']['tipe'].toString();
586
+                                              String from = object['result']['from'].toString();
587
+                                              String to = object['result']['to'].toString();
588
+                                              String deskripsi = object['result']['deskripsi'].toString();
589
+                                              String attachment = object['result']['attachment'].toString();*//*
590
+                                                loading.hide();
591
+                                                deskripsiTeksController.clear();
592
+                                                Widget okButton = TextButton(
593
+                                                  child: Text("OK"),
594
+                                                  onPressed: () {
595
+                                                    Navigator.of(context, rootNavigator: true).pop();
596
+                                                    Navigator.pushReplacement(context, MaterialPageRoute(
597
+                                                        builder: (context) => AjukanCutiScreen()));
598
+                                                  },
599
+                                                );
600
+
601
+                                                // set up the AlertDialog
602
+                                                AlertDialog alert = AlertDialog(
603
+                                                  title: Text("Employee Self Service"),
604
+                                                  content: Text("Berhasil Mengajukan Cuti"),
605
+                                                  actions: [
606
+                                                    okButton,
607
+                                                  ],
608
+                                                );
609
+
610
+                                                // show the dialog
611
+                                                showDialog(
612
+                                                  context: context,
613
+                                                  builder: (BuildContext context) {
614
+                                                    return alert;
615
+                                                  },
616
+                                                );
617
+                                                *//* deskripsiTeksController.clear();
618
+                                                formattedDateFrom = "";
619
+                                                formattedDateTo = "";
620
+                                                visibleDateFrom = false;
621
+                                                visibleDateFrom = false;*//*
622
+                                              } else if (object.containsKey("error").toString() == "true") {
623
+                                                String errorMessage = object['error']['data']['message']
624
+                                                    .toString();
625
+                                                loading.hide();
626
+                                                Widget okButton = TextButton(
627
+                                                  child: Text("OK"),
628
+                                                  onPressed: () {
629
+                                                    Navigator.of(context, rootNavigator: true).pop();
630
+                                                  },
631
+                                                );
632
+
633
+                                                // set up the AlertDialog
634
+                                                AlertDialog alert = AlertDialog(
635
+                                                  title: Text("Employee Self Service"),
636
+                                                  content: Text(errorMessage),
637
+                                                  actions: [
638
+                                                    okButton,
639
+                                                  ],
640
+                                                );
641
+
642
+                                                // show the dialog
643
+                                                showDialog(
644
+                                                  context: context,
645
+                                                  builder: (BuildContext context) {
646
+                                                    return alert;
647
+                                                  },
648
+                                                );
649
+                                              }
650
+                                            });*/
651
+                                          }
652
+                                        },
653
+                                      )
654
+                                    ],
655
+                                  ),
656
+                                ),
657
+                              ],
658
+                            ),
659
+                          ),
660
+                        ),
661
+                      ),
662
+                        ],
663
+                      ),
664
+                    ),
665
+                  ),
666
+                ],
667
+              ),
668
+            ],
669
+          )),
670
+    );
671
+  }
672
+
673
+  void _pickMultipleFiles() async {
674
+    FilePickerResult? result = await FilePicker.platform.pickFiles(allowMultiple: true);
675
+
676
+    if (_fileText != ""){
677
+      _fileText = "";
678
+    }
679
+
680
+    if (result != null) {
681
+      List<File> files = result.paths.map((path) => File(path!)).toList();
682
+      for (int i = 0; i< files.length; i++){
683
+        String fileName = files[i].path.split('/').last;
684
+        _fileText = _fileText + "\n" + fileName;
685
+
686
+        List<int> fileInBytes = files[i].readAsBytesSync();
687
+        String toBase64 = base64Encode(fileInBytes);
688
+        fileAttach.add(toBase64);
689
+      }
690
+      fileAttach.removeAt(0);
691
+      /*logDev.log(fileAttach.toString(), name: "File Attach Base64");*/
692
+      logDev.log(fileAttach.length.toString(), name: "Length File Attach");
693
+      logDev.log(files.toString(), name: "Files Picked");
694
+
695
+      setState(() {
696
+        _fileText;
697
+        _totalFile = "Total File : " + files.length.toString();
698
+      });
699
+    } else {
700
+      // User canceled the picker
701
+    }
702
+  }
703
+
704
+  bool validateFormCuti(BuildContext context) {
705
+    bool result = true;
706
+    if (selectedType == null) {
707
+      Fluttertoast.showToast(
708
+          msg: "Tipe Cuti Belum Dipilih",
709
+          toastLength: Toast.LENGTH_SHORT,
710
+          gravity: ToastGravity.CENTER,
711
+          timeInSecForIosWeb: 1,
712
+          textColor: Colors.white,
713
+          fontSize: 16.0);
714
+      result = false;
715
+    } else if (deskripsiTeksController.text.toString().isEmpty) {
716
+      Fluttertoast.showToast(
717
+          msg: "Deskripsi Harus Diisi",
718
+          toastLength: Toast.LENGTH_SHORT,
719
+          gravity: ToastGravity.CENTER,
720
+          timeInSecForIosWeb: 1,
721
+          textColor: Colors.white,
722
+          fontSize: 16.0);
723
+      result = false;
724
+    }
725
+
726
+    return result;
727
+  }
728
+}
729
+
730
+alertDialogFailedRetrievedData(BuildContext context){
731
+  Widget okButton = TextButton(
732
+    child: Text("Refresh"),
733
+    onPressed: () {
734
+      /*Navigator.of(context, rootNavigator: true).pop();
735
+      Navigator.pushReplacement(context, MaterialPageRoute(
736
+          builder: (context) => AjukanCutiScreen()));*/
737
+    },
738
+  );
739
+
740
+  Widget noButton = TextButton(
741
+    child: Text("Back"),
742
+    onPressed: () {
743
+      Navigator.of(context, rootNavigator: true).pop();
744
+      Navigator.pop(context);
745
+
746
+    },
747
+  );
748
+
749
+  // set up the AlertDialog
750
+  AlertDialog alert = AlertDialog(
751
+    title: Text("Employee Self Service"),
752
+    content: Text("Failed to Retrieve Data"),
753
+    actions: [
754
+      noButton,
755
+      okButton,
756
+    ],
757
+  );
758
+
759
+  // show the dialog
760
+  showDialog(
761
+    context: context,
762
+    builder: (BuildContext context) {
763
+      return alert;
764
+    },
765
+  );
766
+}
767
+
768
+alertDialogFailedResponse(BuildContext context){
769
+  Widget okButton = TextButton(
770
+    child: Text("Refresh"),
771
+    onPressed: () {
772
+     /* Navigator.of(context, rootNavigator: true).pop();
773
+      Navigator.pushReplacement(context, MaterialPageRoute(
774
+          builder: (context) => AjukanCutiScreen()));*/
775
+    },
776
+  );
777
+
778
+  Widget noButton = TextButton(
779
+    child: Text("Back"),
780
+    onPressed: () {
781
+      Navigator.of(context, rootNavigator: true).pop();
782
+      Navigator.pop(context);
783
+
784
+    },
785
+  );
786
+
787
+  // set up the AlertDialog
788
+  AlertDialog alert = AlertDialog(
789
+    title: Text("Employee Self Service"),
790
+    content: Text("Server Response Error"),
791
+    actions: [
792
+      noButton,
793
+      okButton,
794
+    ],
795
+  );
796
+
797
+  // show the dialog
798
+  showDialog(
799
+    context: context,
800
+    builder: (BuildContext context) {
801
+      return alert;
802
+    },
803
+  );
804
+}
805
+
806
+List<String> reimburseCategory = [
807
+  "Paid Time Off",
808
+  "Sick Time Off",
809
+  "Compensatory Days",
810
+  "Unpaid"
811
+];

+ 0
- 0
lib/Screens/Menu/SuratTugas/pengajuan_uangMukaTambahan.dart View File


+ 136
- 8
lib/Screens/Menu/SuratTugas/surattugas_screen.dart View File

@@ -1,6 +1,7 @@
1 1
 import 'package:flutter/cupertino.dart';
2 2
 import 'package:flutter/material.dart';
3 3
 import 'package:google_fonts/google_fonts.dart';
4
+import 'package:hris_selfservice_mobile/Screens/Menu/SuratTugas/pengajuan_st.dart';
4 5
 
5 6
 import '../SlipGaji/background.dart';
6 7
 
@@ -34,8 +35,7 @@ class _SuratTugas_ScreenState extends State<SuratTugas_Screen> {
34 35
                     crossAxisAlignment: CrossAxisAlignment.end,
35 36
                     children: [
36 37
                       Text(
37
-                        'Surat Tugas\t\t',
38
-                        maxLines: 1,
38
+                        'Assignment\nLetter\t\t',
39 39
                         style: GoogleFonts.luckiestGuy(
40 40
                           fontSize: 28,
41 41
                           color: Colors.red,
@@ -61,7 +61,7 @@ class _SuratTugas_ScreenState extends State<SuratTugas_Screen> {
61 61
                     children: [
62 62
                       InkWell(
63 63
                         child: Container(
64
-                            margin: EdgeInsets.only(top: 15, bottom: 15),
64
+                            margin: EdgeInsets.fromLTRB(15, 15, 15, 5),
65 65
                             padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
66 66
                             width: double.infinity,
67 67
                             decoration: BoxDecoration(
@@ -76,14 +76,80 @@ class _SuratTugas_ScreenState extends State<SuratTugas_Screen> {
76 76
                               mainAxisAlignment: MainAxisAlignment.center,
77 77
                               children: [
78 78
                                 Flexible(
79
-                                    child: Text('Lihat Riwayat Surat Tugas\t\t',
79
+                                    child: Text('Assignment Letter Submission\t\t',
80 80
                                         textAlign: TextAlign.center,
81 81
                                         style: TextStyle(
82 82
                                             color: Colors.white,
83 83
                                             fontSize: 17,
84 84
                                             fontWeight: FontWeight.w500))),
85
-                                Image.asset(
86
-                                  'assets/images/ic_history.png',
85
+                                Image.asset('assets/images/submit_st.png',
86
+                                  width: 30,
87
+                                  height: 30,
88
+                                )
89
+                              ],
90
+                            )),
91
+                        onTap: () {
92
+                          Navigator.push(
93
+                              context,
94
+                              MaterialPageRoute(
95
+                                  builder: (context) => PengajuanST_Screen()));
96
+                        },
97
+                      ),
98
+                      InkWell(
99
+                        child: Container(
100
+                            margin: EdgeInsets.fromLTRB(15, 5, 15, 5),
101
+                            padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
102
+                            width: double.infinity,
103
+                            decoration: BoxDecoration(
104
+                                borderRadius: BorderRadius.circular(5),
105
+                                gradient: LinearGradient(colors: [
106
+                                  Color(0xFF2D4059),
107
+                                  Color(0xFF2D4059),
108
+                                  /*Color(0xFFEAFFD0),
109
+                                  Color(0xFF95E1D3),*/
110
+                                ])),
111
+                            child: Row(
112
+                              mainAxisAlignment: MainAxisAlignment.center,
113
+                              children: [
114
+                                Flexible(
115
+                                    child: Text('Declaration of Assignment Letter\t\t',
116
+                                        textAlign: TextAlign.center,
117
+                                        style: TextStyle(
118
+                                            color: Colors.white,
119
+                                            fontSize: 17,
120
+                                            fontWeight: FontWeight.w500))),
121
+                                Image.asset('assets/images/declaration.png',
122
+                                  width: 30,
123
+                                  height: 30,
124
+                                )
125
+                              ],
126
+                            )),
127
+                        onTap: () {},
128
+                      ),
129
+                      InkWell(
130
+                        child: Container(
131
+                            margin: EdgeInsets.fromLTRB(15, 5, 15, 5),
132
+                            padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
133
+                            width: double.infinity,
134
+                            decoration: BoxDecoration(
135
+                                borderRadius: BorderRadius.circular(5),
136
+                                gradient: LinearGradient(colors: [
137
+                                  Color(0xFF2D4059),
138
+                                  Color(0xFF2D4059),
139
+                                  /*Color(0xFFEAFFD0),
140
+                                  Color(0xFF95E1D3),*/
141
+                                ])),
142
+                            child: Row(
143
+                              mainAxisAlignment: MainAxisAlignment.center,
144
+                              children: [
145
+                                Flexible(
146
+                                    child: Text('See Assignment Letter History\t\t',
147
+                                        textAlign: TextAlign.center,
148
+                                        style: TextStyle(
149
+                                            color: Colors.white,
150
+                                            fontSize: 17,
151
+                                            fontWeight: FontWeight.w500))),
152
+                                Image.asset('assets/images/ic_history.png',
87 153
                                   width: 30,
88 154
                                   height: 30,
89 155
                                 )
@@ -91,7 +157,69 @@ class _SuratTugas_ScreenState extends State<SuratTugas_Screen> {
91 157
                             )),
92 158
                         onTap: () {},
93 159
                       ),
94
-                      Container(
160
+                      InkWell(
161
+                        child: Container(
162
+                            margin: EdgeInsets.fromLTRB(15, 5, 15, 5),
163
+                            padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
164
+                            width: double.infinity,
165
+                            decoration: BoxDecoration(
166
+                                borderRadius: BorderRadius.circular(5),
167
+                                gradient: LinearGradient(colors: [
168
+                                  Color(0xFF2D4059),
169
+                                  Color(0xFF2D4059),
170
+                                  /*Color(0xFFEAFFD0),
171
+                                  Color(0xFF95E1D3),*/
172
+                                ])),
173
+                            child: Row(
174
+                              mainAxisAlignment: MainAxisAlignment.center,
175
+                              children: [
176
+                                Flexible(
177
+                                    child: Text('Additional Advance Payment\t\t',
178
+                                        textAlign: TextAlign.center,
179
+                                        style: TextStyle(
180
+                                            color: Colors.white,
181
+                                            fontSize: 17,
182
+                                            fontWeight: FontWeight.w500))),
183
+                                Image.asset('assets/images/extra_money.png',
184
+                                  width: 30,
185
+                                  height: 30,
186
+                                )
187
+                              ],
188
+                            )),
189
+                        onTap: () {},
190
+                      ),
191
+                      InkWell(
192
+                        child: Container(
193
+                            margin: EdgeInsets.fromLTRB(15, 5, 15, 5),
194
+                            padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
195
+                            width: double.infinity,
196
+                            decoration: BoxDecoration(
197
+                                borderRadius: BorderRadius.circular(5),
198
+                                gradient: LinearGradient(colors: [
199
+                                  Color(0xFF2D4059),
200
+                                  Color(0xFF2D4059),
201
+                                  /*Color(0xFFEAFFD0),
202
+                                  Color(0xFF95E1D3),*/
203
+                                ])),
204
+                            child: Row(
205
+                              mainAxisAlignment: MainAxisAlignment.center,
206
+                              children: [
207
+                                Flexible(
208
+                                    child: Text('Return Date Extension\t\t',
209
+                                        textAlign: TextAlign.center,
210
+                                        style: TextStyle(
211
+                                            color: Colors.white,
212
+                                            fontSize: 17,
213
+                                            fontWeight: FontWeight.w500))),
214
+                                Image.asset('assets/images/extend_tanggal.png',
215
+                                  width: 30,
216
+                                  height: 30,
217
+                                )
218
+                              ],
219
+                            )),
220
+                        onTap: () {}
221
+                      ),
222
+                      /*Container(
95 223
                         child: Card(
96 224
                           elevation: 10,
97 225
                           child: Container(
@@ -444,7 +572,7 @@ class _SuratTugas_ScreenState extends State<SuratTugas_Screen> {
444 572
                             ),
445 573
                           ),
446 574
                         ),
447
-                      ),
575
+                      ),*/
448 576
                     ],
449 577
                   ),
450 578
                 ),

+ 10
- 22
lib/Screens/Settings/settings_screen.dart View File

@@ -69,7 +69,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
69 69
               fontSize: 15.0,
70 70
               fontWeight: FontWeight.w600));
71 71
       loading.show();
72
-      logDev.log(getProfileImage().toString(), name: "APA ISINYA");
73 72
       _imageToShow = getProfileImage();
74 73
     });
75 74
   }
@@ -322,15 +321,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
322 321
                             ChangeProfileImage_Post.connectToAPI(
323 322
                                     session!, base64Image)
324 323
                                 .then((valueResult) async {
325
-                              logDev.log(valueResult, name: "HASIL CHANGE");
326 324
                               Map<String, dynamic> object =
327 325
                                   json.decode(valueResult);
328
-                              if (object.containsKey("result").toString() ==
329
-                                  "true") {
330
-                                String status =
331
-                                    object['result']['status'].toString();
332
-                                String message =
333
-                                    object['result']['message'].toString();
326
+                              if (object.containsKey("result").toString() == "true") {
327
+                                String status = object['result']['status'].toString();
328
+                                String message = object['result']['message'].toString();
334 329
                                 if (status == "success") {
335 330
                                   _imageToShow =
336 331
                                       FileImage(File(imagePicked.path));
@@ -365,7 +360,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
365 360
                               setState(() {});
366 361
                             });
367 362
                           } else if (imagePicked == null) {
368
-                            logDev.log("NULL", name: "NO IMAGE SELECTED");
369 363
                             //_imagePath = await getImageFileFromAssets('assets/images/ic_administrator.png') as File?;
370 364
                           }
371 365
                         },
@@ -399,7 +393,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
399 393
                               Padding(
400 394
                                 padding: EdgeInsets.only(bottom: 5),
401 395
                                 child: Text(
402
-                                  "Nama Pegawai",
396
+                                  "Name",
403 397
                                   style: TextStyle(
404 398
                                       fontSize: 16, color: Colors.black87),
405 399
                                 ),
@@ -437,7 +431,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
437 431
                               Padding(
438 432
                                 padding: EdgeInsets.only(bottom: 5),
439 433
                                 child: Text(
440
-                                  "Tanggal Lahir",
434
+                                  "Date of Birth",
441 435
                                   style: TextStyle(
442 436
                                       fontSize: 16, color: Colors.black87),
443 437
                                 ),
@@ -456,7 +450,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
456 450
                               Padding(
457 451
                                 padding: EdgeInsets.only(bottom: 5),
458 452
                                 child: Text(
459
-                                  "Nomor Telepon",
453
+                                  "Phone Number",
460 454
                                   style: TextStyle(
461 455
                                       fontSize: 16, color: Colors.black87),
462 456
                                 ),
@@ -475,7 +469,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
475 469
                               Padding(
476 470
                                 padding: EdgeInsets.only(bottom: 5),
477 471
                                 child: Text(
478
-                                  "Alamat Email",
472
+                                  "Email Address",
479 473
                                   style: TextStyle(
480 474
                                       fontSize: 16, color: Colors.black87),
481 475
                                 ),
@@ -494,7 +488,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
494 488
                               Padding(
495 489
                                 padding: EdgeInsets.only(bottom: 5),
496 490
                                 child: Text(
497
-                                  "Alamat Rumah",
491
+                                  "Address",
498 492
                                   style: TextStyle(
499 493
                                       fontSize: 16, color: Colors.black87),
500 494
                                 ),
@@ -747,10 +741,6 @@ Widget roundDetail(String detail, double marginTop, double marginBottom,
747 741
                   borderRadius:
748 742
                       BorderRadius.all(Radius.circular(radiusCircular))),
749 743
               color: Colors.blueGrey,
750
-              /*gradient: LinearGradient(
751
-                  colors: gradient,
752
-                  begin: Alignment.topLeft,
753
-                  end: Alignment.bottomRight),*/
754 744
             ),
755 745
             child: Padding(
756 746
               padding: EdgeInsets.fromLTRB(3, 3, 3, 3),
@@ -830,7 +820,6 @@ showAlertDialogSavePassword(BuildContext context) {
830 820
             retypeNewPasswordController.clear();
831 821
 
832 822
             final session = await prefs.remove('session');
833
-            logDev.log(session.toString(), name: "SESSION KEHAPUS GAAAA?");
834 823
             await loading.hide();
835 824
             Navigator.of(context, rootNavigator: true).pop();
836 825
             Navigator.pushReplacement(
@@ -961,7 +950,6 @@ showAlertDialogLogout(BuildContext context) {
961 950
           String status = object['result']['status'].toString();
962 951
           if (status == "success") {
963 952
             final session = await prefs.remove('session');
964
-            logDev.log(session.toString(), name: "SESSION KEHAPUS GA?");
965 953
             Navigator.of(context, rootNavigator: true).pop();
966 954
             await loading.hide();
967 955
             Navigator.pushAndRemoveUntil(
@@ -1005,8 +993,8 @@ showAlertDialogLogout(BuildContext context) {
1005 993
   // set up the AlertDialog
1006 994
   AlertDialog alert = AlertDialog(
1007 995
     title: Text("Employee Self Service"),
1008
-    //content: Text("Are you sure you want to logout from this Application?"),
1009
-    content: Text("Apakah Anda yakin ingin keluar dari aplikasi ini?"),
996
+    content: Text("Are you sure you want to logout from this Application?"),
997
+    //content: Text("Apakah Anda yakin ingin keluar dari aplikasi ini?"),
1010 998
     actions: [noButton, okButton],
1011 999
   );
1012 1000
 

+ 2
- 0
macos/Flutter/GeneratedPluginRegistrant.swift View File

@@ -5,6 +5,7 @@
5 5
 import FlutterMacOS
6 6
 import Foundation
7 7
 
8
+import connectivity_plus
8 9
 import device_info_plus
9 10
 import firebase_core
10 11
 import firebase_messaging
@@ -17,6 +18,7 @@ import shared_preferences_macos
17 18
 import url_launcher_macos
18 19
 
19 20
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
21
+  ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
20 22
   DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
21 23
   FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
22 24
   FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))

+ 12
- 0
node_modules/.bin/firebase View File

@@ -0,0 +1,12 @@
1
+#!/bin/sh
2
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+case `uname` in
5
+    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
6
+esac
7
+
8
+if [ -x "$basedir/node" ]; then
9
+  exec "$basedir/node"  "$basedir/../firebase-tools/lib/bin/firebase.js" "$@"
10
+else 
11
+  exec node  "$basedir/../firebase-tools/lib/bin/firebase.js" "$@"
12
+fi

+ 17
- 0
node_modules/.bin/firebase.cmd View File

@@ -0,0 +1,17 @@
1
+@ECHO off
2
+GOTO start
3
+:find_dp0
4
+SET dp0=%~dp0
5
+EXIT /b
6
+:start
7
+SETLOCAL
8
+CALL :find_dp0
9
+
10
+IF EXIST "%dp0%\node.exe" (
11
+  SET "_prog=%dp0%\node.exe"
12
+) ELSE (
13
+  SET "_prog=node"
14
+  SET PATHEXT=%PATHEXT:;.JS;=;%
15
+)
16
+
17
+endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\..\firebase-tools\lib\bin\firebase.js" %*

+ 28
- 0
node_modules/.bin/firebase.ps1 View File

@@ -0,0 +1,28 @@
1
+#!/usr/bin/env pwsh
2
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
+
4
+$exe=""
5
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
6
+  # Fix case when both the Windows and Linux builds of Node
7
+  # are installed in the same directory
8
+  $exe=".exe"
9
+}
10
+$ret=0
11
+if (Test-Path "$basedir/node$exe") {
12
+  # Support pipeline input
13
+  if ($MyInvocation.ExpectingInput) {
14
+    $input | & "$basedir/node$exe"  "$basedir/../firebase-tools/lib/bin/firebase.js" $args
15
+  } else {
16
+    & "$basedir/node$exe"  "$basedir/../firebase-tools/lib/bin/firebase.js" $args
17
+  }
18
+  $ret=$LASTEXITCODE
19
+} else {
20
+  # Support pipeline input
21
+  if ($MyInvocation.ExpectingInput) {
22
+    $input | & "node$exe"  "$basedir/../firebase-tools/lib/bin/firebase.js" $args
23
+  } else {
24
+    & "node$exe"  "$basedir/../firebase-tools/lib/bin/firebase.js" $args
25
+  }
26
+  $ret=$LASTEXITCODE
27
+}
28
+exit $ret

+ 6743
- 0
node_modules/.package-lock.json
File diff suppressed because it is too large
View File


+ 21
- 0
node_modules/firebase-tools/LICENSE View File

@@ -0,0 +1,21 @@
1
+The MIT License (MIT)
2
+
3
+Copyright (c) 2015 Firebase
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 283
- 0
node_modules/firebase-tools/README.md View File

@@ -0,0 +1,283 @@
1
+# Firebase CLI [![Actions Status][gh-actions-badge]][gh-actions] [![Node Version][node-badge]][npm] [![NPM version][npm-badge]][npm]
2
+
3
+The Firebase Command Line Interface (CLI) Tools can be used to test, manage, and deploy your Firebase project from the command line.
4
+
5
+- Deploy code and assets to your Firebase projects
6
+- Run a local web server for your Firebase Hosting site
7
+- Interact with data in your Firebase database
8
+- Import/Export users into/from Firebase Auth
9
+
10
+To get started with the Firebase CLI, read the full list of commands below or check out the [documentation](https://firebase.google.com/docs/cli).
11
+
12
+## Installation
13
+
14
+### Node Package
15
+
16
+You can install the Firebase CLI using npm (the Node Package Manager). Note that you will need to install
17
+[Node.js](http://nodejs.org/) and [npm](https://npmjs.org/). Installing Node.js should install npm as well.
18
+
19
+To download and install the Firebase CLI run the following command:
20
+
21
+```bash
22
+npm install -g firebase-tools
23
+```
24
+
25
+This will provide you with the globally accessible `firebase` command.
26
+
27
+### Standalone Binary
28
+
29
+The standalone binary distribution of the Firebase CLI allows you to download a `firebase` executable
30
+without any dependencies.
31
+
32
+To download and install the CLI run the following command:
33
+
34
+```bash
35
+curl -sL firebase.tools | bash
36
+```
37
+
38
+## Commands
39
+
40
+**The command `firebase --help` lists the available commands and `firebase <command> --help` shows more details for an individual command.**
41
+
42
+If a command is project-specific, you must either be inside a project directory with an
43
+active project alias or specify the Firebase project id with the `-P <project_id>` flag.
44
+
45
+Below is a brief list of the available commands and their function:
46
+
47
+### Configuration Commands
48
+
49
+| Command        | Description                                                                                                                                     |
50
+| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
51
+| **login**      | Authenticate to your Firebase account. Requires access to a web browser.                                                                        |
52
+| **logout**     | Sign out of the Firebase CLI.                                                                                                                   |
53
+| **login:ci**   | Generate an authentication token for use in non-interactive environments.                                                                       |
54
+| **login:add**  | Authorize the CLI for an additional account.                                                                                                    |
55
+| **login:list** | List authorized CLI accounts.                                                                                                                   |
56
+| **login:use**  | Set the default account to use for this project                                                                                                 |
57
+| **use**        | Set active Firebase project, manage project aliases.                                                                                            |
58
+| **open**       | Quickly open a browser to relevant project resources.                                                                                           |
59
+| **init**       | Setup a new Firebase project in the current directory. This command will create a `firebase.json` configuration file in your current directory. |
60
+| **help**       | Display help information about the CLI or specific commands.                                                                                    |
61
+
62
+Append `--no-localhost` to login (i.e., `firebase login --no-localhost`) to copy and paste code instead of starting a local server for authentication. A use case might be if you SSH into an instance somewhere and you need to authenticate to Firebase on that machine.
63
+
64
+### Project Management Commands
65
+
66
+| Command                  | Description                                                |
67
+| ------------------------ | ---------------------------------------------------------- |
68
+| **apps:create**          | Create a new Firebase app in a project.                    |
69
+| **apps:list**            | List the registered apps of a Firebase project.            |
70
+| **apps:sdkconfig**       | Print the configuration of a Firebase app.                 |
71
+| **projects:addfirebase** | Add Firebase resources to a Google Cloud Platform project. |
72
+| **projects:create**      | Create a new Firebase project.                             |
73
+| **projects:list**        | Print a list of all of your Firebase projects.             |
74
+
75
+### Deployment and Local Emulation
76
+
77
+These commands let you deploy and interact with your Firebase services.
78
+
79
+| Command                       | Description                                                                                                                   |
80
+| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
81
+| **emulators:exec**            | Start the local Firebase emulators, run a test script, then shut down the emulators.                                          |
82
+| **emulators:start**           | Start the local Firebase emulators.                                                                                           |
83
+| **deploy**                    | Deploys your Firebase project. Relies on `firebase.json` configuration and your local project folder.                         |
84
+| **serve**                     | Start a local server with your Firebase Hosting configuration and HTTPS-triggered Cloud Functions. Relies on `firebase.json`. |
85
+| **setup:emulators:database**  | Downloads the database emulator.                                                                                              |
86
+| **setup:emulators:firestore** | Downloads the firestore emulator.                                                                                             |
87
+
88
+### App Distribution Commands
89
+
90
+| Command                        | Description            |
91
+| ------------------------------ | ---------------------- |
92
+| **appdistribution:distribute** | Upload a distribution. |
93
+
94
+### Auth Commands
95
+
96
+| Command         | Description                                            |
97
+| --------------- | ------------------------------------------------------ |
98
+| **auth:import** | Batch importing accounts into Firebase from data file. |
99
+| **auth:export** | Batch exporting accounts from Firebase into data file. |
100
+
101
+Detailed doc is [here](https://firebase.google.com/docs/cli/auth).
102
+
103
+### Realtime Database Commands
104
+
105
+| Command                       | Description                                                                                                                                 |
106
+| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
107
+| **database:get**              | Fetch data from the current project's database and display it as JSON. Supports querying on indexed data.                                   |
108
+| **database:set**              | Replace all data at a specified location in the current project's database. Takes input from file, STDIN, or command-line argument.         |
109
+| **database:push**             | Push new data to a list at a specified location in the current project's database. Takes input from file, STDIN, or command-line argument.  |
110
+| **database:remove**           | Delete all data at a specified location in the current project's database.                                                                  |
111
+| **database:update**           | Perform a partial update at a specified location in the current project's database. Takes input from file, STDIN, or command-line argument. |
112
+| **database:profile**          | Profile database usage and generate a report.                                                                                               |
113
+| **database:instances:create** | Create a realtime database instance.                                                                                                        |
114
+| **database:instances:list**   | List realtime database instances.                                                                                                           |
115
+| **database:settings:get**     | Read the realtime database setting at path                                                                                                  |
116
+| **database:settings:set**     | Set the realtime database setting at path.                                                                                                  |
117
+
118
+### Extensions Commands
119
+
120
+| Command           | Description                                                                                 |
121
+| ----------------- | ------------------------------------------------------------------------------------------- |
122
+| **ext**           | Display information on how to use ext commands and extensions installed to your project.    |
123
+| **ext:configure** | Configure an existing extension instance.                                                   |
124
+| **ext:info**      | Display information about an extension by name (extensionName@x.y.z for a specific version) |
125
+| **ext:install**   | Install an extension.                                                                       |
126
+| **ext:list**      | List all the extensions that are installed in your Firebase project.                        |
127
+| **ext:uninstall** | Uninstall an extension that is installed in your Firebase project by Instance ID.           |
128
+| **ext:update**    | Update an existing extension instance to the latest version.                                |
129
+
130
+### Cloud Firestore Commands
131
+
132
+| Command               | Description                                                                                                         |
133
+| --------------------- | ------------------------------------------------------------------------------------------------------------------- |
134
+| **firestore:delete**  | Delete documents or collections from the current project's database. Supports recursive deletion of subcollections. |
135
+| **firestore:indexes** | List all deployed indexes from the current project.                                                                 |
136
+
137
+### Cloud Functions Commands
138
+
139
+| Command                       | Description                                                                                                  |
140
+| ----------------------------- | ------------------------------------------------------------------------------------------------------------ |
141
+| **functions:log**             | Read logs from deployed Cloud Functions.                                                                     |
142
+| **functions:list**            | List all deployed functions in your Firebase project.                                                        |
143
+| **functions:config:set**      | Store runtime configuration values for the current project's Cloud Functions.                                |
144
+| **functions:config:get**      | Retrieve existing configuration values for the current project's Cloud Functions.                            |
145
+| **functions:config:unset**    | Remove values from the current project's runtime configuration.                                              |
146
+| **functions:config:clone**    | Copy runtime configuration from one project environment to another.                                          |
147
+| **functions:secrets:set**     | Create or update a secret for use in Cloud Functions for Firebase.                                           |
148
+| **functions:secrets:get**     | Get metadata for secret and its versions.                                                                    |
149
+| **functions:secrets:access**  | Access secret value given secret and its version. Defaults to accessing the latest version.                  |
150
+| **functions:secrets:prune**   | Destroys unused secrets.                                                                                     |
151
+| **functions:secrets:destroy** | Destroy a secret. Defaults to destroying the latest version.                                                 |
152
+| **functions:delete**          | Delete one or more Cloud Functions by name or group name.                                                    |
153
+| **functions:shell**           | Locally emulate functions and start Node.js shell where these local functions can be invoked with test data. |
154
+
155
+### Hosting Commands
156
+
157
+| Command             | Description                                                                                                                                                          |
158
+| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
159
+| **hosting:disable** | Stop serving Firebase Hosting traffic for the active project. A "Site Not Found" message will be displayed at your project's Hosting URL after running this command. |
160
+
161
+### Remote Config Commands
162
+
163
+| Command                        | Description                                                                                                |
164
+| ------------------------------ | ---------------------------------------------------------------------------------------------------------- |
165
+| **remoteconfig:get**           | Get a Firebase project's Remote Config template.                                                           |
166
+| **remoteconfig:versions:list** | Get a list of the most recent Firebase Remote Config template versions that have been published.           |
167
+| **remoteconfig:rollback**      | Roll back a project's published Remote Config template to the version provided by `--version_number` flag. |
168
+
169
+Use `firebase:deploy --only remoteconfig` to update and publish a project's Firebase Remote Config template.
170
+
171
+## Authentication
172
+
173
+### General
174
+
175
+The Firebase CLI can use one of four authentication methods listed in descending priority:
176
+
177
+- **User Token** - **DEPRECATED: this authentication method will be removed in a future major version of `firebase-tools`; use a service account to authenticate instead** - provide an explicit long-lived Firebase user token generated from `firebase login:ci`. Note that these tokens are extremely sensitive long-lived credentials and are not the right option for most cases. Consider using service account authorization instead. The token can be set in one of two ways:
178
+  - Set the `--token` flag on any command, for example `firebase --token="<token>" projects:list`.
179
+  - Set the `FIREBASE_TOKEN` environment variable.
180
+- **Local Login** - run `firebase login` to log in to the CLI directly as yourself. The CLI will cache an authorized user credential on your machine.
181
+- **Service Account** - set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to point to the path of a JSON service account key file. For more details, see Google Cloud's [Getting started with authentication](https://cloud.google.com/docs/authentication/getting-started) guide.
182
+- **Application Default Credentials** - if you use the `gcloud` CLI and log in with `gcloud auth application-default login`, the Firebase CLI will use them if none of the above credentials are present.
183
+
184
+### Multiple Accounts
185
+
186
+By default `firebase login` sets a single global account for use on all projects.
187
+If you have multiple Google accounts which you use for Firebase projects you can
188
+authorize multiple accounts and use them on a per-project or per-command basis.
189
+
190
+To authorize an additonal account for use with the CLI, run `firebase login:add`.
191
+You can view the list of authorized accounts with `firebase login:list`.
192
+
193
+To set the default account for a specific Firebase project directory, run
194
+`firebase login:use` from within the directory and select the desired account.
195
+To check the default account for a directory, run `firebase login:list` and the
196
+default account for the current context will be listed first.
197
+
198
+To set the account for a specific command invocation, use the `--account` flag
199
+with any command. For example `firebase --account=user@domain.com deploy`. The
200
+specified account must have already been added to the Firebase CLI using
201
+`firebase login:add`.
202
+
203
+### Cloud Functions Emulator
204
+
205
+The Cloud Functions emulator is exposed through commands like `emulators:start`,
206
+`serve` and `functions:shell`. Emulated Cloud Functions run as independent `node` processes
207
+on your development machine which means they have their own credential discovery mechanism.
208
+
209
+By default these `node` processes are not able to discover credentials from `firebase login`.
210
+In order to provide a better development experience, when you are logged in to the CLI
211
+through `firebase login` we take the user credentials and construct a temporary credential
212
+that we pass into the emulator through `GOOGLE_APPLICATION_CREDENTIALS`. We **only** do this
213
+if you have not already set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable
214
+yourself.
215
+
216
+## Using behind a proxy
217
+
218
+The CLI supports HTTP(S) proxies via environment variables. To use a proxy, set the `HTTPS_PROXY`
219
+or `HTTP_PROXY` value in your environment to the URL of your proxy (e.g.
220
+`HTTP_PROXY=http://127.0.0.1:12345`).
221
+
222
+## Using with CI Systems
223
+
224
+The Firebase CLI requires a browser to complete authentication, but is fully
225
+compatible with CI and other headless environments.
226
+
227
+Complete the following steps to run Firebase commands in a CI environment. Find detailed instructions for each step in Google Cloud's [Getting started with authentication](https://cloud.google.com/docs/authentication/getting-started) guide.
228
+
229
+1. Create a service account and grant it the appropriate level of access to your project.
230
+1. Create a service account key (JSON file) for that service account.
231
+1. Store the key file in a secure, accessible way in your CI system.
232
+1. Set `GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json` in your CI system when running Firebase commands.
233
+
234
+To disable access for the service account, [find the service account](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts) for your project in the Google Cloud Console, and then either remove the key, or disable or delete the service account.
235
+
236
+## Using as a Module
237
+
238
+The Firebase CLI can also be used programmatically as a standard Node module.
239
+Each command is exposed as a function that takes positional arguments followed
240
+by an options object and returns a Promise.
241
+
242
+So if we run this command at our command line:
243
+
244
+```bash
245
+$ firebase --project="foo" apps:list ANDROID
246
+```
247
+
248
+That translates to the following in Node:
249
+
250
+```js
251
+const client = require("firebase-tools");
252
+client.apps
253
+  .list("ANDROID", { project: "foo" })
254
+  .then((data) => {
255
+    // ...
256
+  })
257
+  .catch((err) => {
258
+    // ...
259
+  });
260
+```
261
+
262
+The options object must be the very last argument and any unspecified
263
+positional argument will get the default value of `""`. The following
264
+two invocations are equivalent:
265
+
266
+```js
267
+const client = require("firebase-tools");
268
+
269
+// #1 - No arguments or options, defaults will be inferred
270
+client.apps.list();
271
+
272
+// #2 - Explicitly provide "" for all arguments and {} for options
273
+client.apps.list("", {});
274
+```
275
+
276
+Note: when used in a limited environment like Cloud Functions, not all `firebase-tools` commands will work programatically
277
+because they require access to a local filesystem.
278
+
279
+[gh-actions]: https://github.com/firebase/firebase-tools/actions
280
+[npm]: https://www.npmjs.com/package/firebase-tools
281
+[gh-actions-badge]: https://github.com/firebase/firebase-tools/workflows/CI%20Tests/badge.svg
282
+[node-badge]: https://img.shields.io/node/v/firebase-tools.svg
283
+[npm-badge]: https://img.shields.io/npm/v/firebase-tools.svg

+ 205
- 0
node_modules/firebase-tools/lib/accountExporter.js View File

@@ -0,0 +1,205 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.serialExportUsers = exports.validateOptions = void 0;
4
+const os = require("os");
5
+const path = require("path");
6
+const apiv2_1 = require("./apiv2");
7
+const error_1 = require("./error");
8
+const api_1 = require("./api");
9
+const utils = require("./utils");
10
+const apiClient = new apiv2_1.Client({
11
+    urlPrefix: api_1.googleOrigin,
12
+});
13
+const EXPORTED_JSON_KEYS = [
14
+    "localId",
15
+    "email",
16
+    "emailVerified",
17
+    "passwordHash",
18
+    "salt",
19
+    "displayName",
20
+    "photoUrl",
21
+    "lastLoginAt",
22
+    "createdAt",
23
+    "phoneNumber",
24
+    "disabled",
25
+    "customAttributes",
26
+];
27
+const EXPORTED_JSON_KEYS_RENAMING = {
28
+    lastLoginAt: "lastSignedInAt",
29
+};
30
+const EXPORTED_PROVIDER_USER_INFO_KEYS = [
31
+    "providerId",
32
+    "rawId",
33
+    "email",
34
+    "displayName",
35
+    "photoUrl",
36
+];
37
+const PROVIDER_ID_INDEX_MAP = new Map([
38
+    ["google.com", 7],
39
+    ["facebook.com", 11],
40
+    ["twitter.com", 15],
41
+    ["github.com", 19],
42
+]);
43
+function escapeComma(str) {
44
+    if (str.includes(",")) {
45
+        return `"${str}"`;
46
+    }
47
+    return str;
48
+}
49
+function convertToNormalBase64(data) {
50
+    return data.replace(/_/g, "/").replace(/-/g, "+");
51
+}
52
+function addProviderUserInfo(providerInfo, arr, startPos) {
53
+    arr[startPos] = providerInfo.rawId;
54
+    arr[startPos + 1] = providerInfo.email || "";
55
+    arr[startPos + 2] = escapeComma(providerInfo.displayName || "");
56
+    arr[startPos + 3] = providerInfo.photoUrl || "";
57
+}
58
+function transUserToArray(user) {
59
+    const arr = Array(27).fill("");
60
+    arr[0] = user.localId;
61
+    arr[1] = user.email || "";
62
+    arr[2] = user.emailVerified || false;
63
+    arr[3] = convertToNormalBase64(user.passwordHash || "");
64
+    arr[4] = convertToNormalBase64(user.salt || "");
65
+    arr[5] = escapeComma(user.displayName || "");
66
+    arr[6] = user.photoUrl || "";
67
+    for (let i = 0; i < (!user.providerUserInfo ? 0 : user.providerUserInfo.length); i++) {
68
+        const providerInfo = user.providerUserInfo[i];
69
+        if (providerInfo) {
70
+            const providerIndex = PROVIDER_ID_INDEX_MAP.get(providerInfo.providerId);
71
+            if (providerIndex) {
72
+                addProviderUserInfo(providerInfo, arr, providerIndex);
73
+            }
74
+        }
75
+    }
76
+    arr[23] = user.createdAt;
77
+    arr[24] = user.lastLoginAt;
78
+    arr[25] = user.phoneNumber;
79
+    arr[26] = user.disabled;
80
+    arr[27] = user.customAttributes;
81
+    return arr;
82
+}
83
+function transUserJson(user) {
84
+    const newUser = {};
85
+    const pickedUser = {};
86
+    for (const k of EXPORTED_JSON_KEYS) {
87
+        pickedUser[k] = user[k];
88
+    }
89
+    for (const [key, value] of Object.entries(pickedUser)) {
90
+        const newKey = EXPORTED_JSON_KEYS_RENAMING[key] || key;
91
+        newUser[newKey] = value;
92
+    }
93
+    if (newUser.passwordHash) {
94
+        newUser.passwordHash = convertToNormalBase64(newUser.passwordHash);
95
+    }
96
+    if (newUser.salt) {
97
+        newUser.salt = convertToNormalBase64(newUser.salt);
98
+    }
99
+    if (user.providerUserInfo) {
100
+        newUser.providerUserInfo = [];
101
+        for (const providerInfo of user.providerUserInfo) {
102
+            if (PROVIDER_ID_INDEX_MAP.has(providerInfo.providerId)) {
103
+                const picked = {};
104
+                for (const k of EXPORTED_PROVIDER_USER_INFO_KEYS) {
105
+                    picked[k] = providerInfo[k];
106
+                }
107
+                newUser.providerUserInfo.push(picked);
108
+            }
109
+        }
110
+    }
111
+    return newUser;
112
+}
113
+function validateOptions(options, fileName) {
114
+    const exportOptions = {};
115
+    if (fileName === undefined) {
116
+        throw new error_1.FirebaseError("Must specify data file");
117
+    }
118
+    const extName = path.extname(fileName.toLowerCase());
119
+    if (extName === ".csv") {
120
+        exportOptions.format = "csv";
121
+    }
122
+    else if (extName === ".json") {
123
+        exportOptions.format = "json";
124
+    }
125
+    else if (options.format) {
126
+        const format = options.format.toLowerCase();
127
+        if (format === "csv" || format === "json") {
128
+            exportOptions.format = format;
129
+        }
130
+        else {
131
+            throw new error_1.FirebaseError("Unsupported data file format, should be csv or json");
132
+        }
133
+    }
134
+    else {
135
+        throw new error_1.FirebaseError("Please specify data file format in file name, or use `format` parameter");
136
+    }
137
+    return exportOptions;
138
+}
139
+exports.validateOptions = validateOptions;
140
+function createWriteUsersToFile() {
141
+    let jsonSep = "";
142
+    return (userList, format, writeStream) => {
143
+        userList.map((user) => {
144
+            if (user.passwordHash && user.version !== 0) {
145
+                delete user.passwordHash;
146
+                delete user.salt;
147
+            }
148
+            if (format === "csv") {
149
+                writeStream.write(transUserToArray(user).join(",") + "," + os.EOL, "utf8");
150
+            }
151
+            else {
152
+                writeStream.write(jsonSep + JSON.stringify(transUserJson(user), null, 2), "utf8");
153
+                jsonSep = "," + os.EOL;
154
+            }
155
+        });
156
+    };
157
+}
158
+async function serialExportUsers(projectId, options) {
159
+    var _a;
160
+    if (!options.writeUsersToFile) {
161
+        options.writeUsersToFile = createWriteUsersToFile();
162
+    }
163
+    const postBody = {
164
+        targetProjectId: projectId,
165
+        maxResults: options.batchSize,
166
+    };
167
+    if (options.nextPageToken) {
168
+        postBody.nextPageToken = options.nextPageToken;
169
+    }
170
+    if (!options.timeoutRetryCount) {
171
+        options.timeoutRetryCount = 0;
172
+    }
173
+    try {
174
+        const ret = await apiClient.post("/identitytoolkit/v3/relyingparty/downloadAccount", postBody, {
175
+            skipLog: { resBody: true },
176
+        });
177
+        options.timeoutRetryCount = 0;
178
+        const userList = ret.body.users;
179
+        if (userList && userList.length > 0) {
180
+            options.writeUsersToFile(userList, options.format, options.writeStream);
181
+            utils.logSuccess("Exported " + userList.length + " account(s) successfully.");
182
+            if (!ret.body.nextPageToken) {
183
+                return;
184
+            }
185
+            options.nextPageToken = ret.body.nextPageToken;
186
+            return serialExportUsers(projectId, options);
187
+        }
188
+    }
189
+    catch (err) {
190
+        if (((_a = err.original) === null || _a === void 0 ? void 0 : _a.code) === "ETIMEDOUT") {
191
+            options.timeoutRetryCount++;
192
+            if (options.timeoutRetryCount > 5) {
193
+                return err;
194
+            }
195
+            return serialExportUsers(projectId, options);
196
+        }
197
+        if (err instanceof error_1.FirebaseError) {
198
+            throw err;
199
+        }
200
+        else {
201
+            throw new error_1.FirebaseError(`Failed to export accounts: ${err}`, { original: err });
202
+        }
203
+    }
204
+}
205
+exports.serialExportUsers = serialExportUsers;

+ 305
- 0
node_modules/firebase-tools/lib/accountImporter.js View File

@@ -0,0 +1,305 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.serialImportUsers = exports.validateUserJson = exports.validateOptions = exports.transArrayToUser = void 0;
4
+const clc = require("colorette");
5
+const apiv2_1 = require("./apiv2");
6
+const api_1 = require("./api");
7
+const logger_1 = require("./logger");
8
+const error_1 = require("./error");
9
+const utils = require("./utils");
10
+const apiClient = new apiv2_1.Client({
11
+    urlPrefix: api_1.googleOrigin,
12
+});
13
+const ALLOWED_JSON_KEYS = [
14
+    "localId",
15
+    "email",
16
+    "emailVerified",
17
+    "passwordHash",
18
+    "salt",
19
+    "displayName",
20
+    "photoUrl",
21
+    "createdAt",
22
+    "lastSignedInAt",
23
+    "providerUserInfo",
24
+    "phoneNumber",
25
+    "disabled",
26
+    "customAttributes",
27
+];
28
+const ALLOWED_JSON_KEYS_RENAMING = {
29
+    lastSignedInAt: "lastLoginAt",
30
+};
31
+const ALLOWED_PROVIDER_USER_INFO_KEYS = ["providerId", "rawId", "email", "displayName", "photoUrl"];
32
+const ALLOWED_PROVIDER_IDS = ["google.com", "facebook.com", "twitter.com", "github.com"];
33
+function isValidBase64(str) {
34
+    const expected = Buffer.from(str, "base64").toString("base64");
35
+    if (str.length < expected.length && !str.endsWith("=")) {
36
+        str += "=".repeat(expected.length - str.length);
37
+    }
38
+    return expected === str;
39
+}
40
+function toWebSafeBase64(data) {
41
+    return data.replace(/\//g, "_").replace(/\+/g, "-");
42
+}
43
+function addProviderUserInfo(user, providerId, arr) {
44
+    if (arr[0]) {
45
+        user.providerUserInfo.push({
46
+            providerId: providerId,
47
+            rawId: arr[0],
48
+            email: arr[1],
49
+            displayName: arr[2],
50
+            photoUrl: arr[3],
51
+        });
52
+    }
53
+}
54
+function genUploadAccountPostBody(projectId, accounts, hashOptions) {
55
+    const postBody = {
56
+        users: accounts.map((account) => {
57
+            if (account.passwordHash) {
58
+                account.passwordHash = toWebSafeBase64(account.passwordHash);
59
+            }
60
+            if (account.salt) {
61
+                account.salt = toWebSafeBase64(account.salt);
62
+            }
63
+            for (const [key, value] of Object.entries(ALLOWED_JSON_KEYS_RENAMING)) {
64
+                if (account[key]) {
65
+                    account[value] = account[key];
66
+                    delete account[key];
67
+                }
68
+            }
69
+            return account;
70
+        }),
71
+    };
72
+    if (hashOptions.hashAlgo) {
73
+        postBody.hashAlgorithm = hashOptions.hashAlgo;
74
+    }
75
+    if (hashOptions.hashKey) {
76
+        postBody.signerKey = toWebSafeBase64(hashOptions.hashKey);
77
+    }
78
+    if (hashOptions.saltSeparator) {
79
+        postBody.saltSeparator = toWebSafeBase64(hashOptions.saltSeparator);
80
+    }
81
+    if (hashOptions.rounds) {
82
+        postBody.rounds = hashOptions.rounds;
83
+    }
84
+    if (hashOptions.memCost) {
85
+        postBody.memoryCost = hashOptions.memCost;
86
+    }
87
+    if (hashOptions.cpuMemCost) {
88
+        postBody.cpuMemCost = hashOptions.cpuMemCost;
89
+    }
90
+    if (hashOptions.parallelization) {
91
+        postBody.parallelization = hashOptions.parallelization;
92
+    }
93
+    if (hashOptions.blockSize) {
94
+        postBody.blockSize = hashOptions.blockSize;
95
+    }
96
+    if (hashOptions.dkLen) {
97
+        postBody.dkLen = hashOptions.dkLen;
98
+    }
99
+    if (hashOptions.passwordHashOrder) {
100
+        postBody.passwordHashOrder = hashOptions.passwordHashOrder;
101
+    }
102
+    postBody.targetProjectId = projectId;
103
+    return postBody;
104
+}
105
+function transArrayToUser(arr) {
106
+    const user = {
107
+        localId: arr[0],
108
+        email: arr[1],
109
+        emailVerified: arr[2] === "true",
110
+        passwordHash: arr[3],
111
+        salt: arr[4],
112
+        displayName: arr[5],
113
+        photoUrl: arr[6],
114
+        createdAt: arr[23],
115
+        lastLoginAt: arr[24],
116
+        phoneNumber: arr[25],
117
+        providerUserInfo: [],
118
+        disabled: arr[26],
119
+        customAttributes: arr[27],
120
+    };
121
+    addProviderUserInfo(user, "google.com", arr.slice(7, 11));
122
+    addProviderUserInfo(user, "facebook.com", arr.slice(11, 15));
123
+    addProviderUserInfo(user, "twitter.com", arr.slice(15, 19));
124
+    addProviderUserInfo(user, "github.com", arr.slice(19, 23));
125
+    if (user.passwordHash && !isValidBase64(user.passwordHash)) {
126
+        return {
127
+            error: "Password hash should be base64 encoded.",
128
+        };
129
+    }
130
+    if (user.salt && !isValidBase64(user.salt)) {
131
+        return {
132
+            error: "Password salt should be base64 encoded.",
133
+        };
134
+    }
135
+    return user;
136
+}
137
+exports.transArrayToUser = transArrayToUser;
138
+function validateOptions(options) {
139
+    const hashOptions = validateRequiredParameters(options);
140
+    if (!hashOptions.valid) {
141
+        return hashOptions;
142
+    }
143
+    const hashInputOrder = options.hashInputOrder ? options.hashInputOrder.toUpperCase() : undefined;
144
+    if (hashInputOrder) {
145
+        if (hashInputOrder !== "SALT_FIRST" && hashInputOrder !== "PASSWORD_FIRST") {
146
+            throw new error_1.FirebaseError("Unknown password hash order flag");
147
+        }
148
+        else {
149
+            hashOptions["passwordHashOrder"] =
150
+                hashInputOrder === "SALT_FIRST" ? "SALT_AND_PASSWORD" : "PASSWORD_AND_SALT";
151
+        }
152
+    }
153
+    return hashOptions;
154
+}
155
+exports.validateOptions = validateOptions;
156
+function validateRequiredParameters(options) {
157
+    if (!options.hashAlgo) {
158
+        utils.logWarning("No hash algorithm specified. Password users cannot be imported.");
159
+        return { valid: true };
160
+    }
161
+    const hashAlgo = options.hashAlgo.toUpperCase();
162
+    let roundsNum;
163
+    switch (hashAlgo) {
164
+        case "HMAC_SHA512":
165
+        case "HMAC_SHA256":
166
+        case "HMAC_SHA1":
167
+        case "HMAC_MD5":
168
+            if (!options.hashKey || options.hashKey === "") {
169
+                throw new error_1.FirebaseError("Must provide hash key(base64 encoded) for hash algorithm " + options.hashAlgo);
170
+            }
171
+            return { hashAlgo: hashAlgo, hashKey: options.hashKey, valid: true };
172
+        case "MD5":
173
+        case "SHA1":
174
+        case "SHA256":
175
+        case "SHA512":
176
+            roundsNum = parseInt(options.rounds, 10);
177
+            const minRounds = hashAlgo === "MD5" ? 0 : 1;
178
+            if (isNaN(roundsNum) || roundsNum < minRounds || roundsNum > 8192) {
179
+                throw new error_1.FirebaseError(`Must provide valid rounds(${minRounds}..8192) for hash algorithm ${options.hashAlgo}`);
180
+            }
181
+            return { hashAlgo: hashAlgo, rounds: options.rounds, valid: true };
182
+        case "PBKDF_SHA1":
183
+        case "PBKDF2_SHA256":
184
+            roundsNum = parseInt(options.rounds, 10);
185
+            if (isNaN(roundsNum) || roundsNum < 0 || roundsNum > 120000) {
186
+                throw new error_1.FirebaseError("Must provide valid rounds(0..120000) for hash algorithm " + options.hashAlgo);
187
+            }
188
+            return { hashAlgo: hashAlgo, rounds: options.rounds, valid: true };
189
+        case "SCRYPT":
190
+            if (!options.hashKey || options.hashKey === "") {
191
+                throw new error_1.FirebaseError("Must provide hash key(base64 encoded) for hash algorithm " + options.hashAlgo);
192
+            }
193
+            roundsNum = parseInt(options.rounds, 10);
194
+            if (isNaN(roundsNum) || roundsNum <= 0 || roundsNum > 8) {
195
+                throw new error_1.FirebaseError("Must provide valid rounds(1..8) for hash algorithm " + options.hashAlgo);
196
+            }
197
+            const memCost = parseInt(options.memCost, 10);
198
+            if (isNaN(memCost) || memCost <= 0 || memCost > 14) {
199
+                throw new error_1.FirebaseError("Must provide valid memory cost(1..14) for hash algorithm " + options.hashAlgo);
200
+            }
201
+            let saltSeparator = "";
202
+            if (options.saltSeparator) {
203
+                saltSeparator = options.saltSeparator;
204
+            }
205
+            return {
206
+                hashAlgo: hashAlgo,
207
+                hashKey: options.hashKey,
208
+                saltSeparator: saltSeparator,
209
+                rounds: options.rounds,
210
+                memCost: options.memCost,
211
+                valid: true,
212
+            };
213
+        case "BCRYPT":
214
+            return { hashAlgo: hashAlgo, valid: true };
215
+        case "STANDARD_SCRYPT":
216
+            const cpuMemCost = parseInt(options.memCost, 10);
217
+            const parallelization = parseInt(options.parallelization, 10);
218
+            const blockSize = parseInt(options.blockSize, 10);
219
+            const dkLen = parseInt(options.dkLen, 10);
220
+            return {
221
+                hashAlgo: hashAlgo,
222
+                valid: true,
223
+                cpuMemCost: cpuMemCost,
224
+                parallelization: parallelization,
225
+                blockSize: blockSize,
226
+                dkLen: dkLen,
227
+            };
228
+        default:
229
+            throw new error_1.FirebaseError("Unsupported hash algorithm " + clc.bold(options.hashAlgo));
230
+    }
231
+}
232
+function validateProviderUserInfo(providerUserInfo) {
233
+    if (!ALLOWED_PROVIDER_IDS.includes(providerUserInfo.providerId)) {
234
+        return {
235
+            error: JSON.stringify(providerUserInfo, null, 2) + " has unsupported providerId",
236
+        };
237
+    }
238
+    const keydiff = Object.keys(providerUserInfo).filter((k) => !ALLOWED_PROVIDER_USER_INFO_KEYS.includes(k));
239
+    if (keydiff.length) {
240
+        return {
241
+            error: JSON.stringify(providerUserInfo, null, 2) + " has unsupported keys: " + keydiff.join(","),
242
+        };
243
+    }
244
+    return {};
245
+}
246
+function validateUserJson(userJson) {
247
+    const keydiff = Object.keys(userJson).filter((k) => !ALLOWED_JSON_KEYS.includes(k));
248
+    if (keydiff.length) {
249
+        return {
250
+            error: JSON.stringify(userJson, null, 2) + " has unsupported keys: " + keydiff.join(","),
251
+        };
252
+    }
253
+    if (userJson.providerUserInfo) {
254
+        for (let i = 0; i < userJson.providerUserInfo.length; i++) {
255
+            const res = validateProviderUserInfo(userJson.providerUserInfo[i]);
256
+            if (res.error) {
257
+                return res;
258
+            }
259
+        }
260
+    }
261
+    const badFormat = JSON.stringify(userJson, null, 2) + " has invalid data format: ";
262
+    if (userJson.passwordHash && !isValidBase64(userJson.passwordHash)) {
263
+        return {
264
+            error: badFormat + "Password hash should be base64 encoded.",
265
+        };
266
+    }
267
+    if (userJson.salt && !isValidBase64(userJson.salt)) {
268
+        return {
269
+            error: badFormat + "Password salt should be base64 encoded.",
270
+        };
271
+    }
272
+    return {};
273
+}
274
+exports.validateUserJson = validateUserJson;
275
+async function sendRequest(projectId, userList, hashOptions) {
276
+    logger_1.logger.info("Starting importing " + userList.length + " account(s).");
277
+    const postData = genUploadAccountPostBody(projectId, userList, hashOptions);
278
+    return apiClient
279
+        .post("/identitytoolkit/v3/relyingparty/uploadAccount", postData, {
280
+        skipLog: { body: true },
281
+    })
282
+        .then((ret) => {
283
+        if (ret.body.error) {
284
+            logger_1.logger.info("Encountered problems while importing accounts. Details:");
285
+            logger_1.logger.info(ret.body.error.map((rawInfo) => {
286
+                return {
287
+                    account: JSON.stringify(userList[parseInt(rawInfo.index, 10)], null, 2),
288
+                    reason: rawInfo.message,
289
+                };
290
+            }));
291
+        }
292
+        else {
293
+            utils.logSuccess("Imported successfully.");
294
+        }
295
+        logger_1.logger.info();
296
+    });
297
+}
298
+function serialImportUsers(projectId, hashOptions, userListArr, index) {
299
+    return sendRequest(projectId, userListArr[index], hashOptions).then(() => {
300
+        if (index < userListArr.length - 1) {
301
+            return serialImportUsers(projectId, hashOptions, userListArr, index + 1);
302
+        }
303
+    });
304
+}
305
+exports.serialImportUsers = serialImportUsers;

+ 81
- 0
node_modules/firebase-tools/lib/api.js View File

@@ -0,0 +1,81 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.githubClientSecret = exports.githubClientId = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.deployOrigin = exports.consoleOrigin = exports.authOrigin = exports.appengineOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
4
+exports.setScopes = exports.getScopes = void 0;
5
+const constants_1 = require("./emulator/constants");
6
+const logger_1 = require("./logger");
7
+const scopes = require("./scopes");
8
+const utils = require("./utils");
9
+let commandScopes = new Set();
10
+exports.authProxyOrigin = utils.envOverride("FIREBASE_AUTHPROXY_URL", "https://auth.firebase.tools");
11
+exports.clientId = utils.envOverride("FIREBASE_CLIENT_ID", "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com");
12
+exports.clientSecret = utils.envOverride("FIREBASE_CLIENT_SECRET", "j9iVZfS8kkCEFUPaAeJV0sAi");
13
+exports.cloudbillingOrigin = utils.envOverride("FIREBASE_CLOUDBILLING_URL", "https://cloudbilling.googleapis.com");
14
+exports.cloudloggingOrigin = utils.envOverride("FIREBASE_CLOUDLOGGING_URL", "https://logging.googleapis.com");
15
+exports.cloudMonitoringOrigin = utils.envOverride("CLOUD_MONITORING_URL", "https://monitoring.googleapis.com");
16
+exports.containerRegistryDomain = utils.envOverride("CONTAINER_REGISTRY_DOMAIN", "gcr.io");
17
+exports.artifactRegistryDomain = utils.envOverride("ARTIFACT_REGISTRY_DOMAIN", "https://artifactregistry.googleapis.com");
18
+exports.appDistributionOrigin = utils.envOverride("FIREBASE_APP_DISTRIBUTION_URL", "https://firebaseappdistribution.googleapis.com");
19
+exports.appengineOrigin = utils.envOverride("FIREBASE_APPENGINE_URL", "https://appengine.googleapis.com");
20
+exports.authOrigin = utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com");
21
+exports.consoleOrigin = utils.envOverride("FIREBASE_CONSOLE_URL", "https://console.firebase.google.com");
22
+exports.deployOrigin = utils.envOverride("FIREBASE_DEPLOY_URL", utils.envOverride("FIREBASE_UPLOAD_URL", "https://deploy.firebase.com"));
23
+exports.dynamicLinksOrigin = utils.envOverride("FIREBASE_DYNAMIC_LINKS_URL", "https://firebasedynamiclinks.googleapis.com");
24
+exports.dynamicLinksKey = utils.envOverride("FIREBASE_DYNAMIC_LINKS_KEY", "AIzaSyB6PtY5vuiSB8MNgt20mQffkOlunZnHYiQ");
25
+exports.eventarcOrigin = utils.envOverride("EVENTARC_URL", "https://eventarc.googleapis.com");
26
+exports.firebaseApiOrigin = utils.envOverride("FIREBASE_API_URL", "https://firebase.googleapis.com");
27
+exports.firebaseExtensionsRegistryOrigin = utils.envOverride("FIREBASE_EXT_REGISTRY_ORIGIN", "https://extensions-registry.firebaseapp.com");
28
+exports.firedataOrigin = utils.envOverride("FIREBASE_FIREDATA_URL", "https://mobilesdk-pa.googleapis.com");
29
+exports.firestoreOriginOrEmulator = utils.envOverride(constants_1.Constants.FIRESTORE_EMULATOR_HOST, utils.envOverride("FIRESTORE_URL", "https://firestore.googleapis.com"), (val) => {
30
+    if (val.startsWith("http")) {
31
+        return val;
32
+    }
33
+    return `http://${val}`;
34
+});
35
+exports.firestoreOrigin = utils.envOverride("FIRESTORE_URL", "https://firestore.googleapis.com");
36
+exports.functionsOrigin = utils.envOverride("FIREBASE_FUNCTIONS_URL", "https://cloudfunctions.googleapis.com");
37
+exports.functionsV2Origin = utils.envOverride("FIREBASE_FUNCTIONS_V2_URL", "https://cloudfunctions.googleapis.com");
38
+exports.runOrigin = utils.envOverride("CLOUD_RUN_URL", "https://run.googleapis.com");
39
+exports.functionsDefaultRegion = utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "us-central1");
40
+exports.cloudschedulerOrigin = utils.envOverride("FIREBASE_CLOUDSCHEDULER_URL", "https://cloudscheduler.googleapis.com");
41
+exports.cloudTasksOrigin = utils.envOverride("FIREBASE_CLOUD_TAKS_URL", "https://cloudtasks.googleapis.com");
42
+exports.pubsubOrigin = utils.envOverride("FIREBASE_PUBSUB_URL", "https://pubsub.googleapis.com");
43
+exports.googleOrigin = utils.envOverride("FIREBASE_TOKEN_URL", utils.envOverride("FIREBASE_GOOGLE_URL", "https://www.googleapis.com"));
44
+exports.hostingOrigin = utils.envOverride("FIREBASE_HOSTING_URL", "https://web.app");
45
+exports.identityOrigin = utils.envOverride("FIREBASE_IDENTITY_URL", "https://identitytoolkit.googleapis.com");
46
+exports.iamOrigin = utils.envOverride("FIREBASE_IAM_URL", "https://iam.googleapis.com");
47
+exports.extensionsOrigin = utils.envOverride("FIREBASE_EXT_URL", "https://firebaseextensions.googleapis.com");
48
+exports.realtimeOrigin = utils.envOverride("FIREBASE_REALTIME_URL", "https://firebaseio.com");
49
+exports.rtdbManagementOrigin = utils.envOverride("FIREBASE_RTDB_MANAGEMENT_URL", "https://firebasedatabase.googleapis.com");
50
+exports.rtdbMetadataOrigin = utils.envOverride("FIREBASE_RTDB_METADATA_URL", "https://metadata-dot-firebase-prod.appspot.com");
51
+exports.remoteConfigApiOrigin = utils.envOverride("FIREBASE_REMOTE_CONFIG_URL", "https://firebaseremoteconfig.googleapis.com");
52
+exports.resourceManagerOrigin = utils.envOverride("FIREBASE_RESOURCEMANAGER_URL", "https://cloudresourcemanager.googleapis.com");
53
+exports.rulesOrigin = utils.envOverride("FIREBASE_RULES_URL", "https://firebaserules.googleapis.com");
54
+exports.runtimeconfigOrigin = utils.envOverride("FIREBASE_RUNTIMECONFIG_URL", "https://runtimeconfig.googleapis.com");
55
+exports.storageOrigin = utils.envOverride("FIREBASE_STORAGE_URL", "https://storage.googleapis.com");
56
+exports.firebaseStorageOrigin = utils.envOverride("FIREBASE_FIREBASESTORAGE_URL", "https://firebasestorage.googleapis.com");
57
+exports.hostingApiOrigin = utils.envOverride("FIREBASE_HOSTING_API_URL", "https://firebasehosting.googleapis.com");
58
+exports.cloudRunApiOrigin = utils.envOverride("CLOUD_RUN_API_URL", "https://run.googleapis.com");
59
+exports.serviceUsageOrigin = utils.envOverride("FIREBASE_SERVICE_USAGE_URL", "https://serviceusage.googleapis.com");
60
+exports.githubOrigin = utils.envOverride("GITHUB_URL", "https://github.com");
61
+exports.githubApiOrigin = utils.envOverride("GITHUB_API_URL", "https://api.github.com");
62
+exports.secretManagerOrigin = utils.envOverride("CLOUD_SECRET_MANAGER_URL", "https://secretmanager.googleapis.com");
63
+exports.githubClientId = utils.envOverride("GITHUB_CLIENT_ID", "89cf50f02ac6aaed3484");
64
+exports.githubClientSecret = utils.envOverride("GITHUB_CLIENT_SECRET", "3330d14abc895d9a74d5f17cd7a00711fa2c5bf0");
65
+function getScopes() {
66
+    return Array.from(commandScopes);
67
+}
68
+exports.getScopes = getScopes;
69
+function setScopes(sps = []) {
70
+    commandScopes = new Set([
71
+        scopes.EMAIL,
72
+        scopes.OPENID,
73
+        scopes.CLOUD_PROJECTS_READONLY,
74
+        scopes.FIREBASE_PLATFORM,
75
+    ]);
76
+    for (const s of sps) {
77
+        commandScopes.add(s);
78
+    }
79
+    logger_1.logger.debug("> command requires scopes:", Array.from(commandScopes));
80
+}
81
+exports.setScopes = setScopes;

+ 356
- 0
node_modules/firebase-tools/lib/apiv2.js View File

@@ -0,0 +1,356 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.Client = exports.setAccessToken = exports.setRefreshToken = void 0;
4
+const url_1 = require("url");
5
+const stream_1 = require("stream");
6
+const ProxyAgent = require("proxy-agent");
7
+const retry = require("retry");
8
+const abort_controller_1 = require("abort-controller");
9
+const node_fetch_1 = require("node-fetch");
10
+const util_1 = require("util");
11
+const auth = require("./auth");
12
+const error_1 = require("./error");
13
+const logger_1 = require("./logger");
14
+const responseToError_1 = require("./responseToError");
15
+const FormData = require("form-data");
16
+const pkg = require("../package.json");
17
+const CLI_VERSION = pkg.version;
18
+const GOOG_QUOTA_USER = "x-goog-quota-user";
19
+let accessToken = "";
20
+let refreshToken = "";
21
+function setRefreshToken(token = "") {
22
+    refreshToken = token;
23
+}
24
+exports.setRefreshToken = setRefreshToken;
25
+function setAccessToken(token = "") {
26
+    accessToken = token;
27
+}
28
+exports.setAccessToken = setAccessToken;
29
+function proxyURIFromEnv() {
30
+    return (process.env.HTTPS_PROXY ||
31
+        process.env.https_proxy ||
32
+        process.env.HTTP_PROXY ||
33
+        process.env.http_proxy ||
34
+        undefined);
35
+}
36
+class Client {
37
+    constructor(opts) {
38
+        this.opts = opts;
39
+        if (this.opts.auth === undefined) {
40
+            this.opts.auth = true;
41
+        }
42
+        if (this.opts.urlPrefix.endsWith("/")) {
43
+            this.opts.urlPrefix = this.opts.urlPrefix.substring(0, this.opts.urlPrefix.length - 1);
44
+        }
45
+    }
46
+    get(path, options = {}) {
47
+        const reqOptions = Object.assign(options, {
48
+            method: "GET",
49
+            path,
50
+        });
51
+        return this.request(reqOptions);
52
+    }
53
+    post(path, json, options = {}) {
54
+        const reqOptions = Object.assign(options, {
55
+            method: "POST",
56
+            path,
57
+            body: json,
58
+        });
59
+        return this.request(reqOptions);
60
+    }
61
+    patch(path, json, options = {}) {
62
+        const reqOptions = Object.assign(options, {
63
+            method: "PATCH",
64
+            path,
65
+            body: json,
66
+        });
67
+        return this.request(reqOptions);
68
+    }
69
+    put(path, json, options = {}) {
70
+        const reqOptions = Object.assign(options, {
71
+            method: "PUT",
72
+            path,
73
+            body: json,
74
+        });
75
+        return this.request(reqOptions);
76
+    }
77
+    delete(path, options = {}) {
78
+        const reqOptions = Object.assign(options, {
79
+            method: "DELETE",
80
+            path,
81
+        });
82
+        return this.request(reqOptions);
83
+    }
84
+    async request(reqOptions) {
85
+        if (!reqOptions.responseType) {
86
+            reqOptions.responseType = "json";
87
+        }
88
+        if (reqOptions.responseType === "stream" && !reqOptions.resolveOnHTTPError) {
89
+            throw new error_1.FirebaseError("apiv2 will not handle HTTP errors while streaming and you must set `resolveOnHTTPError` and check for res.status >= 400 on your own", { exit: 2 });
90
+        }
91
+        let internalReqOptions = Object.assign(reqOptions, {
92
+            headers: new node_fetch_1.Headers(reqOptions.headers),
93
+        });
94
+        internalReqOptions = this.addRequestHeaders(internalReqOptions);
95
+        if (this.opts.auth) {
96
+            internalReqOptions = await this.addAuthHeader(internalReqOptions);
97
+        }
98
+        try {
99
+            return await this.doRequest(internalReqOptions);
100
+        }
101
+        catch (thrown) {
102
+            if (thrown instanceof error_1.FirebaseError) {
103
+                throw thrown;
104
+            }
105
+            let err;
106
+            if (thrown instanceof Error) {
107
+                err = thrown;
108
+            }
109
+            else {
110
+                err = new Error(thrown);
111
+            }
112
+            throw new error_1.FirebaseError(`Failed to make request: ${err.message}`, { original: err });
113
+        }
114
+    }
115
+    addRequestHeaders(reqOptions) {
116
+        if (!reqOptions.headers) {
117
+            reqOptions.headers = new node_fetch_1.Headers();
118
+        }
119
+        reqOptions.headers.set("Connection", "keep-alive");
120
+        if (!reqOptions.headers.has("User-Agent")) {
121
+            reqOptions.headers.set("User-Agent", `FirebaseCLI/${CLI_VERSION}`);
122
+        }
123
+        reqOptions.headers.set("X-Client-Version", `FirebaseCLI/${CLI_VERSION}`);
124
+        if (!reqOptions.headers.has("Content-Type")) {
125
+            if (reqOptions.responseType === "json") {
126
+                reqOptions.headers.set("Content-Type", "application/json");
127
+            }
128
+        }
129
+        return reqOptions;
130
+    }
131
+    async addAuthHeader(reqOptions) {
132
+        if (!reqOptions.headers) {
133
+            reqOptions.headers = new node_fetch_1.Headers();
134
+        }
135
+        let token;
136
+        if (isLocalInsecureRequest(this.opts.urlPrefix)) {
137
+            token = "owner";
138
+        }
139
+        else {
140
+            token = await this.getAccessToken();
141
+        }
142
+        reqOptions.headers.set("Authorization", `Bearer ${token}`);
143
+        return reqOptions;
144
+    }
145
+    async getAccessToken() {
146
+        if (accessToken) {
147
+            return accessToken;
148
+        }
149
+        const data = (await auth.getAccessToken(refreshToken, []));
150
+        return data.access_token;
151
+    }
152
+    requestURL(options) {
153
+        const versionPath = this.opts.apiVersion ? `/${this.opts.apiVersion}` : "";
154
+        return `${this.opts.urlPrefix}${versionPath}${options.path}`;
155
+    }
156
+    async doRequest(options) {
157
+        var _a;
158
+        if (!options.path.startsWith("/")) {
159
+            options.path = "/" + options.path;
160
+        }
161
+        let fetchURL = this.requestURL(options);
162
+        if (options.queryParams) {
163
+            if (!(options.queryParams instanceof url_1.URLSearchParams)) {
164
+                const sp = new url_1.URLSearchParams();
165
+                for (const key of Object.keys(options.queryParams)) {
166
+                    const value = options.queryParams[key];
167
+                    sp.append(key, `${value}`);
168
+                }
169
+                options.queryParams = sp;
170
+            }
171
+            const queryString = options.queryParams.toString();
172
+            if (queryString) {
173
+                fetchURL += `?${queryString}`;
174
+            }
175
+        }
176
+        const fetchOptions = {
177
+            headers: options.headers,
178
+            method: options.method,
179
+            redirect: options.redirect,
180
+            compress: options.compress,
181
+        };
182
+        if (this.opts.proxy) {
183
+            fetchOptions.agent = new ProxyAgent(this.opts.proxy);
184
+        }
185
+        const envProxy = proxyURIFromEnv();
186
+        if (envProxy) {
187
+            fetchOptions.agent = new ProxyAgent(envProxy);
188
+        }
189
+        if (options.signal) {
190
+            fetchOptions.signal = options.signal;
191
+        }
192
+        let reqTimeout;
193
+        if (options.timeout) {
194
+            const controller = new abort_controller_1.default();
195
+            reqTimeout = setTimeout(() => {
196
+                controller.abort();
197
+            }, options.timeout);
198
+            fetchOptions.signal = controller.signal;
199
+        }
200
+        if (typeof options.body === "string" || isStream(options.body)) {
201
+            fetchOptions.body = options.body;
202
+        }
203
+        else if (options.body !== undefined) {
204
+            fetchOptions.body = JSON.stringify(options.body);
205
+        }
206
+        const operationOptions = {
207
+            retries: ((_a = options.retryCodes) === null || _a === void 0 ? void 0 : _a.length) ? 1 : 2,
208
+            minTimeout: 1 * 1000,
209
+            maxTimeout: 5 * 1000,
210
+        };
211
+        if (typeof options.retries === "number") {
212
+            operationOptions.retries = options.retries;
213
+        }
214
+        if (typeof options.retryMinTimeout === "number") {
215
+            operationOptions.minTimeout = options.retryMinTimeout;
216
+        }
217
+        if (typeof options.retryMaxTimeout === "number") {
218
+            operationOptions.maxTimeout = options.retryMaxTimeout;
219
+        }
220
+        const operation = retry.operation(operationOptions);
221
+        return await new Promise((resolve, reject) => {
222
+            operation.attempt(async (currentAttempt) => {
223
+                var _a;
224
+                let res;
225
+                let body;
226
+                try {
227
+                    if (currentAttempt > 1) {
228
+                        logger_1.logger.debug(`*** [apiv2] Attempting the request again. Attempt number ${currentAttempt}`);
229
+                    }
230
+                    this.logRequest(options);
231
+                    try {
232
+                        res = await (0, node_fetch_1.default)(fetchURL, fetchOptions);
233
+                    }
234
+                    catch (thrown) {
235
+                        const err = thrown instanceof Error ? thrown : new Error(thrown);
236
+                        const isAbortError = err.name.includes("AbortError");
237
+                        if (isAbortError) {
238
+                            throw new error_1.FirebaseError(`Timeout reached making request to ${fetchURL}`, {
239
+                                original: err,
240
+                            });
241
+                        }
242
+                        throw new error_1.FirebaseError(`Failed to make request to ${fetchURL}`, { original: err });
243
+                    }
244
+                    finally {
245
+                        if (reqTimeout) {
246
+                            clearTimeout(reqTimeout);
247
+                        }
248
+                    }
249
+                    if (options.responseType === "json") {
250
+                        const text = await res.text();
251
+                        if (!text.length) {
252
+                            body = undefined;
253
+                        }
254
+                        else {
255
+                            try {
256
+                                body = JSON.parse(text);
257
+                            }
258
+                            catch (err) {
259
+                                this.logResponse(res, text, options);
260
+                                throw new error_1.FirebaseError(`Unable to parse JSON: ${err}`);
261
+                            }
262
+                        }
263
+                    }
264
+                    else if (options.responseType === "xml") {
265
+                        body = (await res.text());
266
+                    }
267
+                    else if (options.responseType === "stream") {
268
+                        body = res.body;
269
+                    }
270
+                    else {
271
+                        throw new error_1.FirebaseError(`Unable to interpret response. Please set responseType.`, {
272
+                            exit: 2,
273
+                        });
274
+                    }
275
+                }
276
+                catch (err) {
277
+                    return err instanceof error_1.FirebaseError ? reject(err) : reject(new error_1.FirebaseError(`${err}`));
278
+                }
279
+                this.logResponse(res, body, options);
280
+                if (res.status >= 400) {
281
+                    if ((_a = options.retryCodes) === null || _a === void 0 ? void 0 : _a.includes(res.status)) {
282
+                        const err = (0, responseToError_1.responseToError)({ statusCode: res.status }, body) || undefined;
283
+                        if (operation.retry(err)) {
284
+                            return;
285
+                        }
286
+                    }
287
+                    if (!options.resolveOnHTTPError) {
288
+                        return reject((0, responseToError_1.responseToError)({ statusCode: res.status }, body));
289
+                    }
290
+                }
291
+                resolve({
292
+                    status: res.status,
293
+                    response: res,
294
+                    body,
295
+                });
296
+            });
297
+        });
298
+    }
299
+    logRequest(options) {
300
+        var _a, _b;
301
+        let queryParamsLog = "[none]";
302
+        if (options.queryParams) {
303
+            queryParamsLog = "[omitted]";
304
+            if (!((_a = options.skipLog) === null || _a === void 0 ? void 0 : _a.queryParams)) {
305
+                queryParamsLog =
306
+                    options.queryParams instanceof url_1.URLSearchParams
307
+                        ? options.queryParams.toString()
308
+                        : JSON.stringify(options.queryParams);
309
+            }
310
+        }
311
+        const logURL = this.requestURL(options);
312
+        logger_1.logger.debug(`>>> [apiv2][query] ${options.method} ${logURL} ${queryParamsLog}`);
313
+        const headers = options.headers;
314
+        if (headers && headers.has(GOOG_QUOTA_USER)) {
315
+            logger_1.logger.debug(`>>> [apiv2][(partial)header] ${options.method} ${logURL} x-goog-quota-user=${headers.get(GOOG_QUOTA_USER) || ""}`);
316
+        }
317
+        if (options.body !== undefined) {
318
+            let logBody = "[omitted]";
319
+            if (!((_b = options.skipLog) === null || _b === void 0 ? void 0 : _b.body)) {
320
+                logBody = bodyToString(options.body);
321
+            }
322
+            logger_1.logger.debug(`>>> [apiv2][body] ${options.method} ${logURL} ${logBody}`);
323
+        }
324
+    }
325
+    logResponse(res, body, options) {
326
+        var _a;
327
+        const logURL = this.requestURL(options);
328
+        logger_1.logger.debug(`<<< [apiv2][status] ${options.method} ${logURL} ${res.status}`);
329
+        let logBody = "[omitted]";
330
+        if (!((_a = options.skipLog) === null || _a === void 0 ? void 0 : _a.resBody)) {
331
+            logBody = bodyToString(body);
332
+        }
333
+        logger_1.logger.debug(`<<< [apiv2][body] ${options.method} ${logURL} ${logBody}`);
334
+    }
335
+}
336
+exports.Client = Client;
337
+function isLocalInsecureRequest(urlPrefix) {
338
+    const u = new url_1.URL(urlPrefix);
339
+    return u.protocol === "http:";
340
+}
341
+function bodyToString(body) {
342
+    if (isStream(body)) {
343
+        return "[stream]";
344
+    }
345
+    else {
346
+        try {
347
+            return JSON.stringify(body);
348
+        }
349
+        catch (_) {
350
+            return util_1.default.inspect(body);
351
+        }
352
+    }
353
+}
354
+function isStream(o) {
355
+    return o instanceof stream_1.Readable || o instanceof FormData;
356
+}

+ 141
- 0
node_modules/firebase-tools/lib/appdistribution/client.js View File

@@ -0,0 +1,141 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.AppDistributionClient = exports.UploadReleaseResult = exports.IntegrationState = void 0;
4
+const utils = require("../utils");
5
+const operationPoller = require("../operation-poller");
6
+const error_1 = require("../error");
7
+const apiv2_1 = require("../apiv2");
8
+const api_1 = require("../api");
9
+var IntegrationState;
10
+(function (IntegrationState) {
11
+    IntegrationState["AAB_INTEGRATION_STATE_UNSPECIFIED"] = "AAB_INTEGRATION_STATE_UNSPECIFIED";
12
+    IntegrationState["INTEGRATED"] = "INTEGRATED";
13
+    IntegrationState["PLAY_ACCOUNT_NOT_LINKED"] = "PLAY_ACCOUNT_NOT_LINKED";
14
+    IntegrationState["NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT"] = "NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT";
15
+    IntegrationState["APP_NOT_PUBLISHED"] = "APP_NOT_PUBLISHED";
16
+    IntegrationState["AAB_STATE_UNAVAILABLE"] = "AAB_STATE_UNAVAILABLE";
17
+    IntegrationState["PLAY_IAS_TERMS_NOT_ACCEPTED"] = "PLAY_IAS_TERMS_NOT_ACCEPTED";
18
+})(IntegrationState = exports.IntegrationState || (exports.IntegrationState = {}));
19
+var UploadReleaseResult;
20
+(function (UploadReleaseResult) {
21
+    UploadReleaseResult["UPLOAD_RELEASE_RESULT_UNSPECIFIED"] = "UPLOAD_RELEASE_RESULT_UNSPECIFIED";
22
+    UploadReleaseResult["RELEASE_CREATED"] = "RELEASE_CREATED";
23
+    UploadReleaseResult["RELEASE_UPDATED"] = "RELEASE_UPDATED";
24
+    UploadReleaseResult["RELEASE_UNMODIFIED"] = "RELEASE_UNMODIFIED";
25
+})(UploadReleaseResult = exports.UploadReleaseResult || (exports.UploadReleaseResult = {}));
26
+class AppDistributionClient {
27
+    constructor() {
28
+        this.appDistroV2Client = new apiv2_1.Client({
29
+            urlPrefix: api_1.appDistributionOrigin,
30
+            apiVersion: "v1",
31
+        });
32
+    }
33
+    async getAabInfo(appName) {
34
+        const apiResponse = await this.appDistroV2Client.get(`/${appName}/aabInfo`);
35
+        return apiResponse.body;
36
+    }
37
+    async uploadRelease(appName, distribution) {
38
+        const client = new apiv2_1.Client({ urlPrefix: api_1.appDistributionOrigin });
39
+        const apiResponse = await client.request({
40
+            method: "POST",
41
+            path: `/upload/v1/${appName}/releases:upload`,
42
+            headers: {
43
+                "X-Goog-Upload-File-Name": distribution.getFileName(),
44
+                "X-Goog-Upload-Protocol": "raw",
45
+                "Content-Type": "application/octet-stream",
46
+            },
47
+            responseType: "json",
48
+            body: distribution.readStream(),
49
+        });
50
+        return apiResponse.body.name;
51
+    }
52
+    async pollUploadStatus(operationName) {
53
+        return operationPoller.pollOperation({
54
+            pollerName: "App Distribution Upload Poller",
55
+            apiOrigin: api_1.appDistributionOrigin,
56
+            apiVersion: "v1",
57
+            operationResourceName: operationName,
58
+            masterTimeout: 5 * 60 * 1000,
59
+            backoff: 1000,
60
+            maxBackoff: 10 * 1000,
61
+        });
62
+    }
63
+    async updateReleaseNotes(releaseName, releaseNotes) {
64
+        if (!releaseNotes) {
65
+            utils.logWarning("no release notes specified, skipping");
66
+            return;
67
+        }
68
+        utils.logBullet("updating release notes...");
69
+        const data = {
70
+            name: releaseName,
71
+            releaseNotes: {
72
+                text: releaseNotes,
73
+            },
74
+        };
75
+        const queryParams = { updateMask: "release_notes.text" };
76
+        try {
77
+            await this.appDistroV2Client.patch(`/${releaseName}`, data, { queryParams });
78
+        }
79
+        catch (err) {
80
+            throw new error_1.FirebaseError(`failed to update release notes with ${err === null || err === void 0 ? void 0 : err.message}`);
81
+        }
82
+        utils.logSuccess("added release notes successfully");
83
+    }
84
+    async distribute(releaseName, testerEmails = [], groupAliases = []) {
85
+        var _a, _b, _c;
86
+        if (testerEmails.length === 0 && groupAliases.length === 0) {
87
+            utils.logWarning("no testers or groups specified, skipping");
88
+            return;
89
+        }
90
+        utils.logBullet("distributing to testers/groups...");
91
+        const data = {
92
+            testerEmails,
93
+            groupAliases,
94
+        };
95
+        try {
96
+            await this.appDistroV2Client.post(`/${releaseName}:distribute`, data);
97
+        }
98
+        catch (err) {
99
+            let errorMessage = err.message;
100
+            const errorStatus = (_c = (_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.status;
101
+            if (errorStatus === "FAILED_PRECONDITION") {
102
+                errorMessage = "invalid testers";
103
+            }
104
+            else if (errorStatus === "INVALID_ARGUMENT") {
105
+                errorMessage = "invalid groups";
106
+            }
107
+            throw new error_1.FirebaseError(`failed to distribute to testers/groups: ${errorMessage}`, {
108
+                exit: 1,
109
+            });
110
+        }
111
+        utils.logSuccess("distributed to testers/groups successfully");
112
+    }
113
+    async addTesters(projectName, emails) {
114
+        try {
115
+            await this.appDistroV2Client.request({
116
+                method: "POST",
117
+                path: `${projectName}/testers:batchAdd`,
118
+                body: { emails: emails },
119
+            });
120
+        }
121
+        catch (err) {
122
+            throw new error_1.FirebaseError(`Failed to add testers ${err}`);
123
+        }
124
+        utils.logSuccess(`Testers created successfully`);
125
+    }
126
+    async removeTesters(projectName, emails) {
127
+        let apiResponse;
128
+        try {
129
+            apiResponse = await this.appDistroV2Client.request({
130
+                method: "POST",
131
+                path: `${projectName}/testers:batchRemove`,
132
+                body: { emails: emails },
133
+            });
134
+        }
135
+        catch (err) {
136
+            throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
137
+        }
138
+        return apiResponse.body;
139
+    }
140
+}
141
+exports.AppDistributionClient = AppDistributionClient;

+ 51
- 0
node_modules/firebase-tools/lib/appdistribution/distribution.js View File

@@ -0,0 +1,51 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.Distribution = exports.DistributionFileType = void 0;
4
+const fs = require("fs-extra");
5
+const error_1 = require("../error");
6
+const logger_1 = require("../logger");
7
+const pathUtil = require("path");
8
+var DistributionFileType;
9
+(function (DistributionFileType) {
10
+    DistributionFileType["IPA"] = "ipa";
11
+    DistributionFileType["APK"] = "apk";
12
+    DistributionFileType["AAB"] = "aab";
13
+})(DistributionFileType = exports.DistributionFileType || (exports.DistributionFileType = {}));
14
+class Distribution {
15
+    constructor(path) {
16
+        this.path = path;
17
+        if (!path) {
18
+            throw new error_1.FirebaseError("must specify a release binary file");
19
+        }
20
+        const distributionType = path.split(".").pop();
21
+        if (distributionType !== DistributionFileType.IPA &&
22
+            distributionType !== DistributionFileType.APK &&
23
+            distributionType !== DistributionFileType.AAB) {
24
+            throw new error_1.FirebaseError("Unsupported file format, should be .ipa, .apk or .aab");
25
+        }
26
+        let stat;
27
+        try {
28
+            stat = fs.statSync(path);
29
+        }
30
+        catch (err) {
31
+            logger_1.logger.info(err);
32
+            throw new error_1.FirebaseError(`File ${path} does not exist: verify that file points to a binary`);
33
+        }
34
+        if (!stat.isFile()) {
35
+            throw new error_1.FirebaseError(`${path} is not a file. Verify that it points to a binary.`);
36
+        }
37
+        this.path = path;
38
+        this.fileType = distributionType;
39
+        this.fileName = pathUtil.basename(path);
40
+    }
41
+    distributionFileType() {
42
+        return this.fileType;
43
+    }
44
+    readStream() {
45
+        return fs.createReadStream(this.path);
46
+    }
47
+    getFileName() {
48
+        return this.fileName;
49
+    }
50
+}
51
+exports.Distribution = Distribution;

+ 51
- 0
node_modules/firebase-tools/lib/appdistribution/options-parser-util.js View File

@@ -0,0 +1,51 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.getAppName = exports.getProjectName = exports.ensureFileExists = exports.getEmails = exports.getTestersOrGroups = void 0;
4
+const fs = require("fs-extra");
5
+const error_1 = require("../error");
6
+const projectUtils_1 = require("../projectUtils");
7
+function getTestersOrGroups(value, file) {
8
+    if (!value && file) {
9
+        ensureFileExists(file);
10
+        value = fs.readFileSync(file, "utf8");
11
+    }
12
+    if (value) {
13
+        return splitter(value);
14
+    }
15
+    return [];
16
+}
17
+exports.getTestersOrGroups = getTestersOrGroups;
18
+function getEmails(emails, file) {
19
+    if (emails.length === 0) {
20
+        ensureFileExists(file);
21
+        const readFile = fs.readFileSync(file, "utf8");
22
+        return splitter(readFile);
23
+    }
24
+    return emails;
25
+}
26
+exports.getEmails = getEmails;
27
+function ensureFileExists(file, message = "") {
28
+    if (!fs.existsSync(file)) {
29
+        throw new error_1.FirebaseError(`File ${file} does not exist: ${message}`);
30
+    }
31
+}
32
+exports.ensureFileExists = ensureFileExists;
33
+function splitter(value) {
34
+    return value
35
+        .split(/[,\n]/)
36
+        .map((entry) => entry.trim())
37
+        .filter((entry) => !!entry);
38
+}
39
+async function getProjectName(options) {
40
+    const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
41
+    return `projects/${projectNumber}`;
42
+}
43
+exports.getProjectName = getProjectName;
44
+function getAppName(options) {
45
+    if (!options.app) {
46
+        throw new error_1.FirebaseError("set the --app option to a valid Firebase app id and try again");
47
+    }
48
+    const appId = options.app;
49
+    return `projects/${appId.split(":")[1]}/apps/${appId}`;
50
+}
51
+exports.getAppName = getAppName;

+ 120
- 0
node_modules/firebase-tools/lib/archiveDirectory.js View File

@@ -0,0 +1,120 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.archiveDirectory = void 0;
4
+const archiver = require("archiver");
5
+const filesize = require("filesize");
6
+const fs = require("fs");
7
+const path = require("path");
8
+const tar = require("tar");
9
+const tmp = require("tmp");
10
+const error_1 = require("./error");
11
+const listFiles_1 = require("./listFiles");
12
+const logger_1 = require("./logger");
13
+const fsAsync = require("./fsAsync");
14
+async function archiveDirectory(sourceDirectory, options = {}) {
15
+    let postfix = ".tar.gz";
16
+    if (options.type === "zip") {
17
+        postfix = ".zip";
18
+    }
19
+    const tempFile = tmp.fileSync({
20
+        prefix: "firebase-archive-",
21
+        postfix,
22
+    });
23
+    if (!options.ignore) {
24
+        options.ignore = [];
25
+    }
26
+    let makeArchive;
27
+    if (options.type === "zip") {
28
+        makeArchive = zipDirectory(sourceDirectory, tempFile, options);
29
+    }
30
+    else {
31
+        makeArchive = tarDirectory(sourceDirectory, tempFile, options);
32
+    }
33
+    try {
34
+        const archive = await makeArchive;
35
+        logger_1.logger.debug(`Archived ${filesize(archive.size)} in ${sourceDirectory}.`);
36
+        return archive;
37
+    }
38
+    catch (err) {
39
+        if (err instanceof error_1.FirebaseError) {
40
+            throw err;
41
+        }
42
+        throw new error_1.FirebaseError("Failed to create archive.", { original: err });
43
+    }
44
+}
45
+exports.archiveDirectory = archiveDirectory;
46
+async function tarDirectory(sourceDirectory, tempFile, options) {
47
+    const allFiles = (0, listFiles_1.listFiles)(sourceDirectory, options.ignore);
48
+    try {
49
+        fs.statSync(sourceDirectory);
50
+    }
51
+    catch (err) {
52
+        if (err.code === "ENOENT") {
53
+            throw new error_1.FirebaseError(`Could not read directory "${sourceDirectory}"`);
54
+        }
55
+        throw err;
56
+    }
57
+    if (!allFiles.length) {
58
+        throw new error_1.FirebaseError(`Cannot create a tar archive with 0 files from directory "${sourceDirectory}"`);
59
+    }
60
+    await tar.create({
61
+        gzip: true,
62
+        file: tempFile.name,
63
+        cwd: sourceDirectory,
64
+        follow: true,
65
+        noDirRecurse: true,
66
+        portable: true,
67
+    }, allFiles);
68
+    const stats = fs.statSync(tempFile.name);
69
+    return {
70
+        file: tempFile.name,
71
+        stream: fs.createReadStream(tempFile.name),
72
+        manifest: allFiles,
73
+        size: stats.size,
74
+        source: sourceDirectory,
75
+    };
76
+}
77
+async function zipDirectory(sourceDirectory, tempFile, options) {
78
+    const archiveFileStream = fs.createWriteStream(tempFile.name, {
79
+        flags: "w",
80
+        encoding: "binary",
81
+    });
82
+    const archive = archiver("zip");
83
+    const archiveDone = pipeAsync(archive, archiveFileStream);
84
+    const allFiles = [];
85
+    let files;
86
+    try {
87
+        files = await fsAsync.readdirRecursive({ path: sourceDirectory, ignore: options.ignore });
88
+    }
89
+    catch (err) {
90
+        if (err.code === "ENOENT") {
91
+            throw new error_1.FirebaseError(`Could not read directory "${sourceDirectory}"`, { original: err });
92
+        }
93
+        throw err;
94
+    }
95
+    for (const file of files) {
96
+        const name = path.relative(sourceDirectory, file.name);
97
+        allFiles.push(name);
98
+        archive.file(file.name, {
99
+            name,
100
+            mode: file.mode,
101
+        });
102
+    }
103
+    void archive.finalize();
104
+    await archiveDone;
105
+    const stats = fs.statSync(tempFile.name);
106
+    return {
107
+        file: tempFile.name,
108
+        stream: fs.createReadStream(tempFile.name),
109
+        manifest: allFiles,
110
+        size: stats.size,
111
+        source: sourceDirectory,
112
+    };
113
+}
114
+async function pipeAsync(from, to) {
115
+    return new Promise((resolve, reject) => {
116
+        to.on("finish", resolve);
117
+        to.on("error", reject);
118
+        from.pipe(to);
119
+    });
120
+}

+ 535
- 0
node_modules/firebase-tools/lib/auth.js View File

@@ -0,0 +1,535 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.logout = exports.getAccessToken = exports.findAccountByEmail = exports.loginGithub = exports.loginGoogle = exports.setGlobalDefaultAccount = exports.setProjectAccount = exports.loginAdditionalAccount = exports.selectAccount = exports.setRefreshToken = exports.setActiveAccount = exports.getAllAccounts = exports.getAdditionalAccounts = exports.getProjectDefaultAccount = exports.getGlobalDefaultAccount = void 0;
4
+const clc = require("colorette");
5
+const FormData = require("form-data");
6
+const fs = require("fs");
7
+const http = require("http");
8
+const jwt = require("jsonwebtoken");
9
+const opn = require("open");
10
+const path = require("path");
11
+const portfinder = require("portfinder");
12
+const url = require("url");
13
+const util = require("util");
14
+const apiv2 = require("./apiv2");
15
+const configstore_1 = require("./configstore");
16
+const error_1 = require("./error");
17
+const utils = require("./utils");
18
+const logger_1 = require("./logger");
19
+const prompt_1 = require("./prompt");
20
+const scopes = require("./scopes");
21
+const defaultCredentials_1 = require("./defaultCredentials");
22
+const uuid_1 = require("uuid");
23
+const crypto_1 = require("crypto");
24
+const track_1 = require("./track");
25
+const api_1 = require("./api");
26
+portfinder.setBasePort(9005);
27
+function getGlobalDefaultAccount() {
28
+    const user = configstore_1.configstore.get("user");
29
+    const tokens = configstore_1.configstore.get("tokens");
30
+    if (!user || !tokens) {
31
+        return undefined;
32
+    }
33
+    return {
34
+        user,
35
+        tokens,
36
+    };
37
+}
38
+exports.getGlobalDefaultAccount = getGlobalDefaultAccount;
39
+function getProjectDefaultAccount(projectDir) {
40
+    if (!projectDir) {
41
+        return getGlobalDefaultAccount();
42
+    }
43
+    const activeAccounts = configstore_1.configstore.get("activeAccounts") || {};
44
+    const email = activeAccounts[projectDir];
45
+    if (!email) {
46
+        return getGlobalDefaultAccount();
47
+    }
48
+    const allAccounts = getAllAccounts();
49
+    return allAccounts.find((a) => a.user.email === email);
50
+}
51
+exports.getProjectDefaultAccount = getProjectDefaultAccount;
52
+function getAdditionalAccounts() {
53
+    return configstore_1.configstore.get("additionalAccounts") || [];
54
+}
55
+exports.getAdditionalAccounts = getAdditionalAccounts;
56
+function getAllAccounts() {
57
+    const res = [];
58
+    const defaultUser = getGlobalDefaultAccount();
59
+    if (defaultUser) {
60
+        res.push(defaultUser);
61
+    }
62
+    res.push(...getAdditionalAccounts());
63
+    return res;
64
+}
65
+exports.getAllAccounts = getAllAccounts;
66
+function setActiveAccount(options, account) {
67
+    if (account.tokens.refresh_token) {
68
+        setRefreshToken(account.tokens.refresh_token);
69
+    }
70
+    options.user = account.user;
71
+    options.tokens = account.tokens;
72
+}
73
+exports.setActiveAccount = setActiveAccount;
74
+function setRefreshToken(token) {
75
+    apiv2.setRefreshToken(token);
76
+}
77
+exports.setRefreshToken = setRefreshToken;
78
+function selectAccount(account, projectRoot) {
79
+    const defaultUser = getProjectDefaultAccount(projectRoot);
80
+    if (!account) {
81
+        return defaultUser;
82
+    }
83
+    if (!defaultUser) {
84
+        throw new error_1.FirebaseError(`Account ${account} not found, have you run "firebase login"?`);
85
+    }
86
+    const matchingAccount = getAllAccounts().find((a) => a.user.email === account);
87
+    if (matchingAccount) {
88
+        return matchingAccount;
89
+    }
90
+    throw new error_1.FirebaseError(`Account ${account} not found, run "firebase login:list" to see existing accounts or "firebase login:add" to add a new one`);
91
+}
92
+exports.selectAccount = selectAccount;
93
+async function loginAdditionalAccount(useLocalhost, email) {
94
+    const result = await loginGoogle(useLocalhost, email);
95
+    if (typeof result.user === "string") {
96
+        throw new error_1.FirebaseError("Failed to parse auth response, see debug log.");
97
+    }
98
+    const resultEmail = result.user.email;
99
+    if (email && resultEmail !== email) {
100
+        utils.logWarning(`Chosen account ${resultEmail} does not match account hint ${email}`);
101
+    }
102
+    const allAccounts = getAllAccounts();
103
+    const newAccount = {
104
+        user: result.user,
105
+        tokens: result.tokens,
106
+    };
107
+    const existingAccount = allAccounts.find((a) => a.user.email === resultEmail);
108
+    if (existingAccount) {
109
+        utils.logWarning(`Already logged in as ${resultEmail}.`);
110
+        updateAccount(newAccount);
111
+    }
112
+    else {
113
+        const additionalAccounts = getAdditionalAccounts();
114
+        additionalAccounts.push(newAccount);
115
+        configstore_1.configstore.set("additionalAccounts", additionalAccounts);
116
+    }
117
+    return newAccount;
118
+}
119
+exports.loginAdditionalAccount = loginAdditionalAccount;
120
+function setProjectAccount(projectDir, email) {
121
+    logger_1.logger.debug(`setProjectAccount(${projectDir}, ${email})`);
122
+    const activeAccounts = configstore_1.configstore.get("activeAccounts") || {};
123
+    activeAccounts[projectDir] = email;
124
+    configstore_1.configstore.set("activeAccounts", activeAccounts);
125
+}
126
+exports.setProjectAccount = setProjectAccount;
127
+function setGlobalDefaultAccount(account) {
128
+    configstore_1.configstore.set("user", account.user);
129
+    configstore_1.configstore.set("tokens", account.tokens);
130
+    const additionalAccounts = getAdditionalAccounts();
131
+    const index = additionalAccounts.findIndex((a) => a.user.email === account.user.email);
132
+    if (index >= 0) {
133
+        additionalAccounts.splice(index, 1);
134
+        configstore_1.configstore.set("additionalAccounts", additionalAccounts);
135
+    }
136
+}
137
+exports.setGlobalDefaultAccount = setGlobalDefaultAccount;
138
+function open(url) {
139
+    opn(url).catch((err) => {
140
+        logger_1.logger.debug("Unable to open URL: " + err.stack);
141
+    });
142
+}
143
+function invalidCredentialError() {
144
+    return new error_1.FirebaseError("Authentication Error: Your credentials are no longer valid. Please run " +
145
+        clc.bold("firebase login --reauth") +
146
+        "\n\n" +
147
+        "For CI servers and headless environments, generate a new token with " +
148
+        clc.bold("firebase login:ci"), { exit: 1 });
149
+}
150
+const FIFTEEN_MINUTES_IN_MS = 15 * 60 * 1000;
151
+const SCOPES = [
152
+    scopes.EMAIL,
153
+    scopes.OPENID,
154
+    scopes.CLOUD_PROJECTS_READONLY,
155
+    scopes.FIREBASE_PLATFORM,
156
+    scopes.CLOUD_PLATFORM,
157
+];
158
+const _nonce = Math.floor(Math.random() * (2 << 29) + 1).toString();
159
+const getPort = portfinder.getPortPromise;
160
+let lastAccessToken;
161
+function getCallbackUrl(port) {
162
+    if (typeof port === "undefined") {
163
+        return "urn:ietf:wg:oauth:2.0:oob";
164
+    }
165
+    return `http://localhost:${port}`;
166
+}
167
+function queryParamString(args) {
168
+    const tokens = [];
169
+    for (const [key, value] of Object.entries(args)) {
170
+        if (typeof value === "string") {
171
+            tokens.push(key + "=" + encodeURIComponent(value));
172
+        }
173
+    }
174
+    return tokens.join("&");
175
+}
176
+function getLoginUrl(callbackUrl, userHint) {
177
+    return (api_1.authOrigin +
178
+        "/o/oauth2/auth?" +
179
+        queryParamString({
180
+            client_id: api_1.clientId,
181
+            scope: SCOPES.join(" "),
182
+            response_type: "code",
183
+            state: _nonce,
184
+            redirect_uri: callbackUrl,
185
+            login_hint: userHint,
186
+        }));
187
+}
188
+async function getTokensFromAuthorizationCode(code, callbackUrl, verifier) {
189
+    const params = {
190
+        code: code,
191
+        client_id: api_1.clientId,
192
+        client_secret: api_1.clientSecret,
193
+        redirect_uri: callbackUrl,
194
+        grant_type: "authorization_code",
195
+    };
196
+    if (verifier) {
197
+        params["code_verifier"] = verifier;
198
+    }
199
+    let res;
200
+    try {
201
+        const client = new apiv2.Client({ urlPrefix: api_1.authOrigin, auth: false });
202
+        const form = new FormData();
203
+        for (const [k, v] of Object.entries(params)) {
204
+            form.append(k, v);
205
+        }
206
+        res = await client.request({
207
+            method: "POST",
208
+            path: "/o/oauth2/token",
209
+            body: form,
210
+            headers: form.getHeaders(),
211
+            skipLog: { body: true, queryParams: true, resBody: true },
212
+        });
213
+    }
214
+    catch (err) {
215
+        if (err instanceof Error) {
216
+            logger_1.logger.debug("Token Fetch Error:", err.stack || "");
217
+        }
218
+        else {
219
+            logger_1.logger.debug("Token Fetch Error");
220
+        }
221
+        throw invalidCredentialError();
222
+    }
223
+    if (!res.body.access_token && !res.body.refresh_token) {
224
+        logger_1.logger.debug("Token Fetch Error:", res.status, res.body);
225
+        throw invalidCredentialError();
226
+    }
227
+    lastAccessToken = Object.assign({
228
+        expires_at: Date.now() + res.body.expires_in * 1000,
229
+    }, res.body);
230
+    return lastAccessToken;
231
+}
232
+const GITHUB_SCOPES = ["read:user", "repo", "public_repo"];
233
+function getGithubLoginUrl(callbackUrl) {
234
+    return (api_1.githubOrigin +
235
+        "/login/oauth/authorize?" +
236
+        queryParamString({
237
+            client_id: api_1.githubClientId,
238
+            state: _nonce,
239
+            redirect_uri: callbackUrl,
240
+            scope: GITHUB_SCOPES.join(" "),
241
+        }));
242
+}
243
+async function getGithubTokensFromAuthorizationCode(code, callbackUrl) {
244
+    const client = new apiv2.Client({ urlPrefix: api_1.githubOrigin, auth: false });
245
+    const data = {
246
+        client_id: api_1.githubClientId,
247
+        client_secret: api_1.githubClientSecret,
248
+        code,
249
+        redirect_uri: callbackUrl,
250
+        state: _nonce,
251
+    };
252
+    const form = new FormData();
253
+    for (const [k, v] of Object.entries(data)) {
254
+        form.append(k, v);
255
+    }
256
+    const headers = form.getHeaders();
257
+    headers.accept = "application/json";
258
+    const res = await client.request({
259
+        method: "POST",
260
+        path: "/login/oauth/access_token",
261
+        body: form,
262
+        headers,
263
+    });
264
+    return res.body.access_token;
265
+}
266
+async function respondWithFile(req, res, statusCode, filename) {
267
+    const response = await util.promisify(fs.readFile)(path.join(__dirname, filename));
268
+    res.writeHead(statusCode, {
269
+        "Content-Length": response.length,
270
+        "Content-Type": "text/html",
271
+    });
272
+    res.end(response);
273
+    req.socket.destroy();
274
+}
275
+function urlsafeBase64(base64string) {
276
+    return base64string.replace(/\+/g, "-").replace(/=+$/, "").replace(/\//g, "_");
277
+}
278
+async function loginRemotely() {
279
+    var _a;
280
+    const authProxyClient = new apiv2.Client({
281
+        urlPrefix: api_1.authProxyOrigin,
282
+        auth: false,
283
+    });
284
+    const sessionId = (0, uuid_1.v4)();
285
+    const codeVerifier = (0, crypto_1.randomBytes)(32).toString("hex");
286
+    const codeChallenge = urlsafeBase64((0, crypto_1.createHash)("sha256").update(codeVerifier).digest("base64"));
287
+    const attestToken = (_a = (await authProxyClient.post("/attest", {
288
+        session_id: sessionId,
289
+    })).body) === null || _a === void 0 ? void 0 : _a.token;
290
+    const loginUrl = `${api_1.authProxyOrigin}/login?code_challenge=${codeChallenge}&session=${sessionId}&attest=${attestToken}`;
291
+    logger_1.logger.info();
292
+    logger_1.logger.info("To sign in to the Firebase CLI:");
293
+    logger_1.logger.info();
294
+    logger_1.logger.info("1. Take note of your session ID:");
295
+    logger_1.logger.info();
296
+    logger_1.logger.info(`   ${clc.bold(sessionId.substring(0, 5).toUpperCase())}`);
297
+    logger_1.logger.info();
298
+    logger_1.logger.info("2. Visit the URL below on any device and follow the instructions to get your code:");
299
+    logger_1.logger.info();
300
+    logger_1.logger.info(`   ${loginUrl}`);
301
+    logger_1.logger.info();
302
+    logger_1.logger.info("3. Paste or enter the authorization code below once you have it:");
303
+    logger_1.logger.info();
304
+    const code = await (0, prompt_1.promptOnce)({
305
+        type: "input",
306
+        message: "Enter authorization code:",
307
+    });
308
+    try {
309
+        const tokens = await getTokensFromAuthorizationCode(code, `${api_1.authProxyOrigin}/complete`, codeVerifier);
310
+        void (0, track_1.track)("login", "google_remote");
311
+        return {
312
+            user: jwt.decode(tokens.id_token),
313
+            tokens: tokens,
314
+            scopes: SCOPES,
315
+        };
316
+    }
317
+    catch (e) {
318
+        throw new error_1.FirebaseError("Unable to authenticate using the provided code. Please try again.");
319
+    }
320
+}
321
+async function loginWithLocalhostGoogle(port, userHint) {
322
+    const callbackUrl = getCallbackUrl(port);
323
+    const authUrl = getLoginUrl(callbackUrl, userHint);
324
+    const successTemplate = "../templates/loginSuccess.html";
325
+    const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokensFromAuthorizationCode);
326
+    void (0, track_1.track)("login", "google_localhost");
327
+    return {
328
+        user: jwt.decode(tokens.id_token),
329
+        tokens: tokens,
330
+        scopes: tokens.scopes,
331
+    };
332
+}
333
+async function loginWithLocalhostGitHub(port) {
334
+    const callbackUrl = getCallbackUrl(port);
335
+    const authUrl = getGithubLoginUrl(callbackUrl);
336
+    const successTemplate = "../templates/loginSuccessGithub.html";
337
+    const tokens = await loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getGithubTokensFromAuthorizationCode);
338
+    void (0, track_1.track)("login", "google_localhost");
339
+    return tokens;
340
+}
341
+async function loginWithLocalhost(port, callbackUrl, authUrl, successTemplate, getTokens) {
342
+    return new Promise((resolve, reject) => {
343
+        const server = http.createServer(async (req, res) => {
344
+            const query = url.parse(`${req.url}`, true).query || {};
345
+            const queryState = query.state;
346
+            const queryCode = query.code;
347
+            if (queryState !== _nonce || typeof queryCode !== "string") {
348
+                await respondWithFile(req, res, 400, "../templates/loginFailure.html");
349
+                reject(new error_1.FirebaseError("Unexpected error while logging in"));
350
+                server.close();
351
+                return;
352
+            }
353
+            try {
354
+                const tokens = await getTokens(queryCode, callbackUrl);
355
+                await respondWithFile(req, res, 200, successTemplate);
356
+                resolve(tokens);
357
+            }
358
+            catch (err) {
359
+                await respondWithFile(req, res, 400, "../templates/loginFailure.html");
360
+                reject(err);
361
+            }
362
+            server.close();
363
+            return;
364
+        });
365
+        server.listen(port, () => {
366
+            logger_1.logger.info();
367
+            logger_1.logger.info("Visit this URL on this device to log in:");
368
+            logger_1.logger.info(clc.bold(clc.underline(authUrl)));
369
+            logger_1.logger.info();
370
+            logger_1.logger.info("Waiting for authentication...");
371
+            open(authUrl);
372
+        });
373
+        server.on("error", (err) => {
374
+            reject(err);
375
+        });
376
+    });
377
+}
378
+async function loginGoogle(localhost, userHint) {
379
+    if (localhost) {
380
+        try {
381
+            const port = await getPort();
382
+            return await loginWithLocalhostGoogle(port, userHint);
383
+        }
384
+        catch (_a) {
385
+            return await loginRemotely();
386
+        }
387
+    }
388
+    return await loginRemotely();
389
+}
390
+exports.loginGoogle = loginGoogle;
391
+async function loginGithub() {
392
+    const port = await getPort();
393
+    return loginWithLocalhostGitHub(port);
394
+}
395
+exports.loginGithub = loginGithub;
396
+function findAccountByEmail(email) {
397
+    return getAllAccounts().find((a) => a.user.email === email);
398
+}
399
+exports.findAccountByEmail = findAccountByEmail;
400
+function haveValidTokens(refreshToken, authScopes) {
401
+    var _a;
402
+    if (!(lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.access_token)) {
403
+        const tokens = configstore_1.configstore.get("tokens");
404
+        if (refreshToken === (tokens === null || tokens === void 0 ? void 0 : tokens.refresh_token)) {
405
+            lastAccessToken = tokens;
406
+        }
407
+    }
408
+    const hasTokens = !!(lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.access_token);
409
+    const oldScopesJSON = JSON.stringify(((_a = lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.scopes) === null || _a === void 0 ? void 0 : _a.sort()) || []);
410
+    const newScopesJSON = JSON.stringify(authScopes.sort());
411
+    const hasSameScopes = oldScopesJSON === newScopesJSON;
412
+    const isExpired = ((lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.expires_at) || 0) < Date.now() + FIFTEEN_MINUTES_IN_MS;
413
+    return hasTokens && hasSameScopes && !isExpired;
414
+}
415
+function deleteAccount(account) {
416
+    const defaultAccount = getGlobalDefaultAccount();
417
+    if (account.user.email === (defaultAccount === null || defaultAccount === void 0 ? void 0 : defaultAccount.user.email)) {
418
+        configstore_1.configstore.delete("user");
419
+        configstore_1.configstore.delete("tokens");
420
+        configstore_1.configstore.delete("usage");
421
+        configstore_1.configstore.delete("analytics-uuid");
422
+    }
423
+    const additionalAccounts = getAdditionalAccounts();
424
+    const remainingAccounts = additionalAccounts.filter((a) => a.user.email !== account.user.email);
425
+    configstore_1.configstore.set("additionalAccounts", remainingAccounts);
426
+    const activeAccounts = configstore_1.configstore.get("activeAccounts") || {};
427
+    for (const [projectDir, projectAccount] of Object.entries(activeAccounts)) {
428
+        if (projectAccount === account.user.email) {
429
+            delete activeAccounts[projectDir];
430
+        }
431
+    }
432
+    configstore_1.configstore.set("activeAccounts", activeAccounts);
433
+}
434
+function updateAccount(account) {
435
+    const defaultAccount = getGlobalDefaultAccount();
436
+    if (account.user.email === (defaultAccount === null || defaultAccount === void 0 ? void 0 : defaultAccount.user.email)) {
437
+        configstore_1.configstore.set("user", account.user);
438
+        configstore_1.configstore.set("tokens", account.tokens);
439
+    }
440
+    const additionalAccounts = getAdditionalAccounts();
441
+    const accountIndex = additionalAccounts.findIndex((a) => a.user.email === account.user.email);
442
+    if (accountIndex >= 0) {
443
+        additionalAccounts.splice(accountIndex, 1, account);
444
+        configstore_1.configstore.set("additionalAccounts", additionalAccounts);
445
+    }
446
+}
447
+function findAccountByRefreshToken(refreshToken) {
448
+    return getAllAccounts().find((a) => a.tokens.refresh_token === refreshToken);
449
+}
450
+function logoutCurrentSession(refreshToken) {
451
+    const account = findAccountByRefreshToken(refreshToken);
452
+    if (!account) {
453
+        return;
454
+    }
455
+    (0, defaultCredentials_1.clearCredentials)(account);
456
+    deleteAccount(account);
457
+}
458
+async function refreshTokens(refreshToken, authScopes) {
459
+    var _a, _b;
460
+    logger_1.logger.debug("> refreshing access token with scopes:", JSON.stringify(authScopes));
461
+    try {
462
+        const client = new apiv2.Client({ urlPrefix: api_1.googleOrigin, auth: false });
463
+        const data = {
464
+            refresh_token: refreshToken,
465
+            client_id: api_1.clientId,
466
+            client_secret: api_1.clientSecret,
467
+            grant_type: "refresh_token",
468
+            scope: (authScopes || []).join(" "),
469
+        };
470
+        const form = new FormData();
471
+        for (const [k, v] of Object.entries(data)) {
472
+            form.append(k, v);
473
+        }
474
+        const res = await client.request({
475
+            method: "POST",
476
+            path: "/oauth2/v3/token",
477
+            body: form,
478
+            headers: form.getHeaders(),
479
+            skipLog: { body: true, queryParams: true, resBody: true },
480
+            resolveOnHTTPError: true,
481
+        });
482
+        if (res.status === 401 || res.status === 400) {
483
+            return { access_token: refreshToken };
484
+        }
485
+        if (typeof res.body.access_token !== "string") {
486
+            throw invalidCredentialError();
487
+        }
488
+        lastAccessToken = Object.assign({
489
+            expires_at: Date.now() + res.body.expires_in * 1000,
490
+            refresh_token: refreshToken,
491
+            scopes: authScopes,
492
+        }, res.body);
493
+        const account = findAccountByRefreshToken(refreshToken);
494
+        if (account && lastAccessToken) {
495
+            account.tokens = lastAccessToken;
496
+            updateAccount(account);
497
+        }
498
+        return lastAccessToken;
499
+    }
500
+    catch (err) {
501
+        if (((_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === "invalid_scope") {
502
+            throw new error_1.FirebaseError("This command requires new authorization scopes not granted to your current session. Please run " +
503
+                clc.bold("firebase login --reauth") +
504
+                "\n\n" +
505
+                "For CI servers and headless environments, generate a new token with " +
506
+                clc.bold("firebase login:ci"), { exit: 1 });
507
+        }
508
+        throw invalidCredentialError();
509
+    }
510
+}
511
+async function getAccessToken(refreshToken, authScopes) {
512
+    if (haveValidTokens(refreshToken, authScopes)) {
513
+        return lastAccessToken;
514
+    }
515
+    return refreshTokens(refreshToken, authScopes);
516
+}
517
+exports.getAccessToken = getAccessToken;
518
+async function logout(refreshToken) {
519
+    if ((lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.refresh_token) === refreshToken) {
520
+        lastAccessToken = undefined;
521
+    }
522
+    logoutCurrentSession(refreshToken);
523
+    try {
524
+        const client = new apiv2.Client({ urlPrefix: api_1.authOrigin, auth: false });
525
+        await client.get("/o/oauth2/revoke", { queryParams: { token: refreshToken } });
526
+    }
527
+    catch (thrown) {
528
+        const err = thrown instanceof Error ? thrown : new Error(thrown);
529
+        throw new error_1.FirebaseError("Authentication Error.", {
530
+            exit: 1,
531
+            original: err,
532
+        });
533
+    }
534
+}
535
+exports.logout = logout;

+ 132
- 0
node_modules/firebase-tools/lib/bin/firebase.js View File

@@ -0,0 +1,132 @@
1
+#!/usr/bin/env node
2
+"use strict";
3
+Object.defineProperty(exports, "__esModule", { value: true });
4
+const semver = require("semver");
5
+const pkg = require("../../package.json");
6
+const nodeVersion = process.version;
7
+if (!semver.satisfies(nodeVersion, pkg.engines.node)) {
8
+    console.error(`Firebase CLI v${pkg.version} is incompatible with Node.js ${nodeVersion} Please upgrade Node.js to version ${pkg.engines.node}`);
9
+    process.exit(1);
10
+}
11
+const updateNotifierPkg = require("update-notifier");
12
+const clc = require("colorette");
13
+const TerminalRenderer = require("marked-terminal");
14
+const updateNotifier = updateNotifierPkg({ pkg });
15
+const marked_1 = require("marked");
16
+marked_1.marked.setOptions({
17
+    renderer: new TerminalRenderer(),
18
+});
19
+const node_path_1 = require("node:path");
20
+const triple_beam_1 = require("triple-beam");
21
+const stripAnsi = require("strip-ansi");
22
+const fs = require("node:fs");
23
+const configstore_1 = require("../configstore");
24
+const errorOut_1 = require("../errorOut");
25
+const handlePreviewToggles_1 = require("../handlePreviewToggles");
26
+const logger_1 = require("../logger");
27
+const client = require("..");
28
+const fsutils = require("../fsutils");
29
+const utils = require("../utils");
30
+const winston = require("winston");
31
+let args = process.argv.slice(2);
32
+let cmd;
33
+function findAvailableLogFile() {
34
+    const candidates = ["firebase-debug.log"];
35
+    for (let i = 1; i < 10; i++) {
36
+        candidates.push(`firebase-debug.${i}.log`);
37
+    }
38
+    for (const c of candidates) {
39
+        const logFilename = (0, node_path_1.join)(process.cwd(), c);
40
+        try {
41
+            const fd = fs.openSync(logFilename, "r+");
42
+            fs.closeSync(fd);
43
+            return logFilename;
44
+        }
45
+        catch (e) {
46
+            if (e.code === "ENOENT") {
47
+                return logFilename;
48
+            }
49
+        }
50
+    }
51
+    throw new Error("Unable to obtain permissions for firebase-debug.log");
52
+}
53
+const logFilename = findAvailableLogFile();
54
+if (!process.env.DEBUG && args.includes("--debug")) {
55
+    process.env.DEBUG = "true";
56
+}
57
+process.env.IS_FIREBASE_CLI = "true";
58
+logger_1.logger.add(new winston.transports.File({
59
+    level: "debug",
60
+    filename: logFilename,
61
+    format: winston.format.printf((info) => {
62
+        const segments = [info.message, ...(info[triple_beam_1.SPLAT] || [])].map(utils.tryStringify);
63
+        return `[${info.level}] ${stripAnsi(segments.join(" "))}`;
64
+    }),
65
+}));
66
+logger_1.logger.debug("-".repeat(70));
67
+logger_1.logger.debug("Command:      ", process.argv.join(" "));
68
+logger_1.logger.debug("CLI Version:  ", pkg.version);
69
+logger_1.logger.debug("Platform:     ", process.platform);
70
+logger_1.logger.debug("Node Version: ", process.version);
71
+logger_1.logger.debug("Time:         ", new Date().toString());
72
+if (utils.envOverrides.length) {
73
+    logger_1.logger.debug("Env Overrides:", utils.envOverrides.join(", "));
74
+}
75
+logger_1.logger.debug("-".repeat(70));
76
+logger_1.logger.debug();
77
+const experiments_1 = require("../experiments");
78
+const fetchMOTD_1 = require("../fetchMOTD");
79
+(0, experiments_1.enableExperimentsFromCliEnvVariable)();
80
+(0, fetchMOTD_1.fetchMOTD)();
81
+process.on("exit", (code) => {
82
+    code = process.exitCode || code;
83
+    if (!process.env.DEBUG && code < 2 && fsutils.fileExistsSync(logFilename)) {
84
+        fs.unlinkSync(logFilename);
85
+    }
86
+    if (code > 0 && process.stdout.isTTY) {
87
+        const lastError = configstore_1.configstore.get("lastError") || 0;
88
+        const timestamp = Date.now();
89
+        if (lastError > timestamp - 120000) {
90
+            let help;
91
+            if (code === 1 && cmd) {
92
+                help = "Having trouble? Try " + clc.bold("firebase [command] --help");
93
+            }
94
+            else {
95
+                help = "Having trouble? Try again or contact support with contents of firebase-debug.log";
96
+            }
97
+            if (cmd) {
98
+                console.log();
99
+                console.log(help);
100
+            }
101
+        }
102
+        configstore_1.configstore.set("lastError", timestamp);
103
+    }
104
+    else {
105
+        configstore_1.configstore.delete("lastError");
106
+    }
107
+    try {
108
+        const updateMessage = `Update available ${clc.gray("{currentVersion}")} → ${clc.green("{latestVersion}")}\n` +
109
+            `To update to the latest version using npm, run\n${clc.cyan("npm install -g firebase-tools")}\n` +
110
+            `For other CLI management options, visit the ${(0, marked_1.marked)("[CLI documentation](https://firebase.google.com/docs/cli#update-cli)")}`;
111
+        updateNotifier.notify({ defer: false, isGlobal: true, message: updateMessage });
112
+    }
113
+    catch (err) {
114
+        logger_1.logger.debug("Error when notifying about new CLI updates:");
115
+        if (err instanceof Error) {
116
+            logger_1.logger.debug(err);
117
+        }
118
+        else {
119
+            logger_1.logger.debug(`${err}`);
120
+        }
121
+    }
122
+});
123
+process.on("uncaughtException", (err) => {
124
+    (0, errorOut_1.errorOut)(err);
125
+});
126
+if (!(0, handlePreviewToggles_1.handlePreviewToggles)(args)) {
127
+    cmd = client.cli.parse(process.argv);
128
+    args = args.filter((arg) => !arg.includes("-"));
129
+    if (!args.length) {
130
+        client.cli.help();
131
+    }
132
+}

+ 14
- 0
node_modules/firebase-tools/lib/checkMinRequiredVersion.js View File

@@ -0,0 +1,14 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.checkMinRequiredVersion = void 0;
4
+const semver = require("semver");
5
+const configstore_1 = require("./configstore");
6
+const error_1 = require("./error");
7
+const pkg = require("../package.json");
8
+function checkMinRequiredVersion(options, key) {
9
+    const minVersion = configstore_1.configstore.get(`motd.${key}`);
10
+    if (minVersion && semver.gt(minVersion, pkg.version)) {
11
+        throw new error_1.FirebaseError(`This command requires at least version ${minVersion} of the CLI to use. To update to the latest version using npm, run \`npm install -g firebase-tools\`. For other CLI management options, see https://firebase.google.com/docs/cli#update-cli`);
12
+    }
13
+}
14
+exports.checkMinRequiredVersion = checkMinRequiredVersion;

+ 46
- 0
node_modules/firebase-tools/lib/checkValidTargetFilters.js View File

@@ -0,0 +1,46 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.checkValidTargetFilters = void 0;
4
+const deploy_1 = require("./commands/deploy");
5
+const error_1 = require("./error");
6
+function targetsForTypes(only, ...types) {
7
+    return only.filter((t) => {
8
+        if (t.includes(":")) {
9
+            return types.includes(t.split(":")[0]);
10
+        }
11
+        else {
12
+            return types.includes(t);
13
+        }
14
+    });
15
+}
16
+function targetsHaveFilters(...targets) {
17
+    return targets.some((t) => t.includes(":"));
18
+}
19
+function targetsHaveNoFilters(...targets) {
20
+    return targets.some((t) => !t.includes(":"));
21
+}
22
+const FILTERABLE_TARGETS = new Set(["hosting", "functions", "firestore", "storage", "database"]);
23
+async function checkValidTargetFilters(options) {
24
+    const only = !options.only ? [] : options.only.split(",");
25
+    return new Promise((resolve, reject) => {
26
+        if (!only.length) {
27
+            return resolve();
28
+        }
29
+        if (options.except) {
30
+            return reject(new error_1.FirebaseError("Cannot specify both --only and --except"));
31
+        }
32
+        const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !FILTERABLE_TARGETS.has(t));
33
+        const targetsForNonFilteredTypes = targetsForTypes(only, ...nonFilteredTypes);
34
+        if (targetsForNonFilteredTypes.length && targetsHaveFilters(...targetsForNonFilteredTypes)) {
35
+            return reject(new error_1.FirebaseError("Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions, hosting, storage, and firestore"));
36
+        }
37
+        const targetsForFunctions = targetsForTypes(only, "functions");
38
+        if (targetsForFunctions.length &&
39
+            targetsHaveFilters(...targetsForFunctions) &&
40
+            targetsHaveNoFilters(...targetsForFunctions)) {
41
+            return reject(new error_1.FirebaseError('Cannot specify "--only functions" and "--only functions:<filter>" at the same time'));
42
+        }
43
+        return resolve();
44
+    });
45
+}
46
+exports.checkValidTargetFilters = checkValidTargetFilters;

+ 235
- 0
node_modules/firebase-tools/lib/command.js View File

@@ -0,0 +1,235 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.validateProjectId = exports.Command = void 0;
4
+const clc = require("colorette");
5
+const lodash_1 = require("lodash");
6
+const error_1 = require("./error");
7
+const utils_1 = require("./utils");
8
+const rc_1 = require("./rc");
9
+const config_1 = require("./config");
10
+const configstore_1 = require("./configstore");
11
+const detectProjectRoot_1 = require("./detectProjectRoot");
12
+const track_1 = require("./track");
13
+const auth_1 = require("./auth");
14
+const projects_1 = require("./management/projects");
15
+const requireAuth_1 = require("./requireAuth");
16
+class Command {
17
+    constructor(cmd) {
18
+        this.cmd = cmd;
19
+        this.name = "";
20
+        this.descriptionText = "";
21
+        this.options = [];
22
+        this.actionFn = () => {
23
+        };
24
+        this.befores = [];
25
+        this.helpText = "";
26
+        this.positionalArgs = [];
27
+        this.name = (0, lodash_1.first)(cmd.split(" ")) || "";
28
+    }
29
+    description(t) {
30
+        this.descriptionText = t;
31
+        return this;
32
+    }
33
+    option(...args) {
34
+        this.options.push(args);
35
+        return this;
36
+    }
37
+    withForce(message) {
38
+        this.options.push(["-f, --force", message || "automatically accept all interactive prompts"]);
39
+        return this;
40
+    }
41
+    before(fn, ...args) {
42
+        this.befores.push({ fn: fn, args: args });
43
+        return this;
44
+    }
45
+    help(t) {
46
+        this.helpText = t;
47
+        return this;
48
+    }
49
+    action(fn) {
50
+        this.actionFn = fn;
51
+        return this;
52
+    }
53
+    register(client) {
54
+        this.client = client;
55
+        const program = client.cli;
56
+        const cmd = program.command(this.cmd);
57
+        if (this.descriptionText) {
58
+            cmd.description(this.descriptionText);
59
+        }
60
+        this.options.forEach((args) => {
61
+            const flags = args.shift();
62
+            cmd.option(flags, ...args);
63
+        });
64
+        if (this.helpText) {
65
+            cmd.on("--help", () => {
66
+                console.log();
67
+                console.log(this.helpText);
68
+            });
69
+        }
70
+        this.positionalArgs = cmd._args;
71
+        cmd.action((...args) => {
72
+            const runner = this.runner();
73
+            const start = process.uptime();
74
+            const options = (0, lodash_1.last)(args);
75
+            if (args.length - 1 > cmd._args.length) {
76
+                client.errorOut(new error_1.FirebaseError(`Too many arguments. Run ${clc.bold("firebase help " + this.name)} for usage instructions`, { exit: 1 }));
77
+                return;
78
+            }
79
+            const isEmulator = this.name.includes("emulator") || this.name === "serve";
80
+            if (isEmulator) {
81
+                void (0, track_1.trackEmulator)("command_start", { command_name: this.name });
82
+            }
83
+            runner(...args)
84
+                .then(async (result) => {
85
+                if ((0, utils_1.getInheritedOption)(options, "json")) {
86
+                    console.log(JSON.stringify({
87
+                        status: "success",
88
+                        result: result,
89
+                    }, null, 2));
90
+                }
91
+                const duration = Math.floor((process.uptime() - start) * 1000);
92
+                const trackSuccess = (0, track_1.track)(this.name, "success", duration);
93
+                if (!isEmulator) {
94
+                    await (0, utils_1.withTimeout)(5000, trackSuccess);
95
+                }
96
+                else {
97
+                    await (0, utils_1.withTimeout)(5000, Promise.all([
98
+                        trackSuccess,
99
+                        (0, track_1.trackEmulator)("command_success", {
100
+                            command_name: this.name,
101
+                            duration,
102
+                        }),
103
+                    ]));
104
+                }
105
+                process.exit();
106
+            })
107
+                .catch(async (err) => {
108
+                if ((0, utils_1.getInheritedOption)(options, "json")) {
109
+                    console.log(JSON.stringify({
110
+                        status: "error",
111
+                        error: err.message,
112
+                    }, null, 2));
113
+                }
114
+                const duration = Math.floor((process.uptime() - start) * 1000);
115
+                await (0, utils_1.withTimeout)(5000, Promise.all([
116
+                    (0, track_1.track)(this.name, "error", duration),
117
+                    (0, track_1.track)(err.exit === 1 ? "Error (User)" : "Error (Unexpected)", "", duration),
118
+                    isEmulator
119
+                        ? (0, track_1.trackEmulator)("command_error", {
120
+                            command_name: this.name,
121
+                            duration,
122
+                            error_type: err.exit === 1 ? "user" : "unexpected",
123
+                        })
124
+                        : Promise.resolve(),
125
+                ]));
126
+                client.errorOut(err);
127
+            });
128
+        });
129
+    }
130
+    async prepare(options) {
131
+        options = options || {};
132
+        options.project = (0, utils_1.getInheritedOption)(options, "project");
133
+        if (!process.stdin.isTTY || (0, utils_1.getInheritedOption)(options, "nonInteractive")) {
134
+            options.nonInteractive = true;
135
+        }
136
+        if ((0, utils_1.getInheritedOption)(options, "interactive")) {
137
+            options.interactive = true;
138
+            options.nonInteractive = false;
139
+        }
140
+        if ((0, utils_1.getInheritedOption)(options, "debug")) {
141
+            options.debug = true;
142
+        }
143
+        if ((0, utils_1.getInheritedOption)(options, "json")) {
144
+            options.nonInteractive = true;
145
+        }
146
+        else {
147
+            (0, utils_1.setupLoggers)();
148
+        }
149
+        if ((0, utils_1.getInheritedOption)(options, "config")) {
150
+            options.configPath = (0, utils_1.getInheritedOption)(options, "config");
151
+        }
152
+        try {
153
+            options.config = config_1.Config.load(options);
154
+        }
155
+        catch (e) {
156
+            options.configError = e;
157
+        }
158
+        const account = (0, utils_1.getInheritedOption)(options, "account");
159
+        options.account = account;
160
+        options.projectRoot = (0, detectProjectRoot_1.detectProjectRoot)(options);
161
+        const projectRoot = options.projectRoot;
162
+        const activeAccount = (0, auth_1.selectAccount)(account, projectRoot);
163
+        if (activeAccount) {
164
+            (0, auth_1.setActiveAccount)(options, activeAccount);
165
+        }
166
+        this.applyRC(options);
167
+        if (options.project) {
168
+            await this.resolveProjectIdentifiers(options);
169
+            validateProjectId(options.projectId);
170
+        }
171
+    }
172
+    applyRC(options) {
173
+        const rc = (0, rc_1.loadRC)(options);
174
+        options.rc = rc;
175
+        options.project =
176
+            options.project || (configstore_1.configstore.get("activeProjects") || {})[options.projectRoot];
177
+        if (options.config && !options.project) {
178
+            options.project = options.config.defaults.project;
179
+        }
180
+        const aliases = rc.projects;
181
+        const rcProject = (0, lodash_1.get)(aliases, options.project);
182
+        if (rcProject) {
183
+            options.projectAlias = options.project;
184
+            options.project = rcProject;
185
+        }
186
+        else if (!options.project && (0, lodash_1.size)(aliases) === 1) {
187
+            options.projectAlias = (0, lodash_1.head)((0, lodash_1.keys)(aliases));
188
+            options.project = (0, lodash_1.head)((0, lodash_1.values)(aliases));
189
+        }
190
+    }
191
+    async resolveProjectIdentifiers(options) {
192
+        var _a;
193
+        if ((_a = options.project) === null || _a === void 0 ? void 0 : _a.match(/^\d+$/)) {
194
+            await (0, requireAuth_1.requireAuth)(options);
195
+            const { projectId, projectNumber } = await (0, projects_1.getFirebaseProject)(options.project);
196
+            options.projectId = projectId;
197
+            options.projectNumber = projectNumber;
198
+        }
199
+        else {
200
+            options.projectId = options.project;
201
+        }
202
+    }
203
+    runner() {
204
+        return async (...args) => {
205
+            if (typeof (0, lodash_1.last)(args) !== "object" || (0, lodash_1.last)(args) === null) {
206
+                args.push({});
207
+            }
208
+            while (args.length < this.positionalArgs.length + 1) {
209
+                args.splice(args.length - 1, 0, "");
210
+            }
211
+            const options = (0, lodash_1.last)(args);
212
+            await this.prepare(options);
213
+            for (const before of this.befores) {
214
+                await before.fn(options, ...before.args);
215
+            }
216
+            return this.actionFn(...args);
217
+        };
218
+    }
219
+}
220
+exports.Command = Command;
221
+const PROJECT_ID_REGEX = /^(?:[^:]+:)?[a-z0-9-]+$/;
222
+function validateProjectId(project) {
223
+    if (PROJECT_ID_REGEX.test(project)) {
224
+        return;
225
+    }
226
+    (0, track_1.track)("Project ID Check", "invalid");
227
+    const invalidMessage = "Invalid project id: " + clc.bold(project) + ".";
228
+    if (project.toLowerCase() !== project) {
229
+        throw new error_1.FirebaseError(invalidMessage + "\nNote: Project id must be all lowercase.");
230
+    }
231
+    else {
232
+        throw new error_1.FirebaseError(invalidMessage);
233
+    }
234
+}
235
+exports.validateProjectId = validateProjectId;

+ 118
- 0
node_modules/firebase-tools/lib/commands/appdistribution-distribute.js View File

@@ -0,0 +1,118 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const fs = require("fs-extra");
5
+const command_1 = require("../command");
6
+const utils = require("../utils");
7
+const requireAuth_1 = require("../requireAuth");
8
+const client_1 = require("../appdistribution/client");
9
+const error_1 = require("../error");
10
+const distribution_1 = require("../appdistribution/distribution");
11
+const options_parser_util_1 = require("../appdistribution/options-parser-util");
12
+function getReleaseNotes(releaseNotes, releaseNotesFile) {
13
+    if (releaseNotes) {
14
+        return releaseNotes.replace(/\\n/g, "\n");
15
+    }
16
+    else if (releaseNotesFile) {
17
+        (0, options_parser_util_1.ensureFileExists)(releaseNotesFile);
18
+        return fs.readFileSync(releaseNotesFile, "utf8");
19
+    }
20
+    return "";
21
+}
22
+exports.command = new command_1.Command("appdistribution:distribute <release-binary-file>")
23
+    .description("upload a release binary")
24
+    .option("--app <app_id>", "the app id of your Firebase app")
25
+    .option("--release-notes <string>", "release notes to include")
26
+    .option("--release-notes-file <file>", "path to file with release notes")
27
+    .option("--testers <string>", "a comma separated list of tester emails to distribute to")
28
+    .option("--testers-file <file>", "path to file with a comma separated list of tester emails to distribute to")
29
+    .option("--groups <string>", "a comma separated list of group aliases to distribute to")
30
+    .option("--groups-file <file>", "path to file with a comma separated list of group aliases to distribute to")
31
+    .before(requireAuth_1.requireAuth)
32
+    .action(async (file, options) => {
33
+    const appName = (0, options_parser_util_1.getAppName)(options);
34
+    const distribution = new distribution_1.Distribution(file);
35
+    const releaseNotes = getReleaseNotes(options.releaseNotes, options.releaseNotesFile);
36
+    const testers = (0, options_parser_util_1.getTestersOrGroups)(options.testers, options.testersFile);
37
+    const groups = (0, options_parser_util_1.getTestersOrGroups)(options.groups, options.groupsFile);
38
+    const requests = new client_1.AppDistributionClient();
39
+    let aabInfo;
40
+    if (distribution.distributionFileType() === distribution_1.DistributionFileType.AAB) {
41
+        try {
42
+            aabInfo = await requests.getAabInfo(appName);
43
+        }
44
+        catch (err) {
45
+            if (err.status === 404) {
46
+                throw new error_1.FirebaseError(`App Distribution could not find your app ${options.app}. ` +
47
+                    `Make sure to onboard your app by pressing the "Get started" ` +
48
+                    "button on the App Distribution page in the Firebase console: " +
49
+                    "https://console.firebase.google.com/project/_/appdistribution", { exit: 1 });
50
+            }
51
+            throw new error_1.FirebaseError(`failed to determine AAB info. ${err.message}`, { exit: 1 });
52
+        }
53
+        if (aabInfo.integrationState !== client_1.IntegrationState.INTEGRATED &&
54
+            aabInfo.integrationState !== client_1.IntegrationState.AAB_STATE_UNAVAILABLE) {
55
+            switch (aabInfo.integrationState) {
56
+                case client_1.IntegrationState.PLAY_ACCOUNT_NOT_LINKED: {
57
+                    throw new error_1.FirebaseError("This project is not linked to a Google Play account.");
58
+                }
59
+                case client_1.IntegrationState.APP_NOT_PUBLISHED: {
60
+                    throw new error_1.FirebaseError('"This app is not published in the Google Play console.');
61
+                }
62
+                case client_1.IntegrationState.NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT: {
63
+                    throw new error_1.FirebaseError("App with matching package name does not exist in Google Play.");
64
+                }
65
+                case client_1.IntegrationState.PLAY_IAS_TERMS_NOT_ACCEPTED: {
66
+                    throw new error_1.FirebaseError("You must accept the Play Internal App Sharing (IAS) terms to upload AABs.");
67
+                }
68
+                default: {
69
+                    throw new error_1.FirebaseError("App Distribution failed to process the AAB: " + aabInfo.integrationState);
70
+                }
71
+            }
72
+        }
73
+    }
74
+    utils.logBullet("uploading binary...");
75
+    let releaseName;
76
+    try {
77
+        const operationName = await requests.uploadRelease(appName, distribution);
78
+        const uploadResponse = await requests.pollUploadStatus(operationName);
79
+        const release = uploadResponse.release;
80
+        switch (uploadResponse.result) {
81
+            case client_1.UploadReleaseResult.RELEASE_CREATED:
82
+                utils.logSuccess(`uploaded new release ${release.displayVersion} (${release.buildVersion}) successfully!`);
83
+                break;
84
+            case client_1.UploadReleaseResult.RELEASE_UPDATED:
85
+                utils.logSuccess(`uploaded update to existing release ${release.displayVersion} (${release.buildVersion}) successfully!`);
86
+                break;
87
+            case client_1.UploadReleaseResult.RELEASE_UNMODIFIED:
88
+                utils.logSuccess(`re-uploaded already existing release ${release.displayVersion} (${release.buildVersion}) successfully!`);
89
+                break;
90
+            default:
91
+                utils.logSuccess(`uploaded release ${release.displayVersion} (${release.buildVersion}) successfully!`);
92
+        }
93
+        releaseName = uploadResponse.release.name;
94
+    }
95
+    catch (err) {
96
+        if (err.status === 404) {
97
+            throw new error_1.FirebaseError(`App Distribution could not find your app ${options.app}. ` +
98
+                `Make sure to onboard your app by pressing the "Get started" ` +
99
+                "button on the App Distribution page in the Firebase console: " +
100
+                "https://console.firebase.google.com/project/_/appdistribution", { exit: 1 });
101
+        }
102
+        throw new error_1.FirebaseError(`failed to upload release. ${err.message}`, { exit: 1 });
103
+    }
104
+    if (aabInfo && !aabInfo.testCertificate) {
105
+        aabInfo = await requests.getAabInfo(appName);
106
+        if (aabInfo.testCertificate) {
107
+            utils.logBullet("After you upload an AAB for the first time, App Distribution " +
108
+                "generates a new test certificate. All AAB uploads are re-signed with this test " +
109
+                "certificate. Use the certificate fingerprints below to register your app " +
110
+                "signing key with API providers, such as Google Sign-In and Google Maps.\n" +
111
+                `MD-1 certificate fingerprint: ${aabInfo.testCertificate.hashMd5}\n` +
112
+                `SHA-1 certificate fingerprint: ${aabInfo.testCertificate.hashSha1}\n` +
113
+                `SHA-256 certificate fingerprint: ${aabInfo.testCertificate.hashSha256}`);
114
+        }
115
+    }
116
+    await requests.updateReleaseNotes(releaseName, releaseNotes);
117
+    await requests.distribute(releaseName, testers, groups);
118
+});

+ 19
- 0
node_modules/firebase-tools/lib/commands/appdistribution-testers-add.js View File

@@ -0,0 +1,19 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const utils = require("../utils");
6
+const requireAuth_1 = require("../requireAuth");
7
+const client_1 = require("../appdistribution/client");
8
+const options_parser_util_1 = require("../appdistribution/options-parser-util");
9
+exports.command = new command_1.Command("appdistribution:testers:add [emails...]")
10
+    .description("add testers to project")
11
+    .option("--file <file>", "a path to a file containing a list of tester emails to be added")
12
+    .before(requireAuth_1.requireAuth)
13
+    .action(async (emails, options) => {
14
+    const projectName = await (0, options_parser_util_1.getProjectName)(options);
15
+    const appDistroClient = new client_1.AppDistributionClient();
16
+    const emailsToAdd = (0, options_parser_util_1.getEmails)(emails, options.file);
17
+    utils.logBullet(`Adding ${emailsToAdd.length} testers to project`);
18
+    await appDistroClient.addTesters(projectName, emailsToAdd);
19
+});

+ 33
- 0
node_modules/firebase-tools/lib/commands/appdistribution-testers-remove.js View File

@@ -0,0 +1,33 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const utils = require("../utils");
6
+const requireAuth_1 = require("../requireAuth");
7
+const error_1 = require("../error");
8
+const client_1 = require("../appdistribution/client");
9
+const options_parser_util_1 = require("../appdistribution/options-parser-util");
10
+const logger_1 = require("../logger");
11
+exports.command = new command_1.Command("appdistribution:testers:remove [emails...]")
12
+    .description("remove testers from a project")
13
+    .option("--file <file>", "a path to a file containing a list of tester emails to be removed")
14
+    .before(requireAuth_1.requireAuth)
15
+    .action(async (emails, options) => {
16
+    const projectName = await (0, options_parser_util_1.getProjectName)(options);
17
+    const appDistroClient = new client_1.AppDistributionClient();
18
+    const emailsArr = (0, options_parser_util_1.getEmails)(emails, options.file);
19
+    let deleteResponse;
20
+    try {
21
+        utils.logBullet(`Deleting ${emailsArr.length} testers from project`);
22
+        deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr);
23
+    }
24
+    catch (err) {
25
+        throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
26
+    }
27
+    if (!deleteResponse.emails) {
28
+        utils.logSuccess(`Testers did not exist`);
29
+        return;
30
+    }
31
+    logger_1.logger.debug(`Testers: ${deleteResponse.emails}, have been successfully deleted`);
32
+    utils.logSuccess(`${deleteResponse.emails.length} testers have successfully been deleted`);
33
+});

+ 29
- 0
node_modules/firebase-tools/lib/commands/apps-android-sha-create.js View File

@@ -0,0 +1,29 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const command_1 = require("../command");
6
+const projectUtils_1 = require("../projectUtils");
7
+const apps_1 = require("../management/apps");
8
+const requireAuth_1 = require("../requireAuth");
9
+const utils_1 = require("../utils");
10
+function getCertHashType(shaHash) {
11
+    shaHash = shaHash.replace(/:/g, "");
12
+    const shaHashCount = shaHash.length;
13
+    if (shaHashCount === 40)
14
+        return apps_1.ShaCertificateType.SHA_1.toString();
15
+    if (shaHashCount === 64)
16
+        return apps_1.ShaCertificateType.SHA_256.toString();
17
+    return apps_1.ShaCertificateType.SHA_CERTIFICATE_TYPE_UNSPECIFIED.toString();
18
+}
19
+exports.command = new command_1.Command("apps:android:sha:create <appId> <shaHash>")
20
+    .description("add a SHA certificate hash for a given app id.")
21
+    .before(requireAuth_1.requireAuth)
22
+    .action(async (appId = "", shaHash = "", options) => {
23
+    const projectId = (0, projectUtils_1.needProjectId)(options);
24
+    const shaCertificate = await (0, utils_1.promiseWithSpinner)(async () => await (0, apps_1.createAppAndroidSha)(projectId, appId, {
25
+        shaHash: shaHash,
26
+        certType: getCertHashType(shaHash),
27
+    }), `Creating Android SHA certificate ${clc.bold(options.shaHash)}with Android app Id ${clc.bold(appId)}`);
28
+    return shaCertificate;
29
+});

+ 16
- 0
node_modules/firebase-tools/lib/commands/apps-android-sha-delete.js View File

@@ -0,0 +1,16 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const command_1 = require("../command");
6
+const projectUtils_1 = require("../projectUtils");
7
+const apps_1 = require("../management/apps");
8
+const requireAuth_1 = require("../requireAuth");
9
+const utils_1 = require("../utils");
10
+exports.command = new command_1.Command("apps:android:sha:delete <appId> <shaId>")
11
+    .description("delete a SHA certificate hash for a given app id.")
12
+    .before(requireAuth_1.requireAuth)
13
+    .action(async (appId = "", shaId = "", options) => {
14
+    const projectId = (0, projectUtils_1.needProjectId)(options);
15
+    await (0, utils_1.promiseWithSpinner)(async () => await (0, apps_1.deleteAppAndroidSha)(projectId, appId, shaId), `Deleting Android SHA certificate hash with SHA id ${clc.bold(shaId)} and Android app Id ${clc.bold(appId)}`);
16
+});

+ 42
- 0
node_modules/firebase-tools/lib/commands/apps-android-sha-list.js View File

@@ -0,0 +1,42 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const Table = require("cli-table");
5
+const command_1 = require("../command");
6
+const projectUtils_1 = require("../projectUtils");
7
+const apps_1 = require("../management/apps");
8
+const requireAuth_1 = require("../requireAuth");
9
+const logger_1 = require("../logger");
10
+const utils_1 = require("../utils");
11
+function logCertificatesList(certificates) {
12
+    if (certificates.length === 0) {
13
+        logger_1.logger.info("No SHA certificate hashes found.");
14
+        return;
15
+    }
16
+    const tableHead = ["App Id", "SHA Id", "SHA Hash", "SHA Hash Type"];
17
+    const table = new Table({ head: tableHead, style: { head: ["green"] } });
18
+    certificates.forEach(({ name, shaHash, certType }) => {
19
+        const splitted = name.split("/");
20
+        const appId = splitted[3];
21
+        const shaId = splitted[5];
22
+        table.push([appId, shaId, shaHash, certType]);
23
+    });
24
+    logger_1.logger.info(table.toString());
25
+}
26
+function logCertificatesCount(count = 0) {
27
+    if (count === 0) {
28
+        return;
29
+    }
30
+    logger_1.logger.info("");
31
+    logger_1.logger.info(`${count} SHA hash(es) total.`);
32
+}
33
+exports.command = new command_1.Command("apps:android:sha:list <appId>")
34
+    .description("list the SHA certificate hashes for a given app id. ")
35
+    .before(requireAuth_1.requireAuth)
36
+    .action(async (appId = "", options) => {
37
+    const projectId = (0, projectUtils_1.needProjectId)(options);
38
+    const shaCertificates = await (0, utils_1.promiseWithSpinner)(async () => await (0, apps_1.listAppAndroidSha)(projectId, appId), "Preparing the list of your Firebase Android app SHA certificate hashes");
39
+    logCertificatesList(shaCertificates);
40
+    logCertificatesCount(shaCertificates.length);
41
+    return shaCertificates;
42
+});

+ 166
- 0
node_modules/firebase-tools/lib/commands/apps-create.js View File

@@ -0,0 +1,166 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const ora = require("ora");
6
+const command_1 = require("../command");
7
+const projectUtils_1 = require("../projectUtils");
8
+const error_1 = require("../error");
9
+const apps_1 = require("../management/apps");
10
+const prompt_1 = require("../prompt");
11
+const requireAuth_1 = require("../requireAuth");
12
+const logger_1 = require("../logger");
13
+const DISPLAY_NAME_QUESTION = {
14
+    type: "input",
15
+    name: "displayName",
16
+    default: "",
17
+    message: "What would you like to call your app?",
18
+};
19
+function logPostAppCreationInformation(appMetadata, appPlatform) {
20
+    logger_1.logger.info("");
21
+    logger_1.logger.info(`🎉🎉🎉 Your Firebase ${appPlatform} App is ready! 🎉🎉🎉`);
22
+    logger_1.logger.info("");
23
+    logger_1.logger.info("App information:");
24
+    logger_1.logger.info(`  - App ID: ${appMetadata.appId}`);
25
+    if (appMetadata.displayName) {
26
+        logger_1.logger.info(`  - Display name: ${appMetadata.displayName}`);
27
+    }
28
+    if (appPlatform === apps_1.AppPlatform.IOS) {
29
+        const iosAppMetadata = appMetadata;
30
+        logger_1.logger.info(`  - Bundle ID: ${iosAppMetadata.bundleId}`);
31
+        if (iosAppMetadata.appStoreId) {
32
+            logger_1.logger.info(`  - App Store ID: ${iosAppMetadata.appStoreId}`);
33
+        }
34
+    }
35
+    else if (appPlatform === apps_1.AppPlatform.ANDROID) {
36
+        logger_1.logger.info(`  - Package name: ${appMetadata.packageName}`);
37
+    }
38
+    logger_1.logger.info("");
39
+    logger_1.logger.info("You can run this command to print out your new app's Google Services config:");
40
+    logger_1.logger.info(`  firebase apps:sdkconfig ${appPlatform} ${appMetadata.appId}`);
41
+}
42
+async function initiateIosAppCreation(options) {
43
+    if (!options.nonInteractive) {
44
+        await (0, prompt_1.prompt)(options, [
45
+            DISPLAY_NAME_QUESTION,
46
+            {
47
+                type: "input",
48
+                default: "",
49
+                name: "bundleId",
50
+                message: "Please specify your iOS app bundle ID:",
51
+            },
52
+            {
53
+                type: "input",
54
+                default: "",
55
+                name: "appStoreId",
56
+                message: "Please specify your iOS app App Store ID:",
57
+            },
58
+        ]);
59
+    }
60
+    if (!options.bundleId) {
61
+        throw new error_1.FirebaseError("Bundle ID for iOS app cannot be empty");
62
+    }
63
+    const spinner = ora("Creating your iOS app").start();
64
+    try {
65
+        const appData = await (0, apps_1.createIosApp)(options.project, {
66
+            displayName: options.displayName,
67
+            bundleId: options.bundleId,
68
+            appStoreId: options.appStoreId,
69
+        });
70
+        spinner.succeed();
71
+        return appData;
72
+    }
73
+    catch (err) {
74
+        spinner.fail();
75
+        throw err;
76
+    }
77
+}
78
+async function initiateAndroidAppCreation(options) {
79
+    if (!options.nonInteractive) {
80
+        await (0, prompt_1.prompt)(options, [
81
+            DISPLAY_NAME_QUESTION,
82
+            {
83
+                type: "input",
84
+                default: "",
85
+                name: "packageName",
86
+                message: "Please specify your Android app package name:",
87
+            },
88
+        ]);
89
+    }
90
+    if (!options.packageName) {
91
+        throw new error_1.FirebaseError("Package name for Android app cannot be empty");
92
+    }
93
+    const spinner = ora("Creating your Android app").start();
94
+    try {
95
+        const appData = await (0, apps_1.createAndroidApp)(options.project, {
96
+            displayName: options.displayName,
97
+            packageName: options.packageName,
98
+        });
99
+        spinner.succeed();
100
+        return appData;
101
+    }
102
+    catch (err) {
103
+        spinner.fail();
104
+        throw err;
105
+    }
106
+}
107
+async function initiateWebAppCreation(options) {
108
+    if (!options.nonInteractive) {
109
+        await (0, prompt_1.prompt)(options, [DISPLAY_NAME_QUESTION]);
110
+    }
111
+    if (!options.displayName) {
112
+        throw new error_1.FirebaseError("Display name for Web app cannot be empty");
113
+    }
114
+    const spinner = ora("Creating your Web app").start();
115
+    try {
116
+        const appData = await (0, apps_1.createWebApp)(options.project, { displayName: options.displayName });
117
+        spinner.succeed();
118
+        return appData;
119
+    }
120
+    catch (err) {
121
+        spinner.fail();
122
+        throw err;
123
+    }
124
+}
125
+exports.command = new command_1.Command("apps:create [platform] [displayName]")
126
+    .description("create a new Firebase app. [platform] can be IOS, ANDROID or WEB (case insensitive).")
127
+    .option("-a, --package-name <packageName>", "required package name for the Android app")
128
+    .option("-b, --bundle-id <bundleId>", "required bundle id for the iOS app")
129
+    .option("-s, --app-store-id <appStoreId>", "(optional) app store id for the iOS app")
130
+    .before(requireAuth_1.requireAuth)
131
+    .action(async (platform = "", displayName, options) => {
132
+    const projectId = (0, projectUtils_1.needProjectId)(options);
133
+    if (!options.nonInteractive && !platform) {
134
+        platform = await (0, prompt_1.promptOnce)({
135
+            type: "list",
136
+            message: "Please choose the platform of the app:",
137
+            choices: [
138
+                { name: "iOS", value: apps_1.AppPlatform.IOS },
139
+                { name: "Android", value: apps_1.AppPlatform.ANDROID },
140
+                { name: "Web", value: apps_1.AppPlatform.WEB },
141
+            ],
142
+        });
143
+    }
144
+    const appPlatform = (0, apps_1.getAppPlatform)(platform);
145
+    if (appPlatform === apps_1.AppPlatform.ANY) {
146
+        throw new error_1.FirebaseError("App platform must be provided");
147
+    }
148
+    logger_1.logger.info(`Create your ${appPlatform} app in project ${clc.bold(projectId)}:`);
149
+    options.displayName = displayName;
150
+    let appData;
151
+    switch (appPlatform) {
152
+        case apps_1.AppPlatform.IOS:
153
+            appData = await initiateIosAppCreation(options);
154
+            break;
155
+        case apps_1.AppPlatform.ANDROID:
156
+            appData = await initiateAndroidAppCreation(options);
157
+            break;
158
+        case apps_1.AppPlatform.WEB:
159
+            appData = await initiateWebAppCreation(options);
160
+            break;
161
+        default:
162
+            throw new error_1.FirebaseError("Unexpected error. This should not happen");
163
+    }
164
+    logPostAppCreationInformation(appData, appPlatform);
165
+    return appData;
166
+});

+ 53
- 0
node_modules/firebase-tools/lib/commands/apps-list.js View File

@@ -0,0 +1,53 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const ora = require("ora");
6
+const Table = require("cli-table");
7
+const command_1 = require("../command");
8
+const projectUtils_1 = require("../projectUtils");
9
+const apps_1 = require("../management/apps");
10
+const requireAuth_1 = require("../requireAuth");
11
+const logger_1 = require("../logger");
12
+const NOT_SPECIFIED = clc.yellow("[Not specified]");
13
+function logAppsList(apps) {
14
+    if (apps.length === 0) {
15
+        logger_1.logger.info(clc.bold("No apps found."));
16
+        return;
17
+    }
18
+    const tableHead = ["App Display Name", "App ID", "Platform"];
19
+    const table = new Table({ head: tableHead, style: { head: ["green"] } });
20
+    apps.forEach(({ appId, displayName, platform }) => {
21
+        table.push([displayName || NOT_SPECIFIED, appId, platform]);
22
+    });
23
+    logger_1.logger.info(table.toString());
24
+}
25
+function logAppCount(count = 0) {
26
+    if (count === 0) {
27
+        return;
28
+    }
29
+    logger_1.logger.info("");
30
+    logger_1.logger.info(`${count} app(s) total.`);
31
+}
32
+exports.command = new command_1.Command("apps:list [platform]")
33
+    .description("list the registered apps of a Firebase project. " +
34
+    "Optionally filter apps by [platform]: IOS, ANDROID or WEB (case insensitive)")
35
+    .before(requireAuth_1.requireAuth)
36
+    .action(async (platform, options) => {
37
+    const projectId = (0, projectUtils_1.needProjectId)(options);
38
+    const appPlatform = (0, apps_1.getAppPlatform)(platform || "");
39
+    let apps;
40
+    const spinner = ora("Preparing the list of your Firebase " +
41
+        `${appPlatform === apps_1.AppPlatform.ANY ? "" : appPlatform + " "}apps`).start();
42
+    try {
43
+        apps = await (0, apps_1.listFirebaseApps)(projectId, appPlatform);
44
+    }
45
+    catch (err) {
46
+        spinner.fail();
47
+        throw err;
48
+    }
49
+    spinner.succeed();
50
+    logAppsList(apps);
51
+    logAppCount(apps.length);
52
+    return apps;
53
+});

+ 103
- 0
node_modules/firebase-tools/lib/commands/apps-sdkconfig.js View File

@@ -0,0 +1,103 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const ora = require("ora");
5
+const fs = require("fs-extra");
6
+const command_1 = require("../command");
7
+const apps_1 = require("../management/apps");
8
+const projectUtils_1 = require("../projectUtils");
9
+const projects_1 = require("../management/projects");
10
+const error_1 = require("../error");
11
+const requireAuth_1 = require("../requireAuth");
12
+const logger_1 = require("../logger");
13
+const prompt_1 = require("../prompt");
14
+function checkForApps(apps, appPlatform) {
15
+    if (!apps.length) {
16
+        throw new error_1.FirebaseError(`There are no ${appPlatform === apps_1.AppPlatform.ANY ? "" : appPlatform + " "}apps ` +
17
+            "associated with this Firebase project");
18
+    }
19
+}
20
+async function selectAppInteractively(apps, appPlatform) {
21
+    checkForApps(apps, appPlatform);
22
+    const choices = apps.map((app) => {
23
+        return {
24
+            name: `${app.displayName || app.bundleId || app.packageName}` +
25
+                ` - ${app.appId} (${app.platform})`,
26
+            value: app,
27
+        };
28
+    });
29
+    return await (0, prompt_1.promptOnce)({
30
+        type: "list",
31
+        message: `Select the ${appPlatform === apps_1.AppPlatform.ANY ? "" : appPlatform + " "}` +
32
+            "app to get the configuration data:",
33
+        choices,
34
+    });
35
+}
36
+exports.command = new command_1.Command("apps:sdkconfig [platform] [appId]")
37
+    .description("print the Google Services config of a Firebase app. " +
38
+    "[platform] can be IOS, ANDROID or WEB (case insensitive)")
39
+    .option("-o, --out [file]", "(optional) write config output to a file")
40
+    .before(requireAuth_1.requireAuth)
41
+    .action(async (platform = "", appId = "", options) => {
42
+    let appPlatform = (0, apps_1.getAppPlatform)(platform);
43
+    if (!appId) {
44
+        let projectId = (0, projectUtils_1.needProjectId)(options);
45
+        if (options.nonInteractive && !projectId) {
46
+            throw new error_1.FirebaseError("Must supply app and project ids in non-interactive mode.");
47
+        }
48
+        else if (!projectId) {
49
+            const result = await (0, projects_1.getOrPromptProject)(options);
50
+            projectId = result.projectId;
51
+        }
52
+        const apps = await (0, apps_1.listFirebaseApps)(projectId, appPlatform);
53
+        checkForApps(apps, appPlatform);
54
+        if (apps.length === 1) {
55
+            appId = apps[0].appId;
56
+            appPlatform = apps[0].platform;
57
+        }
58
+        else if (options.nonInteractive) {
59
+            throw new error_1.FirebaseError(`Project ${projectId} has multiple apps, must specify an app id.`);
60
+        }
61
+        else {
62
+            const appMetadata = await selectAppInteractively(apps, appPlatform);
63
+            appId = appMetadata.appId;
64
+            appPlatform = appMetadata.platform;
65
+        }
66
+    }
67
+    let configData;
68
+    const spinner = ora(`Downloading configuration data of your Firebase ${appPlatform} app`).start();
69
+    try {
70
+        configData = await (0, apps_1.getAppConfig)(appId, appPlatform);
71
+    }
72
+    catch (err) {
73
+        spinner.fail();
74
+        throw err;
75
+    }
76
+    spinner.succeed();
77
+    const fileInfo = (0, apps_1.getAppConfigFile)(configData, appPlatform);
78
+    if (appPlatform === apps_1.AppPlatform.WEB) {
79
+        fileInfo.sdkConfig = configData;
80
+    }
81
+    if (options.out === undefined) {
82
+        logger_1.logger.info(fileInfo.fileContents);
83
+        return fileInfo;
84
+    }
85
+    const shouldUseDefaultFilename = options.out === true || options.out === "";
86
+    const filename = shouldUseDefaultFilename ? configData.fileName : options.out;
87
+    if (fs.existsSync(filename)) {
88
+        if (options.nonInteractive) {
89
+            throw new error_1.FirebaseError(`${filename} already exists`);
90
+        }
91
+        const overwrite = await (0, prompt_1.promptOnce)({
92
+            type: "confirm",
93
+            default: false,
94
+            message: `${filename} already exists. Do you want to overwrite?`,
95
+        });
96
+        if (!overwrite) {
97
+            return configData;
98
+        }
99
+    }
100
+    fs.writeFileSync(filename, fileInfo.fileContents);
101
+    logger_1.logger.info(`App configuration is written in ${filename}`);
102
+    return configData;
103
+});

+ 44
- 0
node_modules/firebase-tools/lib/commands/auth-export.js View File

@@ -0,0 +1,44 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const fs = require("fs");
6
+const os = require("os");
7
+const command_1 = require("../command");
8
+const logger_1 = require("../logger");
9
+const projectUtils_1 = require("../projectUtils");
10
+const requirePermissions_1 = require("../requirePermissions");
11
+const accountExporter_1 = require("../accountExporter");
12
+const MAX_BATCH_SIZE = 1000;
13
+exports.command = new command_1.Command("auth:export [dataFile]")
14
+    .description("Export accounts from your Firebase project into a data file")
15
+    .option("--format <format>", "Format of exported data (csv, json). Ignored if <dataFile> has format extension.")
16
+    .before(requirePermissions_1.requirePermissions, ["firebaseauth.users.get"])
17
+    .action((dataFile, options) => {
18
+    const projectId = (0, projectUtils_1.needProjectId)(options);
19
+    const checkRes = (0, accountExporter_1.validateOptions)(options, dataFile);
20
+    if (!checkRes.format) {
21
+        return checkRes;
22
+    }
23
+    const writeStream = fs.createWriteStream(dataFile);
24
+    if (checkRes.format === "json") {
25
+        writeStream.write('{"users": [' + os.EOL);
26
+    }
27
+    const exportOptions = {
28
+        format: checkRes.format,
29
+        writeStream,
30
+        batchSize: MAX_BATCH_SIZE,
31
+    };
32
+    logger_1.logger.info("Exporting accounts to " + clc.bold(dataFile));
33
+    return (0, accountExporter_1.serialExportUsers)(projectId, exportOptions).then(() => {
34
+        if (exportOptions.format === "json") {
35
+            writeStream.write("]}");
36
+        }
37
+        writeStream.end();
38
+        return new Promise((resolve, reject) => {
39
+            writeStream.on("finish", resolve);
40
+            writeStream.on("close", resolve);
41
+            writeStream.on("error", reject);
42
+        });
43
+    });
44
+});

+ 114
- 0
node_modules/firebase-tools/lib/commands/auth-import.js View File

@@ -0,0 +1,114 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const csv_parse_1 = require("csv-parse");
5
+const Chain = require("stream-chain");
6
+const clc = require("colorette");
7
+const fs = require("fs-extra");
8
+const Pick = require("stream-json/filters/Pick");
9
+const StreamArray = require("stream-json/streamers/StreamArray");
10
+const command_1 = require("../command");
11
+const error_1 = require("../error");
12
+const logger_1 = require("../logger");
13
+const projectUtils_1 = require("../projectUtils");
14
+const requirePermissions_1 = require("../requirePermissions");
15
+const accountImporter_1 = require("../accountImporter");
16
+const MAX_BATCH_SIZE = 1000;
17
+exports.command = new command_1.Command("auth:import [dataFile]")
18
+    .description("import users into your Firebase project from a data file(.csv or .json)")
19
+    .option("--hash-algo <hashAlgo>", "specify the hash algorithm used in password for these accounts")
20
+    .option("--hash-key <hashKey>", "specify the key used in hash algorithm")
21
+    .option("--salt-separator <saltSeparator>", "specify the salt separator which will be appended to salt when verifying password. only used by SCRYPT now.")
22
+    .option("--rounds <rounds>", "specify how many rounds for hash calculation.")
23
+    .option("--mem-cost <memCost>", "specify the memory cost for firebase scrypt, or cpu/memory cost for standard scrypt")
24
+    .option("--parallelization <parallelization>", "specify the parallelization for standard scrypt.")
25
+    .option("--block-size <blockSize>", "specify the block size (normally is 8) for standard scrypt.")
26
+    .option("--dk-len <dkLen>", "specify derived key length for standard scrypt.")
27
+    .option("--hash-input-order <hashInputOrder>", "specify the order of password and salt. Possible values are SALT_FIRST and PASSWORD_FIRST. " +
28
+    "MD5, SHA1, SHA256, SHA512, HMAC_MD5, HMAC_SHA1, HMAC_SHA256, HMAC_SHA512 support this flag.")
29
+    .before(requirePermissions_1.requirePermissions, ["firebaseauth.users.create", "firebaseauth.users.update"])
30
+    .action(async (dataFile, options) => {
31
+    const projectId = (0, projectUtils_1.needProjectId)(options);
32
+    const checkRes = (0, accountImporter_1.validateOptions)(options);
33
+    if (!checkRes.valid) {
34
+        return checkRes;
35
+    }
36
+    const hashOptions = checkRes;
37
+    if (!dataFile.endsWith(".csv") && !dataFile.endsWith(".json")) {
38
+        throw new error_1.FirebaseError("Data file must end with .csv or .json");
39
+    }
40
+    const stats = await fs.stat(dataFile);
41
+    const fileSizeInBytes = stats.size;
42
+    logger_1.logger.info(`Processing ${clc.bold(dataFile)} (${fileSizeInBytes} bytes)`);
43
+    const batches = [];
44
+    let currentBatch = [];
45
+    let counter = 0;
46
+    let userListArr = [];
47
+    const inStream = fs.createReadStream(dataFile);
48
+    if (dataFile.endsWith(".csv")) {
49
+        userListArr = await new Promise((resolve, reject) => {
50
+            const parser = (0, csv_parse_1.parse)();
51
+            parser
52
+                .on("readable", () => {
53
+                let record = [];
54
+                while ((record = parser.read()) !== null) {
55
+                    counter++;
56
+                    const trimmed = record.map((s) => {
57
+                        const str = s.trim().replace(/^["|'](.*)["|']$/, "$1");
58
+                        return str === "" ? undefined : str;
59
+                    });
60
+                    const user = (0, accountImporter_1.transArrayToUser)(trimmed);
61
+                    const err = user.error;
62
+                    if (err) {
63
+                        return reject(new error_1.FirebaseError(`Line ${counter} (${record.join(",")}) has invalid data format: ${err}`));
64
+                    }
65
+                    currentBatch.push(user);
66
+                    if (currentBatch.length === MAX_BATCH_SIZE) {
67
+                        batches.push(currentBatch);
68
+                        currentBatch = [];
69
+                    }
70
+                }
71
+            })
72
+                .on("end", () => {
73
+                if (currentBatch.length) {
74
+                    batches.push(currentBatch);
75
+                }
76
+                resolve(batches);
77
+            });
78
+            inStream.pipe(parser);
79
+        });
80
+    }
81
+    else {
82
+        userListArr = await new Promise((resolve, reject) => {
83
+            const pipeline = new Chain([
84
+                Pick.withParser({ filter: /^users$/ }),
85
+                StreamArray.streamArray(),
86
+                ({ value }) => {
87
+                    counter++;
88
+                    const user = (0, accountImporter_1.validateUserJson)(value);
89
+                    const err = user.error;
90
+                    if (err) {
91
+                        throw new error_1.FirebaseError(`Validation Error: ${err}`);
92
+                    }
93
+                    currentBatch.push(value);
94
+                    if (currentBatch.length === MAX_BATCH_SIZE) {
95
+                        batches.push(currentBatch);
96
+                        currentBatch = [];
97
+                    }
98
+                },
99
+            ]);
100
+            pipeline.once("error", reject);
101
+            pipeline.on("finish", () => {
102
+                if (currentBatch.length) {
103
+                    batches.push(currentBatch);
104
+                }
105
+                resolve(batches);
106
+            });
107
+            inStream.pipe(pipeline);
108
+        });
109
+    }
110
+    logger_1.logger.debug(`Preparing to import ${counter} user records in ${userListArr.length} batches.`);
111
+    if (userListArr.length) {
112
+        return (0, accountImporter_1.serialImportUsers)(projectId, hashOptions, userListArr, 0);
113
+    }
114
+});

+ 26
- 0
node_modules/firebase-tools/lib/commands/crashlytics-mappingfile-generateid.js View File

@@ -0,0 +1,26 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const utils = require("../utils");
6
+const buildToolsJarHelper_1 = require("../crashlytics/buildToolsJarHelper");
7
+const error_1 = require("../error");
8
+exports.command = new command_1.Command("crashlytics:mappingfile:generateid")
9
+    .description("generate a mapping file id and write it to an Android resource file, which will be built into the app")
10
+    .option("--resource-file <resourceFile>", "path to the Android resource XML file that will be created or updated.")
11
+    .action(async (options) => {
12
+    const debug = !!options.debug;
13
+    const resourceFilePath = options.resourceFile;
14
+    if (!resourceFilePath) {
15
+        throw new error_1.FirebaseError("set --resource-file <resourceFile> to an Android resource file path, e.g. app/src/main/res/values/crashlytics.xml");
16
+    }
17
+    const jarFile = await (0, buildToolsJarHelper_1.fetchBuildtoolsJar)();
18
+    const jarOptions = { resourceFilePath };
19
+    utils.logBullet(`Updating resource file: ${resourceFilePath}`);
20
+    const generateIdArgs = buildArgs(jarOptions);
21
+    (0, buildToolsJarHelper_1.runBuildtoolsCommand)(jarFile, generateIdArgs, debug);
22
+    utils.logBullet("Successfully updated mapping file id");
23
+});
24
+function buildArgs(options) {
25
+    return ["-injectMappingFileIdIntoResource", options.resourceFilePath, "-verbose"];
26
+}

+ 46
- 0
node_modules/firebase-tools/lib/commands/crashlytics-mappingfile-upload.js View File

@@ -0,0 +1,46 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const error_1 = require("../error");
6
+const utils = require("../utils");
7
+const buildToolsJarHelper_1 = require("../crashlytics/buildToolsJarHelper");
8
+exports.command = new command_1.Command("crashlytics:mappingfile:upload <mappingFile>")
9
+    .description("upload a ProGuard/R8-compatible mapping file to deobfuscate stack traces")
10
+    .option("--app <appID>", "the app id of your Firebase app")
11
+    .option("--resource-file <resourceFile>", "path to the Android resource XML file that includes the mapping file id")
12
+    .action(async (mappingFile, options) => {
13
+    const app = getGoogleAppID(options);
14
+    const debug = !!options.debug;
15
+    if (!mappingFile) {
16
+        throw new error_1.FirebaseError("set `--mapping-file <mappingFile>` to a valid mapping file path, e.g. app/build/outputs/mapping.txt");
17
+    }
18
+    const mappingFilePath = mappingFile;
19
+    const resourceFilePath = options.resourceFile;
20
+    if (!resourceFilePath) {
21
+        throw new error_1.FirebaseError("set --resource-file <resourceFile> to a valid Android resource file path, e.g. app/main/res/values/strings.xml");
22
+    }
23
+    const jarFile = await (0, buildToolsJarHelper_1.fetchBuildtoolsJar)();
24
+    const jarOptions = { app, mappingFilePath, resourceFilePath };
25
+    utils.logBullet(`Uploading mapping file: ${mappingFilePath}`);
26
+    const uploadArgs = buildArgs(jarOptions);
27
+    (0, buildToolsJarHelper_1.runBuildtoolsCommand)(jarFile, uploadArgs, debug);
28
+    utils.logBullet("Successfully uploaded mapping file");
29
+});
30
+function getGoogleAppID(options) {
31
+    if (!options.app) {
32
+        throw new error_1.FirebaseError("set --app <appId> to a valid Firebase application id, e.g. 1:00000000:android:0000000");
33
+    }
34
+    return options.app;
35
+}
36
+function buildArgs(options) {
37
+    return [
38
+        "-uploadMappingFile",
39
+        options.mappingFilePath,
40
+        "-resourceFile",
41
+        options.resourceFilePath,
42
+        "-googleAppId",
43
+        options.app,
44
+        "-verbose",
45
+    ];
46
+}

+ 78
- 0
node_modules/firebase-tools/lib/commands/crashlytics-symbols-upload.js View File

@@ -0,0 +1,78 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const os = require("os");
5
+const path = require("path");
6
+const uuid = require("uuid");
7
+const command_1 = require("../command");
8
+const error_1 = require("../error");
9
+const utils = require("../utils");
10
+const buildToolsJarHelper_1 = require("../crashlytics/buildToolsJarHelper");
11
+var SymbolGenerator;
12
+(function (SymbolGenerator) {
13
+    SymbolGenerator["breakpad"] = "breakpad";
14
+    SymbolGenerator["csym"] = "csym";
15
+})(SymbolGenerator || (SymbolGenerator = {}));
16
+const SYMBOL_CACHE_ROOT_DIR = process.env.FIREBASE_CRASHLYTICS_CACHE_PATH || os.tmpdir();
17
+exports.command = new command_1.Command("crashlytics:symbols:upload <symbolFiles...>")
18
+    .description("upload symbols for native code, to symbolicate stack traces")
19
+    .option("--app <appID>", "the app id of your Firebase app")
20
+    .option("--generator [breakpad|csym]", "the symbol generator being used, default is breakpad")
21
+    .option("--dry-run", "generate symbols without uploading them")
22
+    .action(async (symbolFiles, options) => {
23
+    const app = getGoogleAppID(options);
24
+    const generator = getSymbolGenerator(options);
25
+    const dryRun = !!options.dryRun;
26
+    const debug = !!options.debug;
27
+    const jarFile = await (0, buildToolsJarHelper_1.fetchBuildtoolsJar)();
28
+    const jarOptions = {
29
+        app,
30
+        generator,
31
+        cachePath: path.join(SYMBOL_CACHE_ROOT_DIR, `crashlytics-${uuid.v4()}`, "nativeSymbols", app.replace(/:/g, "-"), generator),
32
+        symbolFile: "",
33
+        generate: true,
34
+    };
35
+    for (const symbolFile of symbolFiles) {
36
+        utils.logBullet(`Generating symbols for ${symbolFile}`);
37
+        const generateArgs = buildArgs(Object.assign(Object.assign({}, jarOptions), { symbolFile }));
38
+        (0, buildToolsJarHelper_1.runBuildtoolsCommand)(jarFile, generateArgs, debug);
39
+        utils.logBullet(`Generated symbols for ${symbolFile}`);
40
+        utils.logBullet(`Output Path: ${jarOptions.cachePath}`);
41
+    }
42
+    if (dryRun) {
43
+        utils.logBullet("Skipping upload because --dry-run was passed");
44
+        return;
45
+    }
46
+    utils.logBullet(`Uploading all generated symbols...`);
47
+    const uploadArgs = buildArgs(Object.assign(Object.assign({}, jarOptions), { generate: false }));
48
+    (0, buildToolsJarHelper_1.runBuildtoolsCommand)(jarFile, uploadArgs, debug);
49
+    utils.logBullet("Successfully uploaded all symbols");
50
+});
51
+function getGoogleAppID(options) {
52
+    if (!options.app) {
53
+        throw new error_1.FirebaseError("set --app <appId> to a valid Firebase application id, e.g. 1:00000000:android:0000000");
54
+    }
55
+    return options.app;
56
+}
57
+function getSymbolGenerator(options) {
58
+    if (!options.generator) {
59
+        return SymbolGenerator.breakpad;
60
+    }
61
+    if (!Object.values(SymbolGenerator).includes(options.generator)) {
62
+        throw new error_1.FirebaseError('--symbol-generator should be set to either "breakpad" or "csym"');
63
+    }
64
+    return options.generator;
65
+}
66
+function buildArgs(options) {
67
+    const baseArgs = [
68
+        "-symbolGenerator",
69
+        options.generator,
70
+        "-symbolFileCacheDir",
71
+        options.cachePath,
72
+        "-verbose",
73
+    ];
74
+    if (options.generate) {
75
+        return baseArgs.concat(["-generateNativeSymbols", "-unstrippedLibrary", options.symbolFile]);
76
+    }
77
+    return baseArgs.concat(["-uploadNativeSymbols", "-googleAppId", options.app]);
78
+}

+ 122
- 0
node_modules/firebase-tools/lib/commands/database-get.js View File

@@ -0,0 +1,122 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const fs = require("fs");
5
+const url = require("url");
6
+const apiv2_1 = require("../apiv2");
7
+const command_1 = require("../command");
8
+const types_1 = require("../emulator/types");
9
+const error_1 = require("../error");
10
+const database_1 = require("../management/database");
11
+const commandUtils_1 = require("../emulator/commandUtils");
12
+const api_1 = require("../database/api");
13
+const requirePermissions_1 = require("../requirePermissions");
14
+const logger_1 = require("../logger");
15
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
16
+const responseToError_1 = require("../responseToError");
17
+const utils = require("../utils");
18
+function applyStringOpts(dest, src, keys, jsonKeys) {
19
+    for (const key of keys) {
20
+        if (src[key]) {
21
+            dest[key] = src[key];
22
+        }
23
+    }
24
+    for (const key of jsonKeys) {
25
+        let jsonVal;
26
+        try {
27
+            jsonVal = JSON.parse(src[key]);
28
+        }
29
+        catch (_) {
30
+            jsonVal = src[key];
31
+        }
32
+        if (src[key]) {
33
+            dest[key] = JSON.stringify(jsonVal);
34
+        }
35
+    }
36
+}
37
+exports.command = new command_1.Command("database:get <path>")
38
+    .description("fetch and print JSON data at the specified path")
39
+    .option("-o, --output <filename>", "save output to the specified file")
40
+    .option("--pretty", "pretty print response")
41
+    .option("--shallow", "return shallow response")
42
+    .option("--export", "include priorities in the output response")
43
+    .option("--order-by <key>", "select a child key by which to order results")
44
+    .option("--order-by-key", "order by key name")
45
+    .option("--order-by-value", "order by primitive value")
46
+    .option("--limit-to-first <num>", "limit to the first <num> results")
47
+    .option("--limit-to-last <num>", "limit to the last <num> results")
48
+    .option("--start-at <val>", "start results at <val> (based on specified ordering)")
49
+    .option("--end-at <val>", "end results at <val> (based on specified ordering)")
50
+    .option("--equal-to <val>", "restrict results to <val> (based on specified ordering)")
51
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
52
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.get"])
53
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
54
+    .before(database_1.populateInstanceDetails)
55
+    .before(commandUtils_1.printNoticeIfEmulated, types_1.Emulators.DATABASE)
56
+    .action(async (path, options) => {
57
+    if (!path.startsWith("/")) {
58
+        return utils.reject("Path must begin with /", { exit: 1 });
59
+    }
60
+    const dbHost = (0, api_1.realtimeOriginOrEmulatorOrCustomUrl)(options.instanceDetails.databaseUrl);
61
+    const dbUrl = utils.getDatabaseUrl(dbHost, options.instance, path + ".json");
62
+    const query = {};
63
+    if (options.shallow) {
64
+        query.shallow = "true";
65
+    }
66
+    if (options.pretty) {
67
+        query.print = "pretty";
68
+    }
69
+    if (options.export) {
70
+        query.format = "export";
71
+    }
72
+    if (options.orderByKey) {
73
+        options.orderBy = "$key";
74
+    }
75
+    if (options.orderByValue) {
76
+        options.orderBy = "$value";
77
+    }
78
+    applyStringOpts(query, options, ["limitToFirst", "limitToLast"], ["orderBy", "startAt", "endAt", "equalTo"]);
79
+    const urlObj = new url.URL(dbUrl);
80
+    const client = new apiv2_1.Client({
81
+        urlPrefix: urlObj.origin,
82
+        auth: true,
83
+    });
84
+    const res = await client.request({
85
+        method: "GET",
86
+        path: urlObj.pathname,
87
+        queryParams: query,
88
+        responseType: "stream",
89
+        resolveOnHTTPError: true,
90
+    });
91
+    const fileOut = !!options.output;
92
+    const outStream = fileOut ? fs.createWriteStream(options.output) : process.stdout;
93
+    if (res.status >= 400) {
94
+        const r = await res.response.text();
95
+        let d;
96
+        try {
97
+            d = JSON.parse(r);
98
+        }
99
+        catch (e) {
100
+            throw new error_1.FirebaseError("Malformed JSON response", { original: e, exit: 2 });
101
+        }
102
+        throw (0, responseToError_1.responseToError)({ statusCode: res.status }, d);
103
+    }
104
+    res.body.pipe(outStream, { end: false });
105
+    return new Promise((resolve) => {
106
+        res.body.once("end", () => {
107
+            if (outStream === process.stdout) {
108
+                outStream.write("\n");
109
+                resolve();
110
+            }
111
+            else if (outStream instanceof fs.WriteStream) {
112
+                outStream.write("\n");
113
+                outStream.on("close", () => resolve());
114
+                outStream.close();
115
+            }
116
+            else {
117
+                logger_1.logger.debug("[database:get] Could not write line break to outStream");
118
+                resolve();
119
+            }
120
+        });
121
+    });
122
+});

+ 29
- 0
node_modules/firebase-tools/lib/commands/database-instances-create.js View File

@@ -0,0 +1,29 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const logger_1 = require("../logger");
6
+const requirePermissions_1 = require("../requirePermissions");
7
+const commandUtils_1 = require("../emulator/commandUtils");
8
+const types_1 = require("../emulator/types");
9
+const database_1 = require("../management/database");
10
+const projectUtils_1 = require("../projectUtils");
11
+const getDefaultDatabaseInstance_1 = require("../getDefaultDatabaseInstance");
12
+const error_1 = require("../error");
13
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
14
+exports.command = new command_1.Command("database:instances:create <instanceName>")
15
+    .description("create a realtime database instance")
16
+    .option("-l, --location <location>", "(optional) location for the database instance, defaults to us-central1")
17
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.create"])
18
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
19
+    .action(async (instanceName, options) => {
20
+    const projectId = (0, projectUtils_1.needProjectId)(options);
21
+    const defaultDatabaseInstance = await (0, getDefaultDatabaseInstance_1.getDefaultDatabaseInstance)({ project: projectId });
22
+    if (defaultDatabaseInstance === "") {
23
+        throw new error_1.FirebaseError(requireDatabaseInstance_1.MISSING_DEFAULT_INSTANCE_ERROR_MESSAGE);
24
+    }
25
+    const location = (0, database_1.parseDatabaseLocation)(options.location, database_1.DatabaseLocation.US_CENTRAL1);
26
+    const instance = await (0, database_1.createInstance)(projectId, instanceName, location, database_1.DatabaseInstanceType.USER_DATABASE);
27
+    logger_1.logger.info(`created database instance ${instance.name}`);
28
+    return instance;
29
+});

+ 76
- 0
node_modules/firebase-tools/lib/commands/database-instances-list.js View File

@@ -0,0 +1,76 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const Table = require("cli-table");
6
+const clc = require("colorette");
7
+const ora = require("ora");
8
+const logger_1 = require("../logger");
9
+const requirePermissions_1 = require("../requirePermissions");
10
+const projectUtils_1 = require("../projectUtils");
11
+const firedata = require("../gcp/firedata");
12
+const types_1 = require("../emulator/types");
13
+const commandUtils_1 = require("../emulator/commandUtils");
14
+const experiments = require("../experiments");
15
+const projectUtils_2 = require("../projectUtils");
16
+const database_1 = require("../management/database");
17
+function logInstances(instances) {
18
+    if (instances.length === 0) {
19
+        logger_1.logger.info(clc.bold("No database instances found."));
20
+        return;
21
+    }
22
+    const tableHead = ["Database Instance Name", "Location", "Type", "State"];
23
+    const table = new Table({ head: tableHead, style: { head: ["green"] } });
24
+    instances.forEach((db) => {
25
+        table.push([db.name, db.location, db.type, db.state]);
26
+    });
27
+    logger_1.logger.info(table.toString());
28
+}
29
+function logInstancesCount(count = 0) {
30
+    if (count === 0) {
31
+        return;
32
+    }
33
+    logger_1.logger.info("");
34
+    logger_1.logger.info(`${count} database instance(s) total.`);
35
+}
36
+exports.command = new command_1.Command("database:instances:list")
37
+    .description("list realtime database instances, optionally filtered by a specified location")
38
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.list"])
39
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
40
+    .action(async (options) => {
41
+    const location = (0, database_1.parseDatabaseLocation)(options.location, database_1.DatabaseLocation.ANY);
42
+    const spinner = ora("Preparing the list of your Firebase Realtime Database instances" +
43
+        `${location === database_1.DatabaseLocation.ANY ? "" : ` for location: ${location}`}`).start();
44
+    let instances;
45
+    if (experiments.isEnabled("rtdbmanagement")) {
46
+        const projectId = (0, projectUtils_2.needProjectId)(options);
47
+        try {
48
+            instances = await (0, database_1.listDatabaseInstances)(projectId, location);
49
+        }
50
+        catch (err) {
51
+            spinner.fail();
52
+            throw err;
53
+        }
54
+        spinner.succeed();
55
+        logInstances(instances);
56
+        logInstancesCount(instances.length);
57
+        return instances;
58
+    }
59
+    const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
60
+    try {
61
+        instances = await firedata.listDatabaseInstances(projectNumber);
62
+    }
63
+    catch (err) {
64
+        spinner.fail();
65
+        throw err;
66
+    }
67
+    spinner.succeed();
68
+    for (const instance of instances) {
69
+        logger_1.logger.info(instance.instance);
70
+    }
71
+    logger_1.logger.info(`Project ${options.project} has ${instances.length} database instances`);
72
+    return instances;
73
+});
74
+if (experiments.isEnabled("rtdbmanagement")) {
75
+    exports.command = exports.command.option("-l, --location <location>", "(optional) location for the database instance, defaults to us-central1");
76
+}

+ 46
- 0
node_modules/firebase-tools/lib/commands/database-profile.js View File

@@ -0,0 +1,46 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
6
+const database_1 = require("../management/database");
7
+const requirePermissions_1 = require("../requirePermissions");
8
+const utils = require("../utils");
9
+const profiler_1 = require("../profiler");
10
+const types_1 = require("../emulator/types");
11
+const commandUtils_1 = require("../emulator/commandUtils");
12
+const description = "profile the Realtime Database and generate a usage report";
13
+exports.command = new command_1.Command("database:profile")
14
+    .description(description)
15
+    .option("-o, --output <filename>", "save the output to the specified file")
16
+    .option("-d, --duration <seconds>", "collect database usage information for the specified number of seconds")
17
+    .option("--raw", "output the raw stats collected as newline delimited json")
18
+    .option("--no-collapse", "prevent collapsing similar paths into $wildcard locations")
19
+    .option("-i, --input <filename>", "generate the report based on the specified file instead " +
20
+    "of streaming logs from the database")
21
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
22
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
23
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
24
+    .before(database_1.populateInstanceDetails)
25
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
26
+    .action((options) => {
27
+    if (options.raw && options.input) {
28
+        return utils.reject("Cannot specify both an input file and raw format", {
29
+            exit: 1,
30
+        });
31
+    }
32
+    else if (options.parent.json && options.raw) {
33
+        return utils.reject("Cannot output raw data in json format", { exit: 1 });
34
+    }
35
+    else if (options.input && options.duration !== undefined) {
36
+        return utils.reject("Cannot specify a duration for input files", {
37
+            exit: 1,
38
+        });
39
+    }
40
+    else if (options.duration !== undefined && options.duration <= 0) {
41
+        return utils.reject("Must specify a positive number of seconds", {
42
+            exit: 1,
43
+        });
44
+    }
45
+    return (0, profiler_1.profiler)(options);
46
+});

+ 63
- 0
node_modules/firebase-tools/lib/commands/database-push.js View File

@@ -0,0 +1,63 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const fs = require("fs");
6
+const apiv2_1 = require("../apiv2");
7
+const command_1 = require("../command");
8
+const types_1 = require("../emulator/types");
9
+const error_1 = require("../error");
10
+const database_1 = require("../management/database");
11
+const commandUtils_1 = require("../emulator/commandUtils");
12
+const api_1 = require("../database/api");
13
+const requirePermissions_1 = require("../requirePermissions");
14
+const url_1 = require("url");
15
+const logger_1 = require("../logger");
16
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
17
+const utils = require("../utils");
18
+exports.command = new command_1.Command("database:push <path> [infile]")
19
+    .description("add a new JSON object to a list of data in your Firebase")
20
+    .option("-d, --data <data>", "specify escaped JSON directly")
21
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
22
+    .option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
23
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
24
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
25
+    .before(database_1.populateInstanceDetails)
26
+    .before(commandUtils_1.printNoticeIfEmulated, types_1.Emulators.DATABASE)
27
+    .action(async (path, infile, options) => {
28
+    if (!path.startsWith("/")) {
29
+        throw new error_1.FirebaseError("Path must begin with /");
30
+    }
31
+    const inStream = utils.stringToStream(options.data) || (infile ? fs.createReadStream(infile) : process.stdin);
32
+    const origin = (0, api_1.realtimeOriginOrEmulatorOrCustomUrl)(options.instanceDetails.databaseUrl);
33
+    const u = new url_1.URL(utils.getDatabaseUrl(origin, options.instance, path + ".json"));
34
+    if (options.disableTriggers) {
35
+        u.searchParams.set("disableTriggers", "true");
36
+    }
37
+    if (!infile && !options.data) {
38
+        utils.explainStdin();
39
+    }
40
+    logger_1.logger.debug(`Database URL: ${u}`);
41
+    const c = new apiv2_1.Client({ urlPrefix: u.origin, auth: true });
42
+    let res;
43
+    try {
44
+        res = await c.request({
45
+            method: "POST",
46
+            path: u.pathname,
47
+            body: inStream,
48
+            queryParams: u.searchParams,
49
+        });
50
+    }
51
+    catch (err) {
52
+        logger_1.logger.debug(err);
53
+        throw new error_1.FirebaseError(`Unexpected error while pushing data: ${err}`, { exit: 2 });
54
+    }
55
+    if (!path.endsWith("/")) {
56
+        path += "/";
57
+    }
58
+    const consoleUrl = utils.getDatabaseViewDataUrl(origin, options.project, options.instance, path + res.body.name);
59
+    utils.logSuccess("Data pushed successfully");
60
+    logger_1.logger.info();
61
+    logger_1.logger.info(clc.bold("View data at:"), consoleUrl);
62
+    return { key: res.body.name };
63
+});

+ 42
- 0
node_modules/firebase-tools/lib/commands/database-remove.js View File

@@ -0,0 +1,42 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
6
+const requirePermissions_1 = require("../requirePermissions");
7
+const remove_1 = require("../database/remove");
8
+const types_1 = require("../emulator/types");
9
+const commandUtils_1 = require("../emulator/commandUtils");
10
+const database_1 = require("../management/database");
11
+const api_1 = require("../database/api");
12
+const utils = require("../utils");
13
+const prompt_1 = require("../prompt");
14
+const clc = require("colorette");
15
+exports.command = new command_1.Command("database:remove <path>")
16
+    .description("remove data from your Firebase at the specified path")
17
+    .option("-f, --force", "pass this option to bypass confirmation prompt")
18
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
19
+    .option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
20
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
21
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
22
+    .before(database_1.populateInstanceDetails)
23
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
24
+    .action(async (path, options) => {
25
+    if (!path.startsWith("/")) {
26
+        return utils.reject("Path must begin with /", { exit: 1 });
27
+    }
28
+    const origin = (0, api_1.realtimeOriginOrEmulatorOrCustomUrl)(options.instanceDetails.databaseUrl);
29
+    const databaseUrl = utils.getDatabaseUrl(origin, options.instance, path);
30
+    const confirm = await (0, prompt_1.promptOnce)({
31
+        type: "confirm",
32
+        name: "force",
33
+        default: false,
34
+        message: "You are about to remove all data at " + clc.cyan(databaseUrl) + ". Are you sure?",
35
+    }, options);
36
+    if (!confirm) {
37
+        return utils.reject("Command aborted.", { exit: 1 });
38
+    }
39
+    const removeOps = new remove_1.default(options.instance, path, origin, !!options.disableTriggers);
40
+    await removeOps.execute();
41
+    utils.logSuccess("Data removed successfully");
42
+});

+ 24
- 0
node_modules/firebase-tools/lib/commands/database-rules-canary.js View File

@@ -0,0 +1,24 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const requirePermissions_1 = require("../requirePermissions");
6
+const metadata = require("../database/metadata");
7
+const types_1 = require("../emulator/types");
8
+const commandUtils_1 = require("../emulator/commandUtils");
9
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
10
+exports.command = new command_1.Command("database:rules:canary <rulesetId>")
11
+    .description("mark a staged ruleset as the canary ruleset")
12
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, uses default database instance)")
13
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
14
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
15
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
16
+    .action(async (rulesetId, options) => {
17
+    const oldLabels = await metadata.getRulesetLabels(options.instance);
18
+    const newLabels = {
19
+        stable: oldLabels.stable,
20
+        canary: rulesetId,
21
+    };
22
+    await metadata.setRulesetLabels(options.instance, newLabels);
23
+    return newLabels;
24
+});

+ 22
- 0
node_modules/firebase-tools/lib/commands/database-rules-get.js View File

@@ -0,0 +1,22 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const logger_1 = require("../logger");
6
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
7
+const requirePermissions_1 = require("../requirePermissions");
8
+const metadata = require("../database/metadata");
9
+const types_1 = require("../emulator/types");
10
+const commandUtils_1 = require("../emulator/commandUtils");
11
+exports.command = new command_1.Command("database:rules:get <rulesetId>")
12
+    .description("get a realtime database ruleset by id")
13
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, uses default database instance)")
14
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.get"])
15
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
16
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
17
+    .action(async (rulesetId, options) => {
18
+    const ruleset = await metadata.getRuleset(options.instance, rulesetId);
19
+    logger_1.logger.info(`Ruleset ${ruleset.id} was created at ${ruleset.createdAt}`);
20
+    logger_1.logger.info(ruleset.source);
21
+    return ruleset;
22
+});

+ 36
- 0
node_modules/firebase-tools/lib/commands/database-rules-list.js View File

@@ -0,0 +1,36 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const logger_1 = require("../logger");
6
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
7
+const requirePermissions_1 = require("../requirePermissions");
8
+const metadata = require("../database/metadata");
9
+const types_1 = require("../emulator/types");
10
+const commandUtils_1 = require("../emulator/commandUtils");
11
+exports.command = new command_1.Command("database:rules:list")
12
+    .description("list realtime database rulesets")
13
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, uses default database instance)")
14
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.get"])
15
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
16
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
17
+    .action(async (options) => {
18
+    const labeled = await metadata.getRulesetLabels(options.instance);
19
+    const rulesets = await metadata.listAllRulesets(options.instance);
20
+    for (const ruleset of rulesets) {
21
+        const labels = [];
22
+        if (ruleset.id === labeled.stable) {
23
+            labels.push("stable");
24
+        }
25
+        if (ruleset.id === labeled.canary) {
26
+            labels.push("canary");
27
+        }
28
+        logger_1.logger.info(`${ruleset.id}  ${ruleset.createdAt}  ${labels.join(",")}`);
29
+    }
30
+    logger_1.logger.info("Labels:");
31
+    logger_1.logger.info(`  stable: ${labeled.stable}`);
32
+    if (labeled.canary) {
33
+        logger_1.logger.info(`  canary: ${labeled.canary}`);
34
+    }
35
+    return { rulesets, labeled };
36
+});

+ 24
- 0
node_modules/firebase-tools/lib/commands/database-rules-release.js View File

@@ -0,0 +1,24 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
6
+const requirePermissions_1 = require("../requirePermissions");
7
+const metadata = require("../database/metadata");
8
+const types_1 = require("../emulator/types");
9
+const commandUtils_1 = require("../emulator/commandUtils");
10
+exports.command = new command_1.Command("database:rules:release <rulesetId>")
11
+    .description("mark a staged ruleset as the stable ruleset")
12
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, uses default database instance)")
13
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
14
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
15
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
16
+    .action(async (rulesetId, options) => {
17
+    const oldLabels = await metadata.getRulesetLabels(options.instance);
18
+    const newLabels = {
19
+        stable: rulesetId,
20
+        canary: oldLabels.canary,
21
+    };
22
+    await metadata.setRulesetLabels(options.instance, newLabels);
23
+    return newLabels;
24
+});

+ 26
- 0
node_modules/firebase-tools/lib/commands/database-rules-stage.js View File

@@ -0,0 +1,26 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const logger_1 = require("../logger");
6
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
7
+const requirePermissions_1 = require("../requirePermissions");
8
+const metadata = require("../database/metadata");
9
+const fs = require("fs-extra");
10
+const path = require("path");
11
+const types_1 = require("../emulator/types");
12
+const commandUtils_1 = require("../emulator/commandUtils");
13
+exports.command = new command_1.Command("database:rules:stage")
14
+    .description("create a new realtime database ruleset")
15
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, uses default database instance)")
16
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
17
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
18
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
19
+    .action(async (options) => {
20
+    const filepath = options.config.data.database.rules;
21
+    logger_1.logger.info(`staging ruleset from ${filepath}`);
22
+    const source = fs.readFileSync(path.resolve(filepath), "utf8");
23
+    const rulesetId = await metadata.createRuleset(options.instance, source);
24
+    logger_1.logger.info(`staged ruleset ${rulesetId}`);
25
+    return rulesetId;
26
+});

+ 68
- 0
node_modules/firebase-tools/lib/commands/database-set.js View File

@@ -0,0 +1,68 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const fs = require("fs");
6
+const apiv2_1 = require("../apiv2");
7
+const command_1 = require("../command");
8
+const types_1 = require("../emulator/types");
9
+const error_1 = require("../error");
10
+const database_1 = require("../management/database");
11
+const commandUtils_1 = require("../emulator/commandUtils");
12
+const prompt_1 = require("../prompt");
13
+const api_1 = require("../database/api");
14
+const requirePermissions_1 = require("../requirePermissions");
15
+const url_1 = require("url");
16
+const logger_1 = require("../logger");
17
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
18
+const utils = require("../utils");
19
+exports.command = new command_1.Command("database:set <path> [infile]")
20
+    .description("store JSON data at the specified path via STDIN, arg, or file")
21
+    .option("-d, --data <data>", "specify escaped JSON directly")
22
+    .option("-f, --force", "pass this option to bypass confirmation prompt")
23
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
24
+    .option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
25
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
26
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
27
+    .before(database_1.populateInstanceDetails)
28
+    .before(commandUtils_1.printNoticeIfEmulated, types_1.Emulators.DATABASE)
29
+    .action(async (path, infile, options) => {
30
+    if (!path.startsWith("/")) {
31
+        throw new error_1.FirebaseError("Path must begin with /");
32
+    }
33
+    const origin = (0, api_1.realtimeOriginOrEmulatorOrCustomUrl)(options.instanceDetails.databaseUrl);
34
+    const dbPath = utils.getDatabaseUrl(origin, options.instance, path);
35
+    const dbJsonURL = new url_1.URL(utils.getDatabaseUrl(origin, options.instance, path + ".json"));
36
+    if (options.disableTriggers) {
37
+        dbJsonURL.searchParams.set("disableTriggers", "true");
38
+    }
39
+    const confirm = await (0, prompt_1.promptOnce)({
40
+        type: "confirm",
41
+        name: "force",
42
+        default: false,
43
+        message: "You are about to overwrite all data at " + clc.cyan(dbPath) + ". Are you sure?",
44
+    }, options);
45
+    if (!confirm) {
46
+        throw new error_1.FirebaseError("Command aborted.");
47
+    }
48
+    const inStream = utils.stringToStream(options.data) || (infile ? fs.createReadStream(infile) : process.stdin);
49
+    if (!infile && !options.data) {
50
+        utils.explainStdin();
51
+    }
52
+    const c = new apiv2_1.Client({ urlPrefix: dbJsonURL.origin, auth: true });
53
+    try {
54
+        await c.request({
55
+            method: "PUT",
56
+            path: dbJsonURL.pathname,
57
+            body: inStream,
58
+            queryParams: dbJsonURL.searchParams,
59
+        });
60
+    }
61
+    catch (err) {
62
+        logger_1.logger.debug(err);
63
+        throw new error_1.FirebaseError(`Unexpected error while setting data: ${err}`, { exit: 2 });
64
+    }
65
+    utils.logSuccess("Data persisted successfully");
66
+    logger_1.logger.info();
67
+    logger_1.logger.info(clc.bold("View data at:"), utils.getDatabaseViewDataUrl(origin, options.project, options.instance, path));
68
+});

+ 44
- 0
node_modules/firebase-tools/lib/commands/database-settings-get.js View File

@@ -0,0 +1,44 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const url_1 = require("url");
5
+const apiv2_1 = require("../apiv2");
6
+const command_1 = require("../command");
7
+const settings_1 = require("../database/settings");
8
+const types_1 = require("../emulator/types");
9
+const error_1 = require("../error");
10
+const database_1 = require("../management/database");
11
+const api_1 = require("../database/api");
12
+const requirePermissions_1 = require("../requirePermissions");
13
+const commandUtils_1 = require("../emulator/commandUtils");
14
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
15
+const utils = require("../utils");
16
+exports.command = new command_1.Command("database:settings:get <path>")
17
+    .description("read the realtime database setting at path")
18
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, uses default database instance)")
19
+    .help(settings_1.HELP_TEXT)
20
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.get"])
21
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
22
+    .before(database_1.populateInstanceDetails)
23
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
24
+    .action(async (path, options) => {
25
+    if (!settings_1.DATABASE_SETTINGS.has(path)) {
26
+        throw new error_1.FirebaseError(settings_1.INVALID_PATH_ERROR, { exit: 1 });
27
+    }
28
+    const u = new url_1.URL(utils.getDatabaseUrl((0, api_1.realtimeOriginOrCustomUrl)(options.instanceDetails.databaseUrl), options.instance, `/.settings/${path}.json`));
29
+    const c = new apiv2_1.Client({ urlPrefix: u.origin, auth: true });
30
+    let res;
31
+    try {
32
+        res = await c.get(u.pathname);
33
+    }
34
+    catch (err) {
35
+        throw new error_1.FirebaseError(`Unexpected error fetching configs at ${path}`, {
36
+            exit: 2,
37
+            original: err,
38
+        });
39
+    }
40
+    if (typeof res.body === "object") {
41
+        res.body = res.body.value;
42
+    }
43
+    utils.logSuccess(`For database instance ${options.instance}\n\t ${path} = ${res.body}`);
44
+});

+ 46
- 0
node_modules/firebase-tools/lib/commands/database-settings-set.js View File

@@ -0,0 +1,46 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const url_1 = require("url");
5
+const apiv2_1 = require("../apiv2");
6
+const command_1 = require("../command");
7
+const settings_1 = require("../database/settings");
8
+const types_1 = require("../emulator/types");
9
+const error_1 = require("../error");
10
+const database_1 = require("../management/database");
11
+const api_1 = require("../database/api");
12
+const requirePermissions_1 = require("../requirePermissions");
13
+const commandUtils_1 = require("../emulator/commandUtils");
14
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
15
+const utils = require("../utils");
16
+exports.command = new command_1.Command("database:settings:set <path> <value>")
17
+    .description("set the realtime database setting at path.")
18
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
19
+    .help(settings_1.HELP_TEXT)
20
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
21
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
22
+    .before(database_1.populateInstanceDetails)
23
+    .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.DATABASE)
24
+    .action(async (path, value, options) => {
25
+    const setting = settings_1.DATABASE_SETTINGS.get(path);
26
+    if (setting === undefined) {
27
+        return utils.reject(settings_1.INVALID_PATH_ERROR, { exit: 1 });
28
+    }
29
+    const parsedValue = setting.parseInput(value);
30
+    if (parsedValue === undefined) {
31
+        return utils.reject(setting.parseInputErrorMessge, { exit: 1 });
32
+    }
33
+    const u = new url_1.URL(utils.getDatabaseUrl((0, api_1.realtimeOriginOrCustomUrl)(options.instanceDetails.databaseUrl), options.instance, `/.settings/${path}.json`));
34
+    const c = new apiv2_1.Client({ urlPrefix: u.origin, auth: true });
35
+    try {
36
+        await c.put(u.pathname, JSON.stringify(parsedValue));
37
+    }
38
+    catch (err) {
39
+        throw new error_1.FirebaseError(`Unexpected error fetching configs at ${path}`, {
40
+            exit: 2,
41
+            original: err,
42
+        });
43
+    }
44
+    utils.logSuccess("Successfully set setting.");
45
+    utils.logSuccess(`For database instance ${options.instance}\n\t ${path} = ${JSON.stringify(parsedValue)}`);
46
+});

+ 69
- 0
node_modules/firebase-tools/lib/commands/database-update.js View File

@@ -0,0 +1,69 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const url_1 = require("url");
5
+const clc = require("colorette");
6
+const fs = require("fs");
7
+const apiv2_1 = require("../apiv2");
8
+const command_1 = require("../command");
9
+const types_1 = require("../emulator/types");
10
+const error_1 = require("../error");
11
+const database_1 = require("../management/database");
12
+const commandUtils_1 = require("../emulator/commandUtils");
13
+const prompt_1 = require("../prompt");
14
+const api_1 = require("../database/api");
15
+const requirePermissions_1 = require("../requirePermissions");
16
+const logger_1 = require("../logger");
17
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
18
+const utils = require("../utils");
19
+exports.command = new command_1.Command("database:update <path> [infile]")
20
+    .description("update some of the keys for the defined path in your Firebase")
21
+    .option("-d, --data <data>", "specify escaped JSON directly")
22
+    .option("-f, --force", "pass this option to bypass confirmation prompt")
23
+    .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
24
+    .option("--disable-triggers", "suppress any Cloud functions triggered by this operation")
25
+    .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
26
+    .before(requireDatabaseInstance_1.requireDatabaseInstance)
27
+    .before(database_1.populateInstanceDetails)
28
+    .before(commandUtils_1.printNoticeIfEmulated, types_1.Emulators.DATABASE)
29
+    .action(async (path, infile, options) => {
30
+    if (!path.startsWith("/")) {
31
+        throw new error_1.FirebaseError("Path must begin with /");
32
+    }
33
+    const origin = (0, api_1.realtimeOriginOrEmulatorOrCustomUrl)(options.instanceDetails.databaseUrl);
34
+    const url = utils.getDatabaseUrl(origin, options.instance, path);
35
+    const confirmed = await (0, prompt_1.promptOnce)({
36
+        type: "confirm",
37
+        name: "force",
38
+        default: false,
39
+        message: `You are about to modify data at ${clc.cyan(url)}. Are you sure?`,
40
+    }, options);
41
+    if (!confirmed) {
42
+        throw new error_1.FirebaseError("Command aborted.");
43
+    }
44
+    const inStream = utils.stringToStream(options.data) ||
45
+        (infile && fs.createReadStream(infile)) ||
46
+        process.stdin;
47
+    const jsonUrl = new url_1.URL(utils.getDatabaseUrl(origin, options.instance, path + ".json"));
48
+    if (options.disableTriggers) {
49
+        jsonUrl.searchParams.set("disableTriggers", "true");
50
+    }
51
+    if (!infile && !options.data) {
52
+        utils.explainStdin();
53
+    }
54
+    const c = new apiv2_1.Client({ urlPrefix: jsonUrl.origin, auth: true });
55
+    try {
56
+        await c.request({
57
+            method: "PATCH",
58
+            path: jsonUrl.pathname,
59
+            body: inStream,
60
+            queryParams: jsonUrl.searchParams,
61
+        });
62
+    }
63
+    catch (err) {
64
+        throw new error_1.FirebaseError("Unexpected error while setting data");
65
+    }
66
+    utils.logSuccess("Data updated successfully");
67
+    logger_1.logger.info();
68
+    logger_1.logger.info(clc.bold("View data at:"), utils.getDatabaseViewDataUrl(origin, options.project, options.instance, path));
69
+});

+ 80
- 0
node_modules/firebase-tools/lib/commands/deploy.js View File

@@ -0,0 +1,80 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = exports.TARGET_PERMISSIONS = exports.VALID_DEPLOY_TARGETS = void 0;
4
+const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
5
+const requirePermissions_1 = require("../requirePermissions");
6
+const checkIam_1 = require("../deploy/functions/checkIam");
7
+const checkValidTargetFilters_1 = require("../checkValidTargetFilters");
8
+const command_1 = require("../command");
9
+const deploy_1 = require("../deploy");
10
+const requireConfig_1 = require("../requireConfig");
11
+const filterTargets_1 = require("../filterTargets");
12
+const requireHostingSite_1 = require("../requireHostingSite");
13
+exports.VALID_DEPLOY_TARGETS = [
14
+    "database",
15
+    "storage",
16
+    "firestore",
17
+    "functions",
18
+    "hosting",
19
+    "remoteconfig",
20
+    "extensions",
21
+];
22
+exports.TARGET_PERMISSIONS = {
23
+    database: ["firebasedatabase.instances.update"],
24
+    hosting: ["firebasehosting.sites.update"],
25
+    functions: [
26
+        "cloudfunctions.functions.list",
27
+        "cloudfunctions.functions.create",
28
+        "cloudfunctions.functions.get",
29
+        "cloudfunctions.functions.update",
30
+        "cloudfunctions.functions.delete",
31
+        "cloudfunctions.operations.get",
32
+    ],
33
+    firestore: [
34
+        "datastore.indexes.list",
35
+        "datastore.indexes.create",
36
+        "datastore.indexes.update",
37
+        "datastore.indexes.delete",
38
+    ],
39
+    storage: [
40
+        "firebaserules.releases.create",
41
+        "firebaserules.rulesets.create",
42
+        "firebaserules.releases.update",
43
+    ],
44
+    remoteconfig: ["cloudconfig.configs.get", "cloudconfig.configs.update"],
45
+};
46
+exports.command = new command_1.Command("deploy")
47
+    .description("deploy code and assets to your Firebase project")
48
+    .withForce("delete Cloud Functions missing from the current working directory without confirmation")
49
+    .option("-p, --public <path>", "override the Hosting public directory specified in firebase.json")
50
+    .option("-m, --message <message>", "an optional message describing this deploy")
51
+    .option("--only <targets>", 'only deploy to specified, comma-separated targets (e.g. "hosting,storage"). For functions, ' +
52
+    'can specify filters with colons to scope function deploys to only those functions (e.g. "--only functions:func1,functions:func2"). ' +
53
+    "When filtering based on export groups (the exported module object keys), use dots to specify group names " +
54
+    '(e.g. "--only functions:group1.subgroup1,functions:group2)"')
55
+    .option("--except <targets>", 'deploy to all targets except specified (e.g. "database")')
56
+    .before(requireConfig_1.requireConfig)
57
+    .before((options) => {
58
+    options.filteredTargets = (0, filterTargets_1.filterTargets)(options, exports.VALID_DEPLOY_TARGETS);
59
+    const permissions = options.filteredTargets.reduce((perms, target) => {
60
+        return perms.concat(exports.TARGET_PERMISSIONS[target]);
61
+    }, []);
62
+    return (0, requirePermissions_1.requirePermissions)(options, permissions);
63
+})
64
+    .before((options) => {
65
+    if (options.filteredTargets.includes("functions")) {
66
+        return (0, checkIam_1.checkServiceAccountIam)(options.project);
67
+    }
68
+})
69
+    .before(async (options) => {
70
+    if (options.filteredTargets.includes("database")) {
71
+        await (0, requireDatabaseInstance_1.requireDatabaseInstance)(options);
72
+    }
73
+    if (options.filteredTargets.includes("hosting")) {
74
+        await (0, requireHostingSite_1.requireHostingSite)(options);
75
+    }
76
+})
77
+    .before(checkValidTargetFilters_1.checkValidTargetFilters)
78
+    .action((options) => {
79
+    return (0, deploy_1.deploy)(options.filteredTargets, options);
80
+});

+ 18
- 0
node_modules/firebase-tools/lib/commands/emulators-exec.js View File

@@ -0,0 +1,18 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const commandUtils = require("../emulator/commandUtils");
6
+const commandUtils_1 = require("../emulator/commandUtils");
7
+exports.command = new command_1.Command("emulators:exec <script>")
8
+    .before(commandUtils.setExportOnExitOptions)
9
+    .before(commandUtils.beforeEmulatorCommand)
10
+    .description("start the local Firebase emulators, " + "run a test script, then shut down the emulators")
11
+    .option(commandUtils.FLAG_ONLY, commandUtils.DESC_ONLY)
12
+    .option(commandUtils.FLAG_INSPECT_FUNCTIONS, commandUtils.DESC_INSPECT_FUNCTIONS)
13
+    .option(commandUtils.FLAG_IMPORT, commandUtils.DESC_IMPORT)
14
+    .option(commandUtils.FLAG_EXPORT_ON_EXIT, commandUtils.DESC_EXPORT_ON_EXIT)
15
+    .option(commandUtils.FLAG_UI, commandUtils.DESC_UI)
16
+    .action((script, options) => {
17
+    return Promise.race([(0, commandUtils_1.shutdownWhenKilled)(options), (0, commandUtils_1.emulatorExec)(script, options)]);
18
+});

+ 14
- 0
node_modules/firebase-tools/lib/commands/emulators-export.js View File

@@ -0,0 +1,14 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const controller = require("../emulator/controller");
6
+const commandUtils = require("../emulator/commandUtils");
7
+const COMMAND_NAME = "emulators:export";
8
+exports.command = new command_1.Command(`${COMMAND_NAME} <path>`)
9
+    .description("export data from running emulators")
10
+    .withForce("overwrite any export data in the target directory")
11
+    .option(commandUtils.FLAG_ONLY, commandUtils.DESC_ONLY)
12
+    .action((exportPath, options) => {
13
+    return controller.exportEmulatorData(exportPath, options, COMMAND_NAME);
14
+});

+ 114
- 0
node_modules/firebase-tools/lib/commands/emulators-start.js View File

@@ -0,0 +1,114 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const controller = require("../emulator/controller");
6
+const commandUtils = require("../emulator/commandUtils");
7
+const logger_1 = require("../logger");
8
+const registry_1 = require("../emulator/registry");
9
+const types_1 = require("../emulator/types");
10
+const clc = require("colorette");
11
+const constants_1 = require("../emulator/constants");
12
+const utils_1 = require("../utils");
13
+const Table = require("cli-table");
14
+function stylizeLink(url) {
15
+    return clc.underline(clc.bold(url));
16
+}
17
+exports.command = new command_1.Command("emulators:start")
18
+    .before(commandUtils.setExportOnExitOptions)
19
+    .before(commandUtils.beforeEmulatorCommand)
20
+    .description("start the local Firebase emulators")
21
+    .option(commandUtils.FLAG_ONLY, commandUtils.DESC_ONLY)
22
+    .option(commandUtils.FLAG_INSPECT_FUNCTIONS, commandUtils.DESC_INSPECT_FUNCTIONS)
23
+    .option(commandUtils.FLAG_IMPORT, commandUtils.DESC_IMPORT)
24
+    .option(commandUtils.FLAG_EXPORT_ON_EXIT, commandUtils.DESC_EXPORT_ON_EXIT)
25
+    .action((options) => {
26
+    const killSignalPromise = commandUtils.shutdownWhenKilled(options);
27
+    return Promise.race([
28
+        killSignalPromise,
29
+        (async () => {
30
+            let deprecationNotices;
31
+            try {
32
+                ({ deprecationNotices } = await controller.startAll(options));
33
+            }
34
+            catch (e) {
35
+                await controller.cleanShutdown();
36
+                throw e;
37
+            }
38
+            printEmulatorOverview(options);
39
+            for (const notice of deprecationNotices) {
40
+                (0, utils_1.logLabeledWarning)("emulators", notice, "warn");
41
+            }
42
+            return killSignalPromise;
43
+        })(),
44
+    ]);
45
+});
46
+function printEmulatorOverview(options) {
47
+    const reservedPorts = [];
48
+    for (const internalEmulator of [types_1.Emulators.LOGGING]) {
49
+        const info = registry_1.EmulatorRegistry.getInfo(internalEmulator);
50
+        if (info) {
51
+            reservedPorts.push(info.port);
52
+        }
53
+        controller.filterEmulatorTargets(options).forEach((emulator) => {
54
+            var _a;
55
+            reservedPorts.push(...(((_a = registry_1.EmulatorRegistry.getInfo(emulator)) === null || _a === void 0 ? void 0 : _a.reservedPorts) || []));
56
+        });
57
+    }
58
+    const reservedPortsString = reservedPorts.length > 0 ? reservedPorts.join(", ") : "None";
59
+    const uiRunning = registry_1.EmulatorRegistry.isRunning(types_1.Emulators.UI);
60
+    const head = ["Emulator", "Host:Port"];
61
+    if (uiRunning) {
62
+        head.push(`View in ${constants_1.Constants.description(types_1.Emulators.UI)}`);
63
+    }
64
+    const successMessageTable = new Table();
65
+    let successMsg = `${clc.green("✔")}  ${clc.bold("All emulators ready! It is now safe to connect your app.")}`;
66
+    if (uiRunning) {
67
+        successMsg += `\n${clc.cyan("i")}  View Emulator UI at ${stylizeLink(registry_1.EmulatorRegistry.url(types_1.Emulators.UI).toString())}`;
68
+    }
69
+    successMessageTable.push([successMsg]);
70
+    const emulatorsTable = new Table({
71
+        head: head,
72
+        style: {
73
+            head: ["yellow"],
74
+        },
75
+    });
76
+    emulatorsTable.push(...controller
77
+        .filterEmulatorTargets(options)
78
+        .map((emulator) => {
79
+        const emulatorName = constants_1.Constants.description(emulator).replace(/ emulator/i, "");
80
+        const isSupportedByUi = types_1.EMULATORS_SUPPORTED_BY_UI.includes(emulator);
81
+        const listen = commandUtils.getListenOverview(emulator);
82
+        if (!listen) {
83
+            const row = [emulatorName, "Failed to initialize (see above)"];
84
+            if (uiRunning) {
85
+                row.push("");
86
+            }
87
+            return row;
88
+        }
89
+        let uiLink = "n/a";
90
+        if (isSupportedByUi && uiRunning) {
91
+            const url = registry_1.EmulatorRegistry.url(types_1.Emulators.UI);
92
+            url.pathname = `/${emulator}`;
93
+            uiLink = stylizeLink(url.toString());
94
+        }
95
+        return [emulatorName, listen, uiLink];
96
+    })
97
+        .map((col) => col.slice(0, head.length))
98
+        .filter((v) => v));
99
+    let extensionsTable = "";
100
+    if (registry_1.EmulatorRegistry.isRunning(types_1.Emulators.EXTENSIONS)) {
101
+        const extensionsEmulatorInstance = registry_1.EmulatorRegistry.get(types_1.Emulators.EXTENSIONS);
102
+        extensionsTable = extensionsEmulatorInstance.extensionsInfoTable(options);
103
+    }
104
+    logger_1.logger.info(`\n${successMessageTable}
105
+
106
+${emulatorsTable}
107
+${registry_1.EmulatorRegistry.isRunning(types_1.Emulators.HUB)
108
+        ? clc.blackBright("  Emulator Hub running at ") + registry_1.EmulatorRegistry.url(types_1.Emulators.HUB).host
109
+        : clc.blackBright("  Emulator Hub not running.")}
110
+${clc.blackBright("  Other reserved ports:")} ${reservedPortsString}
111
+${extensionsTable}
112
+Issues? Report them at ${stylizeLink("https://github.com/firebase/firebase-tools/issues")} and attach the *-debug.log files.
113
+ `);
114
+}

+ 13
- 0
node_modules/firebase-tools/lib/commands/experimental-functions-shell.js View File

@@ -0,0 +1,13 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const functionsShellCommandAction_1 = require("../functionsShellCommandAction");
5
+const command_1 = require("../command");
6
+const requireConfig_1 = require("../requireConfig");
7
+const requirePermissions_1 = require("../requirePermissions");
8
+exports.command = new command_1.Command("experimental:functions:shell")
9
+    .description("launch full Node shell with emulated functions. (Alias for `firebase functions:shell.)")
10
+    .option("-p, --port <port>", "the port on which to emulate functions (default: 5000)", 5000)
11
+    .before(requireConfig_1.requireConfig)
12
+    .before(requirePermissions_1.requirePermissions)
13
+    .action(functionsShellCommandAction_1.actionFunction);

+ 31
- 0
node_modules/firebase-tools/lib/commands/experiments-describe.js View File

@@ -0,0 +1,31 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const colorette_1 = require("colorette");
5
+const command_1 = require("../command");
6
+const error_1 = require("../error");
7
+const experiments = require("../experiments");
8
+const logger_1 = require("../logger");
9
+const utils_1 = require("../utils");
10
+exports.command = new command_1.Command("experiments:describe <experiment>")
11
+    .description("enable an experiment on this machine")
12
+    .action((experiment) => {
13
+    if (!experiments.isValidExperiment(experiment)) {
14
+        let message = `Cannot find experiment ${(0, colorette_1.bold)(experiment)}`;
15
+        const potentials = experiments.experimentNameAutocorrect(experiment);
16
+        if (potentials.length === 1) {
17
+            message = `${message}\nDid you mean ${potentials[0]}?`;
18
+        }
19
+        else if (potentials.length) {
20
+            message = `${message}\nDid you mean ${potentials.slice(0, -1).join(",")} or ${(0, utils_1.last)(potentials)}?`;
21
+        }
22
+        throw new error_1.FirebaseError(message);
23
+    }
24
+    const spec = experiments.ALL_EXPERIMENTS[experiment];
25
+    logger_1.logger.info(`${(0, colorette_1.bold)("Name")}: ${experiment}`);
26
+    logger_1.logger.info(`${(0, colorette_1.bold)("Enabled")}: ${experiments.isEnabled(experiment) ? "yes" : "no"}`);
27
+    if (spec.docsUri) {
28
+        logger_1.logger.info(`${(0, colorette_1.bold)("Documentation")}: ${spec.docsUri}`);
29
+    }
30
+    logger_1.logger.info(`${(0, colorette_1.bold)("Description")}: ${spec.fullDescription || spec.shortDescription}`);
31
+});

+ 28
- 0
node_modules/firebase-tools/lib/commands/experiments-disable.js View File

@@ -0,0 +1,28 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const colorette_1 = require("colorette");
5
+const command_1 = require("../command");
6
+const error_1 = require("../error");
7
+const experiments = require("../experiments");
8
+const logger_1 = require("../logger");
9
+const utils_1 = require("../utils");
10
+exports.command = new command_1.Command("experiments:disable <experiment>")
11
+    .description("disable an experiment on this machine")
12
+    .action((experiment) => {
13
+    if (experiments.isValidExperiment(experiment)) {
14
+        experiments.setEnabled(experiment, false);
15
+        experiments.flushToDisk();
16
+        logger_1.logger.info(`Disabled experiment ${(0, colorette_1.bold)(experiment)}`);
17
+        return;
18
+    }
19
+    let message = `Cannot find experiment ${(0, colorette_1.bold)(experiment)}`;
20
+    const potentials = experiments.experimentNameAutocorrect(experiment);
21
+    if (potentials.length === 1) {
22
+        message = `${message}\nDid you mean ${potentials[0]}?`;
23
+    }
24
+    else if (potentials.length) {
25
+        message = `${message}\nDid you mean ${potentials.slice(0, -1).join(",")} or ${(0, utils_1.last)(potentials)}?`;
26
+    }
27
+    throw new error_1.FirebaseError(message);
28
+});

+ 28
- 0
node_modules/firebase-tools/lib/commands/experiments-enable.js View File

@@ -0,0 +1,28 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const colorette_1 = require("colorette");
5
+const command_1 = require("../command");
6
+const error_1 = require("../error");
7
+const experiments = require("../experiments");
8
+const logger_1 = require("../logger");
9
+const utils_1 = require("../utils");
10
+exports.command = new command_1.Command("experiments:enable <experiment>")
11
+    .description("enable an experiment on this machine")
12
+    .action((experiment) => {
13
+    if (experiments.isValidExperiment(experiment)) {
14
+        experiments.setEnabled(experiment, true);
15
+        experiments.flushToDisk();
16
+        logger_1.logger.info(`Enabled experiment ${(0, colorette_1.bold)(experiment)}`);
17
+        return;
18
+    }
19
+    let message = `Cannot find experiment ${(0, colorette_1.bold)(experiment)}`;
20
+    const potentials = experiments.experimentNameAutocorrect(experiment);
21
+    if (potentials.length === 1) {
22
+        message = `${message}\nDid you mean ${potentials[0]}?`;
23
+    }
24
+    else if (potentials.length) {
25
+        message = `${message}\nDid you mean ${potentials.slice(0, -1).join(",")} or ${(0, utils_1.last)(potentials)}?`;
26
+    }
27
+    throw new error_1.FirebaseError(message);
28
+});

+ 27
- 0
node_modules/firebase-tools/lib/commands/experiments-list.js View File

@@ -0,0 +1,27 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const Table = require("cli-table");
6
+const experiments = require("../experiments");
7
+const functional_1 = require("../functional");
8
+const logger_1 = require("../logger");
9
+exports.command = new command_1.Command("experiments:list").action(() => {
10
+    const table = new Table({
11
+        head: ["Enabled", "Name", "Description"],
12
+        style: { head: ["yellow"] },
13
+    });
14
+    const [enabled, disabled] = (0, functional_1.partition)(Object.entries(experiments.ALL_EXPERIMENTS), ([name]) => {
15
+        return experiments.isEnabled(name);
16
+    });
17
+    for (const [name, exp] of enabled) {
18
+        table.push(["y", name, exp.shortDescription]);
19
+    }
20
+    for (const [name, exp] of disabled) {
21
+        if (!exp.public) {
22
+            continue;
23
+        }
24
+        table.push(["n", name, exp.shortDescription]);
25
+    }
26
+    logger_1.logger.info(table.toString());
27
+});

+ 108
- 0
node_modules/firebase-tools/lib/commands/ext-configure.js View File

@@ -0,0 +1,108 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const { marked } = require("marked");
5
+const TerminalRenderer = require("marked-terminal");
6
+const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
7
+const command_1 = require("../command");
8
+const error_1 = require("../error");
9
+const projectUtils_1 = require("../projectUtils");
10
+const extensionsApi = require("../extensions/extensionsApi");
11
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
12
+const paramHelper = require("../extensions/paramHelper");
13
+const requirePermissions_1 = require("../requirePermissions");
14
+const utils = require("../utils");
15
+const logger_1 = require("../logger");
16
+const refs = require("../extensions/refs");
17
+const manifest = require("../extensions/manifest");
18
+const functional_1 = require("../functional");
19
+const paramHelper_1 = require("../extensions/paramHelper");
20
+const askUserForEventsConfig = require("../extensions/askUserForEventsConfig");
21
+marked.setOptions({
22
+    renderer: new TerminalRenderer(),
23
+});
24
+exports.command = new command_1.Command("ext:configure <extensionInstanceId>")
25
+    .description("configure an existing extension instance")
26
+    .withForce()
27
+    .option("--local", "deprecated")
28
+    .before(requirePermissions_1.requirePermissions, [
29
+    "firebaseextensions.instances.update",
30
+    "firebaseextensions.instances.get",
31
+])
32
+    .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
33
+    .before(extensionsHelper_1.diagnoseAndFixProject)
34
+    .action(async (instanceId, options) => {
35
+    const projectId = (0, projectUtils_1.getProjectId)(options);
36
+    if (options.nonInteractive) {
37
+        throw new error_1.FirebaseError(`Command not supported in non-interactive mode, edit ./extensions/${instanceId}.env directly instead. ` +
38
+            `See https://firebase.google.com/docs/extensions/manifest for more details.`);
39
+    }
40
+    if (options.local) {
41
+        utils.logLabeledWarning(extensionsHelper_1.logPrefix, "As of firebase-tools@11.0.0, the `--local` flag is no longer required, as it is the default behavior.");
42
+    }
43
+    const config = manifest.loadConfig(options);
44
+    const refOrPath = manifest.getInstanceTarget(instanceId, config);
45
+    const isLocalSource = (0, extensionsHelper_1.isLocalPath)(refOrPath);
46
+    let spec;
47
+    if (isLocalSource) {
48
+        const source = await (0, extensionsHelper_1.createSourceFromLocation)((0, projectUtils_1.needProjectId)({ projectId }), refOrPath);
49
+        spec = source.spec;
50
+    }
51
+    else {
52
+        const extensionVersion = await extensionsApi.getExtensionVersion(refOrPath);
53
+        spec = extensionVersion.spec;
54
+    }
55
+    const oldParamValues = manifest.readInstanceParam({
56
+        instanceId,
57
+        projectDir: config.projectDir,
58
+    });
59
+    const [immutableParams, tbdParams] = (0, functional_1.partition)(spec.params, (param) => { var _a; return (_a = param.immutable) !== null && _a !== void 0 ? _a : false; });
60
+    infoImmutableParams(immutableParams, oldParamValues);
61
+    paramHelper.setNewDefaults(tbdParams, oldParamValues);
62
+    const mutableParamsBindingOptions = await paramHelper.getParams({
63
+        projectId,
64
+        paramSpecs: tbdParams,
65
+        nonInteractive: false,
66
+        paramsEnvPath: "",
67
+        instanceId,
68
+        reconfiguring: true,
69
+    });
70
+    const eventsConfig = spec.events
71
+        ? await askUserForEventsConfig.askForEventsConfig(spec.events, "${param:PROJECT_ID}", instanceId)
72
+        : undefined;
73
+    if (eventsConfig) {
74
+        mutableParamsBindingOptions.EVENTARC_CHANNEL = { baseValue: eventsConfig.channel };
75
+        mutableParamsBindingOptions.ALLOWED_EVENT_TYPES = {
76
+            baseValue: eventsConfig.allowedEventTypes.join(","),
77
+        };
78
+    }
79
+    const newParamOptions = Object.assign(Object.assign({}, (0, paramHelper_1.buildBindingOptionsWithBaseValue)(oldParamValues)), mutableParamsBindingOptions);
80
+    await manifest.writeToManifest([
81
+        {
82
+            instanceId,
83
+            ref: !isLocalSource ? refs.parse(refOrPath) : undefined,
84
+            localPath: isLocalSource ? refOrPath : undefined,
85
+            params: newParamOptions,
86
+            extensionSpec: spec,
87
+        },
88
+    ], config, {
89
+        nonInteractive: false,
90
+        force: true,
91
+    });
92
+    manifest.showPostDeprecationNotice();
93
+    return;
94
+});
95
+function infoImmutableParams(immutableParams, paramValues) {
96
+    if (!immutableParams.length) {
97
+        return;
98
+    }
99
+    const plural = immutableParams.length > 1;
100
+    utils.logLabeledWarning(extensionsHelper_1.logPrefix, marked(`The following param${plural ? "s are" : " is"} immutable and won't be changed:`));
101
+    for (const { param } of immutableParams) {
102
+        logger_1.logger.info(`param: ${param}, value: ${paramValues[param]}`);
103
+    }
104
+    logger_1.logger.info((plural
105
+        ? "To set different values for these params"
106
+        : "To set a different value for this param") +
107
+        ", uninstall the extension, then install a new instance of this extension.");
108
+}

+ 64
- 0
node_modules/firebase-tools/lib/commands/ext-dev-deprecate.js View File

@@ -0,0 +1,64 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const semver = require("semver");
6
+const refs = require("../extensions/refs");
7
+const utils = require("../utils");
8
+const command_1 = require("../command");
9
+const prompt_1 = require("../prompt");
10
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
11
+const extensionsApi_1 = require("../extensions/extensionsApi");
12
+const versionHelper_1 = require("../extensions/versionHelper");
13
+const requireAuth_1 = require("../requireAuth");
14
+const error_1 = require("../error");
15
+exports.command = new command_1.Command("ext:dev:deprecate <extensionRef> <versionPredicate>")
16
+    .description("deprecate extension versions that match the version predicate")
17
+    .option("-m, --message <deprecationMessage>", "deprecation message")
18
+    .option("-f, --force", "override deprecation message for existing deprecated extension versions that match")
19
+    .before(requireAuth_1.requireAuth)
20
+    .before(extensionsHelper_1.ensureExtensionsApiEnabled)
21
+    .action(async (extensionRef, versionPredicate, options) => {
22
+    const { publisherId, extensionId, version } = refs.parse(extensionRef);
23
+    if (version) {
24
+        throw new error_1.FirebaseError(`The input extension reference must be of the format ${clc.bold("<publisherId>/<extensionId>")}. Version should be supplied in the version predicate argument.`);
25
+    }
26
+    if (!publisherId || !extensionId) {
27
+        throw new error_1.FirebaseError(`Error parsing publisher ID and extension ID from extension reference '${clc.bold(extensionRef)}'. Please use the format '${clc.bold("<publisherId>/<extensionId>")}'.`);
28
+    }
29
+    const { comparator, targetSemVer } = (0, versionHelper_1.parseVersionPredicate)(versionPredicate);
30
+    const filter = `id${comparator}"${targetSemVer}"`;
31
+    const extensionVersions = await (0, extensionsApi_1.listExtensionVersions)(extensionRef, filter);
32
+    const filteredExtensionVersions = extensionVersions
33
+        .sort((ev1, ev2) => {
34
+        return -semver.compare(ev1.spec.version, ev2.spec.version);
35
+    })
36
+        .filter((extensionVersion) => {
37
+        if (extensionVersion.state === "DEPRECATED" && !options.force) {
38
+            return false;
39
+        }
40
+        const message = extensionVersion.state === "DEPRECATED" ? ", will overwrite deprecation message" : "";
41
+        utils.logLabeledBullet(extensionVersion.ref, extensionVersion.state + message);
42
+        return true;
43
+    });
44
+    if (filteredExtensionVersions.length > 0) {
45
+        if (!options.force) {
46
+            const confirmMessage = "You are about to deprecate these extension version(s). Do you wish to continue?";
47
+            const consent = await (0, prompt_1.promptOnce)({
48
+                type: "confirm",
49
+                message: confirmMessage,
50
+                default: false,
51
+            });
52
+            if (!consent) {
53
+                throw new error_1.FirebaseError("Deprecation canceled.");
54
+            }
55
+        }
56
+    }
57
+    else {
58
+        throw new error_1.FirebaseError("No extension versions matched the version predicate.");
59
+    }
60
+    await utils.allSettled(filteredExtensionVersions.map(async (extensionVersion) => {
61
+        await (0, extensionsApi_1.deprecateExtensionVersion)(extensionVersion.ref, options.deprecationMessage);
62
+    }));
63
+    utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully deprecated extension version(s).");
64
+});

+ 27
- 0
node_modules/firebase-tools/lib/commands/ext-dev-emulators-exec.js View File

@@ -0,0 +1,27 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
6
+const command_1 = require("../command");
7
+const error_1 = require("../error");
8
+const commandUtils = require("../emulator/commandUtils");
9
+exports.command = new command_1.Command("ext:dev:emulators:exec <script>")
10
+    .description("deprecated: please use `firebase emulators:exec` instead")
11
+    .before(commandUtils.setExportOnExitOptions)
12
+    .option(commandUtils.FLAG_INSPECT_FUNCTIONS, commandUtils.DESC_INSPECT_FUNCTIONS)
13
+    .option(commandUtils.FLAG_TEST_CONFIG, commandUtils.DESC_TEST_CONFIG)
14
+    .option(commandUtils.FLAG_TEST_PARAMS, commandUtils.DESC_TEST_PARAMS)
15
+    .option(commandUtils.FLAG_IMPORT, commandUtils.DESC_IMPORT)
16
+    .option(commandUtils.FLAG_EXPORT_ON_EXIT, commandUtils.DESC_EXPORT_ON_EXIT)
17
+    .option(commandUtils.FLAG_UI, commandUtils.DESC_UI)
18
+    .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
19
+    .action((script) => {
20
+    const localInstallCommand = `firebase ext:install ${process.cwd()}`;
21
+    const emulatorsExecCommand = `firebase emulators:exec '${script}`;
22
+    throw new error_1.FirebaseError("ext:dev:emulators:exec is no longer supported. " +
23
+        "Instead, navigate to a Firebase project directory and add this extension to the extensions manifest by running:\n" +
24
+        clc.bold(localInstallCommand) +
25
+        "\nThen, you can emulate this extension as part of that project by running:\n" +
26
+        clc.bold(emulatorsExecCommand));
27
+});

+ 24
- 0
node_modules/firebase-tools/lib/commands/ext-dev-emulators-start.js View File

@@ -0,0 +1,24 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const command_1 = require("../command");
6
+const commandUtils = require("../emulator/commandUtils");
7
+const error_1 = require("../error");
8
+exports.command = new command_1.Command("ext:dev:emulators:start")
9
+    .description("deprecated: please use `firebase emulators:start`")
10
+    .before(commandUtils.setExportOnExitOptions)
11
+    .option(commandUtils.FLAG_INSPECT_FUNCTIONS, commandUtils.DESC_INSPECT_FUNCTIONS)
12
+    .option(commandUtils.FLAG_TEST_CONFIG, commandUtils.DESC_TEST_CONFIG)
13
+    .option(commandUtils.FLAG_TEST_PARAMS, commandUtils.DESC_TEST_PARAMS)
14
+    .option(commandUtils.FLAG_IMPORT, commandUtils.DESC_IMPORT)
15
+    .option(commandUtils.FLAG_EXPORT_ON_EXIT, commandUtils.DESC_EXPORT_ON_EXIT)
16
+    .action(() => {
17
+    const localInstallCommand = `firebase ext:install ${process.cwd()}`;
18
+    const emulatorsStartCommand = "firebase emulators:start";
19
+    throw new error_1.FirebaseError("ext:dev:emulators:start is no longer supported. " +
20
+        "Instead, navigate to a Firebase project directory and add this extension to the extensions manifest by running:\n" +
21
+        clc.bold(localInstallCommand) +
22
+        "\nThen, you can emulate this extension as part of that project by running:\n" +
23
+        clc.bold(emulatorsStartCommand));
24
+});

+ 45
- 0
node_modules/firebase-tools/lib/commands/ext-dev-extension-delete.js View File

@@ -0,0 +1,45 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const utils = require("../utils");
5
+const clc = require("colorette");
6
+const command_1 = require("../command");
7
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
8
+const extensionsApi_1 = require("../extensions/extensionsApi");
9
+const refs = require("../extensions/refs");
10
+const prompt_1 = require("../prompt");
11
+const requireAuth_1 = require("../requireAuth");
12
+const error_1 = require("../error");
13
+const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
14
+exports.command = new command_1.Command("ext:dev:delete <extensionRef>")
15
+    .description("delete an extension")
16
+    .help("use this command to delete an extension, and make it unavailable for developers to install or reconfigure. " +
17
+    "Specify the extension you want to delete using the format '<publisherId>/<extensionId>.")
18
+    .before(requireAuth_1.requireAuth)
19
+    .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
20
+    .action(async (extensionRef) => {
21
+    const { publisherId, extensionId, version } = refs.parse(extensionRef);
22
+    if (version) {
23
+        throw new error_1.FirebaseError(`Deleting a single version is not currently supported. You can only delete ${clc.bold("ALL versions")} of an extension. To delete all versions, please remove the version from the reference.`);
24
+    }
25
+    utils.logLabeledWarning(extensionsHelper_1.logPrefix, "If you delete this extension, developers won't be able to install it. " +
26
+        "For developers who currently have this extension installed, " +
27
+        "it will continue to run and will appear as unpublished when " +
28
+        "listed in the Firebase console or Firebase CLI.");
29
+    utils.logLabeledWarning("This is a permanent action", `Once deleted, you may never use the extension name '${clc.bold(extensionId)}' again.`);
30
+    await (0, extensionsApi_1.getExtension)(extensionRef);
31
+    const consent = await confirmDelete(publisherId, extensionId);
32
+    if (!consent) {
33
+        throw new error_1.FirebaseError("deletion cancelled.");
34
+    }
35
+    await (0, extensionsApi_1.deleteExtension)(extensionRef);
36
+    utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully deleted all versions of this extension.");
37
+});
38
+async function confirmDelete(publisherId, extensionId) {
39
+    const message = `You are about to delete ALL versions of ${clc.green(`${publisherId}/${extensionId}`)}.\nDo you wish to continue? `;
40
+    return (0, prompt_1.promptOnce)({
41
+        type: "confirm",
42
+        message,
43
+        default: false,
44
+    });
45
+}

+ 136
- 0
node_modules/firebase-tools/lib/commands/ext-dev-init.js View File

@@ -0,0 +1,136 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const fs = require("fs");
5
+const path = require("path");
6
+const { marked } = require("marked");
7
+const TerminalRenderer = require("marked-terminal");
8
+const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
9
+const command_1 = require("../command");
10
+const config_1 = require("../config");
11
+const error_1 = require("../error");
12
+const prompt_1 = require("../prompt");
13
+const logger_1 = require("../logger");
14
+const npmDependencies = require("../init/features/functions/npm-dependencies");
15
+marked.setOptions({
16
+    renderer: new TerminalRenderer(),
17
+});
18
+const TEMPLATE_ROOT = path.resolve(__dirname, "../../templates/extensions/");
19
+const FUNCTIONS_ROOT = path.resolve(__dirname, "../../templates/init/functions/");
20
+function readCommonTemplates() {
21
+    return {
22
+        extSpecTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "extension.yaml"), "utf8"),
23
+        preinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "PREINSTALL.md"), "utf8"),
24
+        postinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "POSTINSTALL.md"), "utf8"),
25
+        changelogTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "CHANGELOG.md"), "utf8"),
26
+    };
27
+}
28
+exports.command = new command_1.Command("ext:dev:init")
29
+    .description("initialize files for writing an extension in the current directory")
30
+    .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
31
+    .action(async (options) => {
32
+    const cwd = options.cwd || process.cwd();
33
+    const config = new config_1.Config({}, { projectDir: cwd, cwd: cwd });
34
+    try {
35
+        const lang = await (0, prompt_1.promptOnce)({
36
+            type: "list",
37
+            name: "language",
38
+            message: "In which language do you want to write the Cloud Functions for your extension?",
39
+            default: "javascript",
40
+            choices: [
41
+                {
42
+                    name: "JavaScript",
43
+                    value: "javascript",
44
+                },
45
+                {
46
+                    name: "TypeScript",
47
+                    value: "typescript",
48
+                },
49
+            ],
50
+        });
51
+        switch (lang) {
52
+            case "javascript": {
53
+                await javascriptSelected(config);
54
+                break;
55
+            }
56
+            case "typescript": {
57
+                await typescriptSelected(config);
58
+                break;
59
+            }
60
+            default: {
61
+                throw new error_1.FirebaseError(`${lang} is not supported.`);
62
+            }
63
+        }
64
+        await npmDependencies.askInstallDependencies({}, config);
65
+        const welcome = fs.readFileSync(path.join(TEMPLATE_ROOT, lang, "WELCOME.md"), "utf8");
66
+        return logger_1.logger.info("\n" + marked(welcome));
67
+    }
68
+    catch (err) {
69
+        if (!(err instanceof error_1.FirebaseError)) {
70
+            throw new error_1.FirebaseError(`Error occurred when initializing files for new extension: ${err.message}`, {
71
+                original: err,
72
+            });
73
+        }
74
+        throw err;
75
+    }
76
+});
77
+async function typescriptSelected(config) {
78
+    const packageLintingTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "typescript", "package.lint.json"), "utf8");
79
+    const packageNoLintingTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "typescript", "package.nolint.json"), "utf8");
80
+    const tsconfigTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "typescript", "tsconfig.json"), "utf8");
81
+    const tsconfigDevTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "typescript", "tsconfig.dev.json"), "utf8");
82
+    const indexTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "typescript", "index.ts"), "utf8");
83
+    const gitignoreTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "typescript", "_gitignore"), "utf8");
84
+    const eslintTemplate = fs.readFileSync(path.join(FUNCTIONS_ROOT, "typescript", "_eslintrc"), "utf8");
85
+    const lint = await (0, prompt_1.promptOnce)({
86
+        name: "lint",
87
+        type: "confirm",
88
+        message: "Do you want to use ESLint to catch probable bugs and enforce style?",
89
+        default: true,
90
+    });
91
+    const templates = readCommonTemplates();
92
+    await config.askWriteProjectFile("extension.yaml", templates.extSpecTemplate);
93
+    await config.askWriteProjectFile("PREINSTALL.md", templates.preinstallTemplate);
94
+    await config.askWriteProjectFile("POSTINSTALL.md", templates.postinstallTemplate);
95
+    await config.askWriteProjectFile("CHANGELOG.md", templates.changelogTemplate);
96
+    await config.askWriteProjectFile("functions/src/index.ts", indexTemplate);
97
+    if (lint) {
98
+        await config.askWriteProjectFile("functions/package.json", packageLintingTemplate);
99
+        await config.askWriteProjectFile("functions/.eslintrc.js", eslintTemplate);
100
+    }
101
+    else {
102
+        await config.askWriteProjectFile("functions/package.json", packageNoLintingTemplate);
103
+    }
104
+    await config.askWriteProjectFile("functions/tsconfig.json", tsconfigTemplate);
105
+    if (lint) {
106
+        await config.askWriteProjectFile("functions/tsconfig.dev.json", tsconfigDevTemplate);
107
+    }
108
+    await config.askWriteProjectFile("functions/.gitignore", gitignoreTemplate);
109
+}
110
+async function javascriptSelected(config) {
111
+    const indexTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "javascript", "index.js"), "utf8");
112
+    const packageLintingTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "javascript", "package.lint.json"), "utf8");
113
+    const packageNoLintingTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "javascript", "package.nolint.json"), "utf8");
114
+    const gitignoreTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "javascript", "_gitignore"), "utf8");
115
+    const eslintTemplate = fs.readFileSync(path.join(FUNCTIONS_ROOT, "javascript", "_eslintrc"), "utf8");
116
+    const lint = await (0, prompt_1.promptOnce)({
117
+        name: "lint",
118
+        type: "confirm",
119
+        message: "Do you want to use ESLint to catch probable bugs and enforce style?",
120
+        default: false,
121
+    });
122
+    const templates = readCommonTemplates();
123
+    await config.askWriteProjectFile("extension.yaml", templates.extSpecTemplate);
124
+    await config.askWriteProjectFile("PREINSTALL.md", templates.preinstallTemplate);
125
+    await config.askWriteProjectFile("POSTINSTALL.md", templates.postinstallTemplate);
126
+    await config.askWriteProjectFile("CHANGELOG.md", templates.changelogTemplate);
127
+    await config.askWriteProjectFile("functions/index.js", indexTemplate);
128
+    if (lint) {
129
+        await config.askWriteProjectFile("functions/package.json", packageLintingTemplate);
130
+        await config.askWriteProjectFile("functions/.eslintrc.js", eslintTemplate);
131
+    }
132
+    else {
133
+        await config.askWriteProjectFile("functions/package.json", packageNoLintingTemplate);
134
+    }
135
+    await config.askWriteProjectFile("functions/.gitignore", gitignoreTemplate);
136
+}

+ 46
- 0
node_modules/firebase-tools/lib/commands/ext-dev-list.js View File

@@ -0,0 +1,46 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const Table = require("cli-table");
6
+const command_1 = require("../command");
7
+const error_1 = require("../error");
8
+const utils_1 = require("../utils");
9
+const extensionsApi_1 = require("../extensions/extensionsApi");
10
+const logger_1 = require("../logger");
11
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
12
+const requireAuth_1 = require("../requireAuth");
13
+const extensionsUtils = require("../extensions/utils");
14
+exports.command = new command_1.Command("ext:dev:list <publisherId>")
15
+    .description("list all published extensions associated with this publisher ID")
16
+    .before(requireAuth_1.requireAuth)
17
+    .action(async (publisherId) => {
18
+    let extensions;
19
+    try {
20
+        extensions = await (0, extensionsApi_1.listExtensions)(publisherId);
21
+    }
22
+    catch (err) {
23
+        throw new error_1.FirebaseError(err);
24
+    }
25
+    if (extensions.length < 1) {
26
+        throw new error_1.FirebaseError(`There are no published extensions associated with publisher ID ${clc.bold(publisherId)}. This could happen for two reasons:\n` +
27
+            "  - The publisher ID doesn't exist or could be misspelled\n" +
28
+            "  - This publisher has not published any extensions\n\n" +
29
+            "If you are expecting some extensions to appear, please make sure you have the correct publisher ID and try again.");
30
+    }
31
+    const table = new Table({
32
+        head: ["Extension ID", "Version", "Published"],
33
+        style: { head: ["yellow"] },
34
+    });
35
+    const sorted = extensions.sort((a, b) => new Date(b.createTime).valueOf() - new Date(a.createTime).valueOf());
36
+    sorted.forEach((extension) => {
37
+        table.push([
38
+            (0, utils_1.last)(extension.ref.split("/")),
39
+            extension.latestVersion,
40
+            extension.createTime ? extensionsUtils.formatTimestamp(extension.createTime) : "",
41
+        ]);
42
+    });
43
+    (0, utils_1.logLabeledBullet)(extensionsHelper_1.logPrefix, `list of published extensions for publisher ${clc.bold(publisherId)}:`);
44
+    logger_1.logger.info(table.toString());
45
+    return { extensions: sorted };
46
+});

+ 48
- 0
node_modules/firebase-tools/lib/commands/ext-dev-publish.js View File

@@ -0,0 +1,48 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const { marked } = require("marked");
6
+const TerminalRenderer = require("marked-terminal");
7
+const command_1 = require("../command");
8
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
9
+const refs = require("../extensions/refs");
10
+const localHelper_1 = require("../extensions/localHelper");
11
+const publishHelpers_1 = require("../extensions/publishHelpers");
12
+const requireAuth_1 = require("../requireAuth");
13
+const error_1 = require("../error");
14
+const utils = require("../utils");
15
+marked.setOptions({
16
+    renderer: new TerminalRenderer(),
17
+});
18
+exports.command = new command_1.Command("ext:dev:publish <extensionRef>")
19
+    .description(`publish a new version of an extension`)
20
+    .option(`-s, --stage <stage>`, `release stage (supports "rc", "alpha", "beta", and "stable")`)
21
+    .withForce()
22
+    .help("if you have not previously published a version of this extension, this will " +
23
+    "create the extension. If you have previously published a version of this extension, this version must " +
24
+    "be greater than previous versions.")
25
+    .before(requireAuth_1.requireAuth)
26
+    .action(async (extensionRef, options) => {
27
+    var _a;
28
+    const { publisherId, extensionId, version } = refs.parse(extensionRef);
29
+    if (version) {
30
+        throw new error_1.FirebaseError(`The input extension reference must be of the format ${clc.bold("<publisherId>/<extensionId>")}. Version should not be supplied and will be inferred directly from extension.yaml. Please increment the version in extension.yaml if you would like to bump/specify a version.`);
31
+    }
32
+    if (!publisherId || !extensionId) {
33
+        throw new error_1.FirebaseError(`Error parsing publisher ID and extension ID from extension reference '${clc.bold(extensionRef)}'. Please use the format '${clc.bold("<publisherId>/<extensionId>")}'.`);
34
+    }
35
+    const extensionYamlDirectory = (0, localHelper_1.findExtensionYaml)(process.cwd());
36
+    const res = await (0, extensionsHelper_1.publishExtensionVersionFromLocalSource)({
37
+        publisherId,
38
+        extensionId,
39
+        rootDirectory: extensionYamlDirectory,
40
+        nonInteractive: options.nonInteractive,
41
+        force: options.force,
42
+        stage: (_a = options.stage) !== null && _a !== void 0 ? _a : "stable",
43
+    });
44
+    if (res) {
45
+        utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`[Install Link](${(0, publishHelpers_1.consoleInstallLink)(res.ref)})`));
46
+    }
47
+    return res;
48
+});

+ 47
- 0
node_modules/firebase-tools/lib/commands/ext-dev-register.js View File

@@ -0,0 +1,47 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const { marked } = require("marked");
6
+const command_1 = require("../command");
7
+const extensionsApi_1 = require("../extensions/extensionsApi");
8
+const projectUtils_1 = require("../projectUtils");
9
+const prompt_1 = require("../prompt");
10
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
11
+const askUserForConsent_1 = require("../extensions/askUserForConsent");
12
+const requirePermissions_1 = require("../requirePermissions");
13
+const error_1 = require("../error");
14
+const utils = require("../utils");
15
+exports.command = new command_1.Command("ext:dev:register")
16
+    .description("register a publisher ID; run this before publishing your first extension.")
17
+    .before(requirePermissions_1.requirePermissions, ["firebaseextensions.sources.create"])
18
+    .before(extensionsHelper_1.ensureExtensionsApiEnabled)
19
+    .action(async (options) => {
20
+    await (0, askUserForConsent_1.promptForPublisherTOS)();
21
+    const projectId = (0, projectUtils_1.needProjectId)(options);
22
+    const msg = "What would you like to register as your publisher ID? " +
23
+        "This value identifies you in Firebase's registry of extensions as the author of your extensions. " +
24
+        "Examples: my-company-name, MyGitHubUsername.\n\n" +
25
+        "You can only do this once for each project.";
26
+    const publisherId = await (0, prompt_1.promptOnce)({
27
+        name: "publisherId",
28
+        type: "input",
29
+        message: msg,
30
+        default: projectId,
31
+    });
32
+    try {
33
+        await (0, extensionsApi_1.registerPublisherProfile)(projectId, publisherId);
34
+    }
35
+    catch (err) {
36
+        if (err.status === 409) {
37
+            const error = `Couldn't register the publisher ID '${clc.bold(publisherId)}' to the project '${clc.bold(projectId)}'.` +
38
+                " This can happen for either of two reasons:\n\n" +
39
+                ` - Publisher ID '${clc.bold(publisherId)}' is registered to another project\n` +
40
+                ` - Project '${clc.bold(projectId)}' already has a publisher ID\n\n` +
41
+                ` Try again with a unique publisher ID or a new project. If your business’s name has been registered to another project, contact Firebase support ${marked("(https://firebase.google.com/support/troubleshooter/contact).")}`;
42
+            throw new error_1.FirebaseError(error, { exit: 1 });
43
+        }
44
+        throw new error_1.FirebaseError(`Failed to register publisher ID ${clc.bold(publisherId)} for project ${clc.bold(projectId)}: ${err.message}`);
45
+    }
46
+    return utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `Publisher ID '${clc.bold(publisherId)}' has been registered to project ${clc.bold(projectId)}`);
47
+});

+ 57
- 0
node_modules/firebase-tools/lib/commands/ext-dev-undeprecate.js View File

@@ -0,0 +1,57 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const semver = require("semver");
6
+const refs = require("../extensions/refs");
7
+const utils = require("../utils");
8
+const command_1 = require("../command");
9
+const prompt_1 = require("../prompt");
10
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
11
+const extensionsApi_1 = require("../extensions/extensionsApi");
12
+const versionHelper_1 = require("../extensions/versionHelper");
13
+const requireAuth_1 = require("../requireAuth");
14
+const error_1 = require("../error");
15
+exports.command = new command_1.Command("ext:dev:undeprecate <extensionRef> <versionPredicate>")
16
+    .description("undeprecate extension versions that match the version predicate")
17
+    .before(requireAuth_1.requireAuth)
18
+    .before(extensionsHelper_1.ensureExtensionsApiEnabled)
19
+    .action(async (extensionRef, versionPredicate, options) => {
20
+    const { publisherId, extensionId, version } = refs.parse(extensionRef);
21
+    if (version) {
22
+        throw new error_1.FirebaseError(`The input extension reference must be of the format ${clc.bold("<publisherId>/<extensionId>")}. Version should be supplied in the version predicate argument.`);
23
+    }
24
+    if (!publisherId || !extensionId) {
25
+        throw new error_1.FirebaseError(`Error parsing publisher ID and extension ID from extension reference '${clc.bold(extensionRef)}'. Please use the format '${clc.bold("<publisherId>/<extensionId>")}'.`);
26
+    }
27
+    const { comparator, targetSemVer } = (0, versionHelper_1.parseVersionPredicate)(versionPredicate);
28
+    const filter = `id${comparator}"${targetSemVer}"`;
29
+    const extensionVersions = await (0, extensionsApi_1.listExtensionVersions)(extensionRef, filter);
30
+    extensionVersions
31
+        .sort((ev1, ev2) => {
32
+        return -semver.compare(ev1.spec.version, ev2.spec.version);
33
+    })
34
+        .forEach((extensionVersion) => {
35
+        utils.logLabeledBullet(extensionVersion.ref, extensionVersion.state);
36
+    });
37
+    if (extensionVersions.length > 0) {
38
+        if (!options.force) {
39
+            const confirmMessage = "You are about to undeprecate these extension version(s). Do you wish to continue?";
40
+            const consent = await (0, prompt_1.promptOnce)({
41
+                type: "confirm",
42
+                message: confirmMessage,
43
+                default: false,
44
+            });
45
+            if (!consent) {
46
+                throw new error_1.FirebaseError("Undeprecation canceled.");
47
+            }
48
+        }
49
+    }
50
+    else {
51
+        throw new error_1.FirebaseError("No extension versions matched the version predicate.");
52
+    }
53
+    await utils.allSettled(extensionVersions.map(async (extensionVersion) => {
54
+        await (0, extensionsApi_1.undeprecateExtensionVersion)(extensionVersion.ref);
55
+    }));
56
+    utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully undeprecated extension version(s).");
57
+});

+ 49
- 0
node_modules/firebase-tools/lib/commands/ext-dev-unpublish.js View File

@@ -0,0 +1,49 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const command_1 = require("../command");
5
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
6
+const extensionsApi_1 = require("../extensions/extensionsApi");
7
+const utils = require("../utils");
8
+const refs = require("../extensions/refs");
9
+const prompt_1 = require("../prompt");
10
+const clc = require("colorette");
11
+const requireAuth_1 = require("../requireAuth");
12
+const error_1 = require("../error");
13
+const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
14
+exports.command = new command_1.Command("ext:dev:unpublish <extensionRef>")
15
+    .description("unpublish an extension")
16
+    .withForce()
17
+    .help("use this command to unpublish an extension, and make it unavailable for developers to install or reconfigure. " +
18
+    "Specify the extension you want to unpublish using the format '<publisherId>/<extensionId>.")
19
+    .before(requireAuth_1.requireAuth)
20
+    .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
21
+    .action(async (extensionRef, options) => {
22
+    const { publisherId, extensionId, version } = refs.parse(extensionRef);
23
+    utils.logLabeledWarning(extensionsHelper_1.logPrefix, "If you unpublish this extension, developers won't be able to install it. For developers who currently have this extension installed, it will continue to run and will appear as unpublished when listed in the Firebase console or Firebase CLI.");
24
+    utils.logLabeledWarning("This is a permanent action", `Once unpublished, you may never use the extension name '${clc.bold(extensionId)}' again.`);
25
+    if (version) {
26
+        throw new error_1.FirebaseError(`Unpublishing a single version is not currently supported. You can only unpublish ${clc.bold("ALL versions")} of an extension. To unpublish all versions, please remove the version from the reference.`);
27
+    }
28
+    await (0, extensionsApi_1.getExtension)(extensionRef);
29
+    const consent = await comfirmUnpublish(publisherId, extensionId, options);
30
+    if (!consent) {
31
+        throw new error_1.FirebaseError("unpublishing cancelled.");
32
+    }
33
+    await (0, extensionsApi_1.unpublishExtension)(extensionRef);
34
+    utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully unpublished all versions of this extension.");
35
+});
36
+async function comfirmUnpublish(publisherId, extensionId, options) {
37
+    if (options.nonInteractive && !options.force) {
38
+        throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
39
+    }
40
+    if (options.nonInteractive && options.force) {
41
+        return true;
42
+    }
43
+    const message = `You are about to unpublish ALL versions of ${clc.green(`${publisherId}/${extensionId}`)}.\nDo you wish to continue? `;
44
+    return (0, prompt_1.promptOnce)({
45
+        type: "confirm",
46
+        message,
47
+        default: false,
48
+    });
49
+}

+ 141
- 0
node_modules/firebase-tools/lib/commands/ext-dev-usage.js View File

@@ -0,0 +1,141 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const Table = require("cli-table");
5
+const clc = require("colorette");
6
+const utils = require("../utils");
7
+const command_1 = require("../command");
8
+const cloudmonitoring_1 = require("../gcp/cloudmonitoring");
9
+const requireAuth_1 = require("../requireAuth");
10
+const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
11
+const metricsUtils_1 = require("../extensions/metricsUtils");
12
+const extensionsApi_1 = require("../extensions/extensionsApi");
13
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
14
+const error_1 = require("../error");
15
+const logger_1 = require("../logger");
16
+const prompt_1 = require("../prompt");
17
+const shortenUrl_1 = require("../shortenUrl");
18
+exports.command = new command_1.Command("ext:dev:usage <publisherId>")
19
+    .description("get usage for an extension")
20
+    .help("use this command to get the usage of extensions you published. " +
21
+    "Specify the publisher ID you used to publish your extensions, " +
22
+    "or the extension ref of your published extension.")
23
+    .before(requireAuth_1.requireAuth)
24
+    .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
25
+    .action(async (input) => {
26
+    const extensionRefRegex = /^[\w\d-]+\/[\w\d-]+$/;
27
+    let extensionName;
28
+    let publisherId;
29
+    if (extensionRefRegex.test(input)) {
30
+        [publisherId, extensionName] = input.split("/");
31
+    }
32
+    else {
33
+        publisherId = input;
34
+        let extensions;
35
+        try {
36
+            extensions = await (0, extensionsApi_1.listExtensions)(publisherId);
37
+        }
38
+        catch (err) {
39
+            throw new error_1.FirebaseError(err);
40
+        }
41
+        if (extensions.length < 1) {
42
+            throw new error_1.FirebaseError(`There are no published extensions associated with publisher ID ${clc.bold(publisherId)}. This could happen for two reasons:\n` +
43
+                "  - The publisher ID doesn't exist or could be misspelled\n" +
44
+                "  - This publisher has not published any extensions\n\n" +
45
+                "If you are expecting some extensions to appear, please make sure you have the correct publisher ID and try again.");
46
+        }
47
+        extensionName = await (0, prompt_1.promptOnce)({
48
+            type: "list",
49
+            name: "extension",
50
+            message: "Which published extension do you want to view the stats for?",
51
+            choices: extensions.map((e) => {
52
+                const [_, name] = e.ref.split("/");
53
+                return {
54
+                    name,
55
+                    value: name,
56
+                };
57
+            }),
58
+        });
59
+    }
60
+    const profile = await (0, extensionsApi_1.getPublisherProfile)("-", publisherId);
61
+    const projectNumber = (0, extensionsHelper_1.getPublisherProjectFromName)(profile.name);
62
+    const past45d = new Date();
63
+    past45d.setDate(past45d.getDate() - 45);
64
+    const query = {
65
+        filter: `metric.type="firebaseextensions.googleapis.com/extension/version/active_instances" ` +
66
+            `resource.type="firebaseextensions.googleapis.com/ExtensionVersion" ` +
67
+            `resource.labels.extension="${extensionName}"`,
68
+        "interval.endTime": new Date().toJSON(),
69
+        "interval.startTime": past45d.toJSON(),
70
+        view: cloudmonitoring_1.TimeSeriesView.FULL,
71
+        "aggregation.alignmentPeriod": (60 * 60 * 24).toString() + "s",
72
+        "aggregation.perSeriesAligner": cloudmonitoring_1.Aligner.ALIGN_MAX,
73
+    };
74
+    let response;
75
+    try {
76
+        response = await (0, cloudmonitoring_1.queryTimeSeries)(query, projectNumber);
77
+    }
78
+    catch (err) {
79
+        throw new error_1.FirebaseError(`Error occurred when fetching usage data for extension ${extensionName}`, {
80
+            original: err,
81
+        });
82
+    }
83
+    if (!response) {
84
+        throw new error_1.FirebaseError(`Couldn't find any usage data for extension ${extensionName}`);
85
+    }
86
+    const metrics = (0, metricsUtils_1.parseTimeseriesResponse)(response);
87
+    const table = new Table({
88
+        head: ["Version", "Active Instances", "Changes last 7 Days", "Changes last 28 Days"],
89
+        style: {
90
+            head: ["yellow"],
91
+        },
92
+        colAligns: ["left", "right", "right", "right"],
93
+    });
94
+    metrics.forEach((m) => {
95
+        table.push((0, metricsUtils_1.buildMetricsTableRow)(m));
96
+    });
97
+    utils.logLabeledBullet(extensionsHelper_1.logPrefix, `showing usage stats for ${clc.bold(extensionName)}:`);
98
+    logger_1.logger.info(table.toString());
99
+    utils.logLabeledBullet(extensionsHelper_1.logPrefix, `How to read this table:`);
100
+    logger_1.logger.info(`* Due to privacy considerations, numbers are reported as ranges.`);
101
+    logger_1.logger.info(`* In the absence of significant changes, we will render a '-' symbol.`);
102
+    logger_1.logger.info(`* You will need more than 10 installs over a period of more than 28 days to render sufficient data.`);
103
+});
104
+async function buildCloudMonitoringLink(args) {
105
+    const pageState = {
106
+        xyChart: {
107
+            dataSets: [
108
+                {
109
+                    timeSeriesFilter: {
110
+                        filter: `metric.type="firebaseextensions.googleapis.com/extension/version/active_instances"` +
111
+                            ` resource.type="firebaseextensions.googleapis.com/ExtensionVersion"` +
112
+                            ` resource.label.extension="${args.extensionName}"`,
113
+                        minAlignmentPeriod: "86400s",
114
+                        aggregations: [
115
+                            {
116
+                                perSeriesAligner: "ALIGN_MEAN",
117
+                                crossSeriesReducer: "REDUCE_MAX",
118
+                                alignmentPeriod: "86400s",
119
+                                groupByFields: ['resource.label."extension"', 'resource.label."version"'],
120
+                            },
121
+                            {
122
+                                crossSeriesReducer: "REDUCE_NONE",
123
+                                alignmentPeriod: "60s",
124
+                                groupByFields: [],
125
+                            },
126
+                        ],
127
+                    },
128
+                },
129
+            ],
130
+        },
131
+        isAutoRefresh: true,
132
+        timeSelection: {
133
+            timeRange: "4w",
134
+        },
135
+    };
136
+    let uri = `https://console.cloud.google.com/monitoring/metrics-explorer?project=${args.projectNumber}` +
137
+        `&pageState=${JSON.stringify(pageState)}`;
138
+    uri = encodeURI(uri);
139
+    uri = await (0, shortenUrl_1.shortenUrl)(uri);
140
+    return uri;
141
+}

+ 68
- 0
node_modules/firebase-tools/lib/commands/ext-export.js View File

@@ -0,0 +1,68 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
5
+const command_1 = require("../command");
6
+const planner = require("../deploy/extensions/planner");
7
+const etags_1 = require("../extensions/etags");
8
+const export_1 = require("../extensions/export");
9
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
10
+const manifest = require("../extensions/manifest");
11
+const paramHelper_1 = require("../extensions/paramHelper");
12
+const functional_1 = require("../functional");
13
+const getProjectNumber_1 = require("../getProjectNumber");
14
+const logger_1 = require("../logger");
15
+const projectUtils_1 = require("../projectUtils");
16
+const prompt_1 = require("../prompt");
17
+const requirePermissions_1 = require("../requirePermissions");
18
+exports.command = new command_1.Command("ext:export")
19
+    .description("export all Extension instances installed on a project to a local Firebase directory")
20
+    .before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.list"])
21
+    .before(extensionsHelper_1.ensureExtensionsApiEnabled)
22
+    .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
23
+    .withForce()
24
+    .action(async (options) => {
25
+    const projectId = (0, projectUtils_1.needProjectId)(options);
26
+    const projectNumber = await (0, getProjectNumber_1.getProjectNumber)(options);
27
+    const have = await Promise.all(await planner.have(projectId));
28
+    if (have.length === 0) {
29
+        logger_1.logger.info(`No extension instances installed on ${projectId}, so there is nothing to export.`);
30
+        return;
31
+    }
32
+    const [withRef, withoutRef] = (0, functional_1.partition)(have, (s) => !!s.ref);
33
+    const withRefSubbed = await Promise.all(withRef.map(async (i) => {
34
+        const subbed = await (0, export_1.setSecretParamsToLatest)(i);
35
+        return (0, export_1.parameterizeProject)(projectId, projectNumber, subbed);
36
+    }));
37
+    (0, export_1.displayExportInfo)(withRefSubbed, withoutRef);
38
+    if (!options.nonInteractive &&
39
+        !options.force &&
40
+        !(await (0, prompt_1.promptOnce)({
41
+            message: "Do you wish to add these Extension instances to firebase.json?",
42
+            type: "confirm",
43
+            default: true,
44
+        }))) {
45
+        logger_1.logger.info("Exiting. No changes made.");
46
+        return;
47
+    }
48
+    const manifestSpecs = withRefSubbed.map((spec) => {
49
+        const paramCopy = Object.assign({}, spec.params);
50
+        if (spec.eventarcChannel) {
51
+            paramCopy.EVENTARC_CHANNEL = spec.eventarcChannel;
52
+        }
53
+        if (spec.allowedEventTypes) {
54
+            paramCopy.ALLOWED_EVENT_TYPES = spec.allowedEventTypes.join(",");
55
+        }
56
+        return {
57
+            instanceId: spec.instanceId,
58
+            ref: spec.ref,
59
+            params: (0, paramHelper_1.buildBindingOptionsWithBaseValue)(paramCopy),
60
+        };
61
+    });
62
+    const existingConfig = manifest.loadConfig(options);
63
+    await manifest.writeToManifest(manifestSpecs, existingConfig, {
64
+        nonInteractive: options.nonInteractive,
65
+        force: options.force,
66
+    }, true);
67
+    (0, etags_1.saveEtags)(options.rc, projectId, have);
68
+});

+ 118
- 0
node_modules/firebase-tools/lib/commands/ext-info.js View File

@@ -0,0 +1,118 @@
1
+"use strict";
2
+Object.defineProperty(exports, "__esModule", { value: true });
3
+exports.command = void 0;
4
+const clc = require("colorette");
5
+const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
6
+const command_1 = require("../command");
7
+const extensionsApi = require("../extensions/extensionsApi");
8
+const extensionsHelper_1 = require("../extensions/extensionsHelper");
9
+const localHelper_1 = require("../extensions/localHelper");
10
+const logger_1 = require("../logger");
11
+const requirePermissions_1 = require("../requirePermissions");
12
+const utils = require("../utils");
13
+const { marked } = require("marked");
14
+const TerminalRenderer = require("marked-terminal");
15
+const FUNCTION_TYPE_REGEX = /\..+\.function/;
16
+exports.command = new command_1.Command("ext:info <extensionName>")
17
+    .description("display information about an extension by name (extensionName@x.y.z for a specific version)")
18
+    .option("--markdown", "output info in Markdown suitable for constructing a README file")
19
+    .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
20
+    .action(async (extensionName, options) => {
21
+    var _a, _b;
22
+    let spec;
23
+    if ((0, localHelper_1.isLocalExtension)(extensionName)) {
24
+        if (!options.markdown) {
25
+            utils.logLabeledBullet(extensionsHelper_1.logPrefix, `reading extension from directory: ${extensionName}`);
26
+        }
27
+        spec = await (0, localHelper_1.getLocalExtensionSpec)(extensionName);
28
+    }
29
+    else {
30
+        await (0, requirePermissions_1.requirePermissions)(options, ["firebaseextensions.sources.get"]);
31
+        await (0, extensionsHelper_1.ensureExtensionsApiEnabled)(options);
32
+        const hasPublisherId = extensionName.split("/").length >= 2;
33
+        if (hasPublisherId) {
34
+            const nameAndVersion = extensionName.split("/")[1];
35
+            if (nameAndVersion.split("@").length < 2) {
36
+                extensionName = extensionName + "@latest";
37
+            }
38
+        }
39
+        else {
40
+            const [name, version] = extensionName.split("@");
41
+            extensionName = `firebase/${name}@${version || "latest"}`;
42
+        }
43
+        const version = await extensionsApi.getExtensionVersion(extensionName);
44
+        spec = version.spec;
45
+    }
46
+    if (!options.markdown) {
47
+        utils.logLabeledBullet(extensionsHelper_1.logPrefix, `information about ${extensionName}:\n`);
48
+    }
49
+    const lines = [];
50
+    if (options.markdown) {
51
+        lines.push(`# ${spec.displayName}`);
52
+    }
53
+    else {
54
+        lines.push(`**Name**: ${spec.displayName}`);
55
+    }
56
+    const authorName = (_a = spec.author) === null || _a === void 0 ? void 0 : _a.authorName;
57
+    const url = (_b = spec.author) === null || _b === void 0 ? void 0 : _b.url;
58
+    const urlMarkdown = url ? `(**[${url}](${url})**)` : "";
59
+    lines.push(`**Author**: ${authorName} ${urlMarkdown}`);
60
+    if (spec.description) {
61
+        lines.push(`**Description**: ${spec.description}`);
62
+    }
63
+    if (spec.preinstallContent) {
64
+        lines.push("", `**Details**: ${spec.preinstallContent}`);
65
+    }
66
+    if (spec.params && Array.isArray(spec.params) && spec.params.length > 0) {
67
+        lines.push("", "**Configuration Parameters:**");
68
+        for (const param of spec.params) {
69
+            lines.push(`* ${param.label}` + (param.description ? `: ${param.description}` : ""));
70
+        }
71
+    }
72
+    const functions = [];
73
+    const otherResources = [];
74
+    for (const resource of spec.resources) {
75
+        if (FUNCTION_TYPE_REGEX.test(resource.type)) {
76
+            functions.push(resource);
77
+        }
78
+        else {
79
+            otherResources.push(resource);
80
+        }
81
+    }
82
+    if (functions.length > 0) {
83
+        lines.push("", "**Cloud Functions:**");
84
+        for (const func of functions) {
85
+            lines.push(`* **${func.name}:** ${func.description}`);
86
+        }
87
+    }
88
+    if (otherResources.length > 0) {
89
+        lines.push("", "**Other Resources**:");
90
+        for (const resource of otherResources) {
91
+            lines.push(`* ${resource.name} (${resource.type})`);
92
+        }
93
+    }
94
+    if (spec.apis) {
95
+        lines.push("", "**APIs Used**:");
96
+        for (const api of spec.apis) {
97
+            lines.push(`* ${api.apiName}` + (api.reason ? ` (Reason: ${api.reason})` : ""));
98
+        }
99
+    }
100
+    if (spec.roles) {
101
+        lines.push("", "**Access Required**:");
102
+        lines.push("", "This extension will operate with the following project IAM roles:");
103
+        for (const role of spec.roles) {
104
+            lines.push(`* ${role.role}` + (role.reason ? ` (Reason: ${role.reason})` : ""));
105
+        }
106
+    }
107
+    if (options.markdown) {
108
+        logger_1.logger.info(lines.join("\n\n"));
109
+    }
110
+    else {
111
+        marked.setOptions({
112
+            renderer: new TerminalRenderer(),
113
+        });
114
+        logger_1.logger.info(marked(lines.join("\n")));
115
+        utils.logLabeledBullet(extensionsHelper_1.logPrefix, `to install this extension, type ` +
116
+            clc.bold(`firebase ext:install ${extensionName} --project=YOUR_PROJECT`));
117
+    }
118
+});

+ 0
- 0
node_modules/firebase-tools/lib/commands/ext-install.js View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save