firebase-tools 13.20.2 → 13.22.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 (41) hide show
  1. package/lib/apphosting/githubConnections.js +19 -1
  2. package/lib/apphosting/index.js +12 -46
  3. package/lib/apphosting/rollout.js +127 -0
  4. package/lib/command.js +10 -3
  5. package/lib/commands/apphosting-rollouts-create.js +13 -13
  6. package/lib/commands/emulators-start.js +1 -1
  7. package/lib/commands/init.js +6 -0
  8. package/lib/config.js +14 -4
  9. package/lib/dataconnect/fileUtils.js +7 -43
  10. package/lib/dataconnect/freeTrial.js +44 -10
  11. package/lib/dataconnect/provisionCloudSql.js +4 -4
  12. package/lib/dataconnect/types.js +3 -2
  13. package/lib/dataconnect/webhook.js +2 -1
  14. package/lib/deploy/dataconnect/prepare.js +5 -0
  15. package/lib/emulator/apphosting/config.js +45 -0
  16. package/lib/emulator/apphosting/index.js +9 -11
  17. package/lib/emulator/apphosting/serve.js +16 -10
  18. package/lib/emulator/apphosting/utils.js +18 -0
  19. package/lib/emulator/controller.js +6 -1
  20. package/lib/emulator/downloadableEmulators.js +12 -12
  21. package/lib/emulator/eventarcEmulator.js +1 -1
  22. package/lib/emulator/extensionsEmulator.js +38 -6
  23. package/lib/emulator/functionsEmulator.js +85 -41
  24. package/lib/emulator/functionsEmulatorShared.js +2 -1
  25. package/lib/error.js +24 -1
  26. package/lib/extensions/emulator/triggerHelper.js +1 -1
  27. package/lib/extensions/extensionsApi.js +11 -8
  28. package/lib/extensions/runtimes/common.js +11 -19
  29. package/lib/extensions/runtimes/node.js +25 -22
  30. package/lib/extensions/types.js +16 -1
  31. package/lib/gcp/cloudmonitoring.js +3 -3
  32. package/lib/gcp/devConnect.js +38 -1
  33. package/lib/init/features/apphosting.js +12 -0
  34. package/lib/init/features/dataconnect/index.js +75 -85
  35. package/lib/init/features/dataconnect/sdk.js +69 -86
  36. package/lib/init/features/index.js +1 -1
  37. package/lib/init/index.js +1 -0
  38. package/lib/init/spawn.js +2 -1
  39. package/lib/requireAuth.js +9 -4
  40. package/package.json +1 -1
  41. package/templates/init/apphosting/apphosting.yaml +23 -0
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.longestCommonPrefix = exports.snakeToCamelCase = exports.lowercaseFirstLetter = exports.capitalizeFirstLetter = exports.toTitleCase = exports.getInstallPathPrefix = exports.getCodebaseDir = exports.writeSDK = exports.getCodebaseRuntime = exports.copyDirectory = exports.writeFile = exports.isTypescriptCodebase = exports.extensionMatchesAnyFilter = exports.extractExtensionsFromBuilds = exports.getErrorMessage = exports.fixDarkBlueText = void 0;
3
+ exports.longestCommonPrefix = exports.snakeToCamelCase = exports.lowercaseFirstLetter = exports.capitalizeFirstLetter = exports.toTitleCase = exports.getInstallPathPrefix = exports.getCodebaseDir = exports.writeSDK = exports.getCodebaseRuntime = exports.copyDirectory = exports.writeFile = exports.isTypescriptCodebase = exports.extensionMatchesAnyFilter = exports.extractExtensionsFromBuilds = exports.fixDarkBlueText = void 0;
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
  const prompt_1 = require("../../prompt");
@@ -8,7 +8,6 @@ const fsutils = require("../../fsutils");
8
8
  const utils_1 = require("../../utils");
9
9
  const error_1 = require("../../error");
10
10
  const projectConfig_1 = require("../../functions/projectConfig");
11
- const types_1 = require("../types");
12
11
  const functionRuntimes = require("../../deploy/functions/runtimes");
13
12
  const nodeRuntime = require("./node");
