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.fetchRepositoryCloneUris = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.ensureSecretManagerAdminGrant = exports.promptGitHubBranch = exports.getOrCreateOauthConnection = exports.listValidInstallations = exports.promptGitHubInstallation = exports.getConnectionForInstallation = exports.linkGitHubRepository = exports.generateRepositoryId = exports.extractRepoSlugFromUri = exports.parseConnectionName = void 0;
3
+ exports.getGitHubCommit = exports.getGitHubBranch = exports.fetchRepositoryCloneUris = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.ensureSecretManagerAdminGrant = exports.promptGitHubBranch = exports.getOrCreateOauthConnection = exports.listValidInstallations = exports.promptGitHubInstallation = exports.getConnectionForInstallation = exports.linkGitHubRepository = exports.generateRepositoryId = exports.extractRepoSlugFromUri = exports.parseConnectionName = void 0;
4
4
  const clc = require("colorette");
5
5
  const devConnect = require("../gcp/devConnect");
6
6
  const rm = require("../gcp/resourceManager");
@@ -12,6 +12,8 @@ const getProjectNumber_1 = require("../getProjectNumber");
12
12
  const api_1 = require("../api");
13
13
  const fuzzy = require("fuzzy");
14
14
  const inquirer = require("inquirer");
15
+ const apiv2_1 = require("../apiv2");
16
+ const githubApiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.githubApiOrigin)(), auth: false });
15
17
  const APPHOSTING_CONN_PATTERN = /.+\/apphosting-github-conn-.+$/;
16
18
  const APPHOSTING_OAUTH_CONN_NAME = "firebase-app-hosting-github-oauth";
17
19
  const CONNECTION_NAME_REGEX = /^projects\/(?<projectId>[^\/]+)\/locations\/(?<location>[^\/]+)\/connections\/(?<id>[^\/]+)$/;
@@ -346,3 +348,19 @@ async function fetchRepositoryCloneUris(projectId, connection) {
346
348
  return cloneUris;
347
349
  }
348
350
  exports.fetchRepositoryCloneUris = fetchRepositoryCloneUris;
351
+ async function getGitHubBranch(owner, repo, branch, readToken) {
352
+ const headers = { Authorization: `Bearer ${readToken}`, "User-Agent": "Firebase CLI" };
353
+ const { body } = await githubApiClient.get(`/repos/${owner}/${repo}/branches/${branch}`, {
354
+ headers,
355
+ });
356
+ return body;
357
+ }
358
+ exports.getGitHubBranch = getGitHubBranch;
359
+ async function getGitHubCommit(owner, repo, ref, readToken) {
360
+ const headers = { Authorization: `Bearer ${readToken}`, "User-Agent": "Firebase CLI" };
361
+ const { body } = await githubApiClient.get(`/repos/${owner}/${repo}/commits/${ref}`, {
362
+ headers,
363
+ });
364
+ return body;
365
+ }
366
+ exports.getGitHubCommit = getGitHubCommit;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getBackendForAmbiguousLocation = exports.promptLocation = exports.deleteBackendAndPoll = exports.orchestrateRollout = exports.setDefaultTrafficPolicy = exports.createBackend = exports.ensureAppHostingComputeServiceAccount = exports.doSetup = void 0;
3
+ exports.getBackendForAmbiguousLocation = exports.promptLocation = exports.deleteBackendAndPoll = exports.setDefaultTrafficPolicy = exports.createBackend = exports.ensureAppHostingComputeServiceAccount = exports.doSetup = void 0;
4
4
  const clc = require("colorette");
5
5
  const poller = require("../operation-poller");
6
6
  const apphosting = require("../gcp/apphosting");
@@ -18,6 +18,7 @@ const deploymentTool = require("../deploymentTool");
18
18
  const app_1 = require("./app");
19
19
  const ora = require("ora");
20
20
  const node_fetch_1 = require("node-fetch");
21
+ const rollout_1 = require("./rollout");
21
22
  const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
