firebase-tools 13.13.2 → 13.14.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 (37) hide show
  1. package/lib/api.js +4 -2
  2. package/lib/apphosting/githubConnections.js +108 -34
  3. package/lib/auth.js +2 -7
  4. package/lib/command.js +1 -1
  5. package/lib/commands/dataconnect-sdk-generate.js +2 -7
  6. package/lib/commands/firestore-backups-schedules-create.js +11 -14
  7. package/lib/commands/firestore-backups-schedules-list.js +1 -1
  8. package/lib/commands/firestore-backups-schedules-update.js +4 -3
  9. package/lib/commands/firestore-databases-create.js +5 -6
  10. package/lib/commands/firestore-databases-restore.js +4 -4
  11. package/lib/commands/firestore-databases-update.js +5 -6
  12. package/lib/commands/init.js +2 -2
  13. package/lib/commands/login.js +1 -2
  14. package/lib/dataconnect/fileUtils.js +2 -2
  15. package/lib/dataconnect/filters.js +12 -1
  16. package/lib/dataconnect/load.js +6 -5
  17. package/lib/dataconnect/schemaMigration.js +22 -0
  18. package/lib/deploy/dataconnect/prepare.js +16 -2
  19. package/lib/emulator/constants.js +1 -0
  20. package/lib/emulator/controller.js +2 -5
  21. package/lib/emulator/dataconnectEmulator.js +3 -2
  22. package/lib/emulator/downloadableEmulators.js +12 -12
  23. package/lib/emulator/eventarcEmulator.js +68 -18
  24. package/lib/emulator/functionsEmulator.js +79 -0
  25. package/lib/emulator/functionsEmulatorShared.js +4 -0
  26. package/lib/emulator/pubsubEmulator.js +1 -1
  27. package/lib/experiments.js +5 -0
  28. package/lib/frameworks/angular/utils.js +13 -5
  29. package/lib/functions/events/v2.js +2 -1
  30. package/lib/gcp/devConnect.js +13 -1
  31. package/lib/init/features/account.js +1 -1
  32. package/lib/init/features/dataconnect/index.js +69 -19
  33. package/lib/init/features/dataconnect/sdk.js +2 -3
  34. package/lib/init/features/hosting/github.js +44 -21
  35. package/lib/requireAuth.js +2 -2
  36. package/package.json +8 -8
  37. package/templates/init/dataconnect/dataconnect.yaml +1 -1
@@ -19,6 +19,19 @@ const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconn
19
19
  const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
20
20
  const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
21
21
  const MUTATIONS_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/mutations.gql");