14
13
  function fixDarkBlueText(txt) {
@@ -17,15 +16,6 @@ function fixDarkBlueText(txt) {
17
16
  return txt.replaceAll(DARK_BLUE, BRIGHT_CYAN);
18
17
  }
19
18
  exports.fixDarkBlueText = fixDarkBlueText;
20
- function getErrorMessage(err, defaultMsg) {
21
- if ((0, types_1.isObject)(err) && err.message && typeof err.message === "string") {
22
- return err.message;
23
- }
24
- else {
25
- return defaultMsg;
26
- }
27
- }
28
- exports.getErrorMessage = getErrorMessage;
29
19
  function extractExtensionsFromBuilds(builds, filters) {
30
20
  const extRecords = {};
31
21
  for (const [codebase, build] of Object.entries(builds)) {
@@ -80,7 +70,7 @@ async function writeFile(filePath, data, options) {
80
70
  (0, utils_1.logLabeledBullet)("extensions", `successfully wrote ${shortFilePath}`);
81
71
  }
82
72
  catch (err) {
83
- throw new error_1.FirebaseError(`Failed to write ${shortFilePath}:\n ${err}`);
73
+ throw new error_1.FirebaseError(`Failed to write ${shortFilePath}:\n ${(0, error_1.getErrMsg)(err)}`);
84
74
  }
85
75
  }
86
76
  else {
@@ -95,11 +85,11 @@ async function writeFile(filePath, data, options) {
95
85
  (0, utils_1.logLabeledBullet)("extensions", `successfully created ${shortFilePath}`);
96
86
  }
97
87
  catch (err) {
98
- throw new error_1.FirebaseError(`Failed to create ${shortFilePath}:\n ${err}`);
88
+ throw new error_1.FirebaseError(`Failed to create ${shortFilePath}:\n ${(0, error_1.getErrMsg)(err)}`);
99
89
  }
100
90
  }
101
91
  catch (err) {
102
- throw new error_1.FirebaseError(`Error during SDK file creation:\n ${err}`);
92
+ throw new error_1.FirebaseError(`Error during SDK file creation:\n ${(0, error_1.getErrMsg)(err)}`);
103
93
  }
104
94
  }
105
95
  }
@@ -121,14 +111,14 @@ async function copyDirectory(src, dest, options) {
121
111
  if (srcPath.includes("node_modules")) {
122
112
  continue;
123
113
  }
124
- await copyDirectory(srcPath, destPath, { force: true });
114
+ await copyDirectory(srcPath, destPath, Object.assign(Object.assign({}, options), { force: true }));
125
115
  }
126
116
  else if (entry.isFile())
127
117
  try {
128
118
  await fs.promises.copyFile(srcPath, destPath);
129
119
  }
130
120
  catch (err) {
131
- throw new error_1.FirebaseError(`Failed to copy ${destPath.replace(process.cwd(), ".")}:\n ${err}`);
121
+ throw new error_1.FirebaseError(`Failed to copy ${destPath.replace(process.cwd(), ".")}:\n ${(0, error_1.getErrMsg)(err)}`);
132
122
  }
133
123
  }
134
124
  }
@@ -137,9 +127,8 @@ async function copyDirectory(src, dest, options) {
137
127
  }
138
128
  }
139
129
  else {
140
- await fs.promises.mkdir(dest, { recursive: true }).then(async () => {
141
- await copyDirectory(src, dest, { force: true });
142
- });
130
+ await fs.promises.mkdir(dest, { recursive: true });
131
+ await copyDirectory(src, dest, Object.assign(Object.assign({}, options), { force: true }));
143
132
  }
144
133
  }
145
134
  exports.copyDirectory = copyDirectory;
@@ -177,6 +166,9 @@ async function writeSDK(extensionRef, localPath, spec, options) {
177
166
  }
178
167
  exports.writeSDK = writeSDK;
