firebase-tools 13.27.0 → 13.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/appdistribution/client.js +2 -1
- package/lib/appdistribution/options-parser-util.js +5 -5
- package/lib/auth.js +13 -3
- package/lib/bin/firebase.js +4 -3
- package/lib/commands/appdistribution-distribute.js +55 -31
- package/lib/commands/firestore-indexes-list.js +4 -2
- package/lib/commands/index.js +2 -2
- package/lib/dataconnect/fileUtils.js +3 -0
- package/lib/dataconnect/schemaMigration.js +8 -15
- package/lib/dataconnect/types.js +1 -1
- package/lib/deploy/extensions/tasks.js +2 -2
- package/lib/deploy/functions/backend.js +1 -3
- package/lib/deploy/functions/build.js +3 -1
- package/lib/deploy/functions/release/planner.js +4 -4
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +4 -0
- package/lib/deploy/functions/runtimes/supported/types.js +1 -1
- package/lib/emulator/apphosting/developmentServer.js +1 -1
- package/lib/emulator/apphosting/index.js +1 -1
- package/lib/emulator/auth/operations.js +8 -5
- package/lib/emulator/controller.js +19 -1
- package/lib/emulator/dataconnect/pgliteServer.js +34 -7
- package/lib/emulator/dataconnectEmulator.js +20 -1
- package/lib/emulator/downloadableEmulators.js +22 -10
- package/lib/emulator/eventarcEmulator.js +1 -0
- package/lib/emulator/initEmulators.js +17 -2
- package/lib/error.js +11 -1
- package/lib/experiments.js +0 -6
- package/lib/extensions/localHelper.js +2 -2
- package/lib/extensions/runtimes/node.js +16 -15
- package/lib/extensions/types.js +6 -9
- package/lib/functions/events/v2.js +7 -3
- package/lib/gcp/cloudfunctionsv2.js +6 -0
- package/lib/init/features/dataconnect/index.js +8 -5
- package/lib/init/features/emulators.js +1 -1
- package/lib/init/features/genkit/index.js +13 -5
- package/lib/init/spawn.js +38 -7
- package/lib/management/projects.js +23 -1
- package/lib/requireAuth.js +2 -1
- package/package.json +3 -2
- package/schema/firebase-config.json +3 -0
- package/templates/init/dataconnect/dataconnect.yaml +1 -0
- package/templates/init/functions/javascript/package.lint.json +1 -1
- package/templates/init/functions/javascript/package.nolint.json +1 -1
- package/templates/init/functions/typescript/package.lint.json +1 -1
- package/templates/init/functions/typescript/package.nolint.json +1 -1
- package/templates/init/functions/typescript/tsconfig.json +3 -1
- package/templates/init/dataconnect/dataconnect-fdccompatiblemode.yaml +0 -12
|
@@ -231,7 +231,7 @@ class AppDistributionClient {
|
|
|
231
231
|
}
|
|
232
232
|
utils.logSuccess(`Testers removed from group successfully`);
|
|
233
233
|
}
|
|
234
|
-
async createReleaseTest(releaseName, devices, loginCredential) {
|
|
234
|
+
async createReleaseTest(releaseName, devices, loginCredential, testCaseName) {
|
|
235
235
|
try {
|
|
236
236
|
const response = await this.appDistroV1AlphaClient.request({
|
|
237
237
|
method: "POST",
|
|
@@ -239,6 +239,7 @@ class AppDistributionClient {
|
|
|
239
239
|
body: {
|
|
240
240
|
deviceExecutions: devices.map(types_1.mapDeviceToExecution),
|
|
241
241
|
loginCredential,
|
|
242
|
+
testCase: testCaseName,
|
|
242
243
|
},
|
|
243
244
|
});
|
|
244
245
|
return response.body;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getLoginCredential = exports.
|
|
3
|
+
exports.getLoginCredential = exports.parseTestDevices = exports.getAppName = exports.getProjectName = exports.ensureFileExists = exports.getEmails = exports.parseIntoStringArray = void 0;
|
|
4
4
|
const fs = require("fs-extra");
|
|
5
5
|
const error_1 = require("../error");
|
|
6
6
|
const projectUtils_1 = require("../projectUtils");
|
|
7
|
-
function
|
|
7
|
+
function parseIntoStringArray(value, file) {
|
|
8
8
|
if (!value && file) {
|
|
9
9
|
ensureFileExists(file);
|
|
10
10
|
value = fs.readFileSync(file, "utf8");
|
|
@@ -14,7 +14,7 @@ function getTestersOrGroups(value, file) {
|
|
|
14
14
|
}
|
|
15
15
|
return [];
|
|
16
16
|
}
|
|
17
|
-
exports.
|
|
17
|
+
exports.parseIntoStringArray = parseIntoStringArray;
|
|
18
18
|
function getEmails(emails, file) {
|
|
19
19
|
if (emails.length === 0) {
|
|
20
20
|
ensureFileExists(file);
|
|
@@ -49,7 +49,7 @@ function getAppName(options) {
|
|
|
49
49
|
return `projects/${appId.split(":")[1]}/apps/${appId}`;
|
|
50
50
|
}
|
|
51
51
|
exports.getAppName = getAppName;
|
|
52
|
-
function
|
|
52
|
+
function parseTestDevices(value, file) {
|
|
53
53
|
if (!value && file) {
|
|
54
54
|
ensureFileExists(file);
|
|
55
55
|
value = fs.readFileSync(file, "utf8");
|
|
@@ -63,7 +63,7 @@ function getTestDevices(value, file) {
|
|
|
63
63
|
.filter((entry) => !!entry)
|
|
64
64
|
.map((str) => parseTestDevice(str));
|
|
65
65
|
}
|
|
66
|
-
exports.
|
|
66
|
+
exports.parseTestDevices = parseTestDevices;
|
|
67
67
|
function parseTestDevice(testDeviceString) {
|
|
68
68
|
const entries = testDeviceString.split(",");
|
|
69
69
|
const allowedKeys = new Set(["model", "version", "orientation", "locale"]);
|
package/lib/auth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.addAdditionalAccount = exports.logout = exports.getAccessToken = exports.haveValidTokens = exports.loggedIn = exports.findAccountByEmail = exports.loginGithub = exports.loginGoogle = exports.setGlobalDefaultAccount = exports.setProjectAccount = exports.loginAdditionalAccount = exports.selectAccount = exports.setRefreshToken = exports.setActiveAccount = exports.getAllAccounts = exports.getAdditionalAccounts = exports.getProjectDefaultAccount = exports.getGlobalDefaultAccount = void 0;
|
|
3
|
+
exports.addAdditionalAccount = exports.logout = exports.getAccessToken = exports.haveValidTokens = exports.isExpired = exports.loggedIn = exports.findAccountByEmail = exports.loginGithub = exports.loginGoogle = exports.setGlobalDefaultAccount = exports.setProjectAccount = exports.loginAdditionalAccount = exports.selectAccount = exports.setRefreshToken = exports.setActiveAccount = exports.getAllAccounts = exports.getAdditionalAccounts = exports.getProjectDefaultAccount = exports.getGlobalDefaultAccount = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const FormData = require("form-data");
|
|
6
6
|
const http = require("http");
|
|
@@ -401,6 +401,16 @@ function loggedIn() {
|
|
|
401
401
|
return !!lastAccessToken;
|
|
402
402
|
}
|
|
403
403
|
exports.loggedIn = loggedIn;
|
|
404
|
+
function isExpired(tokens) {
|
|
405
|
+
const hasExpiration = (p) => !!p.expires_at;
|
|
406
|
+
if (hasExpiration(tokens)) {
|
|
407
|
+
return !(tokens && tokens.expires_at && tokens.expires_at > Date.now());
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
return !tokens;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
exports.isExpired = isExpired;
|
|
404
414
|
function haveValidTokens(refreshToken, authScopes) {
|
|
405
415
|
var _a;
|
|
406
416
|
if (!(lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.access_token)) {
|
|
@@ -413,8 +423,8 @@ function haveValidTokens(refreshToken, authScopes) {
|
|
|
413
423
|
const oldScopesJSON = JSON.stringify(((_a = lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.scopes) === null || _a === void 0 ? void 0 : _a.sort()) || []);
|
|
414
424
|
const newScopesJSON = JSON.stringify(authScopes.sort());
|
|
415
425
|
const hasSameScopes = oldScopesJSON === newScopesJSON;
|
|
416
|
-
const
|
|
417
|
-
const valid = hasTokens && hasSameScopes && !
|
|
426
|
+
const expired = ((lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.expires_at) || 0) < Date.now() + FIFTEEN_MINUTES_IN_MS;
|
|
427
|
+
const valid = hasTokens && hasSameScopes && !expired;
|
|
418
428
|
if (hasTokens) {
|
|
419
429
|
logger_1.logger.debug(`Checked if tokens are valid: ${valid}, expires at: ${lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.expires_at}`);
|
|
420
430
|
}
|
package/lib/bin/firebase.js
CHANGED
|
@@ -26,7 +26,7 @@ const client = require("..");
|
|
|
26
26
|
const fsutils = require("../fsutils");
|
|
27
27
|
const utils = require("../utils");
|
|
28
28
|
const winston = require("winston");
|
|
29
|
-
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
30
|
let cmd;
|
|
31
31
|
function findAvailableLogFile() {
|
|
32
32
|
const candidates = ["firebase-debug.log"];
|
|
@@ -126,9 +126,10 @@ process.on("uncaughtException", (err) => {
|
|
|
126
126
|
(0, errorOut_1.errorOut)(err);
|
|
127
127
|
});
|
|
128
128
|
if (!(0, handlePreviewToggles_1.handlePreviewToggles)(args)) {
|
|
129
|
-
cmd = client.cli.parse(process.argv);
|
|
130
|
-
args = args.filter((arg) => !arg.includes("-"));
|
|
131
129
|
if (!args.length) {
|
|
132
130
|
client.cli.help();
|
|
133
131
|
}
|
|
132
|
+
else {
|
|
133
|
+
cmd = client.cli.parse(process.argv);
|
|
134
|
+
}
|
|
134
135
|
}
|
|
@@ -23,14 +23,14 @@ function getReleaseNotes(releaseNotes, releaseNotesFile) {
|
|
|
23
23
|
return "";
|
|
24
24
|
}
|
|
25
25
|
exports.command = new command_1.Command("appdistribution:distribute <release-binary-file>")
|
|
26
|
-
.description("upload a release binary")
|
|
26
|
+
.description("upload a release binary and optionally distribute it to testers and run automated tests")
|
|
27
27
|
.option("--app <app_id>", "the app id of your Firebase app")
|
|
28
28
|
.option("--release-notes <string>", "release notes to include")
|
|
29
29
|
.option("--release-notes-file <file>", "path to file with release notes")
|
|
30
|
-
.option("--testers <string>", "a comma
|
|
31
|
-
.option("--testers-file <file>", "path to file with a comma separated list of tester emails to distribute to")
|
|
32
|
-
.option("--groups <string>", "a comma
|
|
33
|
-
.option("--groups-file <file>", "path to file with a comma separated list of group aliases to distribute to")
|
|
30
|
+
.option("--testers <string>", "a comma-separated list of tester emails to distribute to")
|
|
31
|
+
.option("--testers-file <file>", "path to file with a comma- or newline-separated list of tester emails to distribute to")
|
|
32
|
+
.option("--groups <string>", "a comma-separated list of group aliases to distribute to")
|
|
33
|
+
.option("--groups-file <file>", "path to file with a comma- or newline-separated list of group aliases to distribute to")
|
|
34
34
|
.option("--test-devices <string>", "semicolon-separated list of devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
|
|
35
35
|
.option("--test-devices-file <string>", "path to file containing a list of semicolon- or newline-separated devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
|
|
36
36
|
.option("--test-username <string>", "username for automatic login")
|
|
@@ -39,14 +39,20 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
39
39
|
.option("--test-username-resource <string>", "resource name for the username field for automatic login")
|
|
40
40
|
.option("--test-password-resource <string>", "resource name for the password field for automatic login")
|
|
41
41
|
.option("--test-non-blocking", "run automated tests without waiting for them to complete. Visit the Firebase console for the test results.")
|
|
42
|
+
.option("--test-case-ids <string>", "a comma-separated list of test case IDs.")
|
|
43
|
+
.option("--test-case-ids-file <file>", "path to file with a comma- or newline-separated list of test case IDs.")
|
|
42
44
|
.before(requireAuth_1.requireAuth)
|
|
43
45
|
.action(async (file, options) => {
|
|
44
46
|
const appName = (0, options_parser_util_1.getAppName)(options);
|
|
45
47
|
const distribution = new distribution_1.Distribution(file);
|
|
46
48
|
const releaseNotes = getReleaseNotes(options.releaseNotes, options.releaseNotesFile);
|
|
47
|
-
const testers = (0, options_parser_util_1.
|
|
48
|
-
const groups = (0, options_parser_util_1.
|
|
49
|
-
const
|
|
49
|
+
const testers = (0, options_parser_util_1.parseIntoStringArray)(options.testers, options.testersFile);
|
|
50
|
+
const groups = (0, options_parser_util_1.parseIntoStringArray)(options.groups, options.groupsFile);
|
|
51
|
+
const testCases = (0, options_parser_util_1.parseIntoStringArray)(options.testCaseIds, options.testCaseIdsFile);
|
|
52
|
+
const testDevices = (0, options_parser_util_1.parseTestDevices)(options.testDevices, options.testDevicesFile);
|
|
53
|
+
if (testCases.length && (options.testUsernameResource || options.testPasswordResource)) {
|
|
54
|
+
throw new error_1.FirebaseError("Password and username resource names are not supported for the AI testing agent.");
|
|
55
|
+
}
|
|
50
56
|
const loginCredential = (0, options_parser_util_1.getLoginCredential)({
|
|
51
57
|
username: options.testUsername,
|
|
52
58
|
password: options.testPassword,
|
|
@@ -137,39 +143,57 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
|
|
|
137
143
|
}
|
|
138
144
|
await requests.updateReleaseNotes(releaseName, releaseNotes);
|
|
139
145
|
await requests.distribute(releaseName, testers, groups);
|
|
140
|
-
if (testDevices
|
|
141
|
-
utils.logBullet("starting automated
|
|
142
|
-
const
|
|
143
|
-
|
|
146
|
+
if (testDevices.length) {
|
|
147
|
+
utils.logBullet("starting automated test (note: this feature is in beta)");
|
|
148
|
+
const releaseTestPromises = [];
|
|
149
|
+
if (!testCases.length) {
|
|
150
|
+
releaseTestPromises.push(requests.createReleaseTest(releaseName, testDevices, loginCredential));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
for (const testCaseId of testCases) {
|
|
154
|
+
releaseTestPromises.push(requests.createReleaseTest(releaseName, testDevices, loginCredential, `${appName}/testCases/${testCaseId}`));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const releaseTests = await Promise.all(releaseTestPromises);
|
|
158
|
+
utils.logSuccess(`${releaseTests.length} release test(s) started successfully`);
|
|
144
159
|
if (!options.testNonBlocking) {
|
|
145
|
-
await awaitTestResults(
|
|
160
|
+
await awaitTestResults(releaseTests, requests);
|
|
146
161
|
}
|
|
147
162
|
}
|
|
148
163
|
});
|
|
149
|
-
async function awaitTestResults(
|
|
164
|
+
async function awaitTestResults(releaseTests, requests) {
|
|
165
|
+
const releaseTestNames = new Set(releaseTests.map((rt) => rt.name));
|
|
150
166
|
for (let i = 0; i < TEST_MAX_POLLING_RETRIES; i++) {
|
|
151
|
-
utils.logBullet(
|
|
167
|
+
utils.logBullet(`${releaseTestNames.size} automated test results are pending...`);
|
|
152
168
|
await delay(TEST_POLLING_INTERVAL_MILLIS);
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
for (const releaseTestName of releaseTestNames) {
|
|
170
|
+
const releaseTest = await requests.getReleaseTest(releaseTestName);
|
|
171
|
+
if (releaseTest.deviceExecutions.every((e) => e.state === "PASSED")) {
|
|
172
|
+
releaseTestNames.delete(releaseTestName);
|
|
173
|
+
if (releaseTestNames.size === 0) {
|
|
174
|
+
utils.logSuccess("Automated test(s) passed!");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
162
178
|
continue;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const execution of releaseTest.deviceExecutions) {
|
|
182
|
+
switch (execution.state) {
|
|
183
|
+
case "PASSED":
|
|
184
|
+
case "IN_PROGRESS":
|
|
185
|
+
continue;
|
|
186
|
+
case "FAILED":
|
|
187
|
+
throw new error_1.FirebaseError(`Automated test failed for ${deviceToString(execution.device)}: ${execution.failedReason}`, { exit: 1 });
|
|
188
|
+
case "INCONCLUSIVE":
|
|
189
|
+
throw new error_1.FirebaseError(`Automated test inconclusive for ${deviceToString(execution.device)}: ${execution.inconclusiveReason}`, { exit: 1 });
|
|
190
|
+
default:
|
|
191
|
+
throw new error_1.FirebaseError(`Unsupported automated test state for ${deviceToString(execution.device)}: ${execution.state}`, { exit: 1 });
|
|
192
|
+
}
|
|
169
193
|
}
|
|
170
194
|
}
|
|
171
195
|
}
|
|
172
|
-
throw new error_1.FirebaseError("It took longer than expected to
|
|
196
|
+
throw new error_1.FirebaseError("It took longer than expected to run your test(s), please try again.", {
|
|
173
197
|
exit: 1,
|
|
174
198
|
});
|
|
175
199
|
}
|
|
@@ -9,6 +9,7 @@ const requirePermissions_1 = require("../requirePermissions");
|
|
|
9
9
|
const types_1 = require("../emulator/types");
|
|
10
10
|
const commandUtils_1 = require("../emulator/commandUtils");
|
|
11
11
|
const pretty_print_1 = require("../firestore/pretty-print");
|
|
12
|
+
const projectUtils_1 = require("../projectUtils");
|
|
12
13
|
exports.command = new command_1.Command("firestore:indexes")
|
|
13
14
|
.description("List indexes in your project's Cloud Firestore database.")
|
|
14
15
|
.option("--pretty", "Pretty print. When not specified the indexes are printed in the " +
|
|
@@ -21,8 +22,9 @@ exports.command = new command_1.Command("firestore:indexes")
|
|
|
21
22
|
const indexApi = new fsi.FirestoreApi();
|
|
22
23
|
const printer = new pretty_print_1.PrettyPrint();
|
|
23
24
|
const databaseId = (_a = options.database) !== null && _a !== void 0 ? _a : "(default)";
|
|
24
|
-
const
|
|
25
|
-
const
|
|
25
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
26
|
+
const indexes = await indexApi.listIndexes(projectId, databaseId);
|
|
27
|
+
const fieldOverrides = await indexApi.listFieldOverrides(projectId, databaseId);
|
|
26
28
|
const indexSpec = indexApi.makeIndexSpec(indexes, fieldOverrides);
|
|
27
29
|
if (options.pretty) {
|
|
28
30
|
logger_1.logger.info(clc.bold(clc.white("Compound Indexes")));
|
package/lib/commands/index.js
CHANGED
|
@@ -172,6 +172,8 @@ function load(client) {
|
|
|
172
172
|
client.apphosting.secrets.grantaccess = loadCommand("apphosting-secrets-grantaccess");
|
|
173
173
|
client.apphosting.secrets.describe = loadCommand("apphosting-secrets-describe");
|
|
174
174
|
client.apphosting.secrets.access = loadCommand("apphosting-secrets-access");
|
|
175
|
+
client.apphosting.rollouts = {};
|
|
176
|
+
client.apphosting.rollouts.create = loadCommand("apphosting-rollouts-create");
|
|
175
177
|
client.apphosting.config = {};
|
|
176
178
|
client.apphosting.config.export = loadCommand("apphosting-config-export");
|
|
177
179
|
if (experiments.isEnabled("internaltesting")) {
|
|
@@ -180,8 +182,6 @@ function load(client) {
|
|
|
180
182
|
client.apphosting.builds.create = loadCommand("apphosting-builds-create");
|
|
181
183
|
client.apphosting.repos = {};
|
|
182
184
|
client.apphosting.repos.create = loadCommand("apphosting-repos-create");
|
|
183
|
-
client.apphosting.rollouts = {};
|
|
184
|
-
client.apphosting.rollouts.create = loadCommand("apphosting-rollouts-create");
|
|
185
185
|
client.apphosting.rollouts.list = loadCommand("apphosting-rollouts-list");
|
|
186
186
|
}
|
|
187
187
|
}
|
|
@@ -53,6 +53,9 @@ function validateConnectorYaml(unvalidated) {
|
|
|
53
53
|
return unvalidated;
|
|
54
54
|
}
|
|
55
55
|
async function readGQLFiles(sourceDir) {
|
|
56
|
+
if (!fs.existsSync(sourceDir)) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
56
59
|
const files = await fs.readdir(sourceDir);
|
|
57
60
|
return files
|
|
58
61
|
.filter((f) => f.endsWith(".gql") || f.endsWith(".graphql"))
|
|
@@ -13,18 +13,15 @@ const prompt_1 = require("../prompt");
|
|
|
13
13
|
const logger_1 = require("../logger");
|
|
14
14
|
const error_1 = require("../error");
|
|
15
15
|
const utils_1 = require("../utils");
|
|
16
|
-
const experiments = require("../experiments");
|
|
17
16
|
const errors = require("./errors");
|
|
18
17
|
async function diffSchema(schema, schemaValidation) {
|
|
19
18
|
const { serviceName, instanceName, databaseId } = getIdentifiers(schema);
|
|
20
19
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
21
20
|
let diffs = [];
|
|
22
|
-
let validationMode =
|
|
23
|
-
? schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE"
|
|
24
|
-
: "STRICT";
|
|
21
|
+
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
|
|
25
22
|
setSchemaValidationMode(schema, validationMode);
|
|
26
23
|
try {
|
|
27
|
-
if (!schemaValidation
|
|
24
|
+
if (!schemaValidation) {
|
|
28
25
|
(0, utils_1.logLabeledBullet)("dataconnect", `generating required schema changes...`);
|
|
29
26
|
}
|
|
30
27
|
await (0, client_1.upsertSchema)(schema, true);
|
|
@@ -52,7 +49,7 @@ async function diffSchema(schema, schemaValidation) {
|
|
|
52
49
|
diffs = incompatible.diffs;
|
|
53
50
|
}
|
|
54
51
|
}
|
|
55
|
-
if (
|
|
52
|
+
if (!schemaValidation) {
|
|
56
53
|
validationMode = "STRICT";
|
|
57
54
|
setSchemaValidationMode(schema, validationMode);
|
|
58
55
|
try {
|
|
@@ -89,9 +86,7 @@ async function migrateSchema(args) {
|
|
|
89
86
|
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
90
87
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
|
|
91
88
|
let diffs = [];
|
|
92
|
-
let validationMode =
|
|
93
|
-
? schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE"
|
|
94
|
-
: "STRICT";
|
|
89
|
+
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
|
|
95
90
|
setSchemaValidationMode(schema, validationMode);
|
|
96
91
|
try {
|
|
97
92
|
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
@@ -124,7 +119,7 @@ async function migrateSchema(args) {
|
|
|
124
119
|
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
125
120
|
}
|
|
126
121
|
}
|
|
127
|
-
if (
|
|
122
|
+
if (!schemaValidation) {
|
|
128
123
|
validationMode = "STRICT";
|
|
129
124
|
setSchemaValidationMode(schema, validationMode);
|
|
130
125
|
try {
|
|
@@ -185,11 +180,9 @@ function diffsEqual(x, y) {
|
|
|
185
180
|
return true;
|
|
186
181
|
}
|
|
187
182
|
function setSchemaValidationMode(schema, schemaValidation) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
postgresDatasource.postgresql.schemaValidation = schemaValidation;
|
|
192
|
-
}
|
|
183
|
+
const postgresDatasource = schema.datasources.find((d) => d.postgresql);
|
|
184
|
+
if (postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) {
|
|
185
|
+
postgresDatasource.postgresql.schemaValidation = schemaValidation;
|
|
193
186
|
}
|
|
194
187
|
}
|
|
195
188
|
function getIdentifiers(schema) {
|
package/lib/dataconnect/types.js
CHANGED
|
@@ -17,7 +17,7 @@ var Platform;
|
|
|
17
17
|
Platform["MULTIPLE"] = "MULTIPLE";
|
|
18
18
|
})(Platform = exports.Platform || (exports.Platform = {}));
|
|
19
19
|
function toDatasource(projectId, locationId, ds) {
|
|
20
|
-
if (ds.postgresql) {
|
|
20
|
+
if (ds === null || ds === void 0 ? void 0 : ds.postgresql) {
|
|
21
21
|
return {
|
|
22
22
|
postgresql: {
|
|
23
23
|
database: ds.postgresql.database,
|
|
@@ -7,7 +7,7 @@ const extensionsApi = require("../../extensions/extensionsApi");
|
|
|
7
7
|
const extensionsHelper_1 = require("../../extensions/extensionsHelper");
|
|
8
8
|
const refs = require("../../extensions/refs");
|
|
9
9
|
const utils = require("../../utils");
|
|
10
|
-
const
|
|
10
|
+
const error_2 = require("../../error");
|
|
11
11
|
const isRetryable = (err) => err.status === 429 || err.status === 409;
|
|
12
12
|
function extensionsDeploymentHandler(errorHandler) {
|
|
13
13
|
return async (task) => {
|
|
@@ -54,7 +54,7 @@ function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
|
|
|
54
54
|
await extensionsApi.createInstance(createArgs);
|
|
55
55
|
}
|
|
56
56
|
catch (err) {
|
|
57
|
-
if ((0,
|
|
57
|
+
if ((0, error_2.isObject)(err) && err.status === 409) {
|
|
58
58
|
throw new error_1.FirebaseError(`Failed to create extension instance. Extension instance ${clc.bold(instanceSpec.instanceId)} already exists.`);
|
|
59
59
|
}
|
|
60
60
|
throw err;
|
|
@@ -25,9 +25,7 @@ function endpointTriggerType(endpoint) {
|
|
|
25
25
|
else if (isBlockingTriggered(endpoint)) {
|
|
26
26
|
return endpoint.blockingTrigger.eventType;
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
|
|
30
|
-
}
|
|
28
|
+
(0, functional_1.assertExhaustive)(endpoint);
|
|
31
29
|
}
|
|
32
30
|
exports.endpointTriggerType = endpointTriggerType;
|
|
33
31
|
exports.AllVpcEgressSettings = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"];
|
|
@@ -259,7 +259,9 @@ function discoverTrigger(endpoint, region, r) {
|
|
|
259
259
|
return { httpsTrigger };
|
|
260
260
|
}
|
|
261
261
|
else if (isCallableTriggered(endpoint)) {
|
|
262
|
-
|
|
262
|
+
const trigger = { callableTrigger: {} };
|
|
263
|
+
proto.copyIfPresent(trigger.callableTrigger, endpoint.callableTrigger, "genkitAction");
|
|
264
|
+
return trigger;
|
|
263
265
|
}
|
|
264
266
|
else if (isBlockingTriggered(endpoint)) {
|
|
265
267
|
return { blockingTrigger: endpoint.blockingTrigger };
|
|
@@ -7,7 +7,6 @@ const error_1 = require("../../../error");
|
|
|
7
7
|
const utils = require("../../../utils");
|
|
8
8
|
const backend = require("../backend");
|
|
9
9
|
const v2events = require("../../../functions/events/v2");
|
|
10
|
-
const v2_1 = require("../../../functions/events/v2");
|
|
11
10
|
function calculateChangesets(want, have, keyFn, deleteAll) {
|
|
12
11
|
const toCreate = utils.groupBy(Object.keys(want)
|
|
13
12
|
.filter((id) => !have[id])
|
|
@@ -167,9 +166,9 @@ function upgradedScheduleFromV1ToV2(want, have) {
|
|
|
167
166
|
exports.upgradedScheduleFromV1ToV2 = upgradedScheduleFromV1ToV2;
|
|
168
167
|
function checkForUnsafeUpdate(want, have) {
|
|
169
168
|
return (backend.isEventTriggered(want) &&
|
|
170
|
-
v2_1.FIRESTORE_EVENT_WITH_AUTH_CONTEXT_REGEX.test(want.eventTrigger.eventType) &&
|
|
171
169
|
backend.isEventTriggered(have) &&
|
|
172
|
-
|
|
170
|
+
want.eventTrigger.eventType ===
|
|
171
|
+
v2events.CONVERTABLE_EVENTS[have.eventTrigger.eventType]);
|
|
173
172
|
}
|
|
174
173
|
exports.checkForUnsafeUpdate = checkForUnsafeUpdate;
|
|
175
174
|
function checkForIllegalUpdate(want, have) {
|
|
@@ -196,7 +195,8 @@ function checkForIllegalUpdate(want, have) {
|
|
|
196
195
|
};
|
|
197
196
|
const wantType = triggerType(want);
|
|
198
197
|
const haveType = triggerType(have);
|
|
199
|
-
|
|
198
|
+
const upgradingHttpsFunction = backend.isHttpsTriggered(have) && backend.isCallableTriggered(want);
|
|
199
|
+
if (wantType !== haveType && !upgradingHttpsFunction) {
|
|
200
200
|
throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Changing from ${haveType} function to ${wantType} function is not allowed. Please delete your function and create a new one instead.`);
|
|
201
201
|
}
|
|
202
202
|
if (want.platform === "gcfv1" && have.platform === "gcfv2") {
|
|
@@ -134,6 +134,9 @@ function assertBuildEndpoint(ep, id) {
|
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
136
|
else if (build.isCallableTriggered(ep)) {
|
|
137
|
+
(0, parsing_1.assertKeyTypes)(prefix + ".callableTrigger", ep.callableTrigger, {
|
|
138
|
+
genkitAction: "string?",
|
|
139
|
+
});
|
|
137
140
|
}
|
|
138
141
|
else if (build.isScheduleTriggered(ep)) {
|
|
139
142
|
(0, parsing_1.assertKeyTypes)(prefix + ".scheduleTrigger", ep.scheduleTrigger, {
|
|
@@ -216,6 +219,7 @@ function parseEndpointForBuild(id, ep, project, defaultRegion, runtime) {
|
|
|
216
219
|
}
|
|
217
220
|
else if (build.isCallableTriggered(ep)) {
|
|
218
221
|
triggered = { callableTrigger: {} };
|
|
222
|
+
(0, proto_1.copyIfPresent)(triggered.callableTrigger, ep.callableTrigger, "genkitAction");
|
|
219
223
|
}
|
|
220
224
|
else if (build.isScheduleTriggered(ep)) {
|
|
221
225
|
const st = {
|
|
@@ -26,7 +26,7 @@ async function detectStartCommand(rootDir) {
|
|
|
26
26
|
return `${packageManager} run dev`;
|
|
27
27
|
}
|
|
28
28
|
catch (e) {
|
|
29
|
-
throw new error_1.FirebaseError("Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.
|
|
29
|
+
throw new error_1.FirebaseError("Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.startCommand`");
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
exports.detectStartCommand = detectStartCommand;
|
|
@@ -11,7 +11,7 @@ class AppHostingEmulator {
|
|
|
11
11
|
async start() {
|
|
12
12
|
const { hostname, port } = await (0, serve_1.start)({
|
|
13
13
|
port: this.args.port,
|
|
14
|
-
startCommand: this.args.
|
|
14
|
+
startCommand: this.args.startCommand,
|
|
15
15
|
rootDirectory: this.args.rootDirectory,
|
|
16
16
|
});
|
|
17
17
|
this.args.options.host = hostname;
|
|
@@ -738,11 +738,7 @@ function setAccountInfo(state, reqBody, ctx) {
|
|
|
738
738
|
}
|
|
739
739
|
function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl = undefined } = {}) {
|
|
740
740
|
var _a, _b;
|
|
741
|
-
const unimplementedFields = [
|
|
742
|
-
"provider",
|
|
743
|
-
"upgradeToFederatedLogin",
|
|
744
|
-
"linkProviderUserInfo",
|
|
745
|
-
];
|
|
741
|
+
const unimplementedFields = ["provider", "upgradeToFederatedLogin"];
|
|
746
742
|
for (const field of unimplementedFields) {
|
|
747
743
|
if (field in reqBody) {
|
|
748
744
|
throw new errors_1.NotImplementedError(`${field} is not implemented yet.`);
|
|
@@ -908,8 +904,15 @@ function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl =
|
|
|
908
904
|
updates.phoneNumber = undefined;
|
|
909
905
|
}
|
|
910
906
|
}
|
|
907
|
+
if (reqBody.linkProviderUserInfo) {
|
|
908
|
+
(0, errors_1.assert)(reqBody.linkProviderUserInfo.providerId, "MISSING_PROVIDER_ID");
|
|
909
|
+
(0, errors_1.assert)(reqBody.linkProviderUserInfo.rawId, "MISSING_RAW_ID");
|
|
910
|
+
}
|
|
911
911
|
user = state.updateUserByLocalId(user.localId, updates, {
|
|
912
912
|
deleteProviders: reqBody.deleteProvider,
|
|
913
|
+
upsertProviders: reqBody.linkProviderUserInfo
|
|
914
|
+
? [reqBody.linkProviderUserInfo]
|
|
915
|
+
: undefined,
|
|
913
916
|
});
|
|
914
917
|
if (signInProvider !== state_1.PROVIDER_ANONYMOUS && user.initialEmail && isEmailUpdate) {
|
|
915
918
|
if (!emulatorUrl) {
|
|
@@ -577,6 +577,20 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
577
577
|
utils.assertIsString(options.import);
|
|
578
578
|
const importDirAbsPath = path.resolve(options.import);
|
|
579
579
|
const exportMetadataFilePath = path.resolve(importDirAbsPath, exportMetadata.dataconnect.path);
|
|
580
|
+
const dataDirectory = options.config.get("emulators.dataconnect.dataDir");
|
|
581
|
+
if (exportMetadataFilePath && dataDirectory) {
|
|
582
|
+
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT).logLabeled("WARN", "dataconnect", "'firebase.json#emulators.dataconnect.dataDir' is set and `--import` flag was passed. " +
|
|
583
|
+
"This will overwrite any data saved from previous runs.");
|
|
584
|
+
if (!options.nonInteractive &&
|
|
585
|
+
!(await (0, prompt_1.promptOnce)({
|
|
586
|
+
type: "confirm",
|
|
587
|
+
message: `Do you wish to continue and overwrite data in ${dataDirectory}?`,
|
|
588
|
+
default: false,
|
|
589
|
+
}))) {
|
|
590
|
+
await cleanShutdown();
|
|
591
|
+
return { deprecationNotices: [] };
|
|
592
|
+
}
|
|
593
|
+
}
|
|
580
594
|
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT).logLabeled("BULLET", "dataconnect", `Importing data from ${exportMetadataFilePath}`);
|
|
581
595
|
args.importPath = exportMetadataFilePath;
|
|
582
596
|
void (0, track_1.trackEmulator)("emulator_import", {
|
|
@@ -615,10 +629,14 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
615
629
|
const apphostingConfig = (_l = options.config.src.emulators) === null || _l === void 0 ? void 0 : _l[types_1.Emulators.APPHOSTING];
|
|
616
630
|
if (listenForEmulator.apphosting) {
|
|
617
631
|
const apphostingAddr = legacyGetFirstAddr(types_1.Emulators.APPHOSTING);
|
|
632
|
+
if (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride) {
|
|
633
|
+
const apphostingLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
|
|
634
|
+
apphostingLogger.logLabeled("WARN", types_1.Emulators.APPHOSTING, "The `firebase.json#emulators.apphosting.startCommandOverride` config is deprecated, please use `firebase.json#emulators.apphosting.startCommand` to set a custom start command instead");
|
|
635
|
+
}
|
|
618
636
|
const apphostingEmulator = new apphosting_1.AppHostingEmulator({
|
|
619
637
|
host: apphostingAddr.host,
|
|
620
638
|
port: apphostingAddr.port,
|
|
621
|
-
|
|
639
|
+
startCommand: (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommand) || (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride),
|
|
622
640
|
rootDirectory: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.rootDirectory,
|
|
623
641
|
options,
|
|
624
642
|
});
|
|
@@ -27,6 +27,7 @@ const fs = require("fs");
|
|
|
27
27
|
const index_1 = require("./pg-gateway/index");
|
|
28
28
|
const node_1 = require("./pg-gateway/platforms/node");
|
|
29
29
|
const logger_1 = require("../../logger");
|
|
30
|
+
const error_1 = require("../../error");
|
|
30
31
|
exports.TRUNCATE_TABLES_SQL = `
|
|
31
32
|
DO $do$
|
|
32
33
|
BEGIN
|
|
@@ -62,6 +63,7 @@ class PostgresServer {
|
|
|
62
63
|
server.emit("error", err);
|
|
63
64
|
});
|
|
64
65
|
});
|
|
66
|
+
this.server = server;
|
|
65
67
|
const listeningPromise = new Promise((resolve) => {
|
|
66
68
|
server.listen(port, host, () => {
|
|
67
69
|
resolve();
|
|
@@ -77,7 +79,7 @@ class PostgresServer {
|
|
|
77
79
|
const pgliteArgs = {
|
|
78
80
|
username: this.username,
|
|
79
81
|
database: this.database,
|
|
80
|
-
debug:
|
|
82
|
+
debug: this.debug,
|
|
81
83
|
extensions: {
|
|
82
84
|
vector,
|
|
83
85
|
uuidOssp,
|
|
@@ -90,7 +92,7 @@ class PostgresServer {
|
|
|
90
92
|
const file = new File([rf], this.importPath);
|
|
91
93
|
pgliteArgs.loadDataDir = file;
|
|
92
94
|
}
|
|
93
|
-
this.db = await
|
|
95
|
+
this.db = await this.forceCreateDB(pgliteArgs);
|
|
94
96
|
await this.db.waitReady;
|
|
95
97
|
}
|
|
96
98
|
return this.db;
|
|
@@ -105,12 +107,37 @@ class PostgresServer {
|
|
|
105
107
|
const arrayBuff = await dump.arrayBuffer();
|
|
106
108
|
fs.writeFileSync(exportPath, new Uint8Array(arrayBuff));
|
|
107
109
|
}
|
|
108
|
-
|
|
110
|
+
async forceCreateDB(pgliteArgs) {
|
|
111
|
+
try {
|
|
112
|
+
const db = await pglite_1.PGlite.create(pgliteArgs);
|
|
113
|
+
return db;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (pgliteArgs.dataDir && (0, error_1.hasMessage)(err) && /Database already exists/.test(err.message)) {
|
|
117
|
+
fs.rmSync(pgliteArgs.dataDir, { force: true, recursive: true });
|
|
118
|
+
const db = await pglite_1.PGlite.create(pgliteArgs);
|
|
119
|
+
return db;
|
|
120
|
+
}
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async stop() {
|
|
125
|
+
if (this.db) {
|
|
126
|
+
await this.db.close();
|
|
127
|
+
}
|
|
128
|
+
if (this.server) {
|
|
129
|
+
this.server.close();
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
constructor(args) {
|
|
109
134
|
this.db = undefined;
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
135
|
+
this.server = undefined;
|
|
136
|
+
this.username = args.username;
|
|
137
|
+
this.database = args.database;
|
|
138
|
+
this.dataDirectory = args.dataDirectory;
|
|
139
|
+
this.importPath = args.importPath;
|
|
140
|
+
this.debug = args.debug ? 5 : 0;
|
|
114
141
|
}
|
|
115
142
|
}
|
|
116
143
|
exports.PostgresServer = PostgresServer;
|