firebase-tools 13.30.0 → 13.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/lib/apphosting/secrets/dialogs.js +2 -2
  2. package/lib/commands/appdistribution-groups-list.js +1 -1
  3. package/lib/commands/appdistribution-testers-list.js +4 -4
  4. package/lib/commands/apphosting-backends-list.js +1 -1
  5. package/lib/commands/apphosting-secrets-describe.js +1 -1
  6. package/lib/commands/apps-android-sha-list.js +1 -1
  7. package/lib/commands/apps-create.js +1 -104
  8. package/lib/commands/apps-init.js +94 -0
  9. package/lib/commands/apps-list.js +1 -1
  10. package/lib/commands/apps-sdkconfig.js +3 -3
  11. package/lib/commands/database-instances-list.js +1 -1
  12. package/lib/commands/dataconnect-services-list.js +1 -1
  13. package/lib/commands/emulators-start.js +1 -1
  14. package/lib/commands/experiments-list.js +1 -1
  15. package/lib/commands/ext-dev-list.js +1 -1
  16. package/lib/commands/ext-dev-usage.js +1 -1
  17. package/lib/commands/functions-list.js +1 -1
  18. package/lib/commands/hosting-channel-list.js +1 -1
  19. package/lib/commands/hosting-sites-get.js +1 -1
  20. package/lib/commands/hosting-sites-list.js +1 -1
  21. package/lib/commands/index.js +1 -0
  22. package/lib/commands/projects-list.js +1 -1
  23. package/lib/commands/remoteconfig-get.js +1 -1
  24. package/lib/commands/remoteconfig-versions-list.js +1 -1
  25. package/lib/crashlytics/buildToolsJarHelper.js +1 -1
  26. package/lib/dataconnect/build.js +6 -0
  27. package/lib/dataconnect/client.js +1 -1
  28. package/lib/dataconnect/dataplaneClient.js +1 -1
  29. package/lib/dataconnect/fileUtils.js +25 -2
  30. package/lib/dataconnect/graphqlError.js +5 -3
  31. package/lib/emulator/commandUtils.js +1 -1
  32. package/lib/emulator/dataconnectEmulator.js +4 -1
  33. package/lib/emulator/dataconnectToolkitController.js +5 -0
  34. package/lib/emulator/downloadableEmulators.js +9 -9
  35. package/lib/emulator/extensionsEmulator.js +2 -2
  36. package/lib/experiments.js +6 -0
  37. package/lib/extensions/change-log.js +1 -25
  38. package/lib/extensions/listExtensions.js +1 -1
  39. package/lib/firestore/pretty-print.js +1 -1
  40. package/lib/frameworks/angular/index.js +11 -6
  41. package/lib/functions/secrets.js +1 -1
  42. package/lib/gcp/cloudsql/interactive.js +1 -1
  43. package/lib/init/features/dataconnect/sdk.js +28 -3
  44. package/lib/init/features/genkit/index.js +9 -7
  45. package/lib/management/apps.js +314 -7
  46. package/lib/profileReport.js +1 -1
  47. package/lib/prompt.js +10 -2
  48. package/package.json +14 -5
  49. package/templates/genkit/firebase.0.9.0.template +1 -1
  50. package/templates/genkit/firebase.1.0.0.template +1 -1
  51. package/templates/setup/web.js +0 -5
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DataConnectToolkitController = void 0;
4
+ const types_1 = require("./types");
4
5
  const error_1 = require("../error");
5
6
  const portUtils = require("./portUtils");
6
7
  const utils_1 = require("../utils");
7
8
  const dataconnectEmulator_1 = require("./dataconnectEmulator");
9
+ const downloadableEmulators_1 = require("./downloadableEmulators");
8
10
  const name = "Data Connect Toolkit";
9
11
  class DataConnectToolkitController {
10
12
  static async start(args) {
@@ -29,6 +31,9 @@ class DataConnectToolkitController {
29
31
  throw new error_1.FirebaseError(`Data Connect Toolkit failed to stop with error: ${e}`);
30
32
  }
31
33
  }
34
+ static getVersion() {
35
+ return (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.DATACONNECT).version;
36
+ }
32
37
  static getInfo() {
33
38
  return this.instance.getInfo();
34
39
  }
@@ -48,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
48
48
  },
49
49
  dataconnect: process.platform === "darwin"