22
23
  const apphostingPollerOptions = {
23
24
  apiOrigin: (0, api_1.apphostingOrigin)(),
@@ -105,12 +106,18 @@ async function doSetup(projectId, webAppName, location, serviceAccount) {
105
106
  const url = `https://${backend.uri}`;
106
107
  (0, utils_1.logBullet)(`You may also track this rollout at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
107
108
  const createRolloutSpinner = ora("Starting a new rollout; this may take a few minutes. It's safe to exit now.").start();
108
- await orchestrateRollout(projectId, location, backendId, {
109
- source: {
110
- codebase: {
111
- branch,
109
+ await (0, rollout_1.orchestrateRollout)({
110
+ projectId,
111
+ location,
112
+ backendId,
113
+ buildInput: {
114
+ source: {
115
+ codebase: {
116
+ branch,
117
+ },
112
118
  },
113
119
  },
120
+ isFirstRollout: true,
114
121
  });
115
122
  createRolloutSpinner.succeed("Rollout complete");
116
123
  if (!(await tlsReady(url))) {
@@ -208,47 +215,6 @@ async function setDefaultTrafficPolicy(projectId, location, backendId, codebaseB
208
215
  await poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `updateTraffic-${projectId}-${location}-${backendId}`, operationResourceName: op.name }));
209
216
  }
210
217
  exports.setDefaultTrafficPolicy = setDefaultTrafficPolicy;
211
- async function orchestrateRollout(projectId, location, backendId, buildInput) {
212
- const buildId = await apphosting.getNextRolloutId(projectId, location, backendId, 1);
213
- const buildOp = await apphosting.createBuild(projectId, location, backendId, buildId, buildInput);
214
- const rolloutBody = {
215
- build: `projects/${projectId}/locations/${location}/backends/${backendId}/builds/${buildId}`,
216
- };
217
- let tries = 0;
218
- let done = false;
219
- while (!done) {
220
- tries++;
221
- try {
222
- const validateOnly = true;
223
- await apphosting.createRollout(projectId, location, backendId, buildId, rolloutBody, validateOnly);
224
- done = true;
225
- }
226
- catch (err) {
227
- if (err instanceof error_1.FirebaseError && err.status === 400) {
228
- if (tries >= 5) {
229
- throw err;
230
- }
231
- await (0, utils_1.sleep)(1000);
232
- }
233
- else {
234
- throw err;
235
- }
236
- }
237
- }
238
- const rolloutOp = await apphosting.createRollout(projectId, location, backendId, buildId, rolloutBody);
239
- const rolloutPoll = poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-backend-${backendId}-rollout-${buildId}`, operationResourceName: rolloutOp.name }));
240
- const buildPoll = poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-backend-${backendId}-build-${buildId}`, operationResourceName: buildOp.name }));
241
- const [rollout, build] = await Promise.all([rolloutPoll, buildPoll]);
242
- if (build.state !== "READY") {
243
- if (!build.buildLogsUri) {
244
- throw new error_1.FirebaseError("Failed to build your app, but failed to get build logs as well. " +
245
- "This is an internal error and should be reported");
246
- }
247
- throw new error_1.FirebaseError(`Failed to build your app. Please inspect the build logs at ${build.buildLogsUri}.`, { children: [build.error] });
248
- }
249
- return { rollout, build };
250
- }
251
- exports.orchestrateRollout = orchestrateRollout;
252
218
  async function deleteBackendAndPoll(projectId, location, backendId) {
253
219
  const op = await apphosting.deleteBackend(projectId, location, backendId);
254
220
  await poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `delete-${projectId}-${location}-${backendId}`, operationResourceName: op.name }));
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.orchestrateRollout = exports.createRollout = void 0;
4
+ const apphosting = require("../gcp/apphosting");
5
+ const error_1 = require("../error");
6
+ const ora = require("ora");
7
+ const devConnect_1 = require("../gcp/devConnect");
8
+ const githubConnections_1 = require("../apphosting/githubConnections");
9
+ const poller = require("../operation-poller");
10
+ const prompt_1 = require("../prompt");
11
+ const utils_1 = require("../utils");
12
+ const api_1 = require("../api");
13
+ const apphostingPollerOptions = {
14
+ apiOrigin: (0, api_1.apphostingOrigin)(),
15
+ apiVersion: apphosting.API_VERSION,
16
+ masterTimeout: 25 * 60 * 1000,
17
+ maxBackoff: 10000,
18
+ };
19
+ const GIT_COMMIT_SHA_REGEX = /^(?:[0-9a-f]{40}|[0-9a-f]{7})$/;
20
+ async function createRollout(backendId, projectId, location, branch, commit, force) {
21
+ const backend = await apphosting.getBackend(projectId, location, backendId);
22
+ if (!backend.codebase.repository) {
23
+ throw new error_1.FirebaseError(`Backend ${backendId} is misconfigured due to missing a connected repository. You can delete and recreate your backend using 'firebase apphosting:backends:delete' and 'firebase apphosting:backends:create'.`);
24
+ }
25
+ const { repoLink, owner, repo, readToken } = await (0, devConnect_1.getRepoDetailsFromBackend)(projectId, location, backend.codebase.repository);
26
+ let targetCommit;
27
+ if (branch) {
28
+ const branches = await (0, devConnect_1.listAllBranches)(repoLink.name);
29
+ if (!branches.has(branch)) {
30
+ throw new error_1.FirebaseError(`Unrecognized git branch ${branch}. Please double-check your branch name and try again.`);
31
+ }
32
+ const branchInfo = await (0, githubConnections_1.getGitHubBranch)(owner, repo, branch, readToken.token);
33
+ targetCommit = branchInfo.commit;
34
+ }
35
+ else if (commit) {
36
+ if (!GIT_COMMIT_SHA_REGEX.test(commit)) {
37
+ throw new error_1.FirebaseError(`Invalid git commit ${commit}. Must be a valid SHA1 hash.`);
38
+ }
39
+ try {
40
+ const commitInfo = await (0, githubConnections_1.getGitHubCommit)(owner, repo, commit, readToken.token);
41
+ targetCommit = commitInfo;
42
+ }
43
+ catch (err) {
44
+ if (err.status === 422) {
45
+ throw new error_1.FirebaseError(`Unrecognized git commit ${commit}. Please double-check your commit hash and try again.`);
46
+ }
47
+ throw err;
48
+ }
49
+ }
50
+ else {
51
+ branch = await (0, githubConnections_1.promptGitHubBranch)(repoLink);
52
+ const branchInfo = await (0, githubConnections_1.getGitHubBranch)(owner, repo, branch, readToken.token);
53
+ targetCommit = branchInfo.commit;
54
+ }
55
+ (0, utils_1.logBullet)(`You are about to deploy [${targetCommit.sha.substring(0, 7)}]: ${targetCommit.commit.message}`);
56
+ const confirmRollout = await (0, prompt_1.confirm)({
57
+ force: !!force,
58
+ message: "Do you want to continue?",
59
+ });
60
+ if (!confirmRollout) {
61
+ return;
62
+ }
63
+ (0, utils_1.logBullet)(`You may also track this rollout at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
64
+ const createRolloutSpinner = ora("Starting a new rollout; this may take a few minutes. It's safe to exit now.").start();
65
+ try {
66
+ await orchestrateRollout({
67
+ projectId,
68
+ location,
69
+ backendId,
70
+ buildInput: {
71
+ source: {
72
+ codebase: {
73
+ commit: targetCommit.sha,
74
+ },
75
+ },
76
+ },
77
+ });
78
+ }
79
+ catch (err) {
80
+ createRolloutSpinner.fail("Rollout failed.");
81
+ throw err;
82
+ }
83
+ createRolloutSpinner.succeed("Successfully created a new rollout!");
84
+ }
85
+ exports.createRollout = createRollout;
86
+ async function orchestrateRollout(args) {
87
+ const { projectId, location, backendId, buildInput, isFirstRollout } = args;
88
+ const buildId = await apphosting.getNextRolloutId(projectId, location, backendId, isFirstRollout ? 1 : undefined);
89
+ const buildOp = await apphosting.createBuild(projectId, location, backendId, buildId, buildInput);
90
+ const rolloutBody = {
91
+ build: `projects/${projectId}/locations/${location}/backends/${backendId}/builds/${buildId}`,
92
+ };
93
+ let tries = 0;
94
+ let done = false;
95
+ while (!done) {
96
+ tries++;
97
+ try {
98
+ const validateOnly = true;
99
+ await apphosting.createRollout(projectId, location, backendId, buildId, rolloutBody, validateOnly);
100
+ done = true;
101
+ }
102
+ catch (err) {
103
+ if (err instanceof error_1.FirebaseError && err.status === 400) {
104
+ if (tries >= 5) {
105
+ throw err;
106
+ }
107
+ await (0, utils_1.sleep)(1000);
108
+ }
109
+ else {
110
+ throw err;
111
+ }
112
+ }
113
+ }
114
+ const rolloutOp = await apphosting.createRollout(projectId, location, backendId, buildId, rolloutBody);
115
+ const rolloutPoll = poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-backend-${backendId}-rollout-${buildId}`, operationResourceName: rolloutOp.name }));
116
+ const buildPoll = poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-backend-${backendId}-build-${buildId}`, operationResourceName: buildOp.name }));
117
+ const [rollout, build] = await Promise.all([rolloutPoll, buildPoll]);
118
+ if (build.state !== "READY") {
119
+ if (!build.buildLogsUri) {
120
+ throw new error_1.FirebaseError("Failed to build your app, but failed to get build logs as well. " +
121
+ "This is an internal error and should be reported");
122
+ }
123
+ throw new error_1.FirebaseError(`Failed to build your app. Please inspect the build logs at ${build.buildLogsUri}.`, { children: [build.error] });
124
+ }
125
+ return { rollout, build };
126
+ }
127
+ exports.orchestrateRollout = orchestrateRollout;
package/lib/command.js CHANGED
@@ -182,15 +182,18 @@ class Command {
182
182
  }
