From 6b107c96b86b9198a527952135744835f516714d Mon Sep 17 00:00:00 2001 From: Mohankumar Ramachandran Date: Thu, 5 Dec 2024 04:59:32 +0530 Subject: [PATCH 01/28] Updated to fix issues-4170 (#7980) * Updated to fix issues-4170 * Fixed as per the review * Fixed linting issues * Update src/emulator/auth/operations.ts Co-authored-by: Yuchen Shi * Update src/emulator/auth/operations.ts Co-authored-by: Yuchen Shi * Update CHANGELOG.md * Update CHANGELOG.md --------- Co-authored-by: Yuchen Shi --- CHANGELOG.md | 1 + src/emulator/auth/operations.ts | 14 ++-- src/emulator/auth/setAccountInfo.spec.ts | 96 ++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b13e382db88..c625820edf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,4 @@ - Deprecated `emulators.apphosting.startCommandOverride`. Please use `emulators.apphosting.startCommand` instead. - Updated `superstatic` to `9.1.0` in package.json. - Updated the Firebase Data Connect local toolkit to v1.7.4, which includes a fix for an issue that caused duplicate installations of the Firebase JS SDK. (#8028) +- Add support for `linkProviderUserInfo` in the Firebase Emulator to allow linking providers to user accounts. (#4170) diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index 7d03b52ab90..4bc93c36b3c 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -1030,11 +1030,7 @@ export function setAccountInfoImpl( { privileged = false, emulatorUrl = undefined }: { privileged?: boolean; emulatorUrl?: URL } = {}, ): Schemas["GoogleCloudIdentitytoolkitV1SetAccountInfoResponse"] { // TODO: Implement these. - const unimplementedFields: (keyof typeof reqBody)[] = [ - "provider", - "upgradeToFederatedLogin", - "linkProviderUserInfo", - ]; + const unimplementedFields: (keyof typeof reqBody)[] = ["provider", "upgradeToFederatedLogin"]; for (const field of unimplementedFields) { if (field in reqBody) { throw new NotImplementedError(`${field} is not implemented yet.`); @@ -1232,8 +1228,16 @@ export function setAccountInfoImpl( } } + if (reqBody.linkProviderUserInfo) { + assert(reqBody.linkProviderUserInfo.providerId, "MISSING_PROVIDER_ID"); + assert(reqBody.linkProviderUserInfo.rawId, "MISSING_RAW_ID"); + } + user = state.updateUserByLocalId(user.localId, updates, { deleteProviders: reqBody.deleteProvider, + upsertProviders: reqBody.linkProviderUserInfo + ? [reqBody.linkProviderUserInfo as ProviderUserInfo] + : undefined, }); // Only initiate the recover email OOB flow for non-anonymous users diff --git a/src/emulator/auth/setAccountInfo.spec.ts b/src/emulator/auth/setAccountInfo.spec.ts index 719a012d8eb..6d2fe4481bc 100644 --- a/src/emulator/auth/setAccountInfo.spec.ts +++ b/src/emulator/auth/setAccountInfo.spec.ts @@ -1254,4 +1254,100 @@ describeAuthEmulator("accounts:update", ({ authApi, getClock }) => { expect(oobs[0].requestType).to.equal("RECOVER_EMAIL"); expect(oobs[0].oobLink).to.include(tenant.tenantId); }); + + it("should link provider account with existing user account", async () => { + const { idToken } = await registerUser(authApi(), { + email: "test@example.com", + password: "password", + }); + + const providerId = "google.com"; + const rawId = "google_user_id"; + const providerUserInfo = { + providerId, + rawId, + email: "linked@example.com", + displayName: "Linked User", + photoUrl: "https://example.com/photo.jpg", + }; + + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:update") + .query({ key: "fake-api-key" }) + .send({ idToken, linkProviderUserInfo: providerUserInfo }) + .then((res) => { + expectStatusCode(200, res); + const providers = res.body.providerUserInfo; + expect(providers).to.have.length(2); // Original email/password + linked provider + + const linkedProvider = providers.find((p: ProviderUserInfo) => p.providerId === providerId); + expect(linkedProvider).to.deep.equal(providerUserInfo); + }); + + const accountInfo = await getAccountInfoByIdToken(authApi(), idToken); + expect(accountInfo.providerUserInfo).to.have.length(2); + const linkedProviderInfo = accountInfo.providerUserInfo?.find( + (p: ProviderUserInfo) => p.providerId === providerId, + ); + expect(linkedProviderInfo).to.deep.equal(providerUserInfo); + }); + + it("should error if linkProviderUserInfo is missing required fields", async () => { + const { idToken } = await registerUser(authApi(), { + email: "test@example.com", + password: "password", + }); + + const incompleteProviderUserInfo1 = { + providerId: "google.com", + email: "linked@example.com", + }; + + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:update") + .query({ key: "fake-api-key" }) + .send({ idToken, linkProviderUserInfo: incompleteProviderUserInfo1 }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).to.contain("MISSING_RAW_ID"); + }); + + const incompleteProviderUserInfo2 = { + rawId: "google_user_id", + email: "linked@example.com", + }; + + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:update") + .query({ key: "fake-api-key" }) + .send({ idToken, linkProviderUserInfo: incompleteProviderUserInfo2 }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).to.contain("MISSING_PROVIDER_ID"); + }); + }); + + it("should error if user is disabled when linking a provider", async () => { + const { localId, idToken } = await registerUser(authApi(), { + email: "test@example.com", + password: "password", + }); + + await updateAccountByLocalId(authApi(), localId, { disableUser: true }); + + const providerUserInfo = { + providerId: "google.com", + rawId: "google_user_id", + email: "linked@example.com", + }; + + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:update") + .query({ key: "fake-api-key" }) + .send({ idToken, linkProviderUserInfo: providerUserInfo }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).to.equal("USER_DISABLED"); + }); + }); }); From 9a855a755e2801778054675552831b11da802ed0 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 4 Dec 2024 15:31:15 -0800 Subject: [PATCH 02/28] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c625820edf8..f1c150552bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,4 +8,4 @@ - Deprecated `emulators.apphosting.startCommandOverride`. Please use `emulators.apphosting.startCommand` instead. - Updated `superstatic` to `9.1.0` in package.json. - Updated the Firebase Data Connect local toolkit to v1.7.4, which includes a fix for an issue that caused duplicate installations of the Firebase JS SDK. (#8028) -- Add support for `linkProviderUserInfo` in the Firebase Emulator to allow linking providers to user accounts. (#4170) +- Add support for `linkProviderUserInfo` in the Auth emulator to allow linking providers to user accounts. (#4170) From bcb03155f5baa1fb0ee4f746ea931e2eff86d498 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 4 Dec 2024 23:39:01 +0000 Subject: [PATCH 03/28] 13.28.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 90d83e18542..77d3aa60b92 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "13.27.0", + "version": "13.28.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "13.27.0", + "version": "13.28.0", "license": "MIT", "dependencies": { "@electric-sql/pglite": "^0.2.0", diff --git a/package.json b/package.json index 23c0e7df5b6..f21c913cba3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "13.27.0", + "version": "13.28.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 99ff49e4c7bc1704ba62eb6185bcd11fa5fe056c Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 4 Dec 2024 23:39:14 +0000 Subject: [PATCH 04/28] [firebase-release] Removed change log and reset repo after 13.28.0 release --- CHANGELOG.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1c150552bf..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +0,0 @@ -- Enable apphosting:rollouts:create command. (#8031) -- Added default value for `emulators.dataconnect.dataDir` to `init dataconnect`. -- Fixed issue where expired auth tokens would not refresh when running on IDX. -- Fixed an issue where `firebase` would error out instead of displaying help text. -- Fixed an issue where `firebase init genkit` would error on Windows machines. -- Fixed an issue where emulator returned error when emulating alerts functions written in python (#8019) -- Better error message for emulator binary architecture incompatibility on MacOS (#7995). -- Deprecated `emulators.apphosting.startCommandOverride`. Please use `emulators.apphosting.startCommand` instead. -- Updated `superstatic` to `9.1.0` in package.json. -- Updated the Firebase Data Connect local toolkit to v1.7.4, which includes a fix for an issue that caused duplicate installations of the Firebase JS SDK. (#8028) -- Add support for `linkProviderUserInfo` in the Auth emulator to allow linking providers to user accounts. (#4170) From 7f060a76936b6902e4b9abd07d479476a6d7cd80 Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Thu, 5 Dec 2024 21:24:40 +0000 Subject: [PATCH 05/28] update vscode to 0.11.0 (#8034) * update to 0.11.0 * Update firebase-vscode/CHANGELOG.md --------- Co-authored-by: joehan --- firebase-vscode/CHANGELOG.md | 1 + firebase-vscode/package-lock.json | 4 ++-- firebase-vscode/package.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/firebase-vscode/CHANGELOG.md b/firebase-vscode/CHANGELOG.md index 8ac9660b24b..ca6fd829d00 100644 --- a/firebase-vscode/CHANGELOG.md +++ b/firebase-vscode/CHANGELOG.md @@ -1,5 +1,6 @@ ## NEXT +- Updated internal firebase-tools dependency to 13.28.0 - [Fixed] Fixed an issue where generating an ad-hoc file would break codelenses ## 0.10.8 diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index 5590a66ff5b..acc8c4ea917 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-dataconnect-vscode", - "version": "0.10.8", + "version": "0.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "firebase-dataconnect-vscode", - "version": "0.10.8", + "version": "0.11.0", "dependencies": { "@preact/signals-core": "^1.4.0", "@preact/signals-react": "1.3.6", diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index b9dd9d1a5d1..6845b6805be 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -4,7 +4,7 @@ "publisher": "GoogleCloudTools", "icon": "./resources/firebase_dataconnect_logo.png", "description": "Firebase Data Connect for VSCode", - "version": "0.10.8", + "version": "0.11.0", "engines": { "vscode": "^1.69.0" }, From a9e8e521147080cf5f9ea405d6fef3a7c6b4172e Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 6 Dec 2024 10:10:15 -0800 Subject: [PATCH 06/28] Support nodejs22 for CF3. --- src/deploy/functions/runtimes/supported/types.ts | 2 +- templates/init/functions/javascript/package.lint.json | 2 +- templates/init/functions/javascript/package.nolint.json | 2 +- templates/init/functions/typescript/package.lint.json | 2 +- templates/init/functions/typescript/package.nolint.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/deploy/functions/runtimes/supported/types.ts b/src/deploy/functions/runtimes/supported/types.ts index 36680c62209..bc741ec2455 100644 --- a/src/deploy/functions/runtimes/supported/types.ts +++ b/src/deploy/functions/runtimes/supported/types.ts @@ -85,7 +85,7 @@ export const RUNTIMES = runtimes({ }, nodejs22: { friendly: "Node.js 22", - status: "beta", + status: "GA", deprecationDate: "2027-04-30", decommissionDate: "2027-10-31", }, diff --git a/templates/init/functions/javascript/package.lint.json b/templates/init/functions/javascript/package.lint.json index 4ca87378275..b8816bdcd98 100644 --- a/templates/init/functions/javascript/package.lint.json +++ b/templates/init/functions/javascript/package.lint.json @@ -10,7 +10,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "18" + "node": "22" }, "main": "index.js", "dependencies": { diff --git a/templates/init/functions/javascript/package.nolint.json b/templates/init/functions/javascript/package.nolint.json index a2db8bcfc5f..0d14456d033 100644 --- a/templates/init/functions/javascript/package.nolint.json +++ b/templates/init/functions/javascript/package.nolint.json @@ -9,7 +9,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "18" + "node": "22" }, "main": "index.js", "dependencies": { diff --git a/templates/init/functions/typescript/package.lint.json b/templates/init/functions/typescript/package.lint.json index c72e8aac981..d7788081e69 100644 --- a/templates/init/functions/typescript/package.lint.json +++ b/templates/init/functions/typescript/package.lint.json @@ -11,7 +11,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "18" + "node": "22" }, "main": "lib/index.js", "dependencies": { diff --git a/templates/init/functions/typescript/package.nolint.json b/templates/init/functions/typescript/package.nolint.json index dd76062513e..d167a0ee473 100644 --- a/templates/init/functions/typescript/package.nolint.json +++ b/templates/init/functions/typescript/package.nolint.json @@ -10,7 +10,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "18" + "node": "22" }, "main": "lib/index.js", "dependencies": { From 9fa6cf8a157809c008bc5e1de9ed116922e93219 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 6 Dec 2024 10:13:51 -0800 Subject: [PATCH 07/28] Revert a9e8e521147080cf5f9ea405d6fef3a7c6b4172e --- src/deploy/functions/runtimes/supported/types.ts | 2 +- templates/init/functions/javascript/package.lint.json | 2 +- templates/init/functions/javascript/package.nolint.json | 2 +- templates/init/functions/typescript/package.lint.json | 2 +- templates/init/functions/typescript/package.nolint.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/deploy/functions/runtimes/supported/types.ts b/src/deploy/functions/runtimes/supported/types.ts index bc741ec2455..36680c62209 100644 --- a/src/deploy/functions/runtimes/supported/types.ts +++ b/src/deploy/functions/runtimes/supported/types.ts @@ -85,7 +85,7 @@ export const RUNTIMES = runtimes({ }, nodejs22: { friendly: "Node.js 22", - status: "GA", + status: "beta", deprecationDate: "2027-04-30", decommissionDate: "2027-10-31", }, diff --git a/templates/init/functions/javascript/package.lint.json b/templates/init/functions/javascript/package.lint.json index b8816bdcd98..4ca87378275 100644 --- a/templates/init/functions/javascript/package.lint.json +++ b/templates/init/functions/javascript/package.lint.json @@ -10,7 +10,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "22" + "node": "18" }, "main": "index.js", "dependencies": { diff --git a/templates/init/functions/javascript/package.nolint.json b/templates/init/functions/javascript/package.nolint.json index 0d14456d033..a2db8bcfc5f 100644 --- a/templates/init/functions/javascript/package.nolint.json +++ b/templates/init/functions/javascript/package.nolint.json @@ -9,7 +9,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "22" + "node": "18" }, "main": "index.js", "dependencies": { diff --git a/templates/init/functions/typescript/package.lint.json b/templates/init/functions/typescript/package.lint.json index d7788081e69..c72e8aac981 100644 --- a/templates/init/functions/typescript/package.lint.json +++ b/templates/init/functions/typescript/package.lint.json @@ -11,7 +11,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "22" + "node": "18" }, "main": "lib/index.js", "dependencies": { diff --git a/templates/init/functions/typescript/package.nolint.json b/templates/init/functions/typescript/package.nolint.json index d167a0ee473..dd76062513e 100644 --- a/templates/init/functions/typescript/package.nolint.json +++ b/templates/init/functions/typescript/package.nolint.json @@ -10,7 +10,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "22" + "node": "18" }, "main": "lib/index.js", "dependencies": { From 01d3413e44f2b9504a9dbdc489286f9b08ddbac7 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 6 Dec 2024 11:12:38 -0800 Subject: [PATCH 08/28] Support nodejs22 in CF3 (#8037) * Support nodejs22 in CF3 * Add changelog. --- CHANGELOG.md | 1 + src/deploy/functions/runtimes/supported/types.ts | 2 +- templates/init/functions/javascript/package.lint.json | 2 +- templates/init/functions/javascript/package.nolint.json | 2 +- templates/init/functions/typescript/package.lint.json | 2 +- templates/init/functions/typescript/package.nolint.json | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..0d51e30f741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Changes default CF3 runtime to nodejs22 (#8037) diff --git a/src/deploy/functions/runtimes/supported/types.ts b/src/deploy/functions/runtimes/supported/types.ts index 36680c62209..bc741ec2455 100644 --- a/src/deploy/functions/runtimes/supported/types.ts +++ b/src/deploy/functions/runtimes/supported/types.ts @@ -85,7 +85,7 @@ export const RUNTIMES = runtimes({ }, nodejs22: { friendly: "Node.js 22", - status: "beta", + status: "GA", deprecationDate: "2027-04-30", decommissionDate: "2027-10-31", }, diff --git a/templates/init/functions/javascript/package.lint.json b/templates/init/functions/javascript/package.lint.json index 4ca87378275..b8816bdcd98 100644 --- a/templates/init/functions/javascript/package.lint.json +++ b/templates/init/functions/javascript/package.lint.json @@ -10,7 +10,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "18" + "node": "22" }, "main": "index.js", "dependencies": { diff --git a/templates/init/functions/javascript/package.nolint.json b/templates/init/functions/javascript/package.nolint.json index a2db8bcfc5f..0d14456d033 100644 --- a/templates/init/functions/javascript/package.nolint.json +++ b/templates/init/functions/javascript/package.nolint.json @@ -9,7 +9,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "18" + "node": "22" }, "main": "index.js", "dependencies": { diff --git a/templates/init/functions/typescript/package.lint.json b/templates/init/functions/typescript/package.lint.json index c72e8aac981..d7788081e69 100644 --- a/templates/init/functions/typescript/package.lint.json +++ b/templates/init/functions/typescript/package.lint.json @@ -11,7 +11,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "18" + "node": "22" }, "main": "lib/index.js", "dependencies": { diff --git a/templates/init/functions/typescript/package.nolint.json b/templates/init/functions/typescript/package.nolint.json index dd76062513e..d167a0ee473 100644 --- a/templates/init/functions/typescript/package.nolint.json +++ b/templates/init/functions/typescript/package.nolint.json @@ -10,7 +10,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "18" + "node": "22" }, "main": "lib/index.js", "dependencies": { From b9117445120637b80776d4fea3a80d5b368cff0f Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 6 Dec 2024 16:32:55 -0800 Subject: [PATCH 09/28] Adding a smoke test for the data connect emulator (#8036) * Adding a smoke test for the data connect emulator * Add the new test directory * Removing and ignoring generated files * Also remove and ignore logs * Clean up debugging statements * EOF newlines * Add --debug to figure whats happening on windows test * more logs * better logging on windows * Format * Maybe this? * Maybe this? * Fix dir issue * giving up on windows for now --- .github/workflows/node-test.yml | 4 +++- .gitignore | 2 ++ package.json | 1 + scripts/clean-install.sh | 2 +- .../fdc-test/connector/connector.yaml | 2 ++ .../fdc-test/connector/queries.gql | 3 +++ .../fdc-test/dataconnect.yaml | 11 +++++++++++ .../fdc-test/schema/schema.gql | 3 +++ scripts/dataconnect-emulator-tests/firebase.json | 5 +++++ scripts/dataconnect-emulator-tests/run.sh | 14 ++++++++++++++ 10 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 scripts/dataconnect-emulator-tests/fdc-test/connector/connector.yaml create mode 100644 scripts/dataconnect-emulator-tests/fdc-test/connector/queries.gql create mode 100644 scripts/dataconnect-emulator-tests/fdc-test/dataconnect.yaml create mode 100644 scripts/dataconnect-emulator-tests/fdc-test/schema/schema.gql create mode 100644 scripts/dataconnect-emulator-tests/firebase.json create mode 100644 scripts/dataconnect-emulator-tests/run.sh diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 6a629ea694c..9ecf3d737a8 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -176,6 +176,7 @@ jobs: - npm run test:triggers-end-to-end - npm run test:triggers-end-to-end:inspect - npm run test:dataconnect-deploy + - npm run test:dataconnect-emulator steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 @@ -231,6 +232,7 @@ jobs: - npm run test:storage-deploy # - npm run test:storage-emulator-integration # - npm run test:dataconnect-deploy # TODO (joehanley): Reenable this - it should be safe to run in parallel + # - npm run test:dataconnect-emulator # TODO (joehanley): Figure out why this is failing - npm run test:frameworks steps: - name: Setup Java JDK @@ -260,7 +262,7 @@ jobs: - run: ${{ matrix.script }} - name: Print debug logs if: failure() - run: type *debug.log + run: dir "*.log" /s/b | type check-package-lock: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index d805d2b5509..695bbf3c996 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/pa src/test/emulators/extensions/firebase/storage-resize-images@0.1.18/functions/package-lock.json scripts/functions-deploy-tests/**/package-lock.json scripts/functions-discover-tests/**/**/package-lock.json +.dataconnect +*-debug.log /.vscode node_modules diff --git a/package.json b/package.json index f21c913cba3..e87e0efa6f5 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "test:client-integration": "bash ./scripts/client-integration-tests/run.sh", "test:compile": "tsc --project tsconfig.compile.json", "test:dataconnect-deploy": "bash ./scripts/dataconnect-test/run.sh", + "test:dataconnect-emulator": "bash ./scripts/dataconnect-emulator-tests/run.sh", "test:all-emulators": "npm run test:emulator && npm run test:extensions-emulator && npm run test:import-export && npm run test:storage-emulator-integration", "test:emulator": "bash ./scripts/emulator-tests/run.sh", "test:extensions-deploy": "bash ./scripts/extensions-deploy-tests/run.sh", diff --git a/scripts/clean-install.sh b/scripts/clean-install.sh index ce66373508f..dcc9f2a5fa3 100755 --- a/scripts/clean-install.sh +++ b/scripts/clean-install.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -e +set -ex function cleanup() { echo "Cleaning up artifacts..." diff --git a/scripts/dataconnect-emulator-tests/fdc-test/connector/connector.yaml b/scripts/dataconnect-emulator-tests/fdc-test/connector/connector.yaml new file mode 100644 index 00000000000..68215053ca4 --- /dev/null +++ b/scripts/dataconnect-emulator-tests/fdc-test/connector/connector.yaml @@ -0,0 +1,2 @@ +connectorId: "connectorId" +authMode: "PUBLIC" diff --git a/scripts/dataconnect-emulator-tests/fdc-test/connector/queries.gql b/scripts/dataconnect-emulator-tests/fdc-test/connector/queries.gql new file mode 100644 index 00000000000..51e05570ca5 --- /dev/null +++ b/scripts/dataconnect-emulator-tests/fdc-test/connector/queries.gql @@ -0,0 +1,3 @@ +mutation createOrder($name: String!) { + order_insert(data : {name: $name}) +} diff --git a/scripts/dataconnect-emulator-tests/fdc-test/dataconnect.yaml b/scripts/dataconnect-emulator-tests/fdc-test/dataconnect.yaml new file mode 100644 index 00000000000..6a2d2fe1c2a --- /dev/null +++ b/scripts/dataconnect-emulator-tests/fdc-test/dataconnect.yaml @@ -0,0 +1,11 @@ +specVersion: "v1beta" +serviceId: "fake-service" +location: "us-central1" +schema: + source: "./schema" + datasource: + postgresql: + database: "postgres" + cloudSql: + instanceId: "dataconnect-test" +connectorDirs: ["./connector"] diff --git a/scripts/dataconnect-emulator-tests/fdc-test/schema/schema.gql b/scripts/dataconnect-emulator-tests/fdc-test/schema/schema.gql new file mode 100644 index 00000000000..b6ea799498c --- /dev/null +++ b/scripts/dataconnect-emulator-tests/fdc-test/schema/schema.gql @@ -0,0 +1,3 @@ +type Order @table { + name: String! +} diff --git a/scripts/dataconnect-emulator-tests/firebase.json b/scripts/dataconnect-emulator-tests/firebase.json new file mode 100644 index 00000000000..bb70b69cbe2 --- /dev/null +++ b/scripts/dataconnect-emulator-tests/firebase.json @@ -0,0 +1,5 @@ +{ + "dataconnect": { + "source": "fdc-test" + } +} diff --git a/scripts/dataconnect-emulator-tests/run.sh b/scripts/dataconnect-emulator-tests/run.sh new file mode 100644 index 00000000000..fcb35afa786 --- /dev/null +++ b/scripts/dataconnect-emulator-tests/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -ex # Immediately exit on failure +# Globally link the CLI for the testing framework +./scripts/clean-install.sh +source scripts/set-default-credentials.sh + +echo "Running in ${CWD}" +echo "Running with node: $(which node)" +echo "Running with npm: $(which npm)" +echo "Running with Application Creds: ${GOOGLE_APPLICATION_CREDENTIALS}" + +cd scripts/dataconnect-emulator-tests +firebase emulators:exec "cd ." --only dataconnect -P demo-test +# rm -rf ../../clean From 1b421a7917fa4a0f3c3348af550fa12a63f2a857 Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Mon, 9 Dec 2024 07:40:07 +0000 Subject: [PATCH 10/28] Setup analytics system for IDX (#7999) * Setup analytics system for IDX * remove console log --- firebase-vscode/src/analytics.ts | 30 +++++++++++-------- firebase-vscode/src/core/index.ts | 11 +++---- firebase-vscode/src/core/project.ts | 12 ++++---- firebase-vscode/src/core/user.ts | 8 ++--- .../src/data-connect/ad-hoc-mutations.ts | 9 +++--- .../src/data-connect/connectors.ts | 9 +++--- firebase-vscode/src/data-connect/deploy.ts | 8 ++--- firebase-vscode/src/data-connect/execution.ts | 7 ++--- firebase-vscode/src/data-connect/index.ts | 16 +++++----- .../src/data-connect/sdk-generation.ts | 11 ++++--- firebase-vscode/src/data-connect/terminal.ts | 17 ++++++----- firebase-vscode/src/extension.ts | 9 +++--- firebase-vscode/src/utils/settings.ts | 9 +++--- 13 files changed, 85 insertions(+), 71 deletions(-) diff --git a/firebase-vscode/src/analytics.ts b/firebase-vscode/src/analytics.ts index 6ae5376a3cb..a92f4213f17 100644 --- a/firebase-vscode/src/analytics.ts +++ b/firebase-vscode/src/analytics.ts @@ -35,12 +35,12 @@ export enum DATA_CONNECT_EVENT_NAME { } export class AnalyticsLogger { - readonly logger: TelemetryLogger; + readonly logger: TelemetryLogger | IDXLogger; private disposable: vscode.Disposable; private sessionCharCount = 0; // Track total chars for the session - constructor() { - this.logger = env.createTelemetryLogger( + constructor(context: vscode.ExtensionContext) { + this.logger = monospaceEnv.value.isMonospace ? new IDXLogger(new GA4TelemetrySender(pluginLogger), context) : env.createTelemetryLogger( new GA4TelemetrySender(pluginLogger), ); @@ -146,6 +146,19 @@ export class AnalyticsLogger { } } +export class IDXLogger { + constructor(private sender: GA4TelemetrySender, private context: vscode.ExtensionContext) {} + public logUsage(eventName: string, data?: any) { + const packageJson = this.context.extension.packageJSON; + data = { ...data, extversion: packageJson.version, extname: this.context.extension.id, isidx: true }; + this.sender.sendEventData(eventName, data); + } + + public logError() { + // TODO + } +} + class GA4TelemetrySender implements TelemetrySender { private hasSentData = false; constructor(readonly pluginLogger: { warn: (s: string) => void }) {} @@ -154,12 +167,6 @@ class GA4TelemetrySender implements TelemetrySender { eventName: string, data?: Record | undefined, ): void { - // telemtry flag does not exist in monospace - if (!env.isTelemetryEnabled && !monospaceEnv.value.isMonospace) { - this.pluginLogger.warn("Telemetry is not enabled."); - return; - } - // telemetry logger adds prefixes to eventName and params that are disallowed in GA4 eventName = eventName.replace( "GoogleCloudTools.firebase-dataconnect-vscode/", @@ -176,15 +183,14 @@ class GA4TelemetrySender implements TelemetrySender { } } data = { ...data }; - const idxPrepend = monospaceEnv.value.isMonospace ? "idx_" : ""; if (!this.hasSentData) { trackVSCode( - `${idxPrepend}${DATA_CONNECT_EVENT_NAME.EXTENSION_USED}`, + DATA_CONNECT_EVENT_NAME.EXTENSION_USED, data as AnalyticsParams, ); this.hasSentData = true; } - trackVSCode(`${idxPrepend}${eventName}`, data as AnalyticsParams); + trackVSCode(eventName, data as AnalyticsParams); } sendErrorData(error: Error, data?: Record | undefined): void { diff --git a/firebase-vscode/src/core/index.ts b/firebase-vscode/src/core/index.ts index 9c20fb6567d..1b70508edd1 100644 --- a/firebase-vscode/src/core/index.ts +++ b/firebase-vscode/src/core/index.ts @@ -1,4 +1,4 @@ -import vscode, { Disposable, ExtensionContext, TelemetryLogger } from "vscode"; +import vscode, { Disposable, ExtensionContext } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { getRootFolders, registerConfig } from "./config"; import { EmulatorsController } from "./emulators"; @@ -14,11 +14,12 @@ import { upsertFile } from "../data-connect/file-utils"; import { registerWebhooks } from "./webhook"; import { createE2eMockable } from "../utils/test_hooks"; import { runTerminalTask } from "../data-connect/terminal"; +import { AnalyticsLogger } from "../analytics"; export async function registerCore( broker: ExtensionBrokerImpl, context: ExtensionContext, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Promise<[EmulatorsController, vscode.Disposable]> { const settings = getSettings(); @@ -69,7 +70,7 @@ export async function registerCore( ? `${settings.firebasePath} init dataconnect --project ${currentProjectId.value}` : `${settings.firebasePath} init dataconnect`; - initSpy.call("firebase init", initCommand, {focus: true}); + initSpy.call("firebase init", initCommand, { focus: true }); }); const emulatorsController = new EmulatorsController(broker); @@ -103,8 +104,8 @@ export async function registerCore( initSpy, registerOptions(context), registerEnv(broker), - registerUser(broker, telemetryLogger), - registerProject(broker, telemetryLogger), + registerUser(broker, analyticsLogger), + registerProject(broker, analyticsLogger), registerQuickstart(broker), await registerWebhooks(), { dispose: sub1 }, diff --git a/firebase-vscode/src/core/project.ts b/firebase-vscode/src/core/project.ts index d55cc5cd240..7bca63c8b63 100644 --- a/firebase-vscode/src/core/project.ts +++ b/firebase-vscode/src/core/project.ts @@ -1,4 +1,4 @@ -import vscode, { Disposable, TelemetryLogger } from "vscode"; +import vscode, { Disposable } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { computed, effect, Signal } from "@preact/signals-react"; import { firebaseRC, updateFirebaseRCProject } from "./config"; @@ -9,7 +9,7 @@ import { pluginLogger } from "../logger-wrapper"; import { globalSignal } from "../utils/globals"; import { firstWhereDefined } from "../utils/signal"; import { User } from "../types/auth"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; /** Available projects */ export const projects = globalSignal>( {}, @@ -30,7 +30,7 @@ const userScopedProjects = computed( export function registerProject( broker: ExtensionBrokerImpl, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { // For testing purposes. const demoProjectCommand = vscode.commands.registerCommand( @@ -103,7 +103,7 @@ export function registerProject( return; } else { try { - telemetryLogger.logUsage( + analyticsLogger.logger.logUsage( DATA_CONNECT_EVENT_NAME.PROJECT_SELECT_CLICKED, ); @@ -120,7 +120,9 @@ export function registerProject( } : undefined, }); - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.PROJECT_SELECTED); + analyticsLogger.logger.logUsage( + DATA_CONNECT_EVENT_NAME.PROJECT_SELECTED, + ); } catch (e: any) { vscode.window.showErrorMessage(e.message); } diff --git a/firebase-vscode/src/core/user.ts b/firebase-vscode/src/core/user.ts index 139bdbc9329..8485cead304 100644 --- a/firebase-vscode/src/core/user.ts +++ b/firebase-vscode/src/core/user.ts @@ -1,11 +1,11 @@ import { Signal, computed, effect } from "@preact/signals-react"; -import { Disposable, TelemetryLogger } from "vscode"; +import { Disposable } from "vscode"; import { ServiceAccountUser } from "../types"; import { User as AuthUser } from "../../../src/types/auth"; import { ExtensionBrokerImpl } from "../extension-broker"; import { login, logoutUser, requireAuthWrapper } from "../cli"; import { globalSignal } from "../utils/globals"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; import * as vscode from "vscode"; type User = ServiceAccountUser | AuthUser; @@ -24,7 +24,7 @@ export async function checkLogin() { export function registerUser( broker: ExtensionBrokerImpl, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { // For testing purposes. const userMockCommand = vscode.commands.registerCommand( @@ -58,7 +58,7 @@ export function registerUser( }); const addUserSub = broker.on("addUser", async () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.LOGIN); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.LOGIN); const { user } = await login(); currentUser.value = user; }); diff --git a/firebase-vscode/src/data-connect/ad-hoc-mutations.ts b/firebase-vscode/src/data-connect/ad-hoc-mutations.ts index a5de72d1e69..eaa3f3157b0 100644 --- a/firebase-vscode/src/data-connect/ad-hoc-mutations.ts +++ b/firebase-vscode/src/data-connect/ad-hoc-mutations.ts @@ -1,4 +1,4 @@ -import vscode, { Disposable, TelemetryLogger } from "vscode"; +import vscode, { Disposable } from "vscode"; import { DocumentNode, GraphQLInputField, @@ -18,10 +18,11 @@ import { DataConnectService } from "./service"; import { DATA_CONNECT_EVENT_NAME } from "../analytics"; import { dataConnectConfigs } from "./config"; import { firstWhereDefined } from "../utils/signal"; +import {AnalyticsLogger} from "../analytics"; export function registerAdHoc( dataConnectService: DataConnectService, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { const defaultScalarValues = { Any: "{}", @@ -262,14 +263,14 @@ query { vscode.commands.registerCommand( "firebase.dataConnect.schemaAddData", (ast, uri) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.ADD_DATA); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.ADD_DATA); schemaAddData(ast, uri); }, ), vscode.commands.registerCommand( "firebase.dataConnect.schemaReadData", (document, ast, uri) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.READ_DATA); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.READ_DATA); schemaReadData(document, ast, uri); }, ), diff --git a/firebase-vscode/src/data-connect/connectors.ts b/firebase-vscode/src/data-connect/connectors.ts index 7de76c517b6..be4d3f5b816 100644 --- a/firebase-vscode/src/data-connect/connectors.ts +++ b/firebase-vscode/src/data-connect/connectors.ts @@ -3,7 +3,6 @@ import vscode, { ExtensionContext, InputBoxValidationMessage, InputBoxValidationSeverity, - TelemetryLogger, } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { @@ -39,13 +38,13 @@ import { DataConnectService } from "./service"; import { OperationLocation } from "./types"; import { checkIfFileExists } from "./file-utils"; import * as path from "path"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; export function registerConnectors( context: ExtensionContext, broker: ExtensionBrokerImpl, dataConnectService: DataConnectService, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { async function moveOperationToConnector( defIndex: number, // The index of the definition to move. @@ -477,7 +476,9 @@ export function registerConnectors( vscode.commands.registerCommand( "firebase.dataConnect.moveOperationToConnector", (number, location, connectorPath) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.MOVE_TO_CONNECTOR); + analyticsLogger.logger.logUsage( + DATA_CONNECT_EVENT_NAME.MOVE_TO_CONNECTOR, + ); moveOperationToConnector(number, location, connectorPath); }, ), diff --git a/firebase-vscode/src/data-connect/deploy.ts b/firebase-vscode/src/data-connect/deploy.ts index a199b723ffd..ba43d285a9d 100644 --- a/firebase-vscode/src/data-connect/deploy.ts +++ b/firebase-vscode/src/data-connect/deploy.ts @@ -5,7 +5,7 @@ import { dataConnectConfigs } from "./config"; import { createE2eMockable } from "../utils/test_hooks"; import { runCommand } from "./terminal"; import { ExtensionBrokerImpl } from "../extension-broker"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; import { getSettings } from "../utils/settings"; function createDeployOnlyCommand(serviceConnectorMap: { @@ -28,7 +28,7 @@ function createDeployOnlyCommand(serviceConnectorMap: { export function registerFdcDeploy( broker: ExtensionBrokerImpl, - telemetryLogger: vscode.TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): vscode.Disposable { const settings = getSettings(); @@ -42,14 +42,14 @@ export function registerFdcDeploy( ); const deployAllCmd = vscode.commands.registerCommand("fdc.deploy-all", () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_ALL, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_ALL, { firebase_binary_kind: settings.firebaseBinaryKind, }); deploySpy.call(`${settings.firebasePath} deploy --only dataconnect`); }); const deployCmd = vscode.commands.registerCommand("fdc.deploy", async () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_INDIVIDUAL, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_INDIVIDUAL, { firebase_binary_kind: settings.firebaseBinaryKind, }); const configs = await firstWhereDefined(dataConnectConfigs).then( diff --git a/firebase-vscode/src/data-connect/execution.ts b/firebase-vscode/src/data-connect/execution.ts index 1a2da686552..25d2282d1b2 100644 --- a/firebase-vscode/src/data-connect/execution.ts +++ b/firebase-vscode/src/data-connect/execution.ts @@ -2,7 +2,6 @@ import vscode, { ConfigurationTarget, Disposable, ExtensionContext, - TelemetryLogger, } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import { registerWebview } from "../webview"; @@ -23,13 +22,13 @@ import { DataConnectService } from "./service"; import { DataConnectError, toSerializedError } from "../../common/error"; import { OperationLocation } from "./types"; import { InstanceType } from "./code-lens-provider"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; export function registerExecution( context: ExtensionContext, broker: ExtensionBrokerImpl, dataConnectService: DataConnectService, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { const treeDataProvider = new ExecutionHistoryTreeDataProvider(); const executionHistoryTreeView = vscode.window.createTreeView( @@ -190,7 +189,7 @@ export function registerExecution( vscode.commands.registerCommand( "firebase.dataConnect.executeOperation", (ast, location, instanceType: InstanceType) => { - telemetryLogger.logUsage( + analyticsLogger.logger.logUsage( instanceType === InstanceType.LOCAL ? DATA_CONNECT_EVENT_NAME.RUN_LOCAL : DATA_CONNECT_EVENT_NAME.RUN_PROD, diff --git a/firebase-vscode/src/data-connect/index.ts b/firebase-vscode/src/data-connect/index.ts index d7a155d5204..ba2f75c227c 100644 --- a/firebase-vscode/src/data-connect/index.ts +++ b/firebase-vscode/src/data-connect/index.ts @@ -28,10 +28,10 @@ import { Result } from "../result"; import { LanguageClient } from "vscode-languageclient/node"; import { registerTerminalTasks } from "./terminal"; import { registerWebview } from "../webview"; - import { DataConnectToolkit } from "./toolkit"; import { registerFdcSdkGeneration } from "./sdk-generation"; import { registerDiagnostics } from "./diagnostics"; +import { AnalyticsLogger } from "../analytics"; class CodeActionsProvider implements vscode.CodeActionProvider { constructor( @@ -133,7 +133,7 @@ export function registerFdc( broker: ExtensionBrokerImpl, authService: AuthService, emulatorController: EmulatorsController, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { registerDiagnostics(context, dataConnectConfigs); const dataConnectToolkit = new DataConnectToolkit(broker); @@ -222,14 +222,14 @@ export function registerFdc( selectedProjectStatus.show(); }), }, - registerExecution(context, broker, fdcService, telemetryLogger), + registerExecution(context, broker, fdcService, analyticsLogger), registerExplorer(context, broker, fdcService), registerWebview({ name: "data-connect", context, broker }), - registerAdHoc(fdcService, telemetryLogger), - registerConnectors(context, broker, fdcService, telemetryLogger), - registerFdcDeploy(broker, telemetryLogger), - registerFdcSdkGeneration(broker, telemetryLogger), - registerTerminalTasks(broker, telemetryLogger), + registerAdHoc(fdcService, analyticsLogger), + registerConnectors(context, broker, fdcService, analyticsLogger), + registerFdcDeploy(broker, analyticsLogger), + registerFdcSdkGeneration(broker, analyticsLogger), + registerTerminalTasks(broker, analyticsLogger), operationCodeLensProvider, vscode.languages.registerCodeLensProvider( // **Hack**: For testing purposes, enable code lenses on all graphql files diff --git a/firebase-vscode/src/data-connect/sdk-generation.ts b/firebase-vscode/src/data-connect/sdk-generation.ts index 4c1ec5ba69c..137671473d6 100644 --- a/firebase-vscode/src/data-connect/sdk-generation.ts +++ b/firebase-vscode/src/data-connect/sdk-generation.ts @@ -16,10 +16,11 @@ import { generateSdkYaml, } from "../../../src/init/features/dataconnect/sdk"; import { createE2eMockable } from "../utils/test_hooks"; +import { AnalyticsLogger} from "../analytics"; export function registerFdcSdkGeneration( broker: ExtensionBrokerImpl, - telemetryLogger: vscode.TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): vscode.Disposable { const settings = getSettings(); @@ -37,7 +38,7 @@ export function registerFdcSdkGeneration( const initSdkCmd = vscode.commands.registerCommand( "fdc.init-sdk", (args: { appFolder: string }) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK_CLI, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK_CLI, { firebase_binary_kind: settings.firebaseBinaryKind, }); // Lets do it from the right directory @@ -50,7 +51,9 @@ export function registerFdcSdkGeneration( const configureSDKCodelense = vscode.commands.registerCommand( "fdc.connector.configure-sdk", async (connectorConfig) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK_CODELENSE); + analyticsLogger.logger.logUsage( + DATA_CONNECT_EVENT_NAME.INIT_SDK_CODELENSE, + ); const configs = await firstWhereDefined(dataConnectConfigs).then( (c) => c.requireValue, ); @@ -62,7 +65,7 @@ export function registerFdcSdkGeneration( const configureSDK = vscode.commands.registerCommand( "fdc.configure-sdk", async () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK); const configs = await firstWhereDefined(dataConnectConfigs).then( (c) => c.requireValue, ); diff --git a/firebase-vscode/src/data-connect/terminal.ts b/firebase-vscode/src/data-connect/terminal.ts index 0de7382da8a..9c836760cc0 100644 --- a/firebase-vscode/src/data-connect/terminal.ts +++ b/firebase-vscode/src/data-connect/terminal.ts @@ -2,7 +2,7 @@ import { TelemetryLogger, TerminalOptions } from "vscode"; import { ExtensionBrokerImpl } from "../extension-broker"; import vscode, { Disposable } from "vscode"; import { checkLogin } from "../core/user"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; import { getSettings } from "../utils/settings"; import { currentProjectId } from "../core/project"; @@ -69,12 +69,12 @@ export function runTerminalTask( export function registerTerminalTasks( broker: ExtensionBrokerImpl, - telemetryLogger: TelemetryLogger, + analyticsLogger: AnalyticsLogger, ): Disposable { const settings = getSettings(); const loginTaskBroker = broker.on("executeLogin", () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.IDX_LOGIN, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.IDX_LOGIN, { firebase_binary_kind: settings.firebaseBinaryKind, }); runTerminalTask( @@ -86,7 +86,7 @@ export function registerTerminalTasks( }); const startEmulatorsTaskBroker = broker.on("runStartEmulators", () => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.START_EMULATORS, { + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.START_EMULATORS, { firebase_binary_kind: settings.firebaseBinaryKind, }); // TODO: optional debug mode @@ -104,9 +104,12 @@ export function registerTerminalTasks( vscode.commands.registerCommand( "firebase.dataConnect.runTerminalTask", (taskName, command) => { - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.COMMAND_EXECUTION, { - commandName: command, - }); + analyticsLogger.logger.logUsage( + DATA_CONNECT_EVENT_NAME.COMMAND_EXECUTION, + { + commandName: command, + }, + ); runTerminalTask(taskName, command); }, ), diff --git a/firebase-vscode/src/extension.ts b/firebase-vscode/src/extension.ts index 8644eddbac0..d74ef6f6831 100644 --- a/firebase-vscode/src/extension.ts +++ b/firebase-vscode/src/extension.ts @@ -20,7 +20,6 @@ import { registerFdc } from "./data-connect"; import { AuthService } from "./auth/service"; import { AnalyticsLogger, - DATA_CONNECT_EVENT_NAME, IDX_METRIC_NOTICE, } from "./analytics"; import { env } from "./core/env"; @@ -29,12 +28,12 @@ import { suggestGraphqlSyntaxExtension } from "./data-connect/graphql-syntax-hig // This method is called when your extension is activated export async function activate(context: vscode.ExtensionContext) { - const analyticsLogger = new AnalyticsLogger(); + const analyticsLogger = new AnalyticsLogger(context); // Suggest installing the GraphQL syntax highlighter extension await suggestGraphqlSyntaxExtension(); - await setupFirebasePath(analyticsLogger.logger); + await setupFirebasePath(analyticsLogger); const settings = getSettings(); logSetup(); pluginLogger.debug("Activating Firebase extension."); @@ -60,7 +59,7 @@ export async function activate(context: vscode.ExtensionContext) { const [emulatorsController, coreDisposable] = await registerCore( broker, context, - analyticsLogger.logger, + analyticsLogger, ); context.subscriptions.push( @@ -78,7 +77,7 @@ export async function activate(context: vscode.ExtensionContext) { broker, authService, emulatorsController, - analyticsLogger.logger, + analyticsLogger, ), ); } diff --git a/firebase-vscode/src/utils/settings.ts b/firebase-vscode/src/utils/settings.ts index 3f9d3ac1b44..a1ba94a1568 100644 --- a/firebase-vscode/src/utils/settings.ts +++ b/firebase-vscode/src/utils/settings.ts @@ -1,6 +1,5 @@ -import * as vscode from "vscode"; -import { ConfigurationTarget, window, workspace } from "vscode"; -import { DATA_CONNECT_EVENT_NAME } from "../analytics"; +import { ConfigurationTarget, workspace } from "vscode"; +import { DATA_CONNECT_EVENT_NAME, AnalyticsLogger } from "../analytics"; export interface Settings { readonly firebasePath: string; @@ -47,7 +46,7 @@ export function updateIdxSetting(shouldShow: boolean) { } // Persist env var as path setting when path setting doesn't exist -export function setupFirebasePath(telemetryLogger: vscode.TelemetryLogger) { +export function setupFirebasePath(analyticsLogger: AnalyticsLogger) { const config = workspace.getConfiguration("firebase"); if (process.env.FIREBASE_BINARY && !config.get("firebasePath")) { config.update( @@ -56,5 +55,5 @@ export function setupFirebasePath(telemetryLogger: vscode.TelemetryLogger) { ConfigurationTarget.Global, ); } - telemetryLogger.logUsage(DATA_CONNECT_EVENT_NAME.SETUP_FIREBASE_BINARY); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.SETUP_FIREBASE_BINARY); } From efd31c4358a579dde479bcf2baa314c0ee1c50ad Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Mon, 9 Dec 2024 16:19:49 +0000 Subject: [PATCH 11/28] update to 0.11.1 (#8043) --- firebase-vscode/CHANGELOG.md | 6 ++++++ firebase-vscode/package-lock.json | 4 ++-- firebase-vscode/package.json | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/firebase-vscode/CHANGELOG.md b/firebase-vscode/CHANGELOG.md index ca6fd829d00..ed107c1d6e6 100644 --- a/firebase-vscode/CHANGELOG.md +++ b/firebase-vscode/CHANGELOG.md @@ -1,5 +1,11 @@ ## NEXT +## 0.11.1 + +- [Fixed] Fixed IDX analytics issue + +## 0.11.0 + - Updated internal firebase-tools dependency to 13.28.0 - [Fixed] Fixed an issue where generating an ad-hoc file would break codelenses diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index acc8c4ea917..a12947a3a2b 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-dataconnect-vscode", - "version": "0.11.0", + "version": "0.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "firebase-dataconnect-vscode", - "version": "0.11.0", + "version": "0.11.1", "dependencies": { "@preact/signals-core": "^1.4.0", "@preact/signals-react": "1.3.6", diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index 6845b6805be..db57bfc9f27 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -4,7 +4,7 @@ "publisher": "GoogleCloudTools", "icon": "./resources/firebase_dataconnect_logo.png", "description": "Firebase Data Connect for VSCode", - "version": "0.11.0", + "version": "0.11.1", "engines": { "vscode": "^1.69.0" }, From 89f1c8a86c223947564dd85b6ea4d67c71d9ac15 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 9 Dec 2024 11:38:10 -0800 Subject: [PATCH 12/28] Fixing an issue with --import and dataDir at the same time (#8048) --- CHANGELOG.md | 1 + src/emulator/controller.ts | 22 +++++++++++++++++++++- src/emulator/dataconnect/pgliteServer.ts | 18 +++++++++++++++++- src/emulator/downloadableEmulators.ts | 3 +-- src/error.ts | 5 +++++ 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d51e30f741..b91ad91680e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Changes default CF3 runtime to nodejs22 (#8037) +- Fixed an issue where `--import` would error for the Data Connect emulator if `dataDir` was also set. diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index 5668381c7b8..a6d574a754f 100755 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -28,7 +28,7 @@ import { LoggingEmulator } from "./loggingEmulator"; import * as dbRulesConfig from "../database/rulesConfig"; import { EmulatorLogger, Verbosity } from "./emulatorLogger"; import { EmulatorHubClient } from "./hubClient"; -import { confirm } from "../prompt"; +import { confirm, promptOnce } from "../prompt"; import { FLAG_EXPORT_ON_EXIT_NAME, JAVA_DEPRECATION_WARNING, @@ -893,6 +893,26 @@ export async function startAll( importDirAbsPath, exportMetadata.dataconnect.path, ); + const dataDirectory = options.config.get("emulators.dataconnect.dataDir"); + if (exportMetadataFilePath && dataDirectory) { + EmulatorLogger.forEmulator(Emulators.DATACONNECT).logLabeled( + "WARN", + "dataconnect", + "'firebase.json#emulators.dataconnect.dataDir' is set and `--import` flag was passed. " + + "This will overwrite any data saved from previous runs.", + ); + if ( + !options.nonInteractive && + !(await promptOnce({ + type: "confirm", + message: `Do you wish to continue and overwrite data in ${dataDirectory}?`, + default: false, + })) + ) { + await cleanShutdown(); + return { deprecationNotices: [] }; + } + } EmulatorLogger.forEmulator(Emulators.DATACONNECT).logLabeled( "BULLET", diff --git a/src/emulator/dataconnect/pgliteServer.ts b/src/emulator/dataconnect/pgliteServer.ts index 3550ff859ad..1aee1c2272c 100644 --- a/src/emulator/dataconnect/pgliteServer.ts +++ b/src/emulator/dataconnect/pgliteServer.ts @@ -17,6 +17,7 @@ import { } from "./pg-gateway/index"; import { fromNodeSocket } from "./pg-gateway/platforms/node"; import { logger } from "../../logger"; +import { hasMessage } from "../../error"; export const TRUNCATE_TABLES_SQL = ` DO $do$ BEGIN @@ -98,7 +99,7 @@ export class PostgresServer { const file = new File([rf], this.importPath); pgliteArgs.loadDataDir = file; } - this.db = await PGlite.create(pgliteArgs); + this.db = await this.forceCreateDB(pgliteArgs); await this.db.waitReady; } return this.db; @@ -116,6 +117,21 @@ export class PostgresServer { fs.writeFileSync(exportPath, new Uint8Array(arrayBuff)); } + async forceCreateDB(pgliteArgs: PGliteOptions): Promise { + try { + const db = await PGlite.create(pgliteArgs); + return db; + } catch (err: unknown) { + if (pgliteArgs.dataDir && hasMessage(err) && /Database already exists/.test(err.message)) { + // Clear out the current pglite data + fs.rmSync(pgliteArgs.dataDir, { force: true, recursive: true }); + const db = await PGlite.create(pgliteArgs); + return db; + } + throw err; + } + } + constructor(database: string, username: string, dataDirectory?: string, importPath?: string) { this.username = username; this.database = database; diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 8df3b721bfd..51431926b66 100755 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -9,7 +9,7 @@ import { } from "./types"; import { Constants } from "./constants"; -import { FirebaseError } from "../error"; +import { FirebaseError, hasMessage } from "../error"; import * as childProcess from "child_process"; import * as utils from "../utils"; import { EmulatorLogger } from "./emulatorLogger"; @@ -655,7 +655,6 @@ export async function start( } export function isIncomaptibleArchError(err: unknown): boolean { - const hasMessage = (e: any): e is { message: string } => !!e?.message; return ( hasMessage(err) && /Unknown system error/.test(err.message ?? "") && diff --git a/src/error.ts b/src/error.ts index 93a5ac62f03..e85ddeaef71 100644 --- a/src/error.ts +++ b/src/error.ts @@ -121,3 +121,8 @@ export function isBillingError(e: { ); }); } + +/** + * Checks whether an unknown object (such as an error) has a message field + */ +export const hasMessage = (e: any): e is { message: string } => !!e?.message; From bc1a390a032aae94a4b95d53039ccba71f3757d5 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 11 Dec 2024 12:20:32 -0800 Subject: [PATCH 13/28] Fixing init dataconnect on some empty schema cases (#8051) * Fixing init dataconnect on some empty schema cases * Ensure schema.gql, not just dir, and add test * Update src/init/features/dataconnect/index.ts Co-authored-by: Maneesh Tewani --------- Co-authored-by: Maneesh Tewani --- CHANGELOG.md | 1 + src/dataconnect/fileUtils.ts | 3 +++ src/init/features/dataconnect/index.spec.ts | 27 +++++++++++++++++++++ src/init/features/dataconnect/index.ts | 5 ++++ 4 files changed, 36 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b91ad91680e..2f98ea1c0f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Changes default CF3 runtime to nodejs22 (#8037) - Fixed an issue where `--import` would error for the Data Connect emulator if `dataDir` was also set. +- Fixed an issue where `firebase init dataconnect` errored when importing a schema with no GQL files. diff --git a/src/dataconnect/fileUtils.ts b/src/dataconnect/fileUtils.ts index 32265246e1a..ce854c15ca2 100644 --- a/src/dataconnect/fileUtils.ts +++ b/src/dataconnect/fileUtils.ts @@ -58,6 +58,9 @@ function validateConnectorYaml(unvalidated: any): ConnectorYaml { } export async function readGQLFiles(sourceDir: string): Promise { + if (!fs.existsSync(sourceDir)) { + return []; + } const files = await fs.readdir(sourceDir); // TODO: Handle files in subdirectories such as `foo/a.gql` and `bar/baz/b.gql`. return files diff --git a/src/init/features/dataconnect/index.spec.ts b/src/init/features/dataconnect/index.spec.ts index 7f792e4172c..c221868fdc7 100644 --- a/src/init/features/dataconnect/index.spec.ts +++ b/src/init/features/dataconnect/index.spec.ts @@ -1,5 +1,6 @@ import * as sinon from "sinon"; import { expect } from "chai"; +import * as fs from "fs-extra"; import * as init from "./index"; import { Config } from "../../../config"; @@ -17,9 +18,11 @@ describe("init dataconnect", () => { const sandbox = sinon.createSandbox(); let provisionCSQLStub: sinon.SinonStub; let askWriteProjectFileStub: sinon.SinonStub; + let ensureSyncStub: sinon.SinonStub; beforeEach(() => { provisionCSQLStub = sandbox.stub(provison, "provisionCloudSql"); + ensureSyncStub = sandbox.stub(fs, "ensureFileSync"); }); afterEach(() => { @@ -33,6 +36,7 @@ describe("init dataconnect", () => { expectedSource: string; expectedFiles: string[]; expectCSQLProvisioning: boolean; + expectEnsureSchemaGQL: boolean; }[] = [ { desc: "empty project should generate template", @@ -47,6 +51,7 @@ describe("init dataconnect", () => { "dataconnect/connector/mutations.gql", ], expectCSQLProvisioning: false, + expectEnsureSchemaGQL: false, }, { desc: "exiting project should use existing directory", @@ -55,6 +60,7 @@ describe("init dataconnect", () => { expectedSource: "not-dataconnect", expectedFiles: ["not-dataconnect/dataconnect.yaml"], expectCSQLProvisioning: false, + expectEnsureSchemaGQL: false, }, { desc: "should write schema files", @@ -70,6 +76,7 @@ describe("init dataconnect", () => { expectedSource: "dataconnect", expectedFiles: ["dataconnect/dataconnect.yaml", "dataconnect/schema/schema.gql"], expectCSQLProvisioning: false, + expectEnsureSchemaGQL: false, }, { desc: "should write connector files", @@ -95,6 +102,7 @@ describe("init dataconnect", () => { "dataconnect/hello/queries.gql", ], expectCSQLProvisioning: false, + expectEnsureSchemaGQL: false, }, { desc: "should provision cloudSQL resources ", @@ -111,6 +119,22 @@ describe("init dataconnect", () => { "dataconnect/connector/mutations.gql", ], expectCSQLProvisioning: true, + expectEnsureSchemaGQL: false, + }, + { + desc: "should handle schema with no files", + requiredInfo: mockRequiredInfo({ + schemaGql: [], + }), + config: mockConfig({ + dataconnect: { + source: "dataconnect", + }, + }), + expectedSource: "dataconnect", + expectedFiles: ["dataconnect/dataconnect.yaml"], + expectCSQLProvisioning: false, + expectEnsureSchemaGQL: true, }, ]; @@ -129,6 +153,9 @@ describe("init dataconnect", () => { c.requiredInfo, ); expect(c.config.get("dataconnect.source")).to.equal(c.expectedSource); + if (c.expectEnsureSchemaGQL) { + expect(ensureSyncStub).to.have.been.calledWith("dataconnect/schema/schema.gql"); + } expect(askWriteProjectFileStub.args.map((a) => a[0])).to.deep.equal(c.expectedFiles); expect(provisionCSQLStub.called).to.equal(c.expectCSQLProvisioning); }); diff --git a/src/init/features/dataconnect/index.ts b/src/init/features/dataconnect/index.ts index 20546eb3851..0b8550de500 100644 --- a/src/init/features/dataconnect/index.ts +++ b/src/init/features/dataconnect/index.ts @@ -1,5 +1,6 @@ import { join, basename } from "path"; import * as clc from "colorette"; +import * as fs from "fs-extra"; import { confirm, promptOnce } from "../../../prompt"; import { Config } from "../../../config"; @@ -196,7 +197,11 @@ async function writeFiles(config: Config, info: RequiredInfo) { for (const f of info.schemaGql) { await config.askWriteProjectFile(join(dir, "schema", f.path), f.content); } + } else { + // Even if the schema is empty, lets give them an empty .gql file to get started. + fs.ensureFileSync(join(dir, "schema", "schema.gql")); } + for (const c of info.connectors) { await writeConnectorFiles(config, c); } From f98ffe4a65326d9e1bdf534151b4c77067ece441 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 11 Dec 2024 15:23:30 -0800 Subject: [PATCH 14/28] Allow a Callable to include a Genkit Action annotation (#8039) In preparation for Genkit 1.0, we are going to have a `onCallGenkit` function declaration in `firebase-functions/https`. This will be a "subclass" of callable functions. To represent this, "Genkit callables" are represented in memory as a callableTriggered with a new "genkitAction" property. Actual Cloud Functions will be created with the label deployed-callable: true still, but will also include a 'genkit-action' label as well, which will eventually be used to annotate the Firebase Console. To allow users to migrate from `@genkit-ai/firebase/functions:onFlow` to `firebase-functions/https:onCallGenkit` we need to relax the restrictions around converting function types in function updates. This change allows an HTTPS function to become a Callable function. I also noticed a bug where you could convert between any type of non-auth-context Firestore function to any type of auth-context Firestore function and fixed it. --- CHANGELOG.md | 3 ++ src/deploy/functions/backend.ts | 10 +++-- src/deploy/functions/build.ts | 9 +++-- src/deploy/functions/release/planner.spec.ts | 10 +++++ src/deploy/functions/release/planner.ts | 15 +++---- .../runtimes/discovery/v1alpha1.spec.ts | 29 ++++++++++++++ .../functions/runtimes/discovery/v1alpha1.ts | 6 ++- src/functions/events/v2.ts | 18 +++++++-- src/gcp/cloudfunctionsv2.spec.ts | 40 +++++++++++++++++++ src/gcp/cloudfunctionsv2.ts | 6 +++ .../init/functions/typescript/tsconfig.json | 4 +- 11 files changed, 130 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f98ea1c0f7..b17de18fd0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ - Changes default CF3 runtime to nodejs22 (#8037) - Fixed an issue where `--import` would error for the Data Connect emulator if `dataDir` was also set. - Fixed an issue where `firebase init dataconnect` errored when importing a schema with no GQL files. +- CF3 callables can now be annotate with a genkit action they are serving (#8039) +- HTTPS functions can now be upgraded to HTTPS Callable functions (#8039) +- Update default tsconfig to support more modern defaults (#8039) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 0b5e4cd041a..113d1ac09dd 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -4,7 +4,7 @@ import * as utils from "../../utils"; import { Runtime } from "./runtimes/supported"; import { FirebaseError } from "../../error"; import { Context } from "./args"; -import { flattenArray } from "../../functional"; +import { assertExhaustive, flattenArray } from "../../functional"; /** Retry settings for a ScheduleSpec. */ export interface ScheduleRetryConfig { @@ -41,7 +41,9 @@ export interface HttpsTriggered { } /** API agnostic version of a Firebase callable function. */ -export type CallableTrigger = Record; +export type CallableTrigger = { + genkitAction?: string; +}; /** Something that has a callable trigger */ export interface CallableTriggered { @@ -135,6 +137,7 @@ export interface BlockingTrigger { eventType: string; options?: Record; } + export interface BlockingTriggered { blockingTrigger: BlockingTrigger; } @@ -153,9 +156,8 @@ export function endpointTriggerType(endpoint: Endpoint): string { return "taskQueue"; } else if (isBlockingTriggered(endpoint)) { return endpoint.blockingTrigger.eventType; - } else { - throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint)); } + assertExhaustive(endpoint); } // TODO(inlined): Enum types should be singularly named diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts index 6dc379c545d..4e5a0e5f6ec 100644 --- a/src/deploy/functions/build.ts +++ b/src/deploy/functions/build.ts @@ -74,8 +74,9 @@ export interface HttpsTrigger { // Trigger definitions for RPCs servers using the HTTP protocol defined at // https://firebase.google.com/docs/functions/callable-reference -// eslint-disable-next-line -interface CallableTrigger {} +interface CallableTrigger { + genkitAction?: string; +} // Trigger definitions for endpoints that should be called as a delegate for other operations. // For example, before user login. @@ -568,7 +569,9 @@ function discoverTrigger(endpoint: Endpoint, region: string, r: Resolver): backe } return { httpsTrigger }; } else if (isCallableTriggered(endpoint)) { - return { callableTrigger: {} }; + const trigger: CallableTriggered = { callableTrigger: {} }; + proto.copyIfPresent(trigger.callableTrigger, endpoint.callableTrigger, "genkitAction"); + return trigger; } else if (isBlockingTriggered(endpoint)) { return { blockingTrigger: endpoint.blockingTrigger }; } else if (isEventTriggered(endpoint)) { diff --git a/src/deploy/functions/release/planner.spec.ts b/src/deploy/functions/release/planner.spec.ts index 0e8de3127a3..80be985ef9f 100644 --- a/src/deploy/functions/release/planner.spec.ts +++ b/src/deploy/functions/release/planner.spec.ts @@ -46,6 +46,16 @@ describe("planner", () => { expect(() => planner.calculateUpdate(httpsFunc, scheduleFunc)).to.throw(); }); + it("allows upgrades of genkit functions from the genkit plugin to firebase-functions SDK", () => { + const httpsFunc = func("a", "b", { httpsTrigger: {} }); + const genkitFunc = func("a", "b", { callableTrigger: { genkitAction: "flows/flow" } }); + expect(planner.calculateUpdate(genkitFunc, httpsFunc)).to.deep.equal({ + // Missing: deleteAndRecreate + endpoint: genkitFunc, + unsafe: false, + }); + }); + it("knows to delete & recreate for v2 topic changes", () => { const original: backend.Endpoint = { ...func("a", "b", { diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index 74a137b24d0..9ae8872e215 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -8,10 +8,6 @@ import { FirebaseError } from "../../../error"; import * as utils from "../../../utils"; import * as backend from "../backend"; import * as v2events from "../../../functions/events/v2"; -import { - FIRESTORE_EVENT_REGEX, - FIRESTORE_EVENT_WITH_AUTH_CONTEXT_REGEX, -} from "../../../functions/events/v2"; export interface EndpointUpdate { endpoint: backend.Endpoint; @@ -261,9 +257,9 @@ export function upgradedScheduleFromV1ToV2( export function checkForUnsafeUpdate(want: backend.Endpoint, have: backend.Endpoint): boolean { return ( backend.isEventTriggered(want) && - FIRESTORE_EVENT_WITH_AUTH_CONTEXT_REGEX.test(want.eventTrigger.eventType) && backend.isEventTriggered(have) && - FIRESTORE_EVENT_REGEX.test(have.eventTrigger.eventType) + want.eventTrigger.eventType === + v2events.CONVERTABLE_EVENTS[have.eventTrigger.eventType as v2events.Event] ); } @@ -289,7 +285,12 @@ export function checkForIllegalUpdate(want: backend.Endpoint, have: backend.Endp }; const wantType = triggerType(want); const haveType = triggerType(have); - if (wantType !== haveType) { + + // Originally, @genkit-ai/firebase/functions defined onFlow which created an HTTPS trigger that implemented the streaming callable protocol for the Flow. + // The new version is firebase-functions/https which defines onCallFlow + const upgradingHttpsFunction = + backend.isHttpsTriggered(have) && backend.isCallableTriggered(want); + if (wantType !== haveType && !upgradingHttpsFunction) { throw new FirebaseError( `[${getFunctionLabel( want, diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index bba3b9b0484..f66349b0699 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -162,6 +162,35 @@ describe("buildFromV1Alpha", () => { }); }); + describe("genkitTriggers", () => { + it("fails with invalid fields", () => { + assertParserError({ + endpoints: { + func: { + ...MIN_ENDPOINT, + genkitTrigger: { + tool: "tools are not supported", + }, + }, + }, + }); + }); + + it("cannot be used with 1st gen", () => { + assertParserError({ + endpoints: { + func: { + ...MIN_ENDPOINT, + platform: "gcfv1", + genkitTrigger: { + flow: "agent", + }, + }, + }, + }); + }); + }); + describe("scheduleTriggers", () => { const validTrigger: build.ScheduleTrigger = { schedule: "every 5 minutes", diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index cc3a6e00c42..0436335d364 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -214,7 +214,9 @@ function assertBuildEndpoint(ep: WireEndpoint, id: string): void { invoker: "array?", }); } else if (build.isCallableTriggered(ep)) { - // no-op + assertKeyTypes(prefix + ".callableTrigger", ep.callableTrigger, { + genkitAction: "string?", + }); } else if (build.isScheduleTriggered(ep)) { assertKeyTypes(prefix + ".scheduleTrigger", ep.scheduleTrigger, { schedule: "Field", @@ -263,6 +265,7 @@ function assertBuildEndpoint(ep: WireEndpoint, id: string): void { options: "object", }); } else { + // TODO: Replace with assertExhaustive, which needs some type magic here because we have an any throw new FirebaseError( `Do not recognize trigger type for endpoint ${id}. Try upgrading ` + "firebase-tools with npm install -g firebase-tools@latest", @@ -310,6 +313,7 @@ function parseEndpointForBuild( copyIfPresent(triggered.httpsTrigger, ep.httpsTrigger, "invoker"); } else if (build.isCallableTriggered(ep)) { triggered = { callableTrigger: {} }; + copyIfPresent(triggered.callableTrigger, ep.callableTrigger, "genkitAction"); } else if (build.isScheduleTriggered(ep)) { const st: build.ScheduleTrigger = { // TODO: consider adding validation for fields like this that reject diff --git a/src/functions/events/v2.ts b/src/functions/events/v2.ts index 15132856a93..cd897d9a5ee 100644 --- a/src/functions/events/v2.ts +++ b/src/functions/events/v2.ts @@ -33,10 +33,6 @@ export const FIRESTORE_EVENTS = [ export const FIREALERTS_EVENT = "google.firebase.firebasealerts.alerts.v1.published"; -export const FIRESTORE_EVENT_REGEX = /^google\.cloud\.firestore\.document\.v1\.[^\.]*$/; -export const FIRESTORE_EVENT_WITH_AUTH_CONTEXT_REGEX = - /^google\.cloud\.firestore\.document\.v1\..*\.withAuthContext$/; - export type Event = | typeof PUBSUB_PUBLISH_EVENT | (typeof STORAGE_EVENTS)[number] @@ -46,3 +42,17 @@ export type Event = | typeof TEST_LAB_EVENT | (typeof FIRESTORE_EVENTS)[number] | typeof FIREALERTS_EVENT; + +// Why can't auth context be removed? This is map was added to correct a bug where a regex +// allowed any non-auth type to be converted to any auth type, but we should follow up for why +// a functon can't opt into reducing PII. +export const CONVERTABLE_EVENTS: Partial> = { + "google.cloud.firestore.document.v1.created": + "google.cloud.firestore.document.v1.created.withAuthContext", + "google.cloud.firestore.document.v1.updated": + "google.cloud.firestore.document.v1.updated.withAuthContext", + "google.cloud.firestore.document.v1.deleted": + "google.cloud.firestore.document.v1.deleted.withAuthContext", + "google.cloud.firestore.document.v1.written": + "google.cloud.firestore.document.v1.written.withAuthContext", +}; diff --git a/src/gcp/cloudfunctionsv2.spec.ts b/src/gcp/cloudfunctionsv2.spec.ts index 4d3b89eab35..ef9486f9b9f 100644 --- a/src/gcp/cloudfunctionsv2.spec.ts +++ b/src/gcp/cloudfunctionsv2.spec.ts @@ -221,6 +221,23 @@ describe("cloudfunctionsv2", () => { [BLOCKING_LABEL]: "before-sign-in", }, }); + + expect( + cloudfunctionsv2.functionFromEndpoint({ + ...ENDPOINT, + platform: "gcfv2", + callableTrigger: { + genkitAction: "flows/flow", + }, + }), + ).to.deep.equal({ + ...CLOUD_FUNCTION_V2, + labels: { + ...CLOUD_FUNCTION_V2.labels, + "deployment-callable": "true", + "genkit-action": "flows/flow", + }, + }); }); it("should copy trival fields", () => { @@ -637,6 +654,29 @@ describe("cloudfunctionsv2", () => { }); }); + it("should translate genkit callables", () => { + expect( + cloudfunctionsv2.endpointFromFunction({ + ...HAVE_CLOUD_FUNCTION_V2, + labels: { + "deployment-callable": "true", + "genkit-action": "flows/flow", + }, + }), + ).to.deep.equal({ + ...ENDPOINT, + callableTrigger: { + genkitAction: "flows/flow", + }, + platform: "gcfv2", + uri: GCF_URL, + labels: { + "deployment-callable": "true", + "genkit-action": "flows/flow", + }, + }); + }); + it("should copy optional fields", () => { const extraFields: backend.ServiceConfiguration = { ingressSettings: "ALLOW_ALL", diff --git a/src/gcp/cloudfunctionsv2.ts b/src/gcp/cloudfunctionsv2.ts index af1cb92984a..6d17c607bbc 100644 --- a/src/gcp/cloudfunctionsv2.ts +++ b/src/gcp/cloudfunctionsv2.ts @@ -609,6 +609,9 @@ export function functionFromEndpoint(endpoint: backend.Endpoint): InputCloudFunc gcfFunction.labels = { ...gcfFunction.labels, "deployment-taskqueue": "true" }; } else if (backend.isCallableTriggered(endpoint)) { gcfFunction.labels = { ...gcfFunction.labels, "deployment-callable": "true" }; + if (endpoint.callableTrigger.genkitAction) { + gcfFunction.labels["genkit-action"] = endpoint.callableTrigger.genkitAction; + } } else if (backend.isBlockingTriggered(endpoint)) { gcfFunction.labels = { ...gcfFunction.labels, @@ -654,6 +657,9 @@ export function endpointFromFunction(gcfFunction: OutputCloudFunction): backend. trigger = { callableTrigger: {}, }; + if (gcfFunction.labels["genkit-action"]) { + trigger.callableTrigger.genkitAction = gcfFunction.labels["genkit-action"]; + } } else if (gcfFunction.labels?.[BLOCKING_LABEL]) { trigger = { blockingTrigger: { diff --git a/templates/init/functions/typescript/tsconfig.json b/templates/init/functions/typescript/tsconfig.json index 7ce05d039d6..57b915f3cc9 100644 --- a/templates/init/functions/typescript/tsconfig.json +++ b/templates/init/functions/typescript/tsconfig.json @@ -1,6 +1,8 @@ { "compilerOptions": { - "module": "commonjs", + "module": "NodeNext", + "esModuleInterop": true, + "moduleResolution": "nodenext", "noImplicitReturns": true, "noUnusedLocals": true, "outDir": "lib", From c3896ad99e53eb83c56655857f415d39ee3bc398 Mon Sep 17 00:00:00 2001 From: Yuchen Shi Date: Thu, 12 Dec 2024 09:13:14 -0800 Subject: [PATCH 15/28] Fix extension crash when there is no root folder. (#8055) --- firebase-vscode/src/logger-wrapper.ts | 51 ++++++++++++++++----------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/firebase-vscode/src/logger-wrapper.ts b/firebase-vscode/src/logger-wrapper.ts index 4b453b8fbb4..20cd3c11023 100644 --- a/firebase-vscode/src/logger-wrapper.ts +++ b/firebase-vscode/src/logger-wrapper.ts @@ -44,28 +44,37 @@ export function logSetup() { // Log to file // Only log to file if firebase.debug extension setting is true. - // Re-implement file logger call from ../../src/bin/firebase.ts to not bring - // in the entire firebase.ts file - const rootFolders = getRootFolders(); - // Default to a central path, but write files to a local path if we're in a Firebase directory. - let filePath = path.join(os.homedir(), ".cache", "firebase", "logs", "vsce-debug.log"); - if (fs.existsSync(path.join(rootFolders[0], "firebase.json"))) { - filePath = path.join(rootFolders[0], ".firebase", "logs", "vsce-debug.log"); - } - pluginLogger.info("Logging to path", filePath); - cliLogger.add( - new transports.File({ - level: "debug", - filename: filePath, - format: format.printf((info) => { - const segments = [info.message, ...(info[SPLAT] || [])].map( - tryStringify, - ); - return `[${info.level}] ${stripVTControlCharacters(segments.join(" "))}`; - }), + // Re-implement file logger call from ../../src/bin/firebase.ts to not bring + // in the entire firebase.ts file + const rootFolders = getRootFolders(); + // Default to a central path, but write files to a local path if we're in a Firebase directory. + let filePath = path.join( + os.homedir(), + ".cache", + "firebase", + "logs", + "vsce-debug.log", + ); + if ( + rootFolders.length > 0 && + fs.existsSync(path.join(rootFolders[0], "firebase.json")) + ) { + filePath = path.join(rootFolders[0], ".firebase", "logs", "vsce-debug.log"); + } + pluginLogger.info("Logging to path", filePath); + cliLogger.add( + new transports.File({ + level: "debug", + filename: filePath, + format: format.printf((info) => { + const segments = [info.message, ...(info[SPLAT] || [])].map( + tryStringify, + ); + return `[${info.level}] ${stripVTControlCharacters(segments.join(" "))}`; }), - ); - cliLogger.add(new VSCodeOutputTransport({ level: "info" })); + }), + ); + cliLogger.add(new VSCodeOutputTransport({ level: "info" })); } /** From 50396433157a4ffa7a710c780d8c51cc70b8def0 Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Thu, 12 Dec 2024 11:33:00 -0800 Subject: [PATCH 16/28] Bump FDC local toolkit to v1.7.5. (#8058) * Bump FDC local toolkit to v1.7.5. * Update changelog. --- CHANGELOG.md | 1 + src/emulator/downloadableEmulators.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b17de18fd0c..907ea7d84e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ - CF3 callables can now be annotate with a genkit action they are serving (#8039) - HTTPS functions can now be upgraded to HTTPS Callable functions (#8039) - Update default tsconfig to support more modern defaults (#8039) +- Update the Firebase Data Connect local toolkit to v1.7.5, which includes a fix for Kotlin codegen that ensures that generated XxxKeys.kt files include the required `@file:UseSerializers(UUIDSerializer::class)` annotation. (#8058) diff --git a/src/emulator/downloadableEmulators.ts b/src/emulator/downloadableEmulators.ts index 51431926b66..b9be3704f2b 100755 --- a/src/emulator/downloadableEmulators.ts +++ b/src/emulator/downloadableEmulators.ts @@ -59,20 +59,20 @@ const EMULATOR_UPDATE_DETAILS: { [s in DownloadableEmulators]: EmulatorUpdateDet dataconnect: process.platform === "darwin" ? { - version: "1.7.4", - expectedSize: 25277184, - expectedChecksum: "74f6b66c79a8a903132c7ab26c644593", + version: "1.7.5", + expectedSize: 25281280, + expectedChecksum: "85d0de96b5c08b553fd8506a2bc381bb", } : process.platform === "win32" ? { - version: "1.7.4", - expectedSize: 25707520, - expectedChecksum: "66eec92e2d57ae42a8b58f33b65b4184", + version: "1.7.5", + expectedSize: 25711616, + expectedChecksum: "c99d67fa8e74d41760b96122b055b8e2", } : { - version: "1.7.4", + version: "1.7.5", expectedSize: 25190552, - expectedChecksum: "acb7be487020afa6e1a597ceb8c6e862", + expectedChecksum: "61d966b781e6f2887f8b38ec271b54e2", }, }; From 4f50803adf5e27e6999de7fbb973af0eb818c732 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 12 Dec 2024 12:46:50 -0800 Subject: [PATCH 17/28] Cleanly shut down PGlite when shutting down Data Connect (#8056) --- CHANGELOG.md | 7 +++-- src/emulator/dataconnect/pgliteServer.ts | 36 +++++++++++++++++++----- src/emulator/dataconnectEmulator.ts | 12 +++++++- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 907ea7d84e4..f22df0b62d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ - Changes default CF3 runtime to nodejs22 (#8037) - Fixed an issue where `--import` would error for the Data Connect emulator if `dataDir` was also set. - Fixed an issue where `firebase init dataconnect` errored when importing a schema with no GQL files. -- CF3 callables can now be annotate with a genkit action they are serving (#8039) -- HTTPS functions can now be upgraded to HTTPS Callable functions (#8039) -- Update default tsconfig to support more modern defaults (#8039) +- Fixed an issue where the Data Connect emulator would not cleanly shut down Postgres and corrupt data. (#8044) +- CF3 callables can now be annotate with a genkit action they are serving. (#8039) +- HTTPS functions can now be upgraded to HTTPS Callable functions. (#8039) +- Update default tsconfig to support more modern defaults. (#8039) - Update the Firebase Data Connect local toolkit to v1.7.5, which includes a fix for Kotlin codegen that ensures that generated XxxKeys.kt files include the required `@file:UseSerializers(UUIDSerializer::class)` annotation. (#8058) diff --git a/src/emulator/dataconnect/pgliteServer.ts b/src/emulator/dataconnect/pgliteServer.ts index 1aee1c2272c..8e6ce558252 100644 --- a/src/emulator/dataconnect/pgliteServer.ts +++ b/src/emulator/dataconnect/pgliteServer.ts @@ -1,6 +1,6 @@ // https://github.com/supabase-community/pg-gateway -import { PGlite, PGliteOptions } from "@electric-sql/pglite"; +import { DebugLevel, PGlite, PGliteOptions } from "@electric-sql/pglite"; // Unfortunately, we need to dynamically import the Postgres extensions. // They are only available as ESM, and if we import them normally, // our tsconfig will convert them to requires, which will cause errors @@ -18,6 +18,7 @@ import { import { fromNodeSocket } from "./pg-gateway/platforms/node"; import { logger } from "../../logger"; import { hasMessage } from "../../error"; + export const TRUNCATE_TABLES_SQL = ` DO $do$ BEGIN @@ -35,8 +36,11 @@ export class PostgresServer { private database: string; private dataDirectory?: string; private importPath?: string; + private debug: DebugLevel; public db: PGlite | undefined = undefined; + private server: net.Server | undefined = undefined; + public async createPGServer(host: string = "127.0.0.1", port: number): Promise { const getDb = this.getDb.bind(this); @@ -67,6 +71,7 @@ export class PostgresServer { server.emit("error", err); }); }); + this.server = server; const listeningPromise = new Promise((resolve) => { server.listen(port, host, () => { @@ -86,7 +91,7 @@ export class PostgresServer { const pgliteArgs: PGliteOptions = { username: this.username, database: this.database, - debug: 0, + debug: this.debug, extensions: { vector, uuidOssp, @@ -132,11 +137,28 @@ export class PostgresServer { } } - constructor(database: string, username: string, dataDirectory?: string, importPath?: string) { - this.username = username; - this.database = database; - this.dataDirectory = dataDirectory; - this.importPath = importPath; + public async stop(): Promise { + if (this.db) { + await this.db.close(); + } + if (this.server) { + this.server.close(); + } + return; + } + + constructor(args: { + database: string; + username: string; + dataDirectory?: string; + importPath?: string; + debug?: boolean; + }) { + this.username = args.username; + this.database = args.database; + this.dataDirectory = args.dataDirectory; + this.importPath = args.importPath; + this.debug = args.debug ? 5 : 0; } } diff --git a/src/emulator/dataconnectEmulator.ts b/src/emulator/dataconnectEmulator.ts index a1e83910bc8..243229c833a 100644 --- a/src/emulator/dataconnectEmulator.ts +++ b/src/emulator/dataconnectEmulator.ts @@ -40,6 +40,7 @@ export interface DataConnectEmulatorArgs { enable_output_schema_extensions: boolean; enable_output_generated_sdk: boolean; importPath?: string; + debug?: boolean; } export interface DataConnectGenerateArgs { @@ -116,7 +117,13 @@ export class DataConnectEmulator implements EmulatorInstance { const postgresDumpPath = this.args.importPath ? path.join(this.args.importPath, "postgres.tar.gz") : undefined; - this.postgresServer = new PostgresServer(dbId, "postgres", dataDirectory, postgresDumpPath); + this.postgresServer = new PostgresServer({ + database: dbId, + username: "fdc", + dataDirectory, + importPath: postgresDumpPath, + debug: this.args.debug, + }); const server = await this.postgresServer.createPGServer(pgHost, pgPort); const connectableHost = connectableHostname(pgHost); connStr = `postgres://${connectableHost}:${pgPort}/${dbId}?sslmode=disable`; @@ -166,6 +173,9 @@ export class DataConnectEmulator implements EmulatorInstance { ); return; } + if (this.postgresServer) { + await this.postgresServer.stop(); + } return stop(Emulators.DATACONNECT); } From 9805e5abb6ec4db1da98c1521b30e98b0f9be1bb Mon Sep 17 00:00:00 2001 From: aalej Date: Fri, 13 Dec 2024 06:33:14 +0800 Subject: [PATCH 18/28] added validation for project id and project name (#8057) Co-authored-by: joehan --- CHANGELOG.md | 1 + src/management/projects.ts | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f22df0b62d0..107066f4315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,4 +5,5 @@ - CF3 callables can now be annotate with a genkit action they are serving. (#8039) - HTTPS functions can now be upgraded to HTTPS Callable functions. (#8039) - Update default tsconfig to support more modern defaults. (#8039) +- Added validation for project ID and project name during `firebase init` (#2514) - Update the Firebase Data Connect local toolkit to v1.7.5, which includes a fix for Kotlin codegen that ensures that generated XxxKeys.kt files include the required `@file:UseSerializers(UUIDSerializer::class)` annotation. (#8058) diff --git a/src/management/projects.ts b/src/management/projects.ts index 93076da1699..9f25bc4552b 100644 --- a/src/management/projects.ts +++ b/src/management/projects.ts @@ -33,12 +33,30 @@ export const PROJECTS_CREATE_QUESTIONS: Question[] = [ message: "Please specify a unique project id " + `(${clc.yellow("warning")}: cannot be modified afterward) [6-30 characters]:\n`, + validate: (projectId: string) => { + if (projectId.length < 6) { + return "Project ID must be at least 6 characters long"; + } else if (projectId.length > 30) { + return "Project ID cannot be longer than 30 characters"; + } else { + return true; + } + }, }, { type: "input", name: "displayName", - default: "", + default: (answers: any) => answers.projectId, message: "What would you like to call your project? (defaults to your project ID)", + validate: (displayName: string) => { + if (displayName.length < 4) { + return "Project name must be at least 4 characters long"; + } else if (displayName.length > 30) { + return "Project name cannot be longer than 30 characters"; + } else { + return true; + } + }, }, ]; From c28c1e2f0841760c158f2369ef9a3362f5b614e0 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 12 Dec 2024 22:43:40 +0000 Subject: [PATCH 19/28] 13.29.0 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 77d3aa60b92..bb54c19f7e9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "13.28.0", + "version": "13.29.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "13.28.0", + "version": "13.29.0", "license": "MIT", "dependencies": { "@electric-sql/pglite": "^0.2.0", diff --git a/package.json b/package.json index e87e0efa6f5..abeaa3c8af3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "13.28.0", + "version": "13.29.0", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 55a70abefb4244b5da9530a7502e427519104545 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 12 Dec 2024 22:43:51 +0000 Subject: [PATCH 20/28] [firebase-release] Removed change log and reset repo after 13.29.0 release --- CHANGELOG.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 107066f4315..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +0,0 @@ -- Changes default CF3 runtime to nodejs22 (#8037) -- Fixed an issue where `--import` would error for the Data Connect emulator if `dataDir` was also set. -- Fixed an issue where `firebase init dataconnect` errored when importing a schema with no GQL files. -- Fixed an issue where the Data Connect emulator would not cleanly shut down Postgres and corrupt data. (#8044) -- CF3 callables can now be annotate with a genkit action they are serving. (#8039) -- HTTPS functions can now be upgraded to HTTPS Callable functions. (#8039) -- Update default tsconfig to support more modern defaults. (#8039) -- Added validation for project ID and project name during `firebase init` (#2514) -- Update the Firebase Data Connect local toolkit to v1.7.5, which includes a fix for Kotlin codegen that ensures that generated XxxKeys.kt files include the required `@file:UseSerializers(UUIDSerializer::class)` annotation. (#8058) From a90eee0f757335431b140c2b7c81f2fc76d1ef31 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Fri, 13 Dec 2024 10:04:00 -0800 Subject: [PATCH 21/28] ensureServiceIsConnectedToCloudSql only need to call backend when schemaValidation==NONE (#8049) --- src/dataconnect/schemaMigration.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dataconnect/schemaMigration.ts b/src/dataconnect/schemaMigration.ts index 1741a35bd5a..c6c05fb76fe 100644 --- a/src/dataconnect/schemaMigration.ts +++ b/src/dataconnect/schemaMigration.ts @@ -543,6 +543,7 @@ async function ensureServiceIsConnectedToCloudSql( { postgresql: { database: databaseId, + schemaValidation: "NONE", cloudSql: { instance: instanceId, }, @@ -566,7 +567,8 @@ async function ensureServiceIsConnectedToCloudSql( `Switching connected Postgres database from ${postgresql?.database} to ${databaseId}`, ); } - if (!postgresql || postgresql.schemaValidation === "STRICT") { + if (!postgresql || postgresql.schemaValidation !== "NONE") { + // Skip provisioning connectvity if it is already connected. return; } postgresql.schemaValidation = "STRICT"; From 0265a798824a71ef1075c01a17e9c7f7e66dd923 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 13 Dec 2024 12:44:33 -0800 Subject: [PATCH 22/28] Multiple fixes for the Data connect emulator (#8063) --- CHANGELOG.md | 2 ++ src/emulator/controller.ts | 1 + src/emulator/dataconnect/pgliteServer.ts | 21 +++++---------------- src/emulator/dataconnectEmulator.ts | 2 -- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..f55b8f11e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Fixed an issue where `clearData` with no tables would cause the Data Connect emulator to crash. +- Fixed an issue where the Data Connect emulator would crash with `Error: Unreachable`. diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index a6d574a754f..87842286e92 100755 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -884,6 +884,7 @@ export async function startAll( postgresListen: listenForEmulator["dataconnect.postgres"], enable_output_generated_sdk: true, // TODO: source from arguments enable_output_schema_extensions: true, + debug: options.debug, }; if (exportMetadata.dataconnect) { diff --git a/src/emulator/dataconnect/pgliteServer.ts b/src/emulator/dataconnect/pgliteServer.ts index 8e6ce558252..22cb7c47cd1 100644 --- a/src/emulator/dataconnect/pgliteServer.ts +++ b/src/emulator/dataconnect/pgliteServer.ts @@ -21,19 +21,18 @@ import { hasMessage } from "../../error"; export const TRUNCATE_TABLES_SQL = ` DO $do$ +DECLARE _clear text; BEGIN - EXECUTE - (SELECT 'TRUNCATE TABLE ' || string_agg(oid::regclass::text, ', ') || ' CASCADE' + SELECT 'TRUNCATE TABLE ' || string_agg(oid::regclass::text, ', ') || ' CASCADE' FROM pg_class WHERE relkind = 'r' AND relnamespace = 'public'::regnamespace - ); + INTO _clear; + EXECUTE COALESCE(_clear, 'select now()'); END $do$;`; export class PostgresServer { - private username: string; - private database: string; private dataDirectory?: string; private importPath?: string; private debug: DebugLevel; @@ -89,8 +88,6 @@ export class PostgresServer { const vector = (await dynamicImport("@electric-sql/pglite/vector")).vector; const uuidOssp = (await dynamicImport("@electric-sql/pglite/contrib/uuid_ossp")).uuid_ossp; const pgliteArgs: PGliteOptions = { - username: this.username, - database: this.database, debug: this.debug, extensions: { vector, @@ -147,15 +144,7 @@ export class PostgresServer { return; } - constructor(args: { - database: string; - username: string; - dataDirectory?: string; - importPath?: string; - debug?: boolean; - }) { - this.username = args.username; - this.database = args.database; + constructor(args: { dataDirectory?: string; importPath?: string; debug?: boolean }) { this.dataDirectory = args.dataDirectory; this.importPath = args.importPath; this.debug = args.debug ? 5 : 0; diff --git a/src/emulator/dataconnectEmulator.ts b/src/emulator/dataconnectEmulator.ts index 243229c833a..9041d66342a 100644 --- a/src/emulator/dataconnectEmulator.ts +++ b/src/emulator/dataconnectEmulator.ts @@ -118,8 +118,6 @@ export class DataConnectEmulator implements EmulatorInstance { ? path.join(this.args.importPath, "postgres.tar.gz") : undefined; this.postgresServer = new PostgresServer({ - database: dbId, - username: "fdc", dataDirectory, importPath: postgresDumpPath, debug: this.args.debug, From d5801dd384724e9ff0febc78d4fef1ffd68f8e14 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 13 Dec 2024 21:20:12 +0000 Subject: [PATCH 23/28] 13.29.1 --- npm-shrinkwrap.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index bb54c19f7e9..81d46dac925 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "firebase-tools", - "version": "13.29.0", + "version": "13.29.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "firebase-tools", - "version": "13.29.0", + "version": "13.29.1", "license": "MIT", "dependencies": { "@electric-sql/pglite": "^0.2.0", diff --git a/package.json b/package.json index abeaa3c8af3..b25f40497f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-tools", - "version": "13.29.0", + "version": "13.29.1", "description": "Command-Line Interface for Firebase", "main": "./lib/index.js", "bin": { From 14fa93a9ad7cdac80cbbe4a580e4f484ce61b5bd Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 13 Dec 2024 21:20:28 +0000 Subject: [PATCH 24/28] [firebase-release] Removed change log and reset repo after 13.29.1 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f55b8f11e94..e69de29bb2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Fixed an issue where `clearData` with no tables would cause the Data Connect emulator to crash. -- Fixed an issue where the Data Connect emulator would crash with `Error: Unreachable`. From 48d818e768332eca00e21371b5ae50eca603a1b2 Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Mon, 16 Dec 2024 05:49:10 +0000 Subject: [PATCH 25/28] Fix firebase binary analytics; upgrade vscode to 0.12.0 (#8066) * fix firebase binary analytics. update to 0.12.0 * remove additional metadata from idx --- firebase-vscode/CHANGELOG.md | 5 +++++ firebase-vscode/package-lock.json | 4 ++-- firebase-vscode/package.json | 2 +- firebase-vscode/src/analytics.ts | 7 +++++++ firebase-vscode/src/data-connect/deploy.ts | 8 ++------ firebase-vscode/src/data-connect/sdk-generation.ts | 4 +--- firebase-vscode/src/data-connect/terminal.ts | 8 ++------ 7 files changed, 20 insertions(+), 18 deletions(-) diff --git a/firebase-vscode/CHANGELOG.md b/firebase-vscode/CHANGELOG.md index ed107c1d6e6..1fdd30c52e4 100644 --- a/firebase-vscode/CHANGELOG.md +++ b/firebase-vscode/CHANGELOG.md @@ -1,5 +1,10 @@ ## NEXT +## 0.12.0 + +- Updated internal firebase-tools dependency to 13.29.1 +- [Fixed] Fixed firebase binary detection for analytics + ## 0.11.1 - [Fixed] Fixed IDX analytics issue diff --git a/firebase-vscode/package-lock.json b/firebase-vscode/package-lock.json index a12947a3a2b..e1822db306a 100644 --- a/firebase-vscode/package-lock.json +++ b/firebase-vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "firebase-dataconnect-vscode", - "version": "0.11.1", + "version": "0.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "firebase-dataconnect-vscode", - "version": "0.11.1", + "version": "0.12.0", "dependencies": { "@preact/signals-core": "^1.4.0", "@preact/signals-react": "1.3.6", diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index db57bfc9f27..6de4ca57c69 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -4,7 +4,7 @@ "publisher": "GoogleCloudTools", "icon": "./resources/firebase_dataconnect_logo.png", "description": "Firebase Data Connect for VSCode", - "version": "0.11.1", + "version": "0.12.0", "engines": { "vscode": "^1.69.0" }, diff --git a/firebase-vscode/src/analytics.ts b/firebase-vscode/src/analytics.ts index a92f4213f17..58516fd17eb 100644 --- a/firebase-vscode/src/analytics.ts +++ b/firebase-vscode/src/analytics.ts @@ -2,6 +2,7 @@ import vscode, { env, TelemetryLogger, TelemetrySender } from "vscode"; import { pluginLogger } from "./logger-wrapper"; import { AnalyticsParams, trackVSCode } from "../../src/track"; import { env as monospaceEnv } from "../src/core/env"; +import { getSettings } from "./utils/settings"; export const IDX_METRIC_NOTICE = ` When you use the Firebase Data Connect Extension, Google collects telemetry data such as usage statistics, error metrics, and crash reports. Telemetry helps us better understand how the Firebase Extension is performing, where improvements need to be made, and how features are being used. Firebase uses this data, consistent with our [Google Privacy Policy](https://policies.google.com/privacy?hl=en-US), to provide, improve, and develop Firebase products and services. @@ -183,6 +184,7 @@ class GA4TelemetrySender implements TelemetrySender { } } data = { ...data }; + data = addFirebaseBinaryMetadata(data); if (!this.hasSentData) { trackVSCode( DATA_CONNECT_EVENT_NAME.EXTENSION_USED, @@ -198,3 +200,8 @@ class GA4TelemetrySender implements TelemetrySender { // TODO: Sanatize error messages for user data } } + +function addFirebaseBinaryMetadata(data?: Record | undefined) { + const settings = getSettings(); + return { ...data, binary_kind: settings.firebaseBinaryKind }; +} diff --git a/firebase-vscode/src/data-connect/deploy.ts b/firebase-vscode/src/data-connect/deploy.ts index ba43d285a9d..e15877503e7 100644 --- a/firebase-vscode/src/data-connect/deploy.ts +++ b/firebase-vscode/src/data-connect/deploy.ts @@ -42,16 +42,12 @@ export function registerFdcDeploy( ); const deployAllCmd = vscode.commands.registerCommand("fdc.deploy-all", () => { - analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_ALL, { - firebase_binary_kind: settings.firebaseBinaryKind, - }); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_ALL); deploySpy.call(`${settings.firebasePath} deploy --only dataconnect`); }); const deployCmd = vscode.commands.registerCommand("fdc.deploy", async () => { - analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_INDIVIDUAL, { - firebase_binary_kind: settings.firebaseBinaryKind, - }); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.DEPLOY_INDIVIDUAL); const configs = await firstWhereDefined(dataConnectConfigs).then( (c) => c.requireValue, ); diff --git a/firebase-vscode/src/data-connect/sdk-generation.ts b/firebase-vscode/src/data-connect/sdk-generation.ts index 137671473d6..abf9b1a20cb 100644 --- a/firebase-vscode/src/data-connect/sdk-generation.ts +++ b/firebase-vscode/src/data-connect/sdk-generation.ts @@ -38,9 +38,7 @@ export function registerFdcSdkGeneration( const initSdkCmd = vscode.commands.registerCommand( "fdc.init-sdk", (args: { appFolder: string }) => { - analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK_CLI, { - firebase_binary_kind: settings.firebaseBinaryKind, - }); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.INIT_SDK_CLI); // Lets do it from the right directory setTerminalEnvVars(FDC_APP_FOLDER, args.appFolder); runCommand(`${settings.firebasePath} init dataconnect:sdk`); diff --git a/firebase-vscode/src/data-connect/terminal.ts b/firebase-vscode/src/data-connect/terminal.ts index 9c836760cc0..d09c8ce73b0 100644 --- a/firebase-vscode/src/data-connect/terminal.ts +++ b/firebase-vscode/src/data-connect/terminal.ts @@ -74,9 +74,7 @@ export function registerTerminalTasks( const settings = getSettings(); const loginTaskBroker = broker.on("executeLogin", () => { - analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.IDX_LOGIN, { - firebase_binary_kind: settings.firebaseBinaryKind, - }); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.IDX_LOGIN); runTerminalTask( "firebase login", `${settings.firebasePath} login --no-localhost`, @@ -86,9 +84,7 @@ export function registerTerminalTasks( }); const startEmulatorsTaskBroker = broker.on("runStartEmulators", () => { - analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.START_EMULATORS, { - firebase_binary_kind: settings.firebaseBinaryKind, - }); + analyticsLogger.logger.logUsage(DATA_CONNECT_EVENT_NAME.START_EMULATORS); // TODO: optional debug mode runTerminalTask( "firebase emulators", From 4e7b4925e9b929d71eea4e9481f412161881055a Mon Sep 17 00:00:00 2001 From: Kai Bolay Date: Mon, 16 Dec 2024 16:50:05 -0500 Subject: [PATCH 26/28] =?UTF-8?q?AITA=20=E2=86=92=20ATA=20(#8068)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/appdistribution-distribute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/appdistribution-distribute.ts b/src/commands/appdistribution-distribute.ts index c5c4e6d5ed2..61fb435fb2c 100644 --- a/src/commands/appdistribution-distribute.ts +++ b/src/commands/appdistribution-distribute.ts @@ -94,7 +94,7 @@ export const command = new Command("appdistribution:distribute Date: Fri, 27 Dec 2024 09:05:40 -0800 Subject: [PATCH 27/28] Fix storage-integeration-test (#8083) * Adding sleeps and explicitly setup chrome on the GH action runner * browset actions * browser actions * Try pinning to ubuntu 22.04 --- .github/workflows/node-test.yml | 12 +++++++++++- scripts/storage-emulator-integration/run.sh | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/node-test.yml b/.github/workflows/node-test.yml index 9ecf3d737a8..fc55932ae09 100644 --- a/.github/workflows/node-test.yml +++ b/.github/workflows/node-test.yml @@ -89,6 +89,11 @@ jobs: distribution: temurin - uses: actions/checkout@v4 + - name: Setup Chrome + uses: browser-actions/setup-chrome@v1.7.2 + with: + install-dependencies: true + install-chromedriver: true - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} @@ -146,7 +151,7 @@ jobs: integration: needs: unit if: contains(fromJSON('["push", "merge_group"]'), github.event_name) - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: FIREBASE_EMULATORS_PATH: ${{ github.workspace }}/emulator-cache @@ -184,6 +189,11 @@ jobs: node-version: ${{ matrix.node-version }} cache: npm cache-dependency-path: npm-shrinkwrap.json + - name: Setup Chrome + uses: browser-actions/setup-chrome@v1.7.2 + with: + install-dependencies: true + install-chromedriver: true - name: Cache firebase emulators uses: actions/cache@v3 with: diff --git a/scripts/storage-emulator-integration/run.sh b/scripts/storage-emulator-integration/run.sh index f2152963321..1b19adf5fef 100755 --- a/scripts/storage-emulator-integration/run.sh +++ b/scripts/storage-emulator-integration/run.sh @@ -12,10 +12,19 @@ firebase setup:emulators:storage mocha scripts/storage-emulator-integration/internal/tests.ts +# Brief sleep between tests to make sure emulators shut down fully. +sleep 5 + mocha scripts/storage-emulator-integration/rules/*.test.ts +sleep 5 + mocha scripts/storage-emulator-integration/import/tests.ts +sleep 5 + mocha scripts/storage-emulator-integration/multiple-targets/tests.ts +sleep 5 + mocha scripts/storage-emulator-integration/conformance/*.test.ts From 774d3f0580c8fa91c8e95378f79f28e60e1b867d Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Fri, 27 Dec 2024 09:50:41 -0800 Subject: [PATCH 28/28] Change how FDC determines whether creating a CSQL free trial is allowed. (#8065) * Fix log statements. * Catch backend quota error instead of querying for free trial metric. --- src/dataconnect/freeTrial.ts | 17 +++++--- src/dataconnect/provisionCloudSql.ts | 58 +++++++++++++++------------- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/dataconnect/freeTrial.ts b/src/dataconnect/freeTrial.ts index c4d3841defb..1a5f2fd910f 100644 --- a/src/dataconnect/freeTrial.ts +++ b/src/dataconnect/freeTrial.ts @@ -35,6 +35,13 @@ export async function getFreeTrialInstanceId(projectId: string): Promise i.settings.userLabels?.["firebase-data-connect"] === "ft")?.name; } +export async function isFreeTrialError(err: any, projectId: string): Promise { + // checkFreeTrialInstanceUsed is also called to ensure the request didn't fail due to an unrelated quota issue. + return err.message.includes("Quota Exhausted") && (await checkFreeTrialInstanceUsed(projectId)) + ? true + : false; +} + export function printFreeTrialUnavailable( projectId: string, configYamlPath: string, @@ -42,26 +49,26 @@ export function printFreeTrialUnavailable( ): void { if (!instanceId) { utils.logLabeledError( - "data connect", + "dataconnect", "The CloudSQL free trial has already been used on this project.", ); utils.logLabeledError( - "data connect", + "dataconnect", `You may create or use a paid CloudSQL instance by visiting https://console.cloud.google.com/sql/instances`, ); return; } utils.logLabeledError( - "data connect", + "dataconnect", `Project '${projectId} already has a CloudSQL instance '${instanceId}' on the Firebase Data Connect no-cost trial.`, ); 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 ` + `${clc.green(configYamlPath)}.`; - utils.logLabeledError("data connect", reuseHint); + utils.logLabeledError("dataconnect", reuseHint); utils.logLabeledError( - "data connect", + "dataconnect", `Alternatively, you may create a new (paid) CloudSQL instance at https://console.cloud.google.com/sql/instances`, ); } diff --git a/src/dataconnect/provisionCloudSql.ts b/src/dataconnect/provisionCloudSql.ts index 57993b9ae44..20b63907308 100755 --- a/src/dataconnect/provisionCloudSql.ts +++ b/src/dataconnect/provisionCloudSql.ts @@ -11,7 +11,7 @@ import { getFreeTrialInstanceId, freeTrialTermsLink, printFreeTrialUnavailable, - checkFreeTrialInstanceUsed, + isFreeTrialError, } from "./freeTrial"; import { FirebaseError } from "../error"; @@ -69,11 +69,6 @@ export async function provisionCloudSql(args: { if (err.status !== 404) { throw err; } - const freeTrialInstanceId = await getFreeTrialInstanceId(projectId); - if (await checkFreeTrialInstanceUsed(projectId)) { - printFreeTrialUnavailable(projectId, configYamlPath, freeTrialInstanceId); - throw new FirebaseError("No-cost Cloud SQL trial has already been used on this project."); - } const cta = dryRun ? "It will be created on your next deploy" : "Creating it now."; silent || utils.logLabeledBullet( @@ -84,27 +79,36 @@ export async function provisionCloudSql(args: { `\nMonitor the progress at ${cloudSqlAdminClient.instanceConsoleLink(projectId, instanceId)}`, ); if (!dryRun) { - const newInstance = await promiseWithSpinner( - () => - cloudSqlAdminClient.createInstance( - projectId, - locationId, - instanceId, - enableGoogleMlIntegration, - waitForCreation, - ), - "Creating your instance...", - ); - if (newInstance) { - silent || utils.logLabeledBullet("dataconnect", "Instance created"); - connectionName = newInstance?.connectionName || ""; - } else { - silent || - utils.logLabeledBullet( - "dataconnect", - "Cloud SQL instance creation started - it should be ready shortly. Database and users will be created on your next deploy.", - ); - return connectionName; + try { + const newInstance = await promiseWithSpinner( + () => + cloudSqlAdminClient.createInstance( + projectId, + locationId, + instanceId, + enableGoogleMlIntegration, + waitForCreation, + ), + "Creating your instance...", + ); + if (newInstance) { + silent || utils.logLabeledBullet("dataconnect", "Instance created"); + connectionName = newInstance?.connectionName || ""; + } else { + silent || + utils.logLabeledBullet( + "dataconnect", + "Cloud SQL instance creation started - it should be ready shortly. Database and users will be created on your next deploy.", + ); + return connectionName; + } + } catch (err: any) { + if (await isFreeTrialError(err, projectId)) { + const freeTrialInstanceId = await getFreeTrialInstanceId(projectId); + printFreeTrialUnavailable(projectId, configYamlPath, freeTrialInstanceId); + throw new FirebaseError("No-cost Cloud SQL trial has already been used on this project."); + } + throw err; } } }