50
50
  ? {
51
- version: "1.7.7",
52
- expectedSize: 25359104,
53
- expectedChecksum: "c5481addc04e14d10538add7aabda183",
51
+ version: "1.8.0",
52
+ expectedSize: 25469696,
53
+ expectedChecksum: "d20a4240b4290a262caff3d0d3a85a4f",
54
54
  }
55
55
  : process.platform === "win32"
56
56
  ? {
57
- version: "1.7.7",
58
- expectedSize: 25788416,
59
- expectedChecksum: "9f7e5b9bcbca47de509fbc26cc1e0fa8",
57
+ version: "1.8.0",
58
+ expectedSize: 25903616,
59
+ expectedChecksum: "dda0350259449493ecc5cc24187ce752",
60
60
  }
61
61
  : {
62
- version: "1.7.7",
63
- expectedSize: 25268376,
64
- expectedChecksum: "fb239ecf5dcbf87b762d12a3e9dee012",
62
+ version: "1.8.0",
63
+ expectedSize: 25383064,
64
+ expectedChecksum: "54a19e336f4118854c4c2d4eeeccb506",
65
65
  },
66
66
  };
67
67
  exports.DownloadDetails = {
@@ -6,7 +6,7 @@ const spawn = require("cross-spawn");
6
6
  const fs = require("fs-extra");
7
7
  const os = require("os");
8
8
  const path = require("path");
9
- const Table = require("cli-table");
9
+ const Table = require("cli-table3");
10
10
  const planner = require("../deploy/extensions/planner");
11
11
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
12
12
  const error_1 = require("../error");
@@ -221,7 +221,7 @@ class ExtensionsEmulator {
221
221
  const enablementUri = await (0, shortenUrl_1.shortenUrl)((0, ensureApiEnabled_1.enableApiURI)(this.args.projectId, apiToWarn.apiName));
222
222
  table.push([
223
223
  apiToWarn.apiName,
224
- apiToWarn.instanceIds,
224
+ apiToWarn.instanceIds.join(", "),
225
225
  apiToWarn.enabled ? "Yes" : "No",
226
226
  apiToWarn.enabled ? "" : clc.bold(clc.underline(enablementUri)),
227
227
  ]);
@@ -106,6 +106,12 @@ exports.ALL_EXPERIMENTS = experiments({
106
106
  default: true,
107
107
  public: false,
108
108
  },
109
+ appsinit: {
110
+ shortDescription: "Adds experimental `apps:init` command.",
111
+ fullDescription: "Adds experimental `apps:init` command. When run from an app directory, this command detects the app's platform and configures required files.",
112
+ default: false,
113
+ public: true,
114
+ },
109
115
  fdcconnectorevolution: {
110
116
  shortDescription: "Enable Data Connect connector evolution warnings.",
111
117
  fullDescription: "Enable Data Connect connector evolution warnings.",
@@ -1,17 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseChangelog = exports.getLocalChangelog = exports.breakingChangesInUpdate = exports.displayReleaseNotes = exports.getReleaseNotesForUpdate = void 0;
4
- const clc = require("colorette");
3
+ exports.parseChangelog = exports.getLocalChangelog = exports.breakingChangesInUpdate = exports.getReleaseNotesForUpdate = void 0;
5
4
  const marked_1 = require("marked");
6
5
  const path = require("path");
7
6
  const semver = require("semver");
8
7
  const marked_terminal_1 = require("marked-terminal");
9
- const Table = require("cli-table");
10
8
  const extensionsApi_1 = require("./extensionsApi");
11
9
  const localHelper_1 = require("./localHelper");
12
- const logger_1 = require("../logger");
13
10
  const refs = require("./refs");
14
- const utils_1 = require("../utils");
15
11
  marked_1.marked.use((0, marked_terminal_1.markedTerminal)());
16
12
  const EXTENSIONS_CHANGELOG = "CHANGELOG.md";
17
13
  const VERSION_LINE_REGEX = /##.+?(\d+\.\d+\.\d+(?:-((\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?).*/;
@@ -31,26 +27,6 @@ async function getReleaseNotesForUpdate(args) {
31
27
  return releaseNotes;
32
28
  }
33
29
  exports.getReleaseNotesForUpdate = getReleaseNotesForUpdate;
34
- function displayReleaseNotes(releaseNotes, fromVersion) {
35
- const versions = [fromVersion].concat(Object.keys(releaseNotes));
36
- const breakingVersions = breakingChangesInUpdate(versions);
37
- const table = new Table({ head: ["Version", "What's New"], style: { head: ["yellow", "bold"] } });
38
- for (const [version, note] of Object.entries(releaseNotes)) {
39
- if (breakingVersions.includes(version)) {
40
- table.push([clc.yellow(clc.bold(version)), (0, marked_1.marked)(note)]);
41
- }
42
- else {
43
- table.push([version, (0, marked_1.marked)(note)]);
44
- }
45
- }
46
- logger_1.logger.info(clc.bold("What's new with this update:"));
47
- if (breakingVersions.length) {
48
- (0, utils_1.logLabeledWarning)("warning", "This is a major version update, which means it may contain breaking changes." +
49
- " Read the release notes carefully before continuing with this update.");
50
- }
51
- logger_1.logger.info(table.toString());
52
- }
53
- exports.displayReleaseNotes = displayReleaseNotes;
54
30
  function breakingChangesInUpdate(versionsInUpdate) {
55
31
  const breakingVersions = [];
56
32
  const semvers = versionsInUpdate.map((v) => semver.parse(v)).sort(semver.compare);
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.listExtensions = void 0;
4
4
  const clc = require("colorette");
5
- const Table = require("cli-table");
5
+ const Table = require("cli-table3");
6
6
  const extensionsApi_1 = require("./extensionsApi");
7
7
  const logger_1 = require("../logger");
8
8
  const utils_1 = require("../utils");
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PrettyPrint = void 0;
4
4
  const clc = require("colorette");
5
- const Table = require("cli-table");
5
+ const Table = require("cli-table3");
6
6
  const sort = require("./api-sort");
7
7
  const types = require("./api-types");
8
8
  const logger_1 = require("../logger");
@@ -15,7 +15,7 @@ exports.support = "preview";
15
15
  exports.type = 3;
16
16
  exports.docsUrl = "https://firebase.google.com/docs/hosting/frameworks/angular";
17
17
  const DEFAULT_BUILD_SCRIPT = ["ng build"];
18
- exports.supportedRange = "16 - 18";
18
+ exports.supportedRange = "16 - 19";
19
19
  async function discover(dir) {
20
20
  if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
21
21
  return;
@@ -182,17 +182,22 @@ exports.handle = function(req,res) {
182
182
  }
183
183
  else if (serverOutputPath) {
184
184
  bootstrapScript = `
185
- const app = new Promise((resolve) => {
185
+ const app = new Promise((resolve, reject) => {
186
186
  setTimeout(() => {
187
187
  const port = process.env.PORT;
188
188
  const socket = 'express.sock';
189
189
  process.env.PORT = socket;
190
-
190
+
191
191
  ${(serverEntry === null || serverEntry === void 0 ? void 0 : serverEntry.endsWith(".mjs"))
192
192
  ? `import(\`./${serverOutputPath}/${serverEntry}\`)`
193
- : `Promise.resolve(require('./${serverOutputPath}/${serverEntry}'))`}.then(({ app }) => {
194
- process.env.PORT = port;
195
- resolve(app());
193
+ : `Promise.resolve(require('./${serverOutputPath}/${serverEntry}'))`}.then(({ default: defHandler, reqHandler, app }) => {
194
+ const handler = app?.() ?? reqHandler ?? defHandler;
195
+ if (!handler) {
196
+ reject(\`The file at "./${serverOutputPath}/${serverEntry}" did not export a valid request handler. Expected exports: 'app', 'default', or 'reqHandler'.\`);
197
+ } else {
198
+ process.env.PORT = port;
199
+ resolve(handler);
200
+ }
196
201
  });
197
202
  }, 0);
198
203
  });
@@ -16,7 +16,7 @@ const functional_1 = require("../functional");
16
16
  const secretManager_2 = require("../gcp/secretManager");
17
17
  const secretManager_3 = require("../gcp/secretManager");
18
18
  const projectUtils_1 = require("../projectUtils");
19
- const Table = require("cli-table");
19
+ const Table = require("cli-table3");
20
20
  const gcfV1PollerOptions = {
21
21
  apiOrigin: (0, api_1.functionsOrigin)(),
22
22
  apiVersion: "v1",
@@ -5,7 +5,7 @@ const ora = require("ora");
5
5
  const clc = require("colorette");
6
6
  const logger_1 = require("../../logger");
7
7
  const prompt_1 = require("../../prompt");
8
- const Table = require("cli-table");
8
+ const Table = require("cli-table3");
9
9
  const destructiveSqlKeywords = ["DROP", "DELETE"];
10
10
  function checkIsDestructiveSql(query) {
11
11
  const upperCaseQuery = query.toUpperCase();
@@ -75,13 +75,30 @@ async function askQuestions(setup, config) {
75
75
  choices: connectorChoices,
76
76
  });
77
77
  const connectorYaml = JSON.parse(JSON.stringify(connectorInfo.connectorYaml));
78
- const newConnectorYaml = generateSdkYaml(targetPlatform, connectorYaml, connectorInfo.directory, appDir);
78
+ const newConnectorYaml = await generateSdkYaml(targetPlatform, connectorYaml, connectorInfo.directory, appDir);
79
+ if (targetPlatform === types_1.Platform.WEB) {
80
+ const unusedFrameworks = fileUtils_1.SUPPORTED_FRAMEWORKS.filter((framework) => { var _a; return (_a = newConnectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk[framework]; });
81
+ if (unusedFrameworks.length > 0) {
82
+ const additionalFrameworks = await (0, prompt_1.prompt)(setup, [
83
+ {
84
+ type: "checkbox",
85
+ name: "features",
86
+ message: "Which framework would you like to generate SDKs for? " +
87
+ "Press Space to select features, then Enter to confirm your choices.",
88
+ choices: unusedFrameworks,
89
+ },
90
+ ]);
91
+ for (const framework of additionalFrameworks.features) {
92
+ newConnectorYaml.generate.javascriptSdk[framework] = true;
93
+ }
94
+ }
95
+ }
79
96
  const connectorYamlContents = yaml.stringify(newConnectorYaml);
80
97
  connectorInfo.connectorYaml = newConnectorYaml;
81
98
  const displayIOSWarning = targetPlatform === types_1.Platform.IOS;
82
99
  return { connectorYamlContents, connectorInfo, displayIOSWarning };
83
100
  }
84
- function generateSdkYaml(targetPlatform, connectorYaml, connectorDir, appDir) {
101
+ async function generateSdkYaml(targetPlatform, connectorYaml, connectorDir, appDir) {
85
102
  if (!connectorYaml.generate) {
86
103
  connectorYaml.generate = {};
87
104
  }
@@ -94,11 +111,19 @@ function generateSdkYaml(targetPlatform, connectorYaml, connectorDir, appDir) {
94
111
  }
95
112
  if (targetPlatform === types_1.Platform.WEB) {
96
113
  const pkg = `${connectorYaml.connectorId}-connector`;
114
+ const packageJsonDir = path.relative(connectorDir, appDir);
97
115
  const javascriptSdk = {
98
116
  outputDir: path.relative(connectorDir, path.join(appDir, `dataconnect-generated/js/${pkg}`)),
99
117
  package: `@firebasegen/${pkg}`,
100
- packageJsonDir: path.relative(connectorDir, appDir),
118
+ packageJsonDir,
101
119
  };
120
+ const packageJson = await (0, fileUtils_1.resolvePackageJson)(appDir);
121
+ if (packageJson) {
122
+ const frameworksUsed = (0, fileUtils_1.getFrameworksFromPackageJson)(packageJson);
123
+ for (const framework of frameworksUsed) {
124
+ javascriptSdk[framework] = true;
125
+ }
126
+ }
102
127
  connectorYaml.generate.javascriptSdk = javascriptSdk;
103
128
  }
104
129
  if (targetPlatform === types_1.Platform.FLUTTER) {
@@ -65,6 +65,12 @@ async function getGenkitVersion() {
65
65
  }
66
66
  return { genkitVersion, templateVersion, useInit, stopInstall };
67
67
  }
68
+ function showStartMessage(config, command) {
69
+ logger_1.logger.info("\nLogin to Google Cloud using:");
70
+ logger_1.logger.info(clc.bold(clc.green(` gcloud auth application-default login --project ${config.options.project}\n`)));
71
+ logger_1.logger.info("Then start the Genkit developer experience by running:");
72
+ logger_1.logger.info(clc.bold(clc.green(` ${command}`)));
73
+ }
68
74
  async function doSetup(setup, config, options) {
69
75
  var _a;
70
76
  const genkitInfo = await getGenkitVersion();
@@ -113,23 +119,19 @@ async function doSetup(setup, config, options) {
113
119
  else {
114
120
  await (0, spawn_1.wrapSpawn)("npm", ["install", "-g", `genkit-cli@${genkitInfo.genkitVersion}`], projectDir);
115
121
  await genkitSetup(options, genkitInfo, projectDir);
116
- logger_1.logger.info("Start the Genkit developer experience by running:");
117
- logger_1.logger.info(` cd ${setup.functions.source} && npm run genkit:start`);
122
+ showStartMessage(config, `cd ${setup.functions.source} && npm run genkit:start`);
118
123
  }
119
124
  }
120
125
  else {
121
126
  if (genkitInfo.useInit) {
122
127
  await (0, spawn_1.wrapSpawn)("npm", ["install", `genkit@${genkitInfo.genkitVersion}`, "--save-dev"], projectDir);
123
128
  await (0, spawn_1.wrapSpawn)("npx", ["genkit", "init", "-p", "firebase"], projectDir);
124
- logger_1.logger.info("Start the Genkit developer experience by running:");
125
- logger_1.logger.info(` cd ${setup.functions.source} && npx genkit start`);
129
+ showStartMessage(config, `cd ${setup.functions.source} && npx genkit start`);
126
130
  }
127
131
  else {
128
132
  await (0, spawn_1.wrapSpawn)("npm", ["install", `genkit-cli@${genkitInfo.genkitVersion}`, "--save-dev"], projectDir);
129
133
  await genkitSetup(options, genkitInfo, projectDir);
130
- logger_1.logger.info("Start the Genkit developer experience by running:");
131
- logger_1.logger.info();
132
- logger_1.logger.info(clc.bold(clc.green(` cd ${setup.functions.source} && npm run genkit:start`)));
134
+ showStartMessage(config, `cd ${setup.functions.source} && npm run genkit:start`);
133
135
  }
134
136
  }
135
137
  }
@@ -1,16 +1,248 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteAppAndroidSha = exports.createAppAndroidSha = exports.listAppAndroidSha = exports.getAppConfig = exports.getAppConfigFile = exports.listFirebaseApps = exports.createWebApp = exports.createAndroidApp = exports.createIosApp = exports.getAppPlatform = exports.ShaCertificateType = exports.AppPlatform = exports.APP_LIST_PAGE_SIZE = void 0;
3
+ exports.findIntelligentPathForAndroid = exports.findIntelligentPathForIOS = exports.deleteAppAndroidSha = exports.createAppAndroidSha = exports.listAppAndroidSha = exports.getAppConfig = exports.writeConfigToFile = exports.getAppConfigFile = exports.listFirebaseApps = exports.createWebApp = exports.createAndroidApp = exports.createIosApp = exports.getAppPlatform = exports.ShaCertificateType = exports.AppPlatform = exports.getSdkConfig = exports.checkForApps = exports.getSdkOutputPath = exports.sdkInit = exports.getPlatform = exports.APP_LIST_PAGE_SIZE = void 0;
4
+ const fs = require("fs-extra");
5
+ const ora = require("ora");
6
+ const path = require("path");
4
7
  const apiv2_1 = require("../apiv2");
5
8
  const api_1 = require("../api");
6
9
  const error_1 = require("../error");
7
10
  const logger_1 = require("../logger");
8
11
  const operation_poller_1 = require("../operation-poller");
9
- const templates_1 = require("../templates");
12
+ const types_1 = require("../dataconnect/types");
13
+ const projectUtils_1 = require("../projectUtils");
14
+ const prompt_1 = require("../prompt");
15
+ const projects_1 = require("./projects");
16
+ const fileUtils_1 = require("../dataconnect/fileUtils");
17
+ const utils_1 = require("../utils");
10
18
  const TIMEOUT_MILLIS = 30000;
11
19
  exports.APP_LIST_PAGE_SIZE = 100;
12
20
  const CREATE_APP_API_REQUEST_TIMEOUT_MILLIS = 15000;
13
- const WEB_CONFIG_FILE_NAME = "google-config.js";
21
+ const DISPLAY_NAME_QUESTION = {
22
+ type: "input",
23
+ name: "displayName",
24
+ default: "",
25
+ message: "What would you like to call your app?",
26
+ };
27
+ async function getPlatform(appDir, config) {
28
+ let targetPlatform = await (0, fileUtils_1.getPlatformFromFolder)(appDir);
29
+ if (targetPlatform === types_1.Platform.NONE) {
30
+ appDir = await (0, prompt_1.promptForDirectory)({
31
+ config,
32
+ relativeTo: appDir,
33
+ message: "We couldn't determine what kind of app you're using. Where is your app directory?",
34
+ });
35
+ targetPlatform = await (0, fileUtils_1.getPlatformFromFolder)(appDir);
36
+ }
37
+ if (targetPlatform === types_1.Platform.NONE || targetPlatform === types_1.Platform.MULTIPLE) {
38
+ if (targetPlatform === types_1.Platform.NONE) {
39
+ (0, utils_1.logBullet)(`Couldn't automatically detect app your in directory ${appDir}.`);
40
+ }
41
+ else {
42
+ (0, utils_1.logSuccess)(`Detected multiple app platforms in directory ${appDir}`);
43
+ }
44
+ const platforms = [
45
+ { name: "iOS (Swift)", value: types_1.Platform.IOS },
46
+ { name: "Web (JavaScript)", value: types_1.Platform.WEB },
47
+ { name: "Android (Kotlin)", value: types_1.Platform.ANDROID },
48
+ ];
49
+ targetPlatform = await (0, prompt_1.promptOnce)({
50
+ message: "Which platform do you want to set up an SDK for? Note: We currently do not support automatically setting up C++ or Unity projects.",
51
+ type: "list",
52
+ choices: platforms,
53
+ });
54
+ }
55
+ else if (targetPlatform === types_1.Platform.FLUTTER) {
56
+ (0, utils_1.logWarning)(`Detected ${targetPlatform} app in directory ${appDir}`);
57
+ throw new error_1.FirebaseError(`Flutter is not supported by apps:configure.
58
+ Please follow the link below to set up firebase for your Flutter app:
59
+ https://firebase.google.com/docs/flutter/setup
60
+ `);
61
+ }
62
+ else {
63
+ (0, utils_1.logSuccess)(`Detected ${targetPlatform} app in directory ${appDir}`);
64
+ }
65
+ return targetPlatform === types_1.Platform.MULTIPLE
66
+ ? AppPlatform.PLATFORM_UNSPECIFIED
67
+ : targetPlatform;
68
+ }
69
+ exports.getPlatform = getPlatform;
70
+ async function initiateIosAppCreation(options) {
71
+ if (!options.nonInteractive) {
72
+ await (0, prompt_1.prompt)(options, [
73
+ DISPLAY_NAME_QUESTION,
74
+ {
75
+ type: "input",
76
+ default: "",
77
+ name: "bundleId",
78
+ message: "Please specify your iOS app bundle ID:",
79
+ },
80
+ {
81
+ type: "input",
82
+ default: "",
83
+ name: "appStoreId",
84
+ message: "Please specify your iOS app App Store ID:",
85
+ },
86
+ ]);
87
+ }
88
+ if (!options.bundleId) {
89
+ throw new error_1.FirebaseError("Bundle ID for iOS app cannot be empty");
90
+ }
91
+ const spinner = ora("Creating your iOS app").start();
92
+ try {
93
+ const appData = await createIosApp(options.project, {
94
+ displayName: options.displayName,
95
+ bundleId: options.bundleId,
96
+ appStoreId: options.appStoreId,
97
+ });
98
+ spinner.succeed();
99
+ return appData;
100
+ }
101
+ catch (err) {
102
+ spinner.fail();
103
+ throw err;
104
+ }
105
+ }
106
+ async function initiateAndroidAppCreation(options) {
107
+ if (!options.nonInteractive) {
108
+ await (0, prompt_1.prompt)(options, [
109
+ DISPLAY_NAME_QUESTION,
110
+ {
111
+ type: "input",
112
+ default: "",
113
+ name: "packageName",
114
+ message: "Please specify your Android app package name:",
115
+ },
116
+ ]);
117
+ }
118
+ if (!options.packageName) {
119
+ throw new error_1.FirebaseError("Package name for Android app cannot be empty");
120
+ }
121
+ const spinner = ora("Creating your Android app").start();
122
+ try {
123
+ const appData = await createAndroidApp(options.project, {
124
+ displayName: options.displayName,
125
+ packageName: options.packageName,
126
+ });
127
+ spinner.succeed();
128
+ return appData;
129
+ }
130
+ catch (err) {
131
+ spinner.fail();
132
+ throw err;
133
+ }
134
+ }
135
+ async function initiateWebAppCreation(options) {
136
+ if (!options.nonInteractive) {
137
+ await (0, prompt_1.prompt)(options, [DISPLAY_NAME_QUESTION]);
138
+ }
139
+ if (!options.displayName) {
140
+ throw new error_1.FirebaseError("Display name for Web app cannot be empty");
141
+ }
142
+ const spinner = ora("Creating your Web app").start();
143
+ try {
144
+ const appData = await createWebApp(options.project, { displayName: options.displayName });
145
+ spinner.succeed();
146
+ return appData;
147
+ }
148
+ catch (err) {
149
+ spinner.fail();
150
+ throw err;
151
+ }
152
+ }
153
+ async function sdkInit(appPlatform, options) {
154
+ let appData;
155
+ switch (appPlatform) {
156
+ case AppPlatform.IOS:
157
+ appData = await initiateIosAppCreation(options);
158
+ break;
159
+ case AppPlatform.ANDROID:
160
+ appData = await initiateAndroidAppCreation(options);
161
+ break;
162
+ case AppPlatform.WEB:
163
+ appData = await initiateWebAppCreation(options);
164
+ break;
165
+ default:
166
+ throw new error_1.FirebaseError("Unexpected error. This should not happen");
167
+ }
168
+ return appData;
169
+ }
170
+ exports.sdkInit = sdkInit;
171
+ async function getSdkOutputPath(appDir, platform, config) {
172
+ switch (platform) {
173
+ case AppPlatform.ANDROID:
174
+ const androidPath = await findIntelligentPathForAndroid(appDir, config);
175
+ return path.join(androidPath, "google-services.json");
176
+ case AppPlatform.WEB:
177
+ return path.join(appDir, "firebase-js-config.json");
178
+ case AppPlatform.IOS:
179
+ const iosPath = await findIntelligentPathForIOS(appDir, config);
180
+ return path.join(iosPath, "GoogleService-Info.plist");
181
+ }
182
+ throw new error_1.FirebaseError("Platform " + platform.toString() + " is not supported yet.");
183
+ }
184
+ exports.getSdkOutputPath = getSdkOutputPath;
185
+ function checkForApps(apps, appPlatform) {
186
+ if (!apps.length) {
187
+ throw new error_1.FirebaseError(`There are no ${appPlatform === AppPlatform.ANY ? "" : appPlatform + " "}apps ` +
188
+ "associated with this Firebase project");
189
+ }
190
+ }
191
+ exports.checkForApps = checkForApps;
192
+ async function selectAppInteractively(apps, appPlatform) {
193
+ checkForApps(apps, appPlatform);
194
+ const choices = apps.map((app) => {
195
+ return {
196
+ name: `${app.displayName || app.bundleId || app.packageName}` +
197
+ ` - ${app.appId} (${app.platform})`,
198
+ value: app,
199
+ };
200
+ });
201
+ return await (0, prompt_1.promptOnce)({
202
+ type: "list",
203
+ message: `Select the ${appPlatform === AppPlatform.ANY ? "" : appPlatform + " "}` +
204
+ "app to get the configuration data:",
205
+ choices,
206
+ });
207
+ }
208
+ async function getSdkConfig(options, appPlatform, appId) {
209
+ if (!appId) {
210
+ let projectId = (0, projectUtils_1.needProjectId)(options);
211
+ if (options.nonInteractive && !projectId) {
212
+ throw new error_1.FirebaseError("Must supply app and project ids in non-interactive mode.");
213
+ }
214
+ else if (!projectId) {
215
+ const result = await (0, projects_1.getOrPromptProject)(options);
216
+ projectId = result.projectId;
217
+ }
218
+ const apps = await listFirebaseApps(projectId, appPlatform);
219
+ checkForApps(apps, appPlatform);
220
+ if (apps.length === 1) {
221
+ appId = apps[0].appId;
222
+ appPlatform = apps[0].platform;
223
+ }
224
+ else if (options.nonInteractive) {
225
+ throw new error_1.FirebaseError(`Project ${projectId} has multiple apps, must specify an app id.`);
226
+ }
227
+ else {
228
+ const appMetadata = await selectAppInteractively(apps, appPlatform);
229
+ appId = appMetadata.appId;
230
+ appPlatform = appMetadata.platform;
231
+ }
232
+ }
233
+ let configData;
234
+ const spinner = ora(`Downloading configuration data for your Firebase ${appPlatform} app`).start();
235
+ try {
236
+ configData = await getAppConfig(appId, appPlatform);
237
+ }
238
+ catch (err) {
239
+ spinner.fail();
240
+ throw err;
241
+ }
242
+ spinner.succeed();
243
+ return configData;
244
+ }
245
+ exports.getSdkConfig = getSdkConfig;
14
246
  var AppPlatform;
15
247
  (function (AppPlatform) {
16
248
  AppPlatform["PLATFORM_UNSPECIFIED"] = "PLATFORM_UNSPECIFIED";
@@ -182,13 +414,12 @@ function getAppConfigResourceString(appId, platform) {
182
414
  }
183
415
  function parseConfigFromResponse(responseBody, platform) {
184
416
  if (platform === AppPlatform.WEB) {
185
- const JS_TEMPLATE = (0, templates_1.readTemplateSync)("setup/web.js");
186
417
  return {
187
- fileName: WEB_CONFIG_FILE_NAME,
188
- fileContents: JS_TEMPLATE.replace("{/*--CONFIG--*/}", JSON.stringify(responseBody, null, 2)),
418
+ fileName: "firebase-js-config.json",
419
+ fileContents: JSON.stringify(responseBody, null, 2),
189
420
  };
190
421
  }
191
- else if (platform === AppPlatform.ANDROID || platform === AppPlatform.IOS) {
422
+ else if ("configFilename" in responseBody) {
192
423
  return {
193
424
  fileName: responseBody.configFilename,
194
425
  fileContents: Buffer.from(responseBody.configFileContents, "base64").toString("utf8"),
@@ -200,6 +431,24 @@ function getAppConfigFile(config, platform) {
200
431
  return parseConfigFromResponse(config, platform);
201
432
  }
202
433
  exports.getAppConfigFile = getAppConfigFile;
434
+ async function writeConfigToFile(filename, nonInteractive, fileContents) {
435
+ if (fs.existsSync(filename)) {
436
+ if (nonInteractive) {
437
+ throw new error_1.FirebaseError(`${filename} already exists`);
438
+ }
439
+ const overwrite = await (0, prompt_1.promptOnce)({
440
+ type: "confirm",
441
+ default: false,
442
+ message: `${filename} already exists. Do you want to overwrite?`,
443
+ });
444
+ if (!overwrite) {
445
+ return false;
446
+ }
447
+ }
448
+ await fs.writeFile(filename, fileContents);
449
+ return true;
450
+ }
451
+ exports.writeConfigToFile = writeConfigToFile;
203
452
  async function getAppConfig(appId, platform) {
204
453
  try {
205
454
  const response = await apiClient.request({
@@ -278,3 +527,61 @@ async function deleteAppAndroidSha(projectId, appId, shaId) {
278
527
  }
279
528
  }
280
529
  exports.deleteAppAndroidSha = deleteAppAndroidSha;
530
+ async function findIntelligentPathForIOS(appDir, options) {
531
+ const currentFiles = await fs.readdir(appDir, { withFileTypes: true });
532
+ for (let i = 0; i < currentFiles.length; i++) {
533
+ const dirent = currentFiles[i];
534
+ const xcodeStr = ".xcodeproj";
535
+ const file = dirent.name;
536
+ if (file.endsWith(xcodeStr)) {
537
+ return path.join(appDir, file.substring(0, file.length - xcodeStr.length));
538
+ }
539
+ else if (file === "Info.plist" ||
540
+ file === "Assets.xcassets" ||
541
+ (dirent.isDirectory() && file === "Preview Content")) {
542
+ return appDir;
543
+ }
544
+ }
545
+ let outputPath = null;
546
+ if (!options.nonInteractive) {
547
+ outputPath = await (0, prompt_1.promptForDirectory)({
548
+ config: options.config,
549
+ message: `We weren't able to automatically determine the output directory. Where would you like to output your config file?`,
550
+ relativeTo: appDir,
551
+ });
552
+ }
553
+ if (!outputPath) {
554
+ throw new Error("We weren't able to automatically determine the output directory.");
555
+ }
556
+ return outputPath;
557
+ }
558
+ exports.findIntelligentPathForIOS = findIntelligentPathForIOS;
559
+ async function findIntelligentPathForAndroid(appDir, options) {
560
+ const paths = appDir.split("/");
561
+ if (paths[0] === "app") {
562
+ return appDir;
563
+ }
564
+ else {
565
+ const currentFiles = await fs.readdir(appDir, { withFileTypes: true });
566
+ const dirs = [];
567
+ for (const fileOrDir of currentFiles) {
568
+ if (fileOrDir.isDirectory()) {
569
+ if (fileOrDir.name === "src") {
570
+ return appDir;
571
+ }
572
+ }
573
+ }
574
+ let module = path.join(appDir, "app");
575
+ if (dirs.length === 1) {
576
+ return module;
577
+ }
578
+ if (!options.nonInteractive) {
579
+ module = await (0, prompt_1.promptForDirectory)({
580
+ config: options.config,
581
+ message: `We weren't able to automatically determine the output directory. Where would you like to output your config file?`,
582
+ });
583
+ }
584
+ return module;
585
+ }
586
+ }
587
+ exports.findIntelligentPathForAndroid = findIntelligentPathForAndroid;