183
183
  }
184
184
  applyRC(options) {
185
+ var _a, _b;
185
186
  const rc = (0, rc_1.loadRC)(options);
186
187
  options.rc = rc;
187
- options.project =
188
- options.project || (configstore_1.configstore.get("activeProjects") || {})[options.projectRoot];
188
+ const activeProject = options.projectRoot
189
+ ? ((_a = configstore_1.configstore.get("activeProjects")) !== null && _a !== void 0 ? _a : {})[options.projectRoot]
190
+ : undefined;
191
+ options.project = (_b = options.project) !== null && _b !== void 0 ? _b : activeProject;
189
192
  if (options.config && !options.project) {
190
193
  options.project = options.config.defaults.project;
191
194
  }
192
195
  const aliases = rc.projects;
193
- const rcProject = (0, lodash_1.get)(aliases, options.project);
196
+ const rcProject = options.project ? aliases[options.project] : undefined;
194
197
  if (rcProject) {
195
198
  options.projectAlias = options.project;
196
199
  options.project = rcProject;
@@ -199,6 +202,10 @@ class Command {
199
202
  options.projectAlias = (0, lodash_1.head)((0, lodash_1.keys)(aliases));
200
203
  options.project = (0, lodash_1.head)((0, lodash_1.values)(aliases));
201
204
  }
205
+ else if (!options.project && aliases["default"]) {
206
+ options.projectAlias = "default";
207
+ options.project = aliases["default"];
208
+ }
202
209
  }
203
210
  async resolveProjectIdentifiers(options) {
204
211
  var _a;
@@ -2,25 +2,25 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
4
  const apphosting = require("../gcp/apphosting");
5
- const logger_1 = require("../logger");
6
5
  const command_1 = require("../command");
7
6
  const projectUtils_1 = require("../projectUtils");
8
- exports.command = new command_1.Command("apphosting:rollouts:create <backendId> <buildId>")
7
+ const error_1 = require("../error");
8
+ const rollout_1 = require("../apphosting/rollout");
9
+ exports.command = new command_1.Command("apphosting:rollouts:create <backendId>")
9
10
  .description("create a rollout using a build for an App Hosting backend")
10
11
  .option("-l, --location <location>", "specify the region of the backend", "us-central1")
11
12
  .option("-i, --id <rolloutId>", "id of the rollout (defaults to autogenerating a random id)", "")
13
+ .option("-gb, --git-branch <gitBranch>", "repository branch to deploy (mutually exclusive with -gc)")
14
+ .option("-gc, --git-commit <gitCommit>", "git commit to deploy (mutually exclusive with -gb)")
15
+ .withForce("Skip confirmation before creating rollout")
12
16
  .before(apphosting.ensureApiEnabled)
13
- .action(async (backendId, buildId, options) => {
17
+ .action(async (backendId, options) => {
14
18
  const projectId = (0, projectUtils_1.needProjectId)(options);
15
19
  const location = options.location;
16
- const rolloutId = options.buildId ||
17
- (await apphosting.getNextRolloutId(projectId, location, backendId));
18
- const build = `projects/${projectId}/backends/${backendId}/builds/${buildId}`;
19
- const op = await apphosting.createRollout(projectId, location, backendId, rolloutId, {
20
- build,
21
- });
22
- logger_1.logger.info(`Started a rollout for backend ${backendId} with build ${buildId}.`);
23
- logger_1.logger.info("Check status by running:");
24
- logger_1.logger.info(`\tfirebase apphosting:rollouts:list --location ${location}`);
25
- return op;
20
+ const branch = options.gitBranch;
21
+ const commit = options.gitCommit;
22
+ if (branch && commit) {
23
+ throw new error_1.FirebaseError("Cannot specify both a branch and commit to deploy. Please specify either --git-branch or --git-commit.");
24
+ }
25
+ await (0, rollout_1.createRollout)(backendId, projectId, location, branch, commit, options.force);
26
26
  });
@@ -103,7 +103,7 @@ function printEmulatorOverview(options) {
103
103
  let extensionsTable = "";
104
104
  if (registry_1.EmulatorRegistry.isRunning(types_1.Emulators.EXTENSIONS)) {
105
105
  const extensionsEmulatorInstance = registry_1.EmulatorRegistry.get(types_1.Emulators.EXTENSIONS);
106
- extensionsTable = extensionsEmulatorInstance.extensionsInfoTable(options);
106
+ extensionsTable = extensionsEmulatorInstance.extensionsInfoTable();
107
107
  }
108
108
  logger_1.logger.info(`\n${successMessageTable}
109
109
 
@@ -79,6 +79,12 @@ let choices = [
79
79
  checked: false,
80
80
  hidden: true,
81
81
  },
82
+ {
83
+ value: "apphosting",
84
+ name: "App Hosting: Configure an apphosting.yaml file for App Hosting",
85
+ checked: false,
86
+ hidden: false,
87
+ },
82
88
  ];
83
89
  if ((0, experiments_1.isEnabled)("genkit")) {
84
90
  choices = [
package/lib/config.js CHANGED
@@ -165,21 +165,31 @@ class Config {
165
165
  deleteProjectFile(p) {
166
166
  fs.removeSync(this.path(p));
167
167
  }
168
- askWriteProjectFile(p, content, force) {
168
+ askWriteProjectFile(p, content, force, confirmByDefault) {
169
169
  const writeTo = this.path(p);
170
170
  let next;
171
- if (fsutils.fileExistsSync(writeTo) && !force) {
171
+ if (typeof content !== "string") {
172
+ content = JSON.stringify(content, null, 2) + "\n";
173
+ }
174
+ let existingContent;
175
+ if (fsutils.fileExistsSync(writeTo)) {
176
+ existingContent = fsutils.readFile(writeTo);
177
+ }
178
+ if (existingContent && existingContent !== content && !force) {
172
179
  next = (0, prompt_1.promptOnce)({
173
180
  type: "confirm",
174
181
  message: "File " + clc.underline(p) + " already exists. Overwrite?",
175
- default: false,
182
+ default: !!confirmByDefault,
176
183
  });
177
184
  }
178
185
  else {
179
186
  next = Promise.resolve(true);
180
187
  }
181
188
  return next.then((result) => {
182
- if (result) {
189
+ if (existingContent === content) {
190
+ utils.logBullet(clc.bold(p) + " is unchanged");
191
+ }
192
+ else if (result) {
183
193
  this.writeProjectFile(p, content);
184
194
  utils.logSuccess("Wrote " + clc.bold(p));
185
195
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateSdkYaml = exports.directoryHasPackageJson = exports.getPlatformFromFolder = exports.pickService = exports.readGQLFiles = exports.readConnectorYaml = exports.readDataConnectYaml = exports.readFirebaseJson = void 0;
3
+ exports.getPlatformFromFolder = exports.pickService = exports.readGQLFiles = exports.readConnectorYaml = exports.readDataConnectYaml = exports.readFirebaseJson = void 0;
4
4
  const fs = require("fs-extra");
5
5
  const path = require("path");
6
6
  const error_1 = require("../error");
@@ -112,7 +112,10 @@ async function getPlatformFromFolder(dirPath) {
112
112
  IOS_POSTFIX_INDICATORS.some((indicator) => cleanedFileName.endsWith(indicator)));
113
113
  hasDart || (hasDart = DART_INDICATORS.some((indicator) => indicator === cleanedFileName));
114
114
  }
115
- if (hasWeb && !hasAndroid && !hasIOS && !hasDart) {
115
+ if (!hasWeb && !hasAndroid && !hasIOS && !hasDart) {
116
+ return types_1.Platform.NONE;
117
+ }
118
+ else if (hasWeb && !hasAndroid && !hasIOS && !hasDart) {
116
119
  return types_1.Platform.WEB;
117
120
  }
118
121
  else if (hasAndroid && !hasWeb && !hasIOS && !hasDart) {
@@ -122,47 +125,8 @@ async function getPlatformFromFolder(dirPath) {
122
125
  return types_1.Platform.IOS;
123
126
  }
124
127
  else if (hasDart && !hasWeb && !hasIOS && !hasAndroid) {
125
- return types_1.Platform.DART;
128
+ return types_1.Platform.FLUTTER;
126
129
  }
127
- return types_1.Platform.UNDETERMINED;
130
+ return types_1.Platform.MULTIPLE;
128
131
  }
129
132
  exports.getPlatformFromFolder = getPlatformFromFolder;
130
- async function directoryHasPackageJson(dirPath) {
131
- const fileNames = await fs.readdir(dirPath);
132
- return fileNames.some((f) => f.toLowerCase() === "package.json");
133
- }
134
- exports.directoryHasPackageJson = directoryHasPackageJson;
135
- function generateSdkYaml(platform, connectorYaml, connectorYamlFolder, appFolder) {
136
- const relPath = path.relative(connectorYamlFolder, appFolder);
137
- const outputDir = path.join(relPath, "dataconnect-generated");
138
- if (!connectorYaml.generate) {
139
- connectorYaml.generate = {};
140
- }
141
- if (platform === types_1.Platform.WEB) {
142
- connectorYaml.generate.javascriptSdk = {
143
- outputDir,
144
- package: `@firebasegen/${connectorYaml.connectorId}`,
145
- packageJsonDir: appFolder,
146
- };
147
- }
148
- if (platform === types_1.Platform.IOS) {
149
- connectorYaml.generate.swiftSdk = {
150
- outputDir,
151
- package: connectorYaml.connectorId,
152
- };
153
- }
154
- if (platform === types_1.Platform.ANDROID) {
155
- connectorYaml.generate.kotlinSdk = {
156
- outputDir,
157
- package: `connectors.${connectorYaml.connectorId}`,
158
- };
159
- }
160
- if (platform === types_1.Platform.DART) {
161
- connectorYaml.generate.dartSdk = {
162
- outputDir,
163
- package: connectorYaml.connectorId,
164
- };
165
- }
166
- return connectorYaml;
167
- }
168
- exports.generateSdkYaml = generateSdkYaml;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.printFreeTrialUnavailable = exports.checkForFreeTrialInstance = exports.freeTrialTermsLink = void 0;
3
+ exports.upgradeInstructions = exports.printFreeTrialUnavailable = exports.getFreeTrialInstanceId = exports.checkFreeTrialInstanceUsed = exports.freeTrialTermsLink = void 0;
4
+ const cloudmonitoring_1 = require("../gcp/cloudmonitoring");
4
5
  const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
5
6
  const utils = require("../utils");
6
7
  const clc = require("colorette");
@@ -8,18 +9,51 @@ function freeTrialTermsLink() {
8
9
  return "https://firebase.google.com/pricing";
9
10
  }
10
11
  exports.freeTrialTermsLink = freeTrialTermsLink;
11
- async function checkForFreeTrialInstance(projectId) {
12
+ const FREE_TRIAL_METRIC = "sqladmin.googleapis.com/fdc_lifetime_free_trial_per_project";
13
+ async function checkFreeTrialInstanceUsed(projectId) {
14
+ const past7d = new Date();
15
+ past7d.setDate(past7d.getDate() - 7);
16
+ const query = {
17
+ filter: `metric.type="serviceruntime.googleapis.com/quota/allocation/usage" AND metric.label.quota_metric = "${FREE_TRIAL_METRIC}"`,
18
+ "interval.endTime": new Date().toJSON(),
19
+ "interval.startTime": past7d.toJSON(),
20
+ };
21
+ try {
22
+ const ts = await (0, cloudmonitoring_1.queryTimeSeries)(query, projectId);
23
+ if (ts.length) {
24
+ return ts[0].points.some((p) => p.value.int64Value);
25
+ }
26
+ return true;
27
+ }
28
+ catch (err) {
29
+ return false;
30
+ }
31
+ }
32
+ exports.checkFreeTrialInstanceUsed = checkFreeTrialInstanceUsed;
33
+ async function getFreeTrialInstanceId(projectId) {
12
34
  var _a;
13
35
  const instances = await (0, cloudsqladmin_1.listInstances)(projectId);
14
36
  return (_a = instances.find((i) => { var _a; return ((_a = i.settings.userLabels) === null || _a === void 0 ? void 0 : _a["firebase-data-connect"]) === "ft"; })) === null || _a === void 0 ? void 0 : _a.name;
15
37
  }
16
- exports.checkForFreeTrialInstance = checkForFreeTrialInstance;
17
- function printFreeTrialUnavailable(projectId, instanceId, configYamlPath) {
18
- utils.logLabeledError("dataconnect", `Project '${projectId} already has a CloudSQL instance '${instanceId}' on the Firebase Data Connect no-cost trial.`);
19
- const reuseHint = `To use a different database in the same instance, ${clc.bold(`change the ${clc.blue("instanceId")} to "${instanceId}"`)} in ` +
20
- `${clc.green(configYamlPath)}. (Also, update the ${clc.blue("database")} field (i.e. DB name in the instance) ` +
21
- `and ${clc.blue("location")} as needed.)`;
22
- utils.logLabeledBullet("dataconnect", reuseHint);
23
- utils.logLabeledBullet("dataconnect", `Or you may create a new (paid) CloudSQL instance at https://console.cloud.google.com/sql/instances`);
38
+ exports.getFreeTrialInstanceId = getFreeTrialInstanceId;
39
+ function printFreeTrialUnavailable(projectId, configYamlPath, instanceId) {
40
+ if (!instanceId) {
41
+ utils.logLabeledError("data connect", "The CloudSQL free trial has already been used on this project.");
42
+ utils.logLabeledError("data connect", `You may create or use a paid CloudSQL instance by visiting https://console.cloud.google.com/sql/instances`);
43
+ return;
44
+ }
45
+ utils.logLabeledError("data connect", `Project '${projectId} already has a CloudSQL instance '${instanceId}' on the Firebase Data Connect no-cost trial.`);
46
+ const reuseHint = `To use a different database in the same instance, ${clc.bold(`change the ${clc.blue("instanceId")} to "${instanceId}"`)} and update ${clc.blue("location")} in ` +
47
+ `${clc.green(configYamlPath)}.`;
48
+ utils.logLabeledError("data connect", reuseHint);
49
+ utils.logLabeledError("data connect", `Alternatively, you may create a new (paid) CloudSQL instance at https://console.cloud.google.com/sql/instances`);
24
50
  }
25
51
  exports.printFreeTrialUnavailable = printFreeTrialUnavailable;
52
+ function upgradeInstructions(projectId) {
53
+ return `If you'd like to provision a CloudSQL Postgres instance on the Firebase Data Connect no-cost trial:
54
+ 1. Please upgrade to the pay-as-you-go (Blaze) billing plan. Visit the following page:
55
+ https://console.firebase.google.com/project/${projectId}/usage/details
56
+ 2. Run ${clc.bold("firebase init dataconnect")} again to configure the Cloud SQL instance.
57
+ 3. Run ${clc.bold("firebase deploy --only dataconnect")} to deploy your Data Connect service.`;
58
+ }
59
+ exports.upgradeInstructions = upgradeInstructions;
@@ -33,10 +33,10 @@ async function provisionCloudSql(args) {
33
33
  if (err.status !== 404) {
34
34
  throw err;
35
35
  }
36
- const freeTrialInstanceId = await (0, freeTrial_1.checkForFreeTrialInstance)(projectId);
37
- if (freeTrialInstanceId) {
38
- (0, freeTrial_1.printFreeTrialUnavailable)(projectId, freeTrialInstanceId, configYamlPath);
39
- throw new error_1.FirebaseError("Cannot create another no-cost trial Cloud SQL instance.");
36
+ const freeTrialInstanceId = await (0, freeTrial_1.getFreeTrialInstanceId)(projectId);
37
+ if (await (0, freeTrial_1.checkFreeTrialInstanceUsed)(projectId)) {
38
+ (0, freeTrial_1.printFreeTrialUnavailable)(projectId, configYamlPath, freeTrialInstanceId);
39
+ throw new error_1.FirebaseError("No-cost Cloud SQL trial has already been used on this project.");
40
40
  }
41
41
  const cta = dryRun ? "It will be created on your next deploy" : "Creating it now.";
42
42
  silent ||
@@ -9,11 +9,12 @@ function requiresVector(dm) {
9
9
  exports.requiresVector = requiresVector;
10
10
  var Platform;
11
11
  (function (Platform) {
12
+ Platform["NONE"] = "NONE";
12
13
  Platform["ANDROID"] = "ANDROID";
13
14
  Platform["WEB"] = "WEB";
14
15
  Platform["IOS"] = "IOS";
15
- Platform["DART"] = "DART";
16
- Platform["UNDETERMINED"] = "UNDETERMINED";
16
+ Platform["FLUTTER"] = "FLUTTER";
17
+ Platform["MULTIPLE"] = "MULTIPLE";
17
18
  })(Platform = exports.Platform || (exports.Platform = {}));
18
19
  function toDatasource(projectId, locationId, ds) {
19
20
  if (ds.postgresql) {
@@ -22,10 +22,11 @@ async function sendVSCodeMessage(body) {
22
22
  "x-mantle-admin": "all",
23
23
  },
24
24
  body: jsonBody,
25
+ signal: AbortSignal.timeout(3000),
25
26
  });
26
27
  }
27
28
  catch (e) {
28
- logger_1.logger.debug(`Could not find VSCode notification endpoint: ${e}`);
29
+ logger_1.logger.debug(`Could not find VSCode notification endpoint: ${e}. If you are not running the Firebase Data Connect VSCode extension, this is expected and not an issue.`);
29
30
  }
30
31
  }
31
32
  exports.sendVSCodeMessage = sendVSCodeMessage;
@@ -12,14 +12,19 @@ const ensureApis_1 = require("../../dataconnect/ensureApis");
12
12
  const requireTosAcceptance_1 = require("../../requireTosAcceptance");
13
13
  const firedata_1 = require("../../gcp/firedata");
14
14
  const provisionCloudSql_1 = require("../../dataconnect/provisionCloudSql");
15
+ const cloudbilling_1 = require("../../gcp/cloudbilling");
15
16
  const names_1 = require("../../dataconnect/names");
16
17
  const error_1 = require("../../error");
17
18
  const types_1 = require("../../dataconnect/types");
18
19
  const schemaMigration_1 = require("../../dataconnect/schemaMigration");
19
20
  const node_path_1 = require("node:path");
21
+ const freeTrial_1 = require("../../dataconnect/freeTrial");
20
22
  async function default_1(context, options) {
21
23
  var _a;
22
24
  const projectId = (0, projectUtils_1.needProjectId)(options);
25
+ if (!(await (0, cloudbilling_1.checkBillingEnabled)(projectId))) {
26
+ throw new error_1.FirebaseError((0, freeTrial_1.upgradeInstructions)(projectId));
27
+ }
23
28
  await (0, ensureApis_1.ensureApis)(projectId);
24
29
  await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
25
30
  const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(options.config);
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLocalAppHostingConfiguration = exports.loadAppHostingYaml = void 0;
4
+ const path_1 = require("path");
5
+ const fs_extra_1 = require("fs-extra");
6
+ const utils_1 = require("../../utils");
7
+ const utils_2 = require("./utils");
8
+ const types_1 = require("../types");
9
+ const APPHOSTING_YAML = "apphosting.yaml";
10
+ const APPHOSTING_LOCAL_YAML = "apphosting.local.yaml";
11
+ async function loadAppHostingYaml(sourceDirectory, fileName) {
12
+ const file = await (0, utils_1.readFileFromDirectory)(sourceDirectory, fileName);
13
+ const apphostingYaml = await (0, utils_1.wrappedSafeLoad)(file.source);
14
+ const environmentVariables = {};
15
+ const secrets = {};
16
+ if (apphostingYaml.env) {
17
+ for (const env of apphostingYaml.env) {
18
+ if (env.value) {
19
+ environmentVariables[env.variable] = env.value;
20
+ }
21
+ if (env.secret) {
22
+ secrets[env.variable] = env.secret;
23
+ }
24
+ }
25
+ }
26
+ return { environmentVariables, secrets };
27
+ }
28
+ exports.loadAppHostingYaml = loadAppHostingYaml;
29
+ async function getLocalAppHostingConfiguration(sourceDirectory) {
30
+ let apphostingBaseConfig = {};
31
+ let apphostingLocalConfig = {};
32
+ if (await (0, fs_extra_1.pathExists)((0, path_1.join)(sourceDirectory, APPHOSTING_YAML))) {
33
+ utils_2.logger.logLabeled("SUCCESS", types_1.Emulators.APPHOSTING, `${APPHOSTING_YAML} found, loading configuration`);
34
+ apphostingBaseConfig = await loadAppHostingYaml(sourceDirectory, APPHOSTING_YAML);
35
+ }
36
+ if (await (0, fs_extra_1.pathExists)((0, path_1.join)(sourceDirectory, APPHOSTING_LOCAL_YAML))) {
37
+ utils_2.logger.logLabeled("SUCCESS", types_1.Emulators.APPHOSTING, `${APPHOSTING_LOCAL_YAML} found, loading configuration`);
38
+ apphostingLocalConfig = await loadAppHostingYaml(sourceDirectory, APPHOSTING_LOCAL_YAML);
39
+ }
40
+ return {
41
+ environmentVariables: Object.assign(Object.assign({}, apphostingBaseConfig.environmentVariables), apphostingLocalConfig.environmentVariables),
42
+ secrets: Object.assign(Object.assign({}, apphostingBaseConfig.secrets), apphostingLocalConfig.secrets),
43
+ };
44
+ }
45
+ exports.getLocalAppHostingConfiguration = getLocalAppHostingConfiguration;