22
+ const defaultConnector = {
23
+ id: "default-connector",
24
+ files: [
25
+ {
26
+ path: "queries.gql",
27
+ content: QUERIES_TEMPLATE,
28
+ },
29
+ {
30
+ path: "mutations.gql",
31
+ content: MUTATIONS_TEMPLATE,
32
+ },
33
+ ],
34
+ };
22
35
  async function doSetup(setup, config) {
23
36
  var _a, _b, _c;
24
37
  let info = {
@@ -28,7 +41,8 @@ async function doSetup(setup, config) {
28
41
  isNewInstance: false,
29
42
  cloudSqlDatabase: "",
30
43
  isNewDatabase: false,
31
- connectorId: "default-connector",
44
+ connectors: [defaultConnector],
45
+ schemaGql: [],
32
46
  };
33
47
  info = await promptForService(setup, info);
34
48
  if (info.cloudSqlInstanceId === "") {
@@ -45,15 +59,7 @@ async function doSetup(setup, config) {
45
59
  default: defaultConnectionString,
46
60
  });
47
61
  setup.rcfile.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
48
- const dir = config.get("dataconnect.source") || "dataconnect";
49
- const subbedDataconnectYaml = subValues(DATACONNECT_YAML_TEMPLATE, info);
50
- const subbedConnectorYaml = subValues(CONNECTOR_YAML_TEMPLATE, info);
51
- config.set("dataconnect", { source: dir });
52
- await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml);
53
- await config.askWriteProjectFile((0, path_1.join)(dir, "schema", "schema.gql"), SCHEMA_TEMPLATE);
54
- await config.askWriteProjectFile((0, path_1.join)(dir, info.connectorId, "connector.yaml"), subbedConnectorYaml);
55
- await config.askWriteProjectFile((0, path_1.join)(dir, info.connectorId, "queries.gql"), QUERIES_TEMPLATE);
56
- await config.askWriteProjectFile((0, path_1.join)(dir, info.connectorId, "mutations.gql"), MUTATIONS_TEMPLATE);
62
+ await writeFiles(config, info);
57
63
  if (setup.projectId &&
58
64
  (info.isNewInstance || info.isNewDatabase) &&
59
65
  (await (0, prompt_1.confirm)({
@@ -73,15 +79,51 @@ async function doSetup(setup, config) {
73
79
  (0, utils_1.logSuccess)(`If you'd like to generate an SDK for your new connector, run ${clc.bold("firebase init dataconnect:sdk")}`);
74
80
  }
75
81
  exports.doSetup = doSetup;
76
- function subValues(template, replacementValues) {
82
+ async function writeFiles(config, info) {
83
+ const dir = config.get("dataconnect.source") || "dataconnect";
84
+ const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorIds: info.connectors.map((c) => `"./${c.id}"`).join(", ") }));
85
+ config.set("dataconnect", { source: dir });
86
+ await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml);
87
+ if (info.schemaGql.length) {
88
+ (0, utils_1.logSuccess)("The service you chose already has GQL files deployed. We'll use those instead of the default templates.");
89
+ for (const f of info.schemaGql) {
90
+ await config.askWriteProjectFile((0, path_1.join)(dir, "schema", f.path), f.content);
91
+ }
92
+ }
93
+ else {
94
+ await config.askWriteProjectFile((0, path_1.join)(dir, "schema", "schema.gql"), SCHEMA_TEMPLATE);
95
+ }
96
+ for (const c of info.connectors) {
97
+ await writeConnectorFiles(config, c);
98
+ }
99
+ }
100
+ async function writeConnectorFiles(config, connectorInfo) {
101
+ const subbedConnectorYaml = subConnectorYamlValues({ connectorId: connectorInfo.id });
102
+ const dir = config.get("dataconnect.source") || "dataconnect";
103
+ await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.id, "connector.yaml"), subbedConnectorYaml);
104
+ for (const f of connectorInfo.files) {
105
+ await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.id, f.path), f.content);
106
+ }
107
+ }
108
+ function subDataconnectYamlValues(replacementValues) {
77
109
  const replacements = {
78
110
  serviceId: "__serviceId__",
79
111
  cloudSqlDatabase: "__cloudSqlDatabase__",
80
112
  cloudSqlInstanceId: "__cloudSqlInstanceId__",
81
- connectorId: "__connectorId__",
113
+ connectorIds: "__connectorIds__",
82
114
  locationId: "__location__",
83
115
  };
84
- let replaced = template;
116
+ let replaced = DATACONNECT_YAML_TEMPLATE;
117
+ for (const [k, v] of Object.entries(replacementValues)) {
118
+ replaced = replaced.replace(replacements[k], v);
119
+ }
120
+ return replaced;
121
+ }
122
+ function subConnectorYamlValues(replacementValues) {
123
+ const replacements = {
124
+ connectorId: "__connectorId__",
125
+ };
126
+ let replaced = CONNECTOR_YAML_TEMPLATE;
85
127
  for (const [k, v] of Object.entries(replacementValues)) {
86
128
  replaced = replaced.replace(replacements[k], v);
87
129
  }
@@ -98,12 +140,8 @@ async function promptForService(setup, info) {
98
140
  schema: await (0, client_1.getSchema)(s.name),
99
141
  };
100
142
  }));