179
168
  function getCodebaseDir(options) {
169
+ if (!options.projectRoot) {
170
+ throw new error_1.FirebaseError("Unable to determine root directory of project");
171
+ }
180
172
  const config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
181
173
  const codebaseConfig = (0, projectConfig_1.configForCodebase)(config, options.codebase || projectConfig_1.DEFAULT_CODEBASE);
182
174
  return `${options.projectRoot}/${codebaseConfig.source}/`;
@@ -1,18 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.writeSDK = exports.TYPESCRIPT_VERSION = exports.FIREBASE_FUNCTIONS_VERSION = exports.SDK_GENERATION_VERSION = void 0;
4
+ const path = require("path");
5
+ const child_process_1 = require("child_process");
6
+ const marked_terminal_1 = require("marked-terminal");
7
+ const marked_1 = require("marked");
4
8
  const types_1 = require("../types");
5
9
  const prompt_1 = require("../../prompt");
6
10
  const secretsUtils = require("../secretsUtils");
7
- const child_process_1 = require("child_process");
8
11
  const utils_1 = require("../../utils");
9
- const path = require("path");
10
12
  const common_1 = require("./common");
11
13
  const askUserForEventsConfig_1 = require("../askUserForEventsConfig");
12
14
  const extensionsHelper_1 = require("../extensionsHelper");
13
15
  const error_1 = require("../../error");
14
- const marked_terminal_1 = require("marked-terminal");
15
- const marked_1 = require("marked");
16
16
  marked_1.marked.use((0, marked_terminal_1.markedTerminal)());
17
17
  exports.SDK_GENERATION_VERSION = "1.0.0";
18
18
  exports.FIREBASE_FUNCTIONS_VERSION = ">=5.1.0";
@@ -67,7 +67,7 @@ function makeClassName(name) {
67
67
  function makeEventName(name, prefix) {
68
68
  let eventName;
69
69
  const versionedEvent = /^(?:[^.]+[.])+(?:[vV]\d+[.])(?<event>.*)$/;
70
- const match = name.match(versionedEvent);
70
+ const match = versionedEvent.exec(name);
71
71
  if (match) {
72
72
  eventName = match[1];
73
73
  }
@@ -79,7 +79,7 @@ function makeEventName(name, prefix) {
79
79
  eventName = parts[parts.length - 1];
80
80
  }
81
81
  const allCaps = /^[A-Z._-]+$/;
82
- eventName = eventName.match(allCaps) ? eventName : eventName.replace(/([A-Z])/g, " $1").trim();
82
+ eventName = allCaps.exec(eventName) ? eventName : eventName.replace(/([A-Z])/g, " $1").trim();
83
83
  eventName = eventName.replace(/[._-]/g, " ");
84
84
  eventName = eventName.toLowerCase().startsWith("on") ? eventName : "on " + eventName;
85
85
  eventName = eventName.replace(/\w\S*/g, common_1.toTitleCase);
@@ -87,6 +87,15 @@ function makeEventName(name, prefix) {
87
87
  eventName = eventName.charAt(0).toLowerCase() + eventName.substring(1);
88
88
  return eventName;
89
89
  }
90
+ function addPeerDependency(pkgJson, dependency, version) {
91
+ if (!pkgJson.peerDependencies) {
92
+ pkgJson.peerDependencies = {};
93
+ }
94
+ if (!(0, types_1.isObject)(pkgJson.peerDependencies)) {
95
+ throw new error_1.FirebaseError("Internal error generating peer dependencies.");
96
+ }
97
+ pkgJson.peerDependencies[dependency] = version;
98
+ }
90
99
  async function writeSDK(extensionRef, localPath, spec, options) {
91
100
  var _a, _b, _c;
92
101
  const sdkLines = [];
@@ -105,7 +114,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
105
114
  })) {
106
115
  const newLocalPath = path.join(dirPath, "src");
107
116
  await (0, common_1.copyDirectory)(localPath, newLocalPath, options);
108
- localPath = newLocalPath.replace(options.projectRoot, ".");
117
+ localPath = newLocalPath.replace(options.projectRoot || ".", ".");
109
118
  }
110
119
  }
111
120
  if (!dirPath) {
@@ -115,7 +124,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
115
124
  const pkgJson = {
116
125
  name: packageName,
117
126
  version: `${exports.SDK_GENERATION_VERSION}`,
118
- description: `Generated SDK for ${spec.displayName}@${spec.version}`,
127
+ description: `Generated SDK for ${spec.displayName || spec.name}@${spec.version}`,
119
128
  main: "./output/index.js",
120
129
  private: true,
121
130
  scripts: {
@@ -138,7 +147,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
138
147
  },
139
148
  };
140
149
  sdkLines.push("/**");
141
- sdkLines.push(` * ${spec.displayName} SDK for ${spec.name}@${spec.version}`);
150
+ sdkLines.push(` * ${spec.displayName || spec.name} SDK for ${spec.name}@${spec.version}`);
142
151
  sdkLines.push(" *");
143
152
  sdkLines.push(" * When filing bugs or feature requests please specify:");
144
153
  if (extensionRef) {
@@ -155,18 +164,12 @@ async function writeSDK(extensionRef, localPath, spec, options) {
155
164
  if (hasEvents) {
156
165
  sdkLines.push(`import { CloudEvent } from "firebase-functions/v2";`);
157
166
  sdkLines.push(`import { onCustomEventPublished, EventarcTriggerOptions } from "firebase-functions/v2/eventarc";`);
158
- if (!pkgJson.peerDependencies) {
159
- pkgJson.peerDependencies = {};
160
- }
161
- pkgJson.peerDependencies["firebase-functions"] = exports.FIREBASE_FUNCTIONS_VERSION;
167
+ addPeerDependency(pkgJson, "firebase-functions", exports.FIREBASE_FUNCTIONS_VERSION);
162
168
  }
163
169
  const usesSecrets = secretsUtils.usesSecrets(spec);
164
170
  if (usesSecrets) {
165
171
  sdkLines.push(`import { defineSecret } from "firebase-functions/params";`);
166
- if (!pkgJson.peerDependencies) {
167
- pkgJson.peerDependencies = {};
168
- }
169
- pkgJson.peerDependencies["firebase-functions"] = exports.FIREBASE_FUNCTIONS_VERSION;
172
+ addPeerDependency(pkgJson, "firebase-functions", exports.FIREBASE_FUNCTIONS_VERSION);
170
173
  }
171
174
  if (hasEvents || usesSecrets) {
172
175
  sdkLines.push("");
@@ -289,7 +292,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
289
292
  sdkLines.push(` ${sysParam.param}${opt}: string;`);
290
293
  break;
291
294
  default:
292
- throw new error_1.FirebaseError(`Error: Unknown systemParam type: ${sysParam.type}.`);
295
+ throw new error_1.FirebaseError(`Error: Unknown systemParam type: ${sysParam.type || "undefined"}.`);
293
296
  }
294
297
  sdkLines.push("");
295
298
  }
@@ -299,7 +302,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
299
302
  sdkLines.push(` return new ${className}(instanceId, params);`);
300
303
  sdkLines.push("}\n");
301
304
  sdkLines.push(`/**`);
302
- sdkLines.push(` * ${spec.displayName}`);
305
+ sdkLines.push(` * ${spec.displayName || spec.name}`);
303
306
  (_c = spec.description) === null || _c === void 0 ? void 0 : _c.split("\n").forEach((val) => {
304
307
  sdkLines.push(` * ${val.replace(/\*\//g, "* /")}`);
305
308
  });
@@ -347,7 +350,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
347
350
  (0, child_process_1.execFileSync)("npm", ["--prefix", dirPath, "install"]);
348
351
  }
349
352
  catch (err) {
350
- const errMsg = (0, common_1.getErrorMessage)(err, "unknown error");
353
+ const errMsg = (0, error_1.getErrMsg)(err, "unknown error");
351
354
  throw new error_1.FirebaseError(`Error during npm install in ${shortDirPath}: ${errMsg}`);
352
355
  }
353
356
  (0, utils_1.logLabeledBullet)("extensions", `running 'npm --prefix ${shortDirPath} run build'`);
@@ -355,7 +358,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
355
358
  (0, child_process_1.execFileSync)("npm", ["--prefix", dirPath, "run", "build"]);
356
359
  }
