firebase-tools 13.27.0 → 13.28.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/schemaMigration.js +8 -15
- package/lib/dataconnect/types.js +1 -1
- package/lib/deploy/extensions/tasks.js +2 -2
- 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 +5 -1
- package/lib/emulator/dataconnectEmulator.js +10 -0
- package/lib/emulator/downloadableEmulators.js +23 -10
- package/lib/emulator/eventarcEmulator.js +1 -0
- package/lib/emulator/initEmulators.js +17 -2
- package/lib/error.js +9 -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/init/features/dataconnect/index.js +4 -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/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/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
|
}
|
|
@@ -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;
|
|
@@ -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) {
|
|
@@ -615,10 +615,14 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
615
615
|
const apphostingConfig = (_l = options.config.src.emulators) === null || _l === void 0 ? void 0 : _l[types_1.Emulators.APPHOSTING];
|
|
616
616
|
if (listenForEmulator.apphosting) {
|
|
617
617
|
const apphostingAddr = legacyGetFirstAddr(types_1.Emulators.APPHOSTING);
|
|
618
|
+
if (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride) {
|
|
619
|
+
const apphostingLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
|
|
620
|
+
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");
|
|
621
|
+
}
|
|
618
622
|
const apphostingEmulator = new apphosting_1.AppHostingEmulator({
|
|
619
623
|
host: apphostingAddr.host,
|
|
620
624
|
port: apphostingAddr.port,
|
|
621
|
-
|
|
625
|
+
startCommand: (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommand) || (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride),
|
|
622
626
|
rootDirectory: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.rootDirectory,
|
|
623
627
|
options,
|
|
624
628
|
});
|
|
@@ -147,6 +147,11 @@ class DataConnectEmulator {
|
|
|
147
147
|
cmd.push("--watch");
|
|
148
148
|
}
|
|
149
149
|
const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
|
|
150
|
+
if ((0, downloadableEmulators_1.isIncomaptibleArchError)(res.error)) {
|
|
151
|
+
throw new error_1.FirebaseError(`Unknown system error when running the Data Connect toolkit. ` +
|
|
152
|
+
`You may be able to fix this by installing Rosetta: ` +
|
|
153
|
+
`softwareupdate --install-rosetta`);
|
|
154
|
+
}
|
|
150
155
|
logger_1.logger.info(res.stderr);
|
|
151
156
|
if (res.error) {
|
|
152
157
|
throw new error_1.FirebaseError(`Error starting up Data Connect generate: ${res.error.message}`, {
|
|
@@ -163,6 +168,11 @@ class DataConnectEmulator {
|
|
|
163
168
|
const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
|
|
164
169
|
const cmd = ["--logtostderr", "-v=2", "build", `--config_dir=${args.configDir}`];
|
|
165
170
|
const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
|
|
171
|
+
if ((0, downloadableEmulators_1.isIncomaptibleArchError)(res.error)) {
|
|
172
|
+
throw new error_1.FirebaseError(`Unkown system error when running the Data Connect toolkit. ` +
|
|
173
|
+
`You may be able to fix this by installing Rosetta: ` +
|
|
174
|
+
`softwareupdate --install-rosetta`);
|
|
175
|
+
}
|
|
166
176
|
if (res.error) {
|
|
167
177
|
throw new error_1.FirebaseError(`Error starting up Data Connect build: ${res.error.message}`, {
|
|
168
178
|
original: res.error,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.requiresJava = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName = exports.DownloadDetails = void 0;
|
|
3
|
+
exports.isIncomaptibleArchError = exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.requiresJava = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName = exports.DownloadDetails = void 0;
|
|
4
4
|
const lsofi = require("lsofi");
|
|
5
5
|
const types_1 = require("./types");
|
|
6
6
|
const constants_1 = require("./constants");
|
|
@@ -48,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
|
|
|
48
48
|
},
|
|
49
49
|
dataconnect: process.platform === "darwin"
|
|
50
50
|
? {
|
|
51
|
-
version: "1.7.
|
|
52
|
-
expectedSize:
|
|
53
|
-
expectedChecksum: "
|
|
51
|
+
version: "1.7.4",
|
|
52
|
+
expectedSize: 25277184,
|
|
53
|
+
expectedChecksum: "74f6b66c79a8a903132c7ab26c644593",
|
|
54
54
|
}
|
|
55
55
|
: process.platform === "win32"
|
|
56
56
|
? {
|
|
57
|
-
version: "1.7.
|
|
58
|
-
expectedSize:
|
|
59
|
-
expectedChecksum: "
|
|
57
|
+
version: "1.7.4",
|
|
58
|
+
expectedSize: 25707520,
|
|
59
|
+
expectedChecksum: "66eec92e2d57ae42a8b58f33b65b4184",
|
|
60
60
|
}
|
|
61
61
|
: {
|
|
62
|
-
version: "1.7.
|
|
63
|
-
expectedSize:
|
|
64
|
-
expectedChecksum: "
|
|
62
|
+
version: "1.7.4",
|
|
63
|
+
expectedSize: 25190552,
|
|
64
|
+
expectedChecksum: "acb7be487020afa6e1a597ceb8c6e862",
|
|
65
65
|
},
|
|
66
66
|
};
|
|
67
67
|
exports.DownloadDetails = {
|
|
@@ -355,6 +355,11 @@ async function _runBinary(emulator, command, extraEnv) {
|
|
|
355
355
|
if (e.code === "EACCES") {
|
|
356
356
|
logger.logLabeled("WARN", emulator.name, `Could not spawn child process for emulator, check that java is installed and on your $PATH.`);
|
|
357
357
|
}
|
|
358
|
+
else if (isIncomaptibleArchError(e)) {
|
|
359
|
+
logger.logLabeled("WARN", emulator.name, `Unknown system error when starting emulator binary. ` +
|
|
360
|
+
`You may be able to fix this by installing Rosetta: ` +
|
|
361
|
+
`softwareupdate --install-rosetta`);
|
|
362
|
+
}
|
|
358
363
|
_fatal(emulator.name, e);
|
|
359
364
|
}
|
|
360
365
|
const description = constants_1.Constants.description(emulator.name);
|
|
@@ -468,3 +473,11 @@ async function start(targetName, args, extraEnv = {}) {
|
|
|
468
473
|
return _runBinary(emulator, command, extraEnv);
|
|
469
474
|
}
|
|
470
475
|
exports.start = start;
|
|
476
|
+
function isIncomaptibleArchError(err) {
|
|
477
|
+
var _a;
|
|
478
|
+
const hasMessage = (e) => !!(e === null || e === void 0 ? void 0 : e.message);
|
|
479
|
+
return (hasMessage(err) &&
|
|
480
|
+
/Unknown system error/.test((_a = err.message) !== null && _a !== void 0 ? _a : "") &&
|
|
481
|
+
process.platform === "darwin");
|
|
482
|
+
}
|
|
483
|
+
exports.isIncomaptibleArchError = isIncomaptibleArchError;
|
|
@@ -140,6 +140,7 @@ class EventarcEmulator {
|
|
|
140
140
|
.request({
|
|
141
141
|
method: "POST",
|
|
142
142
|
path: `/functions/projects/${trigger.projectId}/triggers/${trigger.triggerName}`,
|
|
143
|
+
headers: { "Content-Type": "application/json" },
|
|
143
144
|
body: JSON.stringify(event),
|
|
144
145
|
responseType: "stream",
|
|
145
146
|
resolveOnHTTPError: true,
|
|
@@ -25,10 +25,10 @@ exports.AdditionalInitFns = {
|
|
|
25
25
|
const backendRoot = (0, path_1.join)(cwd, backendRelativeDir);
|
|
26
26
|
try {
|
|
27
27
|
const startCommand = await (0, developmentServer_1.detectStartCommand)(backendRoot);
|
|
28
|
-
additionalConfigs.set("
|
|
28
|
+
additionalConfigs.set("startCommand", startCommand);
|
|
29
29
|
}
|
|
30
30
|
catch (e) {
|
|
31
|
-
logger.log("WARN", "Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.
|
|
31
|
+
logger.log("WARN", "Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.startCommand`");
|
|
32
32
|
}
|
|
33
33
|
try {
|
|
34
34
|
const projectRoot = (_a = (0, detectProjectRoot_1.detectProjectRoot)({})) !== null && _a !== void 0 ? _a : backendRoot;
|
|
@@ -39,6 +39,21 @@ exports.AdditionalInitFns = {
|
|
|
39
39
|
}
|
|
40
40
|
return mapToObject(additionalConfigs);
|
|
41
41
|
},
|
|
42
|
+
[types_1.Emulators.DATACONNECT]: async (config) => {
|
|
43
|
+
const additionalConfig = {};
|
|
44
|
+
const defaultDataConnectDir = config.get("dataconnect.source", "dataconnect");
|
|
45
|
+
const defaultDataDir = config.get("emulators.dataconnect.dataDir", `${defaultDataConnectDir}/.dataconnect/pgliteData`);
|
|
46
|
+
if (await (0, prompt_1.promptOnce)({
|
|
47
|
+
name: "dataDir",
|
|
48
|
+
type: "confirm",
|
|
49
|
+
message: "Do you want to persist Postgres data from the Data Connect emulator between runs? " +
|
|
50
|
+
`Data will be saved to ${defaultDataDir}. ` +
|
|
51
|
+
`You can change this directory by editing 'firebase.json#emulators.dataconnect.dataDir'.`,
|
|
52
|
+
})) {
|
|
53
|
+
additionalConfig["dataDir"] = defaultDataDir;
|
|
54
|
+
}
|
|
55
|
+
return additionalConfig;
|
|
56
|
+
},
|
|
42
57
|
};
|
|
43
58
|
function mapToObject(map) {
|
|
44
59
|
const newObject = {};
|
package/lib/error.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isBillingError = exports.getError = exports.getErrStatus = exports.getErrMsg = exports.FirebaseError = void 0;
|
|
3
|
+
exports.isBillingError = exports.getError = exports.getErrStatus = exports.isObject = exports.getErrStack = exports.getErrMsg = exports.FirebaseError = void 0;
|
|
4
4
|
const lodash_1 = require("lodash");
|
|
5
5
|
const DEFAULT_CHILDREN = [];
|
|
6
6
|
const DEFAULT_EXIT = 1;
|
|
@@ -31,9 +31,17 @@ function getErrMsg(err, defaultMsg) {
|
|
|
31
31
|
return JSON.stringify(err);
|
|
32
32
|
}
|
|
33
33
|
exports.getErrMsg = getErrMsg;
|
|
34
|
+
function getErrStack(err) {
|
|
35
|
+
if (err instanceof Error) {
|
|
36
|
+
return err.stack || err.message;
|
|
37
|
+
}
|
|
38
|
+
return getErrMsg(err);
|
|
39
|
+
}
|
|
40
|
+
exports.getErrStack = getErrStack;
|
|
34
41
|
function isObject(value) {
|
|
35
42
|
return typeof value === "object" && value !== null;
|
|
36
43
|
}
|
|
44
|
+
exports.isObject = isObject;
|
|
37
45
|
function getErrStatus(err, defaultStatus) {
|
|
38
46
|
if (isObject(err) && err.status && typeof err.status === "number") {
|
|
39
47
|
return err.status;
|
package/lib/experiments.js
CHANGED
|
@@ -106,12 +106,6 @@ exports.ALL_EXPERIMENTS = experiments({
|
|
|
106
106
|
default: true,
|
|
107
107
|
public: false,
|
|
108
108
|
},
|
|
109
|
-
fdccompatiblemode: {
|
|
110
|
-
shortDescription: "Enable Data Connect schema migrations in Compatible Mode",
|
|
111
|
-
fullDescription: "Enable Data Connect schema migrations in Compatible Mode",
|
|
112
|
-
default: true,
|
|
113
|
-
public: false,
|
|
114
|
-
},
|
|
115
109
|
});
|
|
116
110
|
function isValidExperiment(name) {
|
|
117
111
|
return Object.keys(exports.ALL_EXPERIMENTS).includes(name);
|
|
@@ -38,9 +38,9 @@ function fixLifecycleEvents(lifecycleEvents) {
|
|
|
38
38
|
stageUnspecified: "STAGE_UNSPECIFIED",
|
|
39
39
|
};
|
|
40
40
|
const arrayLifecycle = [];
|
|
41
|
-
if ((0,
|
|
41
|
+
if ((0, error_1.isObject)(lifecycleEvents)) {
|
|
42
42
|
for (const [key, val] of Object.entries(lifecycleEvents)) {
|
|
43
|
-
if ((0,
|
|
43
|
+
if ((0, error_1.isObject)(val) &&
|
|
44
44
|
typeof val.function === "string" &&
|
|
45
45
|
typeof val.processingMessage === "string") {
|
|
46
46
|
arrayLifecycle.push({
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.writeSDK = exports.TYPESCRIPT_VERSION = exports.FIREBASE_FUNCTIONS_VERSION = exports.SDK_GENERATION_VERSION = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const child_process_1 = require("child_process");
|
|
6
5
|
const marked_terminal_1 = require("marked-terminal");
|
|
7
6
|
const marked_1 = require("marked");
|
|
7
|
+
const error_1 = require("../../error");
|
|
8
8
|
const types_1 = require("../types");
|
|
9
9
|
const prompt_1 = require("../../prompt");
|
|
10
10
|
const secretsUtils = require("../secretsUtils");
|
|
@@ -12,7 +12,8 @@ const utils_1 = require("../../utils");
|
|
|
12
12
|
const common_1 = require("./common");
|
|
13
13
|
const askUserForEventsConfig_1 = require("../askUserForEventsConfig");
|
|
14
14
|
const extensionsHelper_1 = require("../extensionsHelper");
|
|
15
|
-
const
|
|
15
|
+
const error_2 = require("../../error");
|
|
16
|
+
const spawn_1 = require("../../init/spawn");
|
|
16
17
|
marked_1.marked.use((0, marked_terminal_1.markedTerminal)());
|
|
17
18
|
exports.SDK_GENERATION_VERSION = "1.0.0";
|
|
18
19
|
exports.FIREBASE_FUNCTIONS_VERSION = ">=5.1.0";
|
|
@@ -91,8 +92,8 @@ function addPeerDependency(pkgJson, dependency, version) {
|
|
|
91
92
|
if (!pkgJson.peerDependencies) {
|
|
92
93
|
pkgJson.peerDependencies = {};
|
|
93
94
|
}
|
|
94
|
-
if (!(0,
|
|
95
|
-
throw new
|
|
95
|
+
if (!(0, error_1.isObject)(pkgJson.peerDependencies)) {
|
|
96
|
+
throw new error_2.FirebaseError("Internal error generating peer dependencies.");
|
|
96
97
|
}
|
|
97
98
|
pkgJson.peerDependencies[dependency] = version;
|
|
98
99
|
}
|
|
@@ -118,7 +119,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
|
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
if (!dirPath) {
|
|
121
|
-
throw new
|
|
122
|
+
throw new error_2.FirebaseError("Invalid extension definition. Must have either extensionRef or localPath");
|
|
122
123
|
}
|
|
123
124
|
const packageName = makePackageName(extensionRef, spec.name);
|
|
124
125
|
const pkgJson = {
|
|
@@ -292,7 +293,7 @@ async function writeSDK(extensionRef, localPath, spec, options) {
|
|
|
292
293
|
sdkLines.push(` ${sysParam.param}${opt}: string;`);
|
|
293
294
|
break;
|
|
294
295
|
default:
|
|
295
|
-
throw new
|
|
296
|
+
throw new error_2.FirebaseError(`Error: Unknown systemParam type: ${sysParam.type || "undefined"}.`);
|
|
296
297
|
}
|
|
297
298
|
sdkLines.push("");
|
|
298
299
|
}
|
|
@@ -347,19 +348,19 @@ async function writeSDK(extensionRef, localPath, spec, options) {
|
|
|
347
348
|
await (0, common_1.writeFile)(`${dirPath}/tsconfig.json`, JSON.stringify(tsconfigJson, null, 2), options);
|
|
348
349
|
(0, utils_1.logLabeledBullet)("extensions", `running 'npm --prefix ${shortDirPath} install'`);
|
|
349
350
|
try {
|
|
350
|
-
(0,
|
|
351
|
+
await (0, spawn_1.spawnWithOutput)("npm", ["--prefix", dirPath, "install"]);
|
|
351
352
|
}
|
|
352
353
|
catch (err) {
|
|
353
|
-
const errMsg = (0,
|
|
354
|
-
throw new
|
|
354
|
+
const errMsg = (0, error_2.getErrMsg)(err, "unknown error");
|
|
355
|
+
throw new error_2.FirebaseError(`Error during npm install in ${shortDirPath}: ${errMsg}`);
|
|
355
356
|
}
|
|
356
357
|
(0, utils_1.logLabeledBullet)("extensions", `running 'npm --prefix ${shortDirPath} run build'`);
|
|
357
358
|
try {
|
|
358
|
-
(0,
|
|
359
|
+
await (0, spawn_1.spawnWithOutput)("npm", ["--prefix", dirPath, "run", "build"]);
|
|
359
360
|
}
|
|
360
361
|
catch (err) {
|
|
361
|
-
const errMsg = (0,
|
|
362
|
-
throw new
|
|
362
|
+
const errMsg = (0, error_2.getErrMsg)(err, "unknown error");
|
|
363
|
+
throw new error_2.FirebaseError(`Error during npm run build in ${shortDirPath}: ${errMsg}`);
|
|
363
364
|
}
|
|
364
365
|
const codebaseDir = (0, common_1.getCodebaseDir)(options);
|
|
365
366
|
const shortCodebaseDir = codebaseDir.replace(process.cwd(), ".");
|
|
@@ -372,11 +373,11 @@ async function writeSDK(extensionRef, localPath, spec, options) {
|
|
|
372
373
|
})) {
|
|
373
374
|
(0, utils_1.logLabeledBullet)("extensions", `running 'npm --prefix ${shortCodebaseDir} install --save ${shortDirPath}'`);
|
|
374
375
|
try {
|
|
375
|
-
(0,
|
|
376
|
+
await (0, spawn_1.spawnWithOutput)("npm", ["--prefix", codebaseDir, "install", "--save", dirPath]);
|
|
376
377
|
}
|
|
377
378
|
catch (err) {
|
|
378
|
-
const errMsg = (0,
|
|
379
|
-
throw new
|
|
379
|
+
const errMsg = (0, error_2.getErrMsg)(err, "unknown error");
|
|
380
|
+
throw new error_2.FirebaseError(`Error during npm install in ${codebaseDir}: ${errMsg}`);
|
|
380
381
|
}
|
|
381
382
|
}
|
|
382
383
|
else {
|
package/lib/extensions/types.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isExtensionSpec = exports.isResource = exports.isParam = exports.
|
|
3
|
+
exports.isExtensionSpec = exports.isResource = exports.isParam = exports.ParamType = exports.FUNCTIONS_V2_RESOURCE_TYPE = exports.FUNCTIONS_RESOURCE_TYPE = exports.isExtensionInstance = exports.Visibility = exports.RegistryLaunchStage = void 0;
|
|
4
|
+
const error_1 = require("../error");
|
|
4
5
|
var RegistryLaunchStage;
|
|
5
6
|
(function (RegistryLaunchStage) {
|
|
6
7
|
RegistryLaunchStage["EXPERIMENTAL"] = "EXPERIMENTAL";
|
|
@@ -23,7 +24,7 @@ const extensionInstanceState = [
|
|
|
23
24
|
"PAUSED",
|
|
24
25
|
];
|
|
25
26
|
const isExtensionInstance = (value) => {
|
|
26
|
-
if (!isObject(value) || typeof value.name !== "string") {
|
|
27
|
+
if (!(0, error_1.isObject)(value) || typeof value.name !== "string") {
|
|
27
28
|
return false;
|
|
28
29
|
}
|
|
29
30
|
return true;
|
|
@@ -40,20 +41,16 @@ var ParamType;
|
|
|
40
41
|
ParamType["SELECT_RESOURCE"] = "SELECT_RESOURCE";
|
|
41
42
|
ParamType["SECRET"] = "SECRET";
|
|
42
43
|
})(ParamType = exports.ParamType || (exports.ParamType = {}));
|
|
43
|
-
function isObject(value) {
|
|
44
|
-
return typeof value === "object" && value !== null;
|
|
45
|
-
}
|
|
46
|
-
exports.isObject = isObject;
|
|
47
44
|
const isParam = (param) => {
|
|
48
|
-
return (isObject(param) && typeof param["param"] === "string" && typeof param["label"] === "string");
|
|
45
|
+
return ((0, error_1.isObject)(param) && typeof param["param"] === "string" && typeof param["label"] === "string");
|
|
49
46
|
};
|
|
50
47
|
exports.isParam = isParam;
|
|
51
48
|
const isResource = (res) => {
|
|
52
|
-
return isObject(res) && typeof res["name"] === "string";
|
|
49
|
+
return (0, error_1.isObject)(res) && typeof res["name"] === "string";
|
|
53
50
|
};
|
|
54
51
|
exports.isResource = isResource;
|
|
55
52
|
const isExtensionSpec = (spec) => {
|
|
56
|
-
if (!isObject(spec) || typeof spec.name !== "string" || typeof spec.version !== "string") {
|
|
53
|
+
if (!(0, error_1.isObject)(spec) || typeof spec.name !== "string" || typeof spec.version !== "string") {
|
|
57
54
|
return false;
|
|
58
55
|
}
|
|
59
56
|
if (spec.resources && Array.isArray(spec.resources)) {
|
|
@@ -8,7 +8,6 @@ const provisionCloudSql_1 = require("../../../dataconnect/provisionCloudSql");
|
|
|
8
8
|
const freeTrial_1 = require("../../../dataconnect/freeTrial");
|
|
9
9
|
const cloudsql = require("../../../gcp/cloudsql/cloudsqladmin");
|
|
10
10
|
const ensureApis_1 = require("../../../dataconnect/ensureApis");
|
|
11
|
-
const experiments = require("../../../experiments");
|
|
12
11
|
const client_1 = require("../../../dataconnect/client");
|
|
13
12
|
const types_1 = require("../../../dataconnect/types");
|
|
14
13
|
const names_1 = require("../../../dataconnect/names");
|
|
@@ -19,7 +18,6 @@ const cloudbilling_1 = require("../../../gcp/cloudbilling");
|
|
|
19
18
|
const sdk = require("./sdk");
|
|
20
19
|
const fileUtils_1 = require("../../../dataconnect/fileUtils");
|
|
21
20
|
const DATACONNECT_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect.yaml");
|
|
22
|
-
const DATACONNECT_YAML_COMPAT_EXPERIMENT_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect-fdccompatiblemode.yaml");
|
|
23
21
|
const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/connector.yaml");
|
|
24
22
|
const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
|
|
25
23
|
const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
|
|
@@ -50,6 +48,9 @@ async function doSetup(setup, config) {
|
|
|
50
48
|
isBillingEnabled ? await (0, ensureApis_1.ensureApis)(setup.projectId) : await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
|
|
51
49
|
}
|
|
52
50
|
const info = await askQuestions(setup, isBillingEnabled);
|
|
51
|
+
const dir = config.get("dataconnect.source", "dataconnect");
|
|
52
|
+
const dataDir = config.get("emulators.dataconnect.dataDir", `${dir}/.dataconnect/pgliteData`);
|
|
53
|
+
config.set("emulators.dataconnect.dataDir", dataDir);
|
|
53
54
|
await actuate(setup, config, info);
|
|
54
55
|
const cwdPlatformGuess = await (0, fileUtils_1.getPlatformFromFolder)(process.cwd());
|
|
55
56
|
if (cwdPlatformGuess !== types_1.Platform.NONE) {
|
|
@@ -156,9 +157,7 @@ function subDataconnectYamlValues(replacementValues) {
|
|
|
156
157
|
connectorDirs: "__connectorDirs__",
|
|
157
158
|
locationId: "__location__",
|
|
158
159
|
};
|
|
159
|
-
let replaced =
|
|
160
|
-
? DATACONNECT_YAML_COMPAT_EXPERIMENT_TEMPLATE
|
|
161
|
-
: DATACONNECT_YAML_TEMPLATE;
|
|
160
|
+
let replaced = DATACONNECT_YAML_TEMPLATE;
|
|
162
161
|
for (const [k, v] of Object.entries(replacementValues)) {
|
|
163
162
|
replaced = replaced.replace(replacements[k], JSON.stringify(v));
|
|
164
163
|
}
|
|
@@ -49,7 +49,7 @@ async function doSetup(setup, config) {
|
|
|
49
49
|
}
|
|
50
50
|
const additionalInitFn = initEmulators_1.AdditionalInitFns[selected];
|
|
51
51
|
if (additionalInitFn) {
|
|
52
|
-
const additionalOptions = await additionalInitFn();
|
|
52
|
+
const additionalOptions = await additionalInitFn(config);
|
|
53
53
|
if (additionalOptions) {
|
|
54
54
|
setup.config.emulators[selected] = Object.assign(Object.assign({}, setup.config.emulators[selected]), additionalOptions);
|
|
55
55
|
}
|
|
@@ -6,7 +6,6 @@ const inquirer = require("inquirer");
|
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const semver = require("semver");
|
|
8
8
|
const clc = require("colorette");
|
|
9
|
-
const child_process_1 = require("child_process");
|
|
10
9
|
const functions_1 = require("../functions");
|
|
11
10
|
const prompt_1 = require("../../../prompt");
|
|
12
11
|
const spawn_1 = require("../../spawn");
|
|
@@ -15,7 +14,6 @@ const ensureApiEnabled_1 = require("../../../ensureApiEnabled");
|
|
|
15
14
|
const logger_1 = require("../../../logger");
|
|
16
15
|
const error_1 = require("../../../error");
|
|
17
16
|
const utils_1 = require("../../../utils");
|
|
18
|
-
const types_1 = require("../../../extensions/types");
|
|
19
17
|
async function getGenkitVersion() {
|
|
20
18
|
let genkitVersion;
|
|
21
19
|
let templateVersion = "0.9.0";
|
|
@@ -26,7 +24,17 @@ async function getGenkitVersion() {
|
|
|
26
24
|
genkitVersion = process.env.GENKIT_DEV_VERSION;
|
|
27
25
|
}
|
|
28
26
|
else {
|
|
29
|
-
|
|
27
|
+
try {
|
|
28
|
+
genkitVersion = await (0, spawn_1.spawnWithOutput)("npm", ["view", "genkit", "version"]);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
throw new error_1.FirebaseError("Unable to determine which genkit version to install.\n" +
|
|
32
|
+
`npm Error: ${(0, error_1.getErrMsg)(err)}\n\n` +
|
|
33
|
+
"For a possible workaround run\n npm view genkit version\n" +
|
|
34
|
+
"and then set an environment variable:\n" +
|
|
35
|
+
" export GENKIT_DEV_VERSION=<output from previous command>\n" +
|
|
36
|
+
"and run `firebase init genkit` again");
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
if (!genkitVersion) {
|
|
32
40
|
throw new error_1.FirebaseError("Unable to determine genkit version to install");
|
|
@@ -236,7 +244,7 @@ async function genkitSetup(options, genkitInfo, projectDir) {
|
|
|
236
244
|
}
|
|
237
245
|
exports.genkitSetup = genkitSetup;
|
|
238
246
|
const isTsConfig = (value) => {
|
|
239
|
-
if (!(0,
|
|
247
|
+
if (!(0, error_1.isObject)(value) || (value.compilerOptions && !(0, error_1.isObject)(value.compilerOptions))) {
|
|
240
248
|
return false;
|
|
241
249
|
}
|
|
242
250
|
return true;
|
|
@@ -339,7 +347,7 @@ function generateSampleFile(modelPlugin, configPlugins, projectDir, templateVers
|
|
|
339
347
|
}
|
|
340
348
|
}
|
|
341
349
|
const isPackageJson = (value) => {
|
|
342
|
-
if (!(0,
|
|
350
|
+
if (!(0, error_1.isObject)(value) || (value.scripts && !(0, error_1.isObject)(value.scripts))) {
|
|
343
351
|
return false;
|
|
344
352
|
}
|
|
345
353
|
return true;
|
package/lib/init/spawn.js
CHANGED
|
@@ -1,27 +1,58 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.spawnWithCommandString = exports.wrapSpawn = void 0;
|
|
3
|
+
exports.spawnWithCommandString = exports.spawnWithOutput = exports.wrapSpawn = void 0;
|
|
4
4
|
const spawn = require("cross-spawn");
|
|
5
5
|
const logger_1 = require("../logger");
|
|
6
|
-
|
|
6
|
+
const error_1 = require("../error");
|
|
7
|
+
function wrapSpawn(cmd, args, projectDir) {
|
|
7
8
|
return new Promise((resolve, reject) => {
|
|
8
9
|
const installer = spawn(cmd, args, {
|
|
9
10
|
cwd: projectDir,
|
|
10
11
|
stdio: "inherit",
|
|
11
|
-
env: Object.assign(
|
|
12
|
+
env: Object.assign({}, process.env),
|
|
12
13
|
});
|
|
13
14
|
installer.on("error", (err) => {
|
|
14
|
-
logger_1.logger.debug(
|
|
15
|
+
logger_1.logger.debug((0, error_1.getErrStack)(err));
|
|
15
16
|
});
|
|
16
17
|
installer.on("close", (code) => {
|
|
17
18
|
if (code === 0) {
|
|
18
19
|
return resolve();
|
|
19
20
|
}
|
|
20
|
-
return reject();
|
|
21
|
+
return reject(new Error(`Error: spawn(${cmd}, [${args.join(", ")}]) \n exited with code: ${code || "null"}`));
|
|
21
22
|
});
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
exports.wrapSpawn = wrapSpawn;
|
|
26
|
+
function spawnWithOutput(cmd, args) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
var _a, _b;
|
|
29
|
+
const child = spawn(cmd, args);
|
|
30
|
+
let output = "";
|
|
31
|
+
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (data) => {
|
|
32
|
+
if ((0, error_1.isObject)(data) && data.toString) {
|
|
33
|
+
output += data.toString();
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
output += JSON.stringify(data);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
(_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (data) => {
|
|
40
|
+
logger_1.logger.debug(`Error: spawn(${cmd}, ${args.join(", ")})\n Stderr:\n${JSON.stringify(data)}\n`);
|
|
41
|
+
});
|
|
42
|
+
child.on("error", (err) => {
|
|
43
|
+
logger_1.logger.debug((0, error_1.getErrStack)(err));
|
|
44
|
+
});
|
|
45
|
+
child.on("close", (code) => {
|
|
46
|
+
if (code === 0) {
|
|
47
|
+
resolve(output);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
reject(new Error(`Error: spawn(${cmd}, [${args.join(", ")}]) \n exited with code: ${code || "null"}`));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
exports.spawnWithOutput = spawnWithOutput;
|
|
25
56
|
function spawnWithCommandString(cmd, projectDir, environmentVariables) {
|
|
26
57
|
return new Promise((resolve, reject) => {
|
|
27
58
|
const installer = spawn(cmd, {
|
|
@@ -31,13 +62,13 @@ function spawnWithCommandString(cmd, projectDir, environmentVariables) {
|
|
|
31
62
|
env: Object.assign(Object.assign({}, process.env), environmentVariables),
|
|
32
63
|
});
|
|
33
64
|
installer.on("error", (err) => {
|
|
34
|
-
logger_1.logger.log("DEBUG",
|
|
65
|
+
logger_1.logger.log("DEBUG", (0, error_1.getErrStack)(err));
|
|
35
66
|
});
|
|
36
67
|
installer.on("close", (code) => {
|
|
37
68
|
if (code === 0) {
|
|
38
69
|
return resolve();
|
|
39
70
|
}
|
|
40
|
-
return reject();
|
|
71
|
+
return reject(new Error(`Error: spawn(${cmd}) \n exited with code: ${code || "null"}`));
|
|
41
72
|
});
|
|
42
73
|
});
|
|
43
74
|
}
|
package/lib/requireAuth.js
CHANGED
|
@@ -25,6 +25,7 @@ async function autoAuth(options, authScopes) {
|
|
|
25
25
|
const client = getAuthClient({ scopes: authScopes, projectId: options.project });
|
|
26
26
|
const token = await client.getAccessToken();
|
|
27
27
|
token !== null ? apiv2.setAccessToken(token) : false;
|
|
28
|
+
logger_1.logger.debug(`Running auto auth`);
|
|
28
29
|
let clientEmail;
|
|
29
30
|
try {
|
|
30
31
|
const credentials = await client.getCredentials();
|
|
@@ -72,7 +73,7 @@ async function requireAuth(options) {
|
|
|
72
73
|
utils.logWarning("Authenticating with `FIREBASE_TOKEN` is deprecated and will be removed in a future major version of `firebase-tools`. " +
|
|
73
74
|
"Instead, use a service account key with `GOOGLE_APPLICATION_CREDENTIALS`: https://cloud.google.com/docs/authentication/getting-started");
|
|
74
75
|
}
|
|
75
|
-
else if (user) {
|
|
76
|
+
else if (user && (!(0, auth_1.isExpired)(tokens) || (tokens === null || tokens === void 0 ? void 0 : tokens.refresh_token))) {
|
|
76
77
|
logger_1.logger.debug(`> authorizing via signed-in user (${user.email})`);
|
|
77
78
|
}
|
|
78
79
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.28.0",
|
|
4
4
|
"description": "Command-Line Interface for Firebase",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"google-auth-library": "^9.11.0",
|
|
92
92
|
"inquirer": "^8.2.6",
|
|
93
93
|
"inquirer-autocomplete-prompt": "^2.0.1",
|
|
94
|
+
"js-yaml": "^3.14.1",
|
|
94
95
|
"jsonwebtoken": "^9.0.0",
|
|
95
96
|
"leven": "^3.1.0",
|
|
96
97
|
"libsodium-wrappers": "^0.7.10",
|
|
@@ -114,7 +115,7 @@
|
|
|
114
115
|
"sql-formatter": "^15.3.0",
|
|
115
116
|
"stream-chain": "^2.2.4",
|
|
116
117
|
"stream-json": "^1.7.3",
|
|
117
|
-
"superstatic": "^9.0
|
|
118
|
+
"superstatic": "^9.1.0",
|
|
118
119
|
"tar": "^6.1.11",
|
|
119
120
|
"tcp-port-used": "^1.0.2",
|
|
120
121
|
"tmp": "^0.2.3",
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
specVersion: "v1beta"
|
|
2
|
-
serviceId: __serviceId__
|
|
3
|
-
location: __location__
|
|
4
|
-
schema:
|
|
5
|
-
source: "./schema"
|
|
6
|
-
datasource:
|
|
7
|
-
postgresql:
|
|
8
|
-
database: __cloudSqlDatabase__
|
|
9
|
-
cloudSql:
|
|
10
|
-
instanceId: __cloudSqlInstanceId__
|
|
11
|
-
# schemaValidation: "COMPATIBLE"
|
|
12
|
-
connectorDirs: __connectorDirs__
|