101
- const existingFreshServicesAndSchemas = existingServicesAndSchemas.filter((s) => {
102
- var _a, _b;
103
- return !((_b = (_a = s.schema) === null || _a === void 0 ? void 0 : _a.source.files) === null || _b === void 0 ? void 0 : _b.length);
104
- });
105
- if (existingFreshServicesAndSchemas.length) {
106
- const choices = existingFreshServicesAndSchemas.map((s) => {
143
+ if (existingServicesAndSchemas.length) {
144
+ const choices = existingServicesAndSchemas.map((s) => {
107
145
  const serviceName = (0, names_1.parseServiceName)(s.service.name);
108
146
  return {
109
147
  name: `${serviceName.location}/${serviceName.serviceId}`,
@@ -125,7 +163,19 @@ async function promptForService(setup, info) {
125
163
  const instanceName = (0, names_1.parseCloudSQLInstanceName)((_b = choice.schema.primaryDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql.instance);
126
164
  info.cloudSqlInstanceId = instanceName.instanceId;
127
165
  }
166
+ if (choice.schema.source.files) {
167
+ info.schemaGql = choice.schema.source.files;
168
+ }
128
169
  info.cloudSqlDatabase = (_d = (_c = choice.schema.primaryDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database) !== null && _d !== void 0 ? _d : "";
170
+ const connectors = await (0, client_1.listConnectors)(choice.service.name);
171
+ if (connectors.length) {
172
+ info.connectors = connectors.map((c) => {
173
+ return {
174
+ id: c.name.split("/").pop(),
175
+ files: c.source.files || [],
176
+ };
177
+ });
178
+ }
129
179
  }
130
180
  }
131
181
  }
@@ -3,9 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.doSetup = void 0;
4
4
  const yaml = require("yaml");
5
5
  const fs = require("fs-extra");
6
- const prompt_1 = require("../../../prompt");
7
6
  const clc = require("colorette");
8
- const path = require("path");
7
+ const prompt_1 = require("../../../prompt");
9
8
  const fileUtils_1 = require("../../../dataconnect/fileUtils");
10
9
  const load_1 = require("../../../dataconnect/load");
11
10
  const logger_1 = require("../../../logger");
@@ -16,7 +15,7 @@ const ANDROID = "android";
16
15
  async function doSetup(setup, config) {
17
16
  var _a, _b, _c, _d, _e, _f, _g, _h;
18
17
  const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(config);
19
- const serviceInfos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(setup.projectId || "", path.join(process.cwd(), c.source))));
18
+ const serviceInfos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(setup.projectId || "", config, c.source)));
20
19
  const connectorChoices = serviceInfos
21
20
  .map((si) => {
22
21
  return si.connectorInfo.map((ci) => {
@@ -29,6 +29,7 @@ const HOSTING_GITHUB_ACTION_NAME = "FirebaseExtended/action-hosting-deploy@v0";
29
29
  const SERVICE_ACCOUNT_MAX_KEY_NUMBER = 10;
30
30
  const githubApiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.githubApiOrigin)(), auth: false });
31
31
  async function initGitHub(setup) {
32
+ var _a, _b, _c, _d, _e;
32
33
  if (!setup.projectId) {
33
34
  return (0, utils_1.reject)("Could not determine Project ID, can't set up GitHub workflow.", { exit: 1 });
34
35
  }
@@ -69,7 +70,7 @@ async function initGitHub(setup) {
69
70
  if (setup.config.hosting.predeploy) {
70
71
  (0, utils_1.logBullet)(`You have a predeploy script configured in firebase.json.`);
71
72
  }
72
- const { script } = await promptForBuildScript();
73
+ const { script } = await promptForBuildScript((_a = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _a === void 0 ? void 0 : _a.useWebFrameworks);
73
74
  const ymlDeployDoc = loadYMLDeploy();
74
75
  let shouldWriteYMLHostingFile = true;
75
76
  let shouldWriteYMLDeployFile = false;
@@ -80,7 +81,7 @@ async function initGitHub(setup) {
80
81
  shouldWriteYMLHostingFile = overwrite;
81
82
  }
82
83
  if (shouldWriteYMLHostingFile) {
83
- writeChannelActionYMLFile(YML_FULL_PATH_PULL_REQUEST, githubSecretName, setup.projectId, script);
84
+ writeChannelActionYMLFile(YML_FULL_PATH_PULL_REQUEST, githubSecretName, setup.projectId, script, (_b = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _b === void 0 ? void 0 : _b.useWebFrameworks, (_c = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _c === void 0 ? void 0 : _c.source);
84
85
  logger_1.logger.info();
85
86
  (0, utils_1.logSuccess)(`Created workflow file ${(0, colorette_1.bold)(YML_FULL_PATH_PULL_REQUEST)}`);
86
87
  }
@@ -101,7 +102,7 @@ async function initGitHub(setup) {
101
102
  shouldWriteYMLDeployFile = true;
102
103
  }
103
104
  if (shouldWriteYMLDeployFile) {
104
- writeDeployToProdActionYMLFile(YML_FULL_PATH_MERGE, branch, githubSecretName, setup.projectId, script);
105
+ writeDeployToProdActionYMLFile(YML_FULL_PATH_MERGE, branch, githubSecretName, setup.projectId, script, (_d = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _d === void 0 ? void 0 : _d.useWebFrameworks, (_e = setup === null || setup === void 0 ? void 0 : setup.hosting) === null || _e === void 0 ? void 0 : _e.source);
105
106
  logger_1.logger.info();
106
107
  (0, utils_1.logSuccess)(`Created workflow file ${(0, colorette_1.bold)(YML_FULL_PATH_MERGE)}`);
107
108
  }
@@ -155,7 +156,7 @@ function mkdirNotExists(dir) {
155
156
  fs.mkdirSync(dir);
156
157
  }
157
158
  }
158
- function writeChannelActionYMLFile(ymlPath, secretName, projectId, script) {
159
+ function writeChannelActionYMLFile(ymlPath, secretName, projectId, script, useWebFrameworks, hostingSource) {
159
160
  const workflowConfig = {
160
161
  name: "Deploy to Firebase Hosting on PR",
161
162
  on: "pull_request",
@@ -172,19 +173,31 @@ function writeChannelActionYMLFile(ymlPath, secretName, projectId, script) {
172
173
  },
173
174
  },
174
175
  };
175
- if (script) {
176
- workflowConfig.jobs.build_and_preview.steps.push({
177
- run: script,
178
- });
179
- }
180
- workflowConfig.jobs.build_and_preview.steps.push({
176
+ const buildAndPreviewParams = {
181
177
  uses: HOSTING_GITHUB_ACTION_NAME,
182
178
  with: {
183
179
  repoToken: "${{ secrets.GITHUB_TOKEN }}",
184
180
  firebaseServiceAccount: `\${{ secrets.${secretName} }}`,
185
181
  projectId: projectId,
186
182
  },
187
- });
183
+ };
184
+ if (useWebFrameworks) {
185
+ workflowConfig.jobs.build_and_preview.steps.push({ run: "npm ci" });
186
+ buildAndPreviewParams.env = {
187
+ FIREBASE_CLI_EXPERIMENTS: "webframeworks",
188
+ };
189
+ if (hostingSource && hostingSource !== ".") {
190
+ workflowConfig.jobs.build_and_preview.defaults = {
191
+ run: { "working-directory": hostingSource },
192
+ };
193
+ }
194
+ }
195
+ if (script) {
196
+ workflowConfig.jobs.build_and_preview.steps.push({
197
+ run: script,
198
+ });
199
+ }
200
+ workflowConfig.jobs.build_and_preview.steps.push(buildAndPreviewParams);
188
201
  const ymlContents = `# This file was auto-generated by the Firebase CLI
189
202
  # https://github.com/firebase/firebase-tools
190
203
 
@@ -193,7 +206,7 @@ ${yaml.stringify(workflowConfig)}`;
193
206
  mkdirNotExists(WORKFLOW_DIR);
194
207
  fs.writeFileSync(ymlPath, ymlContents, "utf8");
195
208
  }
196
- function writeDeployToProdActionYMLFile(ymlPath, branch, secretName, projectId, script) {
209
+ function writeDeployToProdActionYMLFile(ymlPath, branch, secretName, projectId, script, useWebFrameworks, hostingSource) {
197
210
  const workflowConfig = {
198
211
  name: "Deploy to Firebase Hosting on merge",
199
212
  on: { push: { branches: [branch || "master"] } },
@@ -204,12 +217,7 @@ function writeDeployToProdActionYMLFile(ymlPath, branch, secretName, projectId,
204
217
  },
205
218
  },
206
219
  };
207
- if (script) {
208
- workflowConfig.jobs.build_and_deploy.steps.push({
209
- run: script,
210
- });
211
- }
212
- workflowConfig.jobs.build_and_deploy.steps.push({
220
+ const buildAndDeployParams = {
213
221
  uses: HOSTING_GITHUB_ACTION_NAME,
214
222
  with: {
215
223
  repoToken: "${{ secrets.GITHUB_TOKEN }}",
@@ -217,7 +225,22 @@ function writeDeployToProdActionYMLFile(ymlPath, branch, secretName, projectId,
217
225
  channelId: "live",
218
226
  projectId: projectId,
219
227
  },
220
- });
228
+ };
229
+ if (useWebFrameworks) {
230
+ workflowConfig.jobs.build_and_deploy.steps.push({ run: "npm ci" });
231
+ buildAndDeployParams.env = {
232
+ FIREBASE_CLI_EXPERIMENTS: "webframeworks",
233
+ };
234
+ if (hostingSource && hostingSource !== ".") {
235
+ workflowConfig.jobs.build_and_deploy.defaults = {
236
+ run: { "working-directory": hostingSource },
237
+ };
238
+ }
239
+ }
240
+ if (script) {
241
+ workflowConfig.jobs.build_and_deploy.steps.push({ run: script });
242
+ }
243
+ workflowConfig.jobs.build_and_deploy.steps.push(buildAndDeployParams);
221
244
  const ymlContents = `# This file was auto-generated by the Firebase CLI
222
245
  # https://github.com/firebase/firebase-tools
223
246
 
@@ -270,7 +293,7 @@ async function promptForRepo(options, ghAccessToken) {
270
293
  ]);
271
294
  return { repo, key, keyId };
272
295
  }
273
- async function promptForBuildScript() {
296
+ async function promptForBuildScript(useWebFrameworks) {
274
297
  const { shouldSetupScript } = await (0, prompt_1.prompt)({}, [
275
298
  {
276
299
  type: "confirm",
@@ -286,7 +309,7 @@ async function promptForBuildScript() {
286
309
  {
287
310
  type: "input",
288
311
  name: "script",
289
- default: "npm ci && npm run build",
312
+ default: useWebFrameworks ? undefined : "npm ci && npm run build",
290
313
  message: "What script should be run before every deploy?",
291
314
  },
292
315
  ]);
@@ -32,7 +32,7 @@ async function autoAuth(options, authScopes) {
32
32
  logger_1.logger.debug(`Error getting account credentials.`);
33
33
  }
34
34
  if (process.env.MONOSPACE_ENV && token && clientEmail) {
35
- await (0, auth_1.setActiveAccount)(options, {
35
+ (0, auth_1.setActiveAccount)(options, {
36
36
  user: { email: clientEmail },
37
37
  tokens: { access_token: token },
38
38
  });
@@ -76,7 +76,7 @@ async function requireAuth(options) {
76
76
  if (!user || !tokens) {
77
77
  throw new error_1.FirebaseError(AUTH_ERROR_MESSAGE);
78
78
  }
79
- await (0, auth_1.setActiveAccount)(options, { user, tokens });
79
+ (0, auth_1.setActiveAccount)(options, { user, tokens });
80
80
  return user.email;
81
81
  }
82
82
  exports.requireAuth = requireAuth;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.13.2",
3
+ "version": "13.14.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -60,14 +60,14 @@
60
60
  ]
61
61
  },
62
62
  "dependencies": {
63
- "@google-cloud/cloud-sql-connector": "^1.2.3",
64
- "@google-cloud/pubsub": "^4.4.0",
63
+ "@google-cloud/cloud-sql-connector": "^1.3.3",
64
+ "@google-cloud/pubsub": "^4.5.0",
65
65
  "abort-controller": "^3.0.0",
66
66
  "ajv": "^6.12.6",
67
67
  "archiver": "^7.0.0",
68
- "async-lock": "1.3.2",
68
+ "async-lock": "1.4.1",
69
69
  "body-parser": "^1.19.0",
70
- "chokidar": "^3.0.2",
70
+ "chokidar": "^3.6.0",
71
71
  "cjson": "^0.3.1",
72
72
  "cli-table": "0.3.11",
73
73
  "colorette": "^2.0.19",
@@ -78,16 +78,16 @@
78
78
  "cross-spawn": "^7.0.3",
79
79
  "csv-parse": "^5.0.4",
80
80
  "deep-equal-in-any-order": "^2.0.6",
81
- "exegesis": "^4.1.2",
81
+ "exegesis": "^4.2.0",
82
82
  "exegesis-express": "^4.0.0",
83
83
  "express": "^4.16.4",
84
84
  "filesize": "^6.1.0",
85
85
  "form-data": "^4.0.0",
86
86
  "fs-extra": "^10.1.0",
87
87
  "fuzzy": "^0.1.3",
88
- "gaxios": "^6.1.1",
88
+ "gaxios": "^6.7.0",
89
89
  "glob": "^10.4.1",
90
- "google-auth-library": "^9.7.0",
90
+ "google-auth-library": "^9.11.0",
91
91
  "inquirer": "^8.2.6",
92
92
  "inquirer-autocomplete-prompt": "^2.0.1",
93
93
  "jsonwebtoken": "^9.0.0",
@@ -8,4 +8,4 @@ schema:
8
8
  database: "__cloudSqlDatabase__"
9
9
  cloudSql:
10
10
  instanceId: "__cloudSqlInstanceId__"
11
- connectorDirs: ["./__connectorId__"]
11
+ connectorDirs: [__connectorIds__]