357
360
  catch (err) {
358
- const errMsg = (0, common_1.getErrorMessage)(err, "unknown error");
361
+ const errMsg = (0, error_1.getErrMsg)(err, "unknown error");
359
362
  throw new error_1.FirebaseError(`Error during npm run build in ${shortDirPath}: ${errMsg}`);
360
363
  }
361
364
  const codebaseDir = (0, common_1.getCodebaseDir)(options);
@@ -372,7 +375,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
372
375
  (0, child_process_1.execFileSync)("npm", ["--prefix", codebaseDir, "install", "--save", dirPath]);
373
376
  }
374
377
  catch (err) {
375
- const errMsg = (0, common_1.getErrorMessage)(err, "unknown error");
378
+ const errMsg = (0, error_1.getErrMsg)(err, "unknown error");
376
379
  throw new error_1.FirebaseError(`Error during npm install in ${codebaseDir}: ${errMsg}`);
377
380
  }
378
381
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isExtensionSpec = exports.isResource = exports.isParam = exports.isObject = exports.ParamType = exports.FUNCTIONS_V2_RESOURCE_TYPE = exports.FUNCTIONS_RESOURCE_TYPE = exports.Visibility = exports.RegistryLaunchStage = void 0;
3
+ exports.isExtensionSpec = exports.isResource = exports.isParam = exports.isObject = exports.ParamType = exports.FUNCTIONS_V2_RESOURCE_TYPE = exports.FUNCTIONS_RESOURCE_TYPE = exports.isExtensionInstance = exports.Visibility = exports.RegistryLaunchStage = void 0;
4
4
  var RegistryLaunchStage;
5
5
  (function (RegistryLaunchStage) {
6
6
  RegistryLaunchStage["EXPERIMENTAL"] = "EXPERIMENTAL";
@@ -14,6 +14,21 @@ var Visibility;
14
14
  Visibility["UNLISTED"] = "unlisted";
15
15
  Visibility["PUBLIC"] = "public";
16
16
  })(Visibility = exports.Visibility || (exports.Visibility = {}));
17
+ const extensionInstanceState = [
18
+ "STATE_UNSPECIFIED",
19
+ "DEPLOYING",
20
+ "UNINSTALLING",
21
+ "ACTIVE",
22
+ "ERRORED",
23
+ "PAUSED",
24
+ ];
25
+ const isExtensionInstance = (value) => {
26
+ if (!isObject(value) || typeof value.name !== "string") {
27
+ return false;
28
+ }
29
+ return true;
30
+ };
31
+ exports.isExtensionInstance = isExtensionInstance;
17
32
  const lifecycleStages = ["STAGE_UNSPECIFIED", "ON_INSTALL", "ON_UPDATE", "ON_CONFIGURE"];
18
33
  exports.FUNCTIONS_RESOURCE_TYPE = "firebaseextensions.v1beta.function";
19
34
  exports.FUNCTIONS_V2_RESOURCE_TYPE = "firebaseextensions.v1beta.v2function";
@@ -57,19 +57,19 @@ var ValueType;
57
57
  ValueType["DOUBLE"] = "DOUBLE";
58
58
  ValueType["STRING"] = "STRING";
59
59
  })(ValueType = exports.ValueType || (exports.ValueType = {}));
60
- async function queryTimeSeries(query, projectNumber) {
60
+ async function queryTimeSeries(query, project) {
61
61
  const client = new apiv2_1.Client({
62
62
  urlPrefix: (0, api_1.cloudMonitoringOrigin)(),
63
63
  apiVersion: exports.CLOUD_MONITORING_VERSION,
64
64
  });
65
65
  try {
66
- const res = await client.get(`/projects/${projectNumber}/timeSeries/`, {
66
+ const res = await client.get(`/projects/${project}/timeSeries/`, {
67
67
  queryParams: query,
68
68
  });
69
69
  return res.body.timeSeries;
70
70
  }
71
71
  catch (err) {
72
- throw new error_1.FirebaseError(`Failed to get extension usage: ${err}`, {
72
+ throw new error_1.FirebaseError(`Failed to get Cloud Monitoring metric: ${err}`, {
73
73
  status: err.status,
74
74
  });
75
75
  }
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateP4SA = exports.serviceAgentEmail = exports.sortConnectionsByCreateTime = exports.getGitRepositoryLink = exports.createGitRepositoryLink = exports.fetchGitHubInstallations = exports.listAllBranches = exports.listAllLinkableGitRepositories = exports.listAllConnections = exports.getConnection = exports.deleteConnection = exports.createConnection = exports.client = void 0;
3
+ exports.getRepoDetailsFromBackend = exports.extractGitRepositoryLinkComponents = exports.generateP4SA = exports.serviceAgentEmail = exports.sortConnectionsByCreateTime = exports.fetchGitRepositoryLinkReadToken = exports.getGitRepositoryLink = exports.createGitRepositoryLink = exports.fetchGitHubInstallations = exports.listAllBranches = exports.listAllLinkableGitRepositories = exports.listAllConnections = exports.getConnection = exports.deleteConnection = exports.createConnection = exports.client = void 0;
4
4
  const apiv2_1 = require("../apiv2");
5
5
  const api_1 = require("../api");
6
6
  const serviceusage_1 = require("./serviceusage");
7
+ const error_1 = require("../error");
8
+ const githubConnections_1 = require("../apphosting/githubConnections");
7
9
  const PAGE_SIZE_MAX = 1000;
8
10
  const LOCATION_OVERRIDE = process.env.FIREBASE_DEVELOPERCONNECT_LOCATION_OVERRIDE;
9
11
  exports.client = new apiv2_1.Client({
@@ -112,6 +114,12 @@ async function getGitRepositoryLink(projectId, location, connectionId, gitReposi
112
114
  return res.body;
113
115
  }
114
116
  exports.getGitRepositoryLink = getGitRepositoryLink;
117
+ async function fetchGitRepositoryLinkReadToken(projectId, location, connectionId, gitRepositoryLinkId) {
118
+ const name = `projects/${projectId}/locations/${LOCATION_OVERRIDE !== null && LOCATION_OVERRIDE !== void 0 ? LOCATION_OVERRIDE : location}/connections/${connectionId}/gitRepositoryLinks/${gitRepositoryLinkId}:fetchReadToken`;
119
+ const res = await exports.client.post(name);
120
+ return res.body;
121
+ }
122
+ exports.fetchGitRepositoryLinkReadToken = fetchGitRepositoryLinkReadToken;
115
123
  function sortConnectionsByCreateTime(connections) {
116
124
  return connections.sort((a, b) => {
117
125
  return Date.parse(a.createTime) - Date.parse(b.createTime);
@@ -127,3 +135,32 @@ async function generateP4SA(projectNumber) {
127
135
  await (0, serviceusage_1.generateServiceIdentityAndPoll)(projectNumber, new URL(devConnectOrigin).hostname, "apphosting");
128
136
  }
129
137
  exports.generateP4SA = generateP4SA;
138
+ function extractGitRepositoryLinkComponents(path) {
139
+ const connectionMatch = /connections\/([^\/]+)/.exec(path);
140
+ const repositoryMatch = /gitRepositoryLinks\/([^\/]+)/.exec(path);
141
+ const connection = connectionMatch ? connectionMatch[1] : null;
142
+ const gitRepoLink = repositoryMatch ? repositoryMatch[1] : null;
143
+ return { connection, gitRepoLink };
144
+ }
145
+ exports.extractGitRepositoryLinkComponents = extractGitRepositoryLinkComponents;
146
+ async function getRepoDetailsFromBackend(projectId, location, gitRepoLinkPath) {
147
+ const { connection, gitRepoLink } = extractGitRepositoryLinkComponents(gitRepoLinkPath);
148
+ if (!connection || !gitRepoLink) {
149
+ throw new error_1.FirebaseError(`Failed to extract connection or repository resource names from backend repository name.`);
150
+ }
151
+ const repoLink = await getGitRepositoryLink(projectId, location, connection, gitRepoLink);
152
+ const repoSlug = (0, githubConnections_1.extractRepoSlugFromUri)(repoLink.cloneUri);
153
+ const owner = repoSlug === null || repoSlug === void 0 ? void 0 : repoSlug.split("/")[0];
154
+ const repo = repoSlug === null || repoSlug === void 0 ? void 0 : repoSlug.split("/")[1];
155
+ if (!owner || !repo) {
156
+ throw new error_1.FirebaseError("Failed to parse owner and repo from git repository link");
157
+ }
158
+ const readToken = await fetchGitRepositoryLinkReadToken(projectId, location, connection, gitRepoLink);
159
+ return {
160
+ repoLink,
161
+ owner,
162
+ repo,
163
+ readToken,
164
+ };
165
+ }
166
+ exports.getRepoDetailsFromBackend = getRepoDetailsFromBackend;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.doSetup = void 0;
4
+ const clc = require("colorette");
5
+ const utils = require("../../utils");
6
+ const templates_1 = require("../../templates");
7
+ const APPHOSTING_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/apphosting/apphosting.yaml");
8
+ async function doSetup(setup, config) {
9
+ utils.logBullet("Writing default settings to " + clc.bold("apphosting.yaml") + "...");
10
+ await config.askWriteProjectFile("apphosting.yaml", APPHOSTING_YAML_TEMPLATE);
11
+ }
12
+ exports.doSetup = doSetup;
@@ -24,6 +24,11 @@ const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconn
24
24
  const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
25
25
  const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
26
26
  const MUTATIONS_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/mutations.gql");
27
+ const emptyConnector = {
28
+ id: "default",
29
+ path: "./connector",
30
+ files: [],
31
+ };
27
32
  const defaultConnector = {
28
33
  id: "default",
29
34
  path: "./connector",
@@ -38,29 +43,27 @@ const defaultConnector = {
38
43
  },
39
44
  ],
40
45
  };
46
+ const defaultSchema = { path: "schema.gql", content: SCHEMA_TEMPLATE };
41
47
  async function doSetup(setup, config) {
42
- const info = await askQuestions(setup);
48
+ const isBillingEnabled = setup.projectId ? await (0, cloudbilling_1.checkBillingEnabled)(setup.projectId) : false;
49
+ if (setup.projectId) {
50
+ isBillingEnabled ? await (0, ensureApis_1.ensureApis)(setup.projectId) : await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
51
+ }
52
+ const info = await askQuestions(setup, isBillingEnabled);
43
53
  await actuate(setup, config, info);
44
54
  const cwdPlatformGuess = await (0, fileUtils_1.getPlatformFromFolder)(process.cwd());
45
- if (cwdPlatformGuess !== types_1.Platform.UNDETERMINED) {
55
+ if (cwdPlatformGuess !== types_1.Platform.NONE) {
46
56
  await sdk.doSetup(setup, config);
47
57
  }
48
58
  else {
49
- const promptForSDKGeneration = await (0, prompt_1.confirm)({
50
- message: `Would you like to configure generated SDKs now?`,
51
- default: false,
52
- });
53
- if (promptForSDKGeneration) {
54
- await sdk.doSetup(setup, config);
55
- }
56
- else {
57
- (0, utils_1.logBullet)(`If you'd like to generate an SDK for your new connector later, run ${clc.bold("firebase init dataconnect:sdk")}`);
58
- }
59
+ (0, utils_1.logBullet)(`If you'd like to add the generated SDK to your app your, run ${clc.bold("firebase init dataconnect:sdk")}`);
60
+ }
61
+ if (setup.projectId && !isBillingEnabled) {
62
+ (0, utils_1.logBullet)((0, freeTrial_1.upgradeInstructions)(setup.projectId));
59
63
  }
60
- logger_1.logger.info("");
61
64
  }
62
65
  exports.doSetup = doSetup;
63
- async function askQuestions(setup) {
66
+ async function askQuestions(setup, isBillingEnabled) {
64
67
  let info = {
65
68
  serviceId: "",
66
69
  locationId: "",
@@ -69,14 +72,10 @@ async function askQuestions(setup) {
69
72
  cloudSqlDatabase: "",
70
73
  isNewDatabase: false,
71
74
  connectors: [defaultConnector],
72
- schemaGql: [],
75
+ schemaGql: [defaultSchema],
73
76
  shouldProvisionCSQL: false,
74
77
  };
75
- const isBillingEnabled = setup.projectId ? await (0, cloudbilling_1.checkBillingEnabled)(setup.projectId) : false;
76
- if (setup.projectId) {
77
- isBillingEnabled ? await (0, ensureApis_1.ensureApis)(setup.projectId) : await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
78
- }
79
- info = await checkExistingInstances(setup, info, isBillingEnabled);
78
+ info = await promptForExistingServices(setup, info, isBillingEnabled);
80
79
  const requiredConfigUnset = info.serviceId === "" ||
81
80
  info.cloudSqlInstanceId === "" ||
82
81
  info.locationId === "" ||
@@ -84,13 +83,12 @@ async function askQuestions(setup) {
84
83
  const shouldConfigureBackend = isBillingEnabled && requiredConfigUnset
85
84
  ? await (0, prompt_1.confirm)({
86
85
  message: `Would you like to configure your backend resources now?`,
87
- default: false,
86
+ default: true,
88
87
  })
89
88
  : false;
90
89
  if (shouldConfigureBackend) {
91
90
  info = await promptForService(info);
92
- info = await promptForCloudSQLInstance(setup, info);
93
- info = await promptForDatabase(info);
91
+ info = await promptForCloudSQL(setup, info);
94
92
  info.shouldProvisionCSQL = !!(setup.projectId &&
95
93
  (info.isNewInstance || info.isNewDatabase) &&
96
94
  isBillingEnabled &&
@@ -100,14 +98,10 @@ async function askQuestions(setup) {
100
98
  })));
101
99
  }
102
100
  else {
103
- if (requiredConfigUnset) {
104
- (0, utils_1.logBullet)(`Setting placeholder values in dataconnect.yaml. You can edit these before you deploy to specify different IDs or regions.`);
105
- }
106
- info.serviceId = info.serviceId !== "" ? info.serviceId : (0, path_1.basename)(process.cwd());
107
- info.cloudSqlInstanceId =
108
- info.cloudSqlInstanceId !== "" ? info.cloudSqlInstanceId : `${info.serviceId || "app"}-fdc`;
109
- info.locationId = info.locationId !== "" ? info.locationId : `us-central1`;
110
- info.cloudSqlDatabase = info.cloudSqlDatabase !== "" ? info.cloudSqlDatabase : `fdcdb`;
101
+ info.serviceId = info.serviceId || (0, path_1.basename)(process.cwd());
102
+ info.cloudSqlInstanceId = info.cloudSqlInstanceId || `${info.serviceId || "app"}-fdc`;
103
+ info.locationId = info.locationId || `us-central1`;
104
+ info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
111
105
  }
112
106
  return info;
113
107
  }
@@ -129,17 +123,19 @@ exports.actuate = actuate;
129
123
  async function writeFiles(config, info) {
130
124
  const dir = config.get("dataconnect.source") || "dataconnect";
131
125
  const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: info.connectors.map((c) => c.path) }));
126
+ if (!config.get("dataconnect.source")) {
127
+ if (!info.schemaGql.length && !info.connectors.flatMap((r) => r.files).length) {
128
+ info.schemaGql = [defaultSchema];
129
+ info.connectors = [defaultConnector];
130
+ }
131
+ }
132
132
  config.set("dataconnect", { source: dir });
133
- await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml);
133
+ await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml, false, true);
134
134
  if (info.schemaGql.length) {
135
- (0, utils_1.logSuccess)("The service you chose already has GQL files deployed. We'll use those instead of the default templates.");
136
135
  for (const f of info.schemaGql) {
137
136
  await config.askWriteProjectFile((0, path_1.join)(dir, "schema", f.path), f.content);
138
137
  }
139
138
  }
140
- else {
141
- await config.askWriteProjectFile((0, path_1.join)(dir, "schema", "schema.gql"), SCHEMA_TEMPLATE);
142
- }
143
139
  for (const c of info.connectors) {
144
140
  await writeConnectorFiles(config, c);
145
141
  }
@@ -178,8 +174,8 @@ function subConnectorYamlValues(replacementValues) {
178
174
  }
179
175
  return replaced;
180
176
  }
181
- async function checkExistingInstances(setup, info, isBillingEnabled) {
182
- var _a, _b, _c;
177
+ async function promptForExistingServices(setup, info, isBillingEnabled) {
178
+ var _a, _b, _c, _d;
183
179
  if (!setup.projectId || !isBillingEnabled) {
184
180
  return info;
185
181
  }
@@ -208,16 +204,18 @@ async function checkExistingInstances(setup, info, isBillingEnabled) {
208
204
  const serviceName = (0, names_1.parseServiceName)(choice.service.name);
209
205
  info.serviceId = serviceName.serviceId;
210
206
  info.locationId = serviceName.location;
207
+ info.schemaGql = [];
208
+ info.connectors = [emptyConnector];
211
209
  if (choice.schema) {
212
210
  const primaryDatasource = choice.schema.datasources.find((d) => d.postgresql);
213
211
  if ((_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance) {
214
212
  const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
215
213
  info.cloudSqlInstanceId = instanceName.instanceId;
216
214
  }
217
- if (choice.schema.source.files) {
215
+ if ((_b = choice.schema.source.files) === null || _b === void 0 ? void 0 : _b.length) {
218
216
  info.schemaGql = choice.schema.source.files;
219
217
  }
220
- info.cloudSqlDatabase = (_c = (_b = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database) !== null && _c !== void 0 ? _c : "";
218
+ info.cloudSqlDatabase = (_d = (_c = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database) !== null && _d !== void 0 ? _d : "";
221
219
  const connectors = await (0, client_1.listConnectors)(choice.service.name, [
222
220
  "connectors.name",
223
221
  "connectors.source.files",
@@ -234,20 +232,24 @@ async function checkExistingInstances(setup, info, isBillingEnabled) {
234
232
  }
235
233
  }
236
234
  }
237
- else {
238
- info = await promptForService(info);
239
- }
240
235
  }
241
- if (info.cloudSqlInstanceId === "") {
236
+ return info;
237
+ }
238
+ async function promptForCloudSQL(setup, info) {
239
+ if (info.cloudSqlInstanceId === "" && setup.projectId) {
242
240
  const instances = await cloudsql.listInstances(setup.projectId);
243
241
  let choices = instances.map((i) => {
244
- return { name: i.name, value: i.name, location: i.region };
242
+ var _a;
243
+ let display = `${i.name} (${i.region})`;
244
+ if (((_a = i.settings.userLabels) === null || _a === void 0 ? void 0 : _a["firebase-data-connect"]) === "ft") {
245
+ display += " (no cost trial)";
246
+ }
247
+ return { name: display, value: i.name, location: i.region };
245
248
  });
246
249
  choices = choices.filter((c) => info.locationId === "" || info.locationId === c.location);
247
250
  if (choices.length) {
248
- const freeTrialInstanceId = await (0, freeTrial_1.checkForFreeTrialInstance)(setup.projectId);
249
- if (!freeTrialInstanceId) {
250
- choices.push({ name: "Create a new instance", value: "", location: "" });
251
+ if (!(await (0, freeTrial_1.checkFreeTrialInstanceUsed)(setup.projectId))) {
252
+ choices.push({ name: "Create a new free trial instance", value: "", location: "" });
251
253
  }
252
254
  info.cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
253
255
  message: `Which CloudSQL instance would you like to use?`,
@@ -257,12 +259,25 @@ async function checkExistingInstances(setup, info, isBillingEnabled) {
257
259
  if (info.cloudSqlInstanceId !== "") {
258
260
  info.locationId = choices.find((c) => c.value === info.cloudSqlInstanceId).location;
259
261
  }
260
- else {
261
- info = await promptForCloudSQLInstance(setup, info);
262
- }
263
262
  }
264
263
  }
265
- if (info.cloudSqlDatabase === "" && info.cloudSqlInstanceId !== "") {
264
+ if (info.cloudSqlInstanceId === "") {
265
+ info.isNewInstance = true;
266
+ info.cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
267
+ message: `What ID would you like to use for your new CloudSQL instance?`,
268
+ type: "input",
269
+ default: `${info.serviceId || "app"}-fdc`,
270
+ });
271
+ }
272
+ if (info.locationId === "") {
273
+ const choices = await locationChoices(setup);
274
+ info.locationId = await (0, prompt_1.promptOnce)({
275
+ message: "What location would like to use?",
276
+ type: "list",
277
+ choices,
278
+ });
279
+ }
280
+ if (info.cloudSqlDatabase === "" && setup.projectId) {
266
281
  try {
267
282
  const dbs = await cloudsql.listDatabases(setup.projectId, info.cloudSqlInstanceId);
268
283
  const choices = dbs.map((d) => {
@@ -275,15 +290,20 @@ async function checkExistingInstances(setup, info, isBillingEnabled) {
275
290
  type: "list",
276
291
  choices,
277
292
  });
278
- if (info.cloudSqlDatabase === "") {
279
- info = await promptForDatabase(info);
280
- }
281
293
  }
282
294
  }
283
295
  catch (err) {
284
296
  logger_1.logger.debug(`[dataconnect] Cannot list databases during init: ${err}`);
285
297
  }
286
298
  }
299
+ if (info.cloudSqlDatabase === "") {
300
+ info.isNewDatabase = true;
301
+ info.cloudSqlDatabase = await (0, prompt_1.promptOnce)({
302
+ message: `What ID would you like to use for your new database in ${info.cloudSqlInstanceId}?`,
303
+ type: "input",
304
+ default: `fdcdb`,
305
+ });
306
+ }
287
307
  return info;
288
308
  }
289
309
  async function promptForService(info) {
@@ -296,25 +316,6 @@ async function promptForService(info) {
296
316
  }
297
317
  return info;
298
318
  }
299
- async function promptForCloudSQLInstance(setup, info) {
300
- if (info.cloudSqlInstanceId === "") {
301
- info.isNewInstance = true;
302
- info.cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
303
- message: `What ID would you like to use for your new CloudSQL instance?`,
304
- type: "input",
305
- default: `${info.serviceId || "app"}-fdc`,
306
- });
307
- }
308
- if (info.locationId === "") {
309
- const choices = await locationChoices(setup);
310
- info.locationId = await (0, prompt_1.promptOnce)({
311
- message: "What location would like to use?",
312
- type: "list",
313
- choices,
314
- });
315
- }
316
- return info;
317
- }
318
319
  async function locationChoices(setup) {
319
320
  if (setup.projectId) {
320
321
  const locations = await (0, client_1.listLocations)(setup.projectId);
@@ -335,14 +336,3 @@ async function locationChoices(setup) {
335
336
  ];
336
337
  }
337
338
  }
338
- async function promptForDatabase(info) {
339
- if (info.cloudSqlDatabase === "") {
340
- info.isNewDatabase = true;
341
- info.cloudSqlDatabase = await (0, prompt_1.promptOnce)({
342
- message: `What ID would you like to use for your new database in ${info.cloudSqlInstanceId}?`,
343
- type: "input",
344
- default: `fdcdb`,
345
- });
346
- }
347
- return info;
348
- }