firebase-tools 14.20.0 → 14.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/appUtils.js +2 -1
- package/lib/command.js +5 -9
- package/lib/commands/dataconnect-sdk-generate.js +66 -11
- package/lib/commands/deploy.js +6 -4
- package/lib/commands/firestore-databases-clone.js +99 -0
- package/lib/commands/functions-secrets-set.js +19 -1
- package/lib/commands/hosting-sites-create.js +4 -3
- package/lib/commands/index.js +1 -0
- package/lib/commands/init.js +12 -8
- package/lib/commands/internaltesting-functions-discover.js +1 -3
- package/lib/dataconnect/provisionCloudSql.js +3 -2
- package/lib/deploy/extensions/prepare.js +3 -1
- package/lib/deploy/functions/checkIam.js +1 -1
- package/lib/deploy/functions/functionsDeployHelper.js +8 -7
- package/lib/deploy/functions/params.js +15 -5
- package/lib/deploy/functions/prepare.js +9 -6
- package/lib/detectProjectRoot.js +1 -1
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/emulator/hubExport.js +5 -0
- package/lib/experiments.js +0 -7
- package/lib/firestore/api.js +15 -0
- package/lib/firestore/util.js +22 -1
- package/lib/frameworks/angular/index.js +1 -1
- package/lib/frameworks/flutter/index.js +1 -1
- package/lib/frameworks/next/index.js +1 -1
- package/lib/frameworks/nuxt/index.js +1 -1
- package/lib/frameworks/vite/index.js +5 -2
- package/lib/functions/projectConfig.js +5 -1
- package/lib/functions/secrets.js +14 -1
- package/lib/hosting/interactive.js +14 -19
- package/lib/init/features/dataconnect/index.js +21 -20
- package/lib/init/features/dataconnect/sdk.js +44 -21
- package/lib/init/features/functions/index.js +1 -0
- package/lib/init/features/hosting/index.js +96 -93
- package/lib/init/features/index.js +3 -2
- package/lib/init/index.js +7 -3
- package/lib/mcp/index.js +46 -18
- package/lib/mcp/prompt.js +4 -1
- package/lib/mcp/prompts/core/consult.js +1 -1
- package/lib/mcp/prompts/core/deploy.js +1 -1
- package/lib/mcp/prompts/core/init.js +1 -1
- package/lib/mcp/prompts/crashlytics/connect.js +1 -1
- package/lib/mcp/prompts/dataconnect/schema.js +1 -1
- package/lib/mcp/prompts/index.js +20 -10
- package/lib/mcp/resources/guides/init_backend.js +3 -26
- package/lib/mcp/resources/guides/init_hosting.js +15 -10
- package/lib/mcp/tool.js +17 -2
- package/lib/mcp/tools/apphosting/fetch_logs.js +1 -1
- package/lib/mcp/tools/apphosting/list_backends.js +1 -1
- package/lib/mcp/tools/auth/get_users.js +1 -1
- package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
- package/lib/mcp/tools/auth/update_user.js +1 -1
- package/lib/mcp/tools/core/create_android_sha.js +1 -1
- package/lib/mcp/tools/core/create_app.js +1 -1
- package/lib/mcp/tools/core/create_project.js +1 -1
- package/lib/mcp/tools/core/get_environment.js +1 -1
- package/lib/mcp/tools/core/get_project.js +1 -1
- package/lib/mcp/tools/core/get_sdk_config.js +1 -1
- package/lib/mcp/tools/core/get_security_rules.js +1 -1
- package/lib/mcp/tools/core/init.js +30 -2
- package/lib/mcp/tools/core/list_apps.js +1 -1
- package/lib/mcp/tools/core/list_projects.js +1 -1
- package/lib/mcp/tools/core/login.js +1 -1
- package/lib/mcp/tools/core/logout.js +1 -1
- package/lib/mcp/tools/core/read_resources.js +1 -1
- package/lib/mcp/tools/core/update_environment.js +1 -1
- package/lib/mcp/tools/core/validate_security_rules.js +15 -1
- package/lib/mcp/tools/crashlytics/events.js +3 -3
- package/lib/mcp/tools/crashlytics/issues.js +3 -3
- package/lib/mcp/tools/crashlytics/notes.js +4 -4
- package/lib/mcp/tools/crashlytics/reports.js +6 -6
- package/lib/mcp/tools/dataconnect/compile.js +1 -1
- package/lib/mcp/tools/dataconnect/execute.js +1 -1
- package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
- package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
- package/lib/mcp/tools/dataconnect/list_services.js +1 -1
- package/lib/mcp/tools/firestore/delete_document.js +1 -1
- package/lib/mcp/tools/firestore/get_documents.js +1 -1
- package/lib/mcp/tools/firestore/list_collections.js +1 -1
- package/lib/mcp/tools/firestore/query_collection.js +1 -1
- package/lib/mcp/tools/functions/get_logs.js +1 -1
- package/lib/mcp/tools/index.js +14 -4
- package/lib/mcp/tools/messaging/send_message.js +1 -1
- package/lib/mcp/tools/realtime_database/get_data.js +1 -1
- package/lib/mcp/tools/realtime_database/set_data.js +1 -1
- package/lib/mcp/tools/remoteconfig/get_template.js +1 -1
- package/lib/mcp/tools/remoteconfig/update_template.js +1 -1
- package/lib/mcp/tools/storage/get_download_url.js +1 -1
- package/lib/mcp/util/availability.js +22 -0
- package/lib/mcp/util/crashlytics/availability.js +81 -0
- package/lib/mcp/util.js +26 -6
- package/package.json +1 -1
- package/schema/firebase-config.json +3 -0
package/lib/appUtils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractAppIdentifiersAndroid = exports.extractAppIdentifierIos = exports.extractAppIdentifiersFlutter = exports.detectApps = exports.getPlatformsFromFolder = exports.appDescription = exports.Framework = exports.Platform = void 0;
|
|
3
|
+
exports.detectFiles = exports.extractAppIdentifiersAndroid = exports.extractAppIdentifierIos = exports.extractAppIdentifiersFlutter = exports.detectApps = exports.getPlatformsFromFolder = exports.appDescription = exports.Framework = exports.Platform = void 0;
|
|
4
4
|
const fs = require("fs-extra");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const glob_1 = require("glob");
|
|
@@ -228,3 +228,4 @@ async function detectFiles(dirPath, filePattern) {
|
|
|
228
228
|
};
|
|
229
229
|
return (0, glob_1.glob)(`**/${filePattern}`, options);
|
|
230
230
|
}
|
|
231
|
+
exports.detectFiles = detectFiles;
|
package/lib/command.js
CHANGED
|
@@ -106,9 +106,8 @@ class Command {
|
|
|
106
106
|
const trackSuccess = (0, track_1.trackGA4)("command_execution", {
|
|
107
107
|
command_name: this.name,
|
|
108
108
|
result: "success",
|
|
109
|
-
duration,
|
|
110
109
|
interactive: (0, utils_1.getInheritedOption)(options, "nonInteractive") ? "false" : "true",
|
|
111
|
-
});
|
|
110
|
+
}, duration);
|
|
112
111
|
if (!isEmulator) {
|
|
113
112
|
await (0, utils_1.withTimeout)(5000, trackSuccess);
|
|
114
113
|
}
|
|
@@ -154,21 +153,18 @@ class Command {
|
|
|
154
153
|
async prepare(options) {
|
|
155
154
|
options = options || {};
|
|
156
155
|
options.project = (0, utils_1.getInheritedOption)(options, "project");
|
|
157
|
-
if (!process.stdin.isTTY ||
|
|
156
|
+
if (!process.stdin.isTTY ||
|
|
157
|
+
(0, utils_1.getInheritedOption)(options, "nonInteractive") ||
|
|
158
|
+
(0, utils_1.getInheritedOption)(options, "json")) {
|
|
158
159
|
options.nonInteractive = true;
|
|
159
160
|
}
|
|
160
161
|
if ((0, utils_1.getInheritedOption)(options, "interactive")) {
|
|
161
|
-
options.interactive = true;
|
|
162
162
|
options.nonInteractive = false;
|
|
163
163
|
}
|
|
164
164
|
if ((0, utils_1.getInheritedOption)(options, "debug")) {
|
|
165
165
|
options.debug = true;
|
|
166
166
|
}
|
|
167
|
-
if ((0, utils_1.getInheritedOption)(options, "json")) {
|
|
168
|
-
options.interactive = false;
|
|
169
|
-
options.nonInteractive = true;
|
|
170
|
-
}
|
|
171
|
-
else if (!options.isMCP) {
|
|
167
|
+
if (!(0, utils_1.getInheritedOption)(options, "json") && !options.isMCP) {
|
|
172
168
|
(0, logger_1.useConsoleLoggers)();
|
|
173
169
|
}
|
|
174
170
|
if ((0, utils_1.getInheritedOption)(options, "config")) {
|
|
@@ -6,28 +6,80 @@ const command_1 = require("../command");
|
|
|
6
6
|
const dataconnectEmulator_1 = require("../emulator/dataconnectEmulator");
|
|
7
7
|
const projectUtils_1 = require("../projectUtils");
|
|
8
8
|
const load_1 = require("../dataconnect/load");
|
|
9
|
-
const logger_1 = require("../logger");
|
|
10
9
|
const auth_1 = require("../auth");
|
|
11
10
|
const utils_1 = require("../utils");
|
|
11
|
+
const config_1 = require("../config");
|
|
12
|
+
const dataconnectInit = require("../init/features/dataconnect");
|
|
13
|
+
const dataconnectSdkInit = require("../init/features/dataconnect/sdk");
|
|
14
|
+
const error_1 = require("../error");
|
|
15
|
+
const init_1 = require("./init");
|
|
16
|
+
const hub_1 = require("../emulator/hub");
|
|
12
17
|
exports.command = new command_1.Command("dataconnect:sdk:generate")
|
|
13
18
|
.description("generate typed SDKs for your Data Connect connectors")
|
|
14
19
|
.option("--watch", "watch for changes to your connector GQL files and regenerate your SDKs when updates occur")
|
|
15
20
|
.action(async (options) => {
|
|
16
|
-
const projectId = (0, projectUtils_1.
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
22
|
+
let justRanInit = false;
|
|
23
|
+
let config = options.config;
|
|
24
|
+
if (!config || !config.has("dataconnect")) {
|
|
25
|
+
if (options.nonInteractive) {
|
|
26
|
+
throw new error_1.FirebaseError(`No dataconnect project directory found. Please run ${clc.bold("firebase init dataconnect")} to set it up first.`);
|
|
27
|
+
}
|
|
28
|
+
(0, utils_1.logWarning)("No dataconnect project directory found.");
|
|
29
|
+
(0, utils_1.logBullet)(`Running ${clc.bold("firebase init dataconnect")} to setup a dataconnect project directory.`);
|
|
30
|
+
if (!config) {
|
|
31
|
+
const cwd = options.cwd || process.cwd();
|
|
32
|
+
config = new config_1.Config({}, { projectDir: cwd, cwd: cwd });
|
|
33
|
+
}
|
|
34
|
+
const setup = {
|
|
35
|
+
config: config.src,
|
|
36
|
+
projectId: projectId,
|
|
37
|
+
rcfile: options.rc.data,
|
|
38
|
+
featureInfo: {
|
|
39
|
+
dataconnectSource: "gen_sdk_init",
|
|
40
|
+
},
|
|
41
|
+
instructions: [],
|
|
42
|
+
};
|
|
43
|
+
await dataconnectInit.askQuestions(setup);
|
|
44
|
+
await dataconnectInit.actuate(setup, config, options);
|
|
45
|
+
await (0, init_1.postInitSaves)(setup, config);
|
|
46
|
+
justRanInit = true;
|
|
47
|
+
options.config = config;
|
|
48
|
+
}
|
|
49
|
+
let serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config);
|
|
50
|
+
if (!serviceInfosWithSDKs.length) {
|
|
51
|
+
if (justRanInit || options.nonInteractive) {
|
|
52
|
+
throw new error_1.FirebaseError(`No generated SDKs are configured during init. Please run ${clc.bold("firebase init dataconnect:sdk")} to configure a generated SDK.`);
|
|
53
|
+
}
|
|
54
|
+
(0, utils_1.logWarning)("No generated SDKs have been configured.");
|
|
55
|
+
(0, utils_1.logBullet)(`Running ${clc.bold("firebase init dataconnect:sdk")} to configure a generated SDK.`);
|
|
56
|
+
const setup = {
|
|
57
|
+
config: config.src,
|
|
58
|
+
projectId: projectId,
|
|
59
|
+
rcfile: options.rc.data,
|
|
60
|
+
featureInfo: {
|
|
61
|
+
dataconnectSource: "gen_sdk_init_sdk",
|
|
62
|
+
},
|
|
63
|
+
instructions: [],
|
|
64
|
+
};
|
|
65
|
+
await dataconnectSdkInit.askQuestions(setup);
|
|
66
|
+
await dataconnectSdkInit.actuate(setup, config);
|
|
67
|
+
justRanInit = true;
|
|
68
|
+
serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config);
|
|
69
|
+
}
|
|
70
|
+
await generateSDKsInAll(options, serviceInfosWithSDKs, justRanInit);
|
|
71
|
+
});
|
|
72
|
+
async function loadAllWithSDKs(projectId, config) {
|
|
73
|
+
const serviceInfos = await (0, load_1.loadAll)(projectId || hub_1.EmulatorHub.MISSING_PROJECT_PLACEHOLDER, config);
|
|
74
|
+
return serviceInfos.filter((serviceInfo) => serviceInfo.connectorInfo.some((c) => {
|
|
19
75
|
var _a, _b, _c, _d;
|
|
20
76
|
return (((_a = c.connectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk) ||
|
|
21
77
|
((_b = c.connectorYaml.generate) === null || _b === void 0 ? void 0 : _b.kotlinSdk) ||
|
|
22
78
|
((_c = c.connectorYaml.generate) === null || _c === void 0 ? void 0 : _c.swiftSdk) ||
|
|
23
79
|
((_d = c.connectorYaml.generate) === null || _d === void 0 ? void 0 : _d.dartSdk));
|
|
24
80
|
}));
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
logger_1.logger.warn(`Run ${clc.bold("firebase init dataconnect:sdk")} to configure a generated SDK.`);
|
|
28
|
-
logger_1.logger.warn(`See https://firebase.google.com/docs/data-connect/web-sdk for more details of how to configure generated SDKs.`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
81
|
+
}
|
|
82
|
+
async function generateSDKsInAll(options, serviceInfosWithSDKs, justRanInit) {
|
|
31
83
|
async function generateSDK(serviceInfo) {
|
|
32
84
|
return dataconnectEmulator_1.DataConnectEmulator.generate({
|
|
33
85
|
configDir: serviceInfo.sourceDirectory,
|
|
@@ -39,10 +91,13 @@ exports.command = new command_1.Command("dataconnect:sdk:generate")
|
|
|
39
91
|
await Promise.race(serviceInfosWithSDKs.map(generateSDK));
|
|
40
92
|
}
|
|
41
93
|
else {
|
|
94
|
+
if (justRanInit) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
42
97
|
for (const s of serviceInfosWithSDKs) {
|
|
43
98
|
await generateSDK(s);
|
|
44
99
|
}
|
|
45
100
|
const services = serviceInfosWithSDKs.map((s) => s.dataConnectYaml.serviceId).join(", ");
|
|
46
101
|
(0, utils_1.logLabeledSuccess)("dataconnect", `Successfully Generated SDKs for services: ${clc.bold(services)}`);
|
|
47
102
|
}
|
|
48
|
-
}
|
|
103
|
+
}
|
package/lib/commands/deploy.js
CHANGED
|
@@ -15,6 +15,7 @@ const error_1 = require("../error");
|
|
|
15
15
|
const colorette_1 = require("colorette");
|
|
16
16
|
const interactive_1 = require("../hosting/interactive");
|
|
17
17
|
const utils_1 = require("../utils");
|
|
18
|
+
const api_1 = require("../hosting/api");
|
|
18
19
|
exports.VALID_DEPLOY_TARGETS = [
|
|
19
20
|
"database",
|
|
20
21
|
"storage",
|
|
@@ -107,7 +108,7 @@ exports.command = new command_1.Command("deploy")
|
|
|
107
108
|
await (0, requireDatabaseInstance_1.requireDatabaseInstance)(options);
|
|
108
109
|
}
|
|
109
110
|
if (options.filteredTargets.includes("hosting")) {
|
|
110
|
-
let
|
|
111
|
+
let shouldCreateSite = false;
|
|
111
112
|
try {
|
|
112
113
|
await (0, requireHostingSite_1.requireHostingSite)(options);
|
|
113
114
|
}
|
|
@@ -119,17 +120,18 @@ exports.command = new command_1.Command("deploy")
|
|
|
119
120
|
throw err;
|
|
120
121
|
}
|
|
121
122
|
else if (err === getDefaultHostingSite_1.errNoDefaultSite) {
|
|
122
|
-
|
|
123
|
+
shouldCreateSite = true;
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
|
-
if (!
|
|
126
|
+
if (!shouldCreateSite) {
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
128
129
|
if (options.nonInteractive) {
|
|
129
130
|
throw new error_1.FirebaseError(`Unable to deploy to Hosting as there is no Hosting site. Use ${(0, colorette_1.bold)("firebase hosting:sites:create")} to create a site.`);
|
|
130
131
|
}
|
|
131
132
|
(0, utils_1.logBullet)("No Hosting site detected.");
|
|
132
|
-
await (0, interactive_1.
|
|
133
|
+
const siteId = await (0, interactive_1.pickHostingSiteName)("", options);
|
|
134
|
+
await (0, api_1.createSite)(options.project, siteId);
|
|
133
135
|
}
|
|
134
136
|
})
|
|
135
137
|
.before(checkValidTargetFilters_1.checkValidTargetFilters)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const clc = require("colorette");
|
|
5
|
+
const command_1 = require("../command");
|
|
6
|
+
const fsi = require("../firestore/api");
|
|
7
|
+
const util_1 = require("../firestore/util");
|
|
8
|
+
const logger_1 = require("../logger");
|
|
9
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
10
|
+
const types_1 = require("../emulator/types");
|
|
11
|
+
const commandUtils_1 = require("../emulator/commandUtils");
|
|
12
|
+
const options_1 = require("../firestore/options");
|
|
13
|
+
const pretty_print_1 = require("../firestore/pretty-print");
|
|
14
|
+
const error_1 = require("../error");
|
|
15
|
+
exports.command = new command_1.Command("firestore:databases:clone <sourceDatabase> <targetDatabase>")
|
|
16
|
+
.description("clone one Firestore database to another")
|
|
17
|
+
.option("-e, --encryption-type <encryptionType>", `encryption method of the cloned database; one of ${options_1.EncryptionType.USE_SOURCE_ENCRYPTION} (default), ` +
|
|
18
|
+
`${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}, ${options_1.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION}`)
|
|
19
|
+
.option("-k, --kms-key-name <kmsKeyName>", "resource ID of the Cloud KMS key to encrypt the cloned database. This " +
|
|
20
|
+
"feature is allowlist only in initial launch")
|
|
21
|
+
.option("-s, --snapshot-time <snapshotTime>", "snapshot time of the source database to use, in ISO 8601 format. Can be any minutely snapshot after the database's earliest version time. If unspecified, takes the most recent available snapshot")
|
|
22
|
+
.before(requirePermissions_1.requirePermissions, ["datastore.databases.clone"])
|
|
23
|
+
.before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.FIRESTORE)
|
|
24
|
+
.action(async (sourceDatabase, targetDatabase, options) => {
|
|
25
|
+
const api = new fsi.FirestoreApi();
|
|
26
|
+
const printer = new pretty_print_1.PrettyPrint();
|
|
27
|
+
const helpCommandText = "See firebase firestore:databases:clone --help for more info.";
|
|
28
|
+
if (options.database) {
|
|
29
|
+
throw new error_1.FirebaseError(`--database is not a supported flag for 'firestoree:databases:clone'. ${helpCommandText}`);
|
|
30
|
+
}
|
|
31
|
+
let snapshotTime;
|
|
32
|
+
if (options.snapshotTime) {
|
|
33
|
+
snapshotTime = options.snapshotTime;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
snapshotTime = (0, util_1.getCurrentMinuteAsIsoString)();
|
|
37
|
+
}
|
|
38
|
+
let encryptionConfig = undefined;
|
|
39
|
+
switch (options.encryptionType) {
|
|
40
|
+
case options_1.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION:
|
|
41
|
+
throwIfKmsKeyNameIsSet(options.kmsKeyName);
|
|
42
|
+
encryptionConfig = { googleDefaultEncryption: {} };
|
|
43
|
+
break;
|
|
44
|
+
case options_1.EncryptionType.USE_SOURCE_ENCRYPTION:
|
|
45
|
+
throwIfKmsKeyNameIsSet(options.kmsKeyName);
|
|
46
|
+
encryptionConfig = { useSourceEncryption: {} };
|
|
47
|
+
break;
|
|
48
|
+
case options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION:
|
|
49
|
+
encryptionConfig = {
|
|
50
|
+
customerManagedEncryption: { kmsKeyName: getKmsKeyOrThrow(options.kmsKeyName) },
|
|
51
|
+
};
|
|
52
|
+
break;
|
|
53
|
+
case undefined:
|
|
54
|
+
throwIfKmsKeyNameIsSet(options.kmsKeyName);
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
throw new error_1.FirebaseError(`Invalid value for flag --encryption-type. ${helpCommandText}`);
|
|
58
|
+
}
|
|
59
|
+
const targetDatabaseName = (0, util_1.parseDatabaseName)(targetDatabase);
|
|
60
|
+
const parentProject = targetDatabaseName.projectId;
|
|
61
|
+
const targetDatabaseId = targetDatabaseName.databaseId;
|
|
62
|
+
const sourceProject = (0, util_1.parseDatabaseName)(sourceDatabase).projectId;
|
|
63
|
+
if (parentProject !== sourceProject) {
|
|
64
|
+
throw new error_1.FirebaseError(`Cloning across projects is not supported.`);
|
|
65
|
+
}
|
|
66
|
+
const lro = await api.cloneDatabase(sourceProject, {
|
|
67
|
+
database: sourceDatabase,
|
|
68
|
+
snapshotTime,
|
|
69
|
+
}, targetDatabaseId, encryptionConfig);
|
|
70
|
+
if (lro.error) {
|
|
71
|
+
logger_1.logger.error(clc.bold(`Clone to ${printer.prettyDatabaseString(targetDatabase)} failed. See below for details.`));
|
|
72
|
+
printer.prettyPrintOperation(lro);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
logger_1.logger.info(clc.bold(`Successfully initiated clone to ${printer.prettyDatabaseString(targetDatabase)}`));
|
|
76
|
+
logger_1.logger.info("Please be sure to configure Firebase rules in your Firebase config file for\n" +
|
|
77
|
+
"the new database. By default, created databases will have closed rules that\n" +
|
|
78
|
+
"block any incoming third-party traffic.");
|
|
79
|
+
logger_1.logger.info();
|
|
80
|
+
logger_1.logger.info(`You can monitor the progress of this clone by executing this command:`);
|
|
81
|
+
logger_1.logger.info();
|
|
82
|
+
logger_1.logger.info(`firebase firestore:operations:describe --database="${targetDatabaseId}" ${lro.name}`);
|
|
83
|
+
logger_1.logger.info();
|
|
84
|
+
logger_1.logger.info(`Once the clone is complete, your database may be viewed at ${printer.firebaseConsoleDatabaseUrl(options.project, targetDatabaseId)}`);
|
|
85
|
+
}
|
|
86
|
+
return lro;
|
|
87
|
+
function throwIfKmsKeyNameIsSet(kmsKeyName) {
|
|
88
|
+
if (kmsKeyName) {
|
|
89
|
+
throw new error_1.FirebaseError("--kms-key-name can only be set when specifying an --encryption-type " +
|
|
90
|
+
`of ${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function getKmsKeyOrThrow(kmsKeyName) {
|
|
94
|
+
if (kmsKeyName)
|
|
95
|
+
return kmsKeyName;
|
|
96
|
+
throw new error_1.FirebaseError("--kms-key-name must be provided when specifying an --encryption-type " +
|
|
97
|
+
`of ${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}.`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.command = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
|
+
const tty = require("tty");
|
|
5
6
|
const logger_1 = require("../logger");
|
|
7
|
+
const error_1 = require("../error");
|
|
6
8
|
const secrets_1 = require("../functions/secrets");
|
|
7
9
|
const command_1 = require("../command");
|
|
8
10
|
const requirePermissions_1 = require("../requirePermissions");
|
|
@@ -26,12 +28,28 @@ exports.command = new command_1.Command("functions:secrets:set <KEY>")
|
|
|
26
28
|
"secretmanager.versions.add",
|
|
27
29
|
])
|
|
28
30
|
.option("--data-file <dataFile>", 'file path from which to read secret data. Set to "-" to read the secret data from stdin')
|
|
31
|
+
.option("--format <format>", "format of the secret value. 'string' (default) or 'json'")
|
|
29
32
|
.action(async (unvalidatedKey, options) => {
|
|
30
33
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
31
34
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
32
35
|
const key = await (0, secrets_1.ensureValidKey)(unvalidatedKey, options);
|
|
33
36
|
const secret = await (0, secrets_1.ensureSecret)(projectId, key, options);
|
|
34
|
-
|
|
37
|
+
let format = options.format;
|
|
38
|
+
const dataFile = options.dataFile;
|
|
39
|
+
if (!format && dataFile && dataFile !== "-") {
|
|
40
|
+
if (dataFile.endsWith(".json")) {
|
|
41
|
+
format = "json";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!dataFile && tty.isatty(0) && options.nonInteractive) {
|
|
45
|
+
throw new error_1.FirebaseError(`Cannot prompt for secret value in non-interactive mode.\n` +
|
|
46
|
+
`Use --data-file to provide the secret value from a file.`);
|
|
47
|
+
}
|
|
48
|
+
const promptSuffix = format === "json" ? " (JSON format)" : "";
|
|
49
|
+
const secretValue = await (0, utils_1.readSecretValue)(`Enter a value for ${key}${promptSuffix}:`, dataFile);
|
|
50
|
+
if (format === "json") {
|
|
51
|
+
(0, secrets_1.validateJsonSecret)(key, secretValue);
|
|
52
|
+
}
|
|
35
53
|
const secretVersion = await (0, secretManager_1.addVersion)(projectId, key, secretValue);
|
|
36
54
|
(0, utils_1.logSuccess)(`Created a new secret version ${(0, secretManager_1.toSecretVersionResourceName)(secretVersion)}`);
|
|
37
55
|
if (!(0, secretManager_1.isFunctionsManaged)(secret)) {
|
|
@@ -8,6 +8,7 @@ const utils_1 = require("../utils");
|
|
|
8
8
|
const logger_1 = require("../logger");
|
|
9
9
|
const projectUtils_1 = require("../projectUtils");
|
|
10
10
|
const requirePermissions_1 = require("../requirePermissions");
|
|
11
|
+
const api_1 = require("../hosting/api");
|
|
11
12
|
const error_1 = require("../error");
|
|
12
13
|
const LOG_TAG = "hosting:sites";
|
|
13
14
|
exports.command = new command_1.Command("hosting:sites:create [siteId]")
|
|
@@ -18,10 +19,10 @@ exports.command = new command_1.Command("hosting:sites:create [siteId]")
|
|
|
18
19
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
19
20
|
const appId = options.app;
|
|
20
21
|
if (options.nonInteractive && !siteId) {
|
|
21
|
-
throw new error_1.FirebaseError(`${(0, colorette_1.bold)(siteId)} is required in a non-interactive environment`);
|
|
22
|
+
throw new error_1.FirebaseError(`${(0, colorette_1.bold)("siteId")} is required in a non-interactive environment`);
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
siteId = await (0, interactive_1.pickHostingSiteName)(siteId !== null && siteId !== void 0 ? siteId : "", options);
|
|
25
|
+
const site = await (0, api_1.createSite)(projectId, siteId, appId);
|
|
25
26
|
logger_1.logger.info();
|
|
26
27
|
(0, utils_1.logLabeledSuccess)(LOG_TAG, `Site ${(0, colorette_1.bold)(siteId)} has been created in project ${(0, colorette_1.bold)(projectId)}.`);
|
|
27
28
|
if (appId) {
|
package/lib/commands/index.js
CHANGED
|
@@ -110,6 +110,7 @@ function load(client) {
|
|
|
110
110
|
client.firestore.databases.update = loadCommand("firestore-databases-update");
|
|
111
111
|
client.firestore.databases.delete = loadCommand("firestore-databases-delete");
|
|
112
112
|
client.firestore.databases.restore = loadCommand("firestore-databases-restore");
|
|
113
|
+
client.firestore.databases.clone = loadCommand("firestore-databases-clone");
|
|
113
114
|
client.firestore.backups = {};
|
|
114
115
|
client.firestore.backups.schedules = {};
|
|
115
116
|
client.firestore.backups.list = loadCommand("firestore-backups-list");
|
package/lib/commands/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.initAction = exports.command = void 0;
|
|
3
|
+
exports.postInitSaves = exports.initAction = exports.command = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const os = require("os");
|
|
6
6
|
const path = require("path");
|
|
@@ -217,6 +217,16 @@ async function initAction(feature, options) {
|
|
|
217
217
|
setup.features = setup.features.filter((f) => f !== "dataconnect:sdk");
|
|
218
218
|
}
|
|
219
219
|
await (0, init_1.init)(setup, config, options);
|
|
220
|
+
await postInitSaves(setup, config);
|
|
221
|
+
if (setup.instructions.length) {
|
|
222
|
+
logger_1.logger.info(`\n${clc.bold("To get started:")}\n`);
|
|
223
|
+
for (const i of setup.instructions) {
|
|
224
|
+
(0, utils_1.logBullet)(i + "\n");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
exports.initAction = initAction;
|
|
229
|
+
async function postInitSaves(setup, config) {
|
|
220
230
|
logger_1.logger.info();
|
|
221
231
|
config.writeProjectFile("firebase.json", setup.config);
|
|
222
232
|
config.writeProjectFile(".firebaserc", setup.rcfile);
|
|
@@ -225,11 +235,5 @@ async function initAction(feature, options) {
|
|
|
225
235
|
}
|
|
226
236
|
logger_1.logger.info();
|
|
227
237
|
utils.logSuccess("Firebase initialization complete!");
|
|
228
|
-
if (setup.instructions.length) {
|
|
229
|
-
logger_1.logger.info(`\n${clc.bold("To get started:")}\n`);
|
|
230
|
-
for (const i of setup.instructions) {
|
|
231
|
-
(0, utils_1.logBullet)(i + "\n");
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
238
|
}
|
|
235
|
-
exports.
|
|
239
|
+
exports.postInitSaves = postInitSaves;
|
|
@@ -10,7 +10,6 @@ const projectUtils_1 = require("../projectUtils");
|
|
|
10
10
|
const error_1 = require("../error");
|
|
11
11
|
const ensureApiEnabled = require("../ensureApiEnabled");
|
|
12
12
|
const api_1 = require("../api");
|
|
13
|
-
const experiments = require("../experiments");
|
|
14
13
|
const prepareFunctionsUpload_1 = require("../deploy/functions/prepareFunctionsUpload");
|
|
15
14
|
exports.command = new command_1.Command("internaltesting:functions:discover")
|
|
16
15
|
.description("discover function triggers defined in the current project directory")
|
|
@@ -22,8 +21,7 @@ exports.command = new command_1.Command("internaltesting:functions:discover")
|
|
|
22
21
|
throw new error_1.FirebaseError("Admin SDK config unexpectedly undefined - have you run firebase init?");
|
|
23
22
|
}
|
|
24
23
|
let runtimeConfig = { firebase: firebaseConfig };
|
|
25
|
-
|
|
26
|
-
if (allowFunctionsConfig) {
|
|
24
|
+
if (fnConfig.some(projectConfig_1.shouldUseRuntimeConfig)) {
|
|
27
25
|
try {
|
|
28
26
|
const runtimeConfigApiEnabled = await ensureApiEnabled.check(projectId, (0, api_1.runtimeconfigOrigin)(), "runtimeconfig", true);
|
|
29
27
|
if (runtimeConfigApiEnabled) {
|
|
@@ -22,7 +22,7 @@ async function setupCloudSql(args) {
|
|
|
22
22
|
}
|
|
23
23
|
finally {
|
|
24
24
|
if (!dryRun) {
|
|
25
|
-
|
|
25
|
+
void (0, track_1.trackGA4)("dataconnect_cloud_sql", {
|
|
26
26
|
source: args.source,
|
|
27
27
|
action: success ? stats.action : `${stats.action}_error`,
|
|
28
28
|
location: args.location,
|
|
@@ -44,7 +44,8 @@ async function upsertInstance(stats, args) {
|
|
|
44
44
|
const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
|
|
45
45
|
utils.logLabeledBullet("dataconnect", `Found existing Cloud SQL instance ${clc.bold(instanceId)}.`);
|
|
46
46
|
stats.databaseVersion = existingInstance.databaseVersion;
|
|
47
|
-
stats.dataconnectLabel =
|
|
47
|
+
stats.dataconnectLabel =
|
|
48
|
+
((_b = (_a = existingInstance.settings) === null || _a === void 0 ? void 0 : _a.userLabels) === null || _b === void 0 ? void 0 : _b["firebase-data-connect"]) || "absent";
|
|
48
49
|
const why = getUpdateReason(existingInstance, requireGoogleMlIntegration);
|
|
49
50
|
if (why) {
|
|
50
51
|
if (dryRun) {
|
|
@@ -18,6 +18,7 @@ const v2FunctionHelper_1 = require("./v2FunctionHelper");
|
|
|
18
18
|
const tos_1 = require("../../extensions/tos");
|
|
19
19
|
const common_1 = require("../../extensions/runtimes/common");
|
|
20
20
|
const functionsDeployHelper_1 = require("../functions/functionsDeployHelper");
|
|
21
|
+
const projectConfig_1 = require("../../functions/projectConfig");
|
|
21
22
|
const matchesInstanceId = (dep) => (test) => {
|
|
22
23
|
return dep.instanceId === test.instanceId;
|
|
23
24
|
};
|
|
@@ -117,7 +118,8 @@ async function prepareHelper(context, options, payload, wantExtensions, haveExte
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
async function prepareDynamicExtensions(context, options, payload, builds) {
|
|
120
|
-
const
|
|
121
|
+
const functionsConfig = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
|
|
122
|
+
const filters = (0, functionsDeployHelper_1.getEndpointFilters)(options, functionsConfig);
|
|
121
123
|
const extensions = (0, common_1.extractExtensionsFromBuilds)(builds, filters);
|
|
122
124
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
123
125
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
@@ -44,7 +44,7 @@ async function checkHttpIam(context, options, payload) {
|
|
|
44
44
|
if (!payload.functions) {
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
const filters = context.filters || (0, functionsDeployHelper_1.getEndpointFilters)(options);
|
|
47
|
+
const filters = context.filters || (0, functionsDeployHelper_1.getEndpointFilters)(options, context.config);
|
|
48
48
|
const wantBackends = Object.values(payload.functions).map(({ wantBackend }) => wantBackend);
|
|
49
49
|
const httpEndpoints = [...(0, functional_1.flattenArray)(wantBackends.map((b) => backend.allEndpoints(b)))]
|
|
50
50
|
.filter(backend.isHttpsTriggered)
|
|
@@ -32,13 +32,14 @@ function endpointMatchesFilter(endpoint, filter) {
|
|
|
32
32
|
return true;
|
|
33
33
|
}
|
|
34
34
|
exports.endpointMatchesFilter = endpointMatchesFilter;
|
|
35
|
-
function parseFunctionSelector(selector) {
|
|
35
|
+
function parseFunctionSelector(selector, config) {
|
|
36
36
|
const fragments = selector.split(":");
|
|
37
37
|
if (fragments.length < 2) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{ codebase:
|
|
41
|
-
|
|
38
|
+
const codebaseNames = config.map((c) => c.codebase);
|
|
39
|
+
if (codebaseNames.includes(fragments[0])) {
|
|
40
|
+
return [{ codebase: fragments[0] }];
|
|
41
|
+
}
|
|
42
|
+
return [{ codebase: projectConfig_1.DEFAULT_CODEBASE, idChunks: fragments[0].split(/[-.]/) }];
|
|
42
43
|
}
|
|
43
44
|
return [
|
|
44
45
|
{
|
|
@@ -48,7 +49,7 @@ function parseFunctionSelector(selector) {
|
|
|
48
49
|
];
|
|
49
50
|
}
|
|
50
51
|
exports.parseFunctionSelector = parseFunctionSelector;
|
|
51
|
-
function getEndpointFilters(options) {
|
|
52
|
+
function getEndpointFilters(options, config) {
|
|
52
53
|
if (!options.only) {
|
|
53
54
|
return undefined;
|
|
54
55
|
}
|
|
@@ -58,7 +59,7 @@ function getEndpointFilters(options) {
|
|
|
58
59
|
if (selector.startsWith("functions:")) {
|
|
59
60
|
selector = selector.replace("functions:", "");
|
|
60
61
|
if (selector.length > 0) {
|
|
61
|
-
filters.push(...parseFunctionSelector(selector));
|
|
62
|
+
filters.push(...parseFunctionSelector(selector, config));
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
}
|
|
@@ -4,6 +4,7 @@ exports.resolveParams = exports.ParamValue = exports.isMultiSelectInput = export
|
|
|
4
4
|
const logger_1 = require("../../logger");
|
|
5
5
|
const error_1 = require("../../error");
|
|
6
6
|
const prompt_1 = require("../../prompt");
|
|
7
|
+
const secrets_1 = require("../../functions/secrets");
|
|
7
8
|
const functional_1 = require("../../functional");
|
|
8
9
|
const secretManager = require("../../gcp/secretManager");
|
|
9
10
|
const storage_1 = require("../../gcp/storage");
|
|
@@ -169,11 +170,11 @@ async function resolveParams(params, firebaseConfig, userEnvs, nonInteractive, i
|
|
|
169
170
|
const [needSecret, needPrompt] = (0, functional_1.partition)(outstanding, (param) => param.type === "secret");
|
|
170
171
|
if (!isEmulator) {
|
|
171
172
|
for (const param of needSecret) {
|
|
172
|
-
await handleSecret(param, firebaseConfig.projectId);
|
|
173
|
+
await handleSecret(param, firebaseConfig.projectId, nonInteractive);
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
176
|
if (nonInteractive && needPrompt.length > 0) {
|
|
176
|
-
const envNames =
|
|
177
|
+
const envNames = needPrompt.map((p) => p.name).join(", ");
|
|
177
178
|
throw new error_1.FirebaseError(`In non-interactive mode but have no value for the following environment variables: ${envNames}\n` +
|
|
178
179
|
"To continue, either run `firebase deploy` with an interactive terminal, or add values to a dotenv file. " +
|
|
179
180
|
"For information regarding how to use dotenv files, see https://firebase.google.com/docs/functions/config-env");
|
|
@@ -220,15 +221,24 @@ function populateDefaultParams(config) {
|
|
|
220
221
|
}
|
|
221
222
|
return defaultParams;
|
|
222
223
|
}
|
|
223
|
-
async function handleSecret(secretParam, projectId) {
|
|
224
|
+
async function handleSecret(secretParam, projectId, nonInteractive) {
|
|
224
225
|
const metadata = await secretManager.getSecretMetadata(projectId, secretParam.name, "latest");
|
|
225
226
|
if (!metadata.secret) {
|
|
227
|
+
if (nonInteractive) {
|
|
228
|
+
throw new error_1.FirebaseError(`In non-interactive mode but have no value for the secret: ${secretParam.name}\n\n` +
|
|
229
|
+
"Set this secret before deploying:\n" +
|
|
230
|
+
`\tfirebase functions:secrets:set ${secretParam.name}${secretParam.format === "json" ? " --format=json --data-file <file.json>" : ""}`);
|
|
231
|
+
}
|
|
232
|
+
const promptMessage = `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${secretParam.name}. Enter ${secretParam.format === "json" ? "a JSON value" : "a value"} for ${secretParam.label || secretParam.name}:`;
|
|
226
233
|
const secretValue = await (0, prompt_1.password)({
|
|
227
|
-
message:
|
|
234
|
+
message: promptMessage,
|
|
228
235
|
});
|
|
236
|
+
if (secretParam.format === "json") {
|
|
237
|
+
(0, secrets_1.validateJsonSecret)(secretParam.name, secretValue);
|
|
238
|
+
}
|
|
229
239
|
await secretManager.createSecret(projectId, secretParam.name, (0, secretManager_1.labels)());
|
|
230
240
|
await secretManager.addVersion(projectId, secretParam.name, secretValue);
|
|
231
|
-
return
|
|
241
|
+
return;
|
|
232
242
|
}
|
|
233
243
|
else if (!metadata.secretVersion) {
|
|
234
244
|
throw new error_1.FirebaseError(`Cloud Secret Manager has no latest version of the secret defined by param ${secretParam.label || secretParam.name}`);
|
|
@@ -12,7 +12,6 @@ const runtimes = require("./runtimes");
|
|
|
12
12
|
const supported = require("./runtimes/supported");
|
|
13
13
|
const validate = require("./validate");
|
|
14
14
|
const ensure = require("./ensure");
|
|
15
|
-
const experiments = require("../../experiments");
|
|
16
15
|
const api_1 = require("../../api");
|
|
17
16
|
const functionsDeployHelper_1 = require("./functionsDeployHelper");
|
|
18
17
|
const utils_1 = require("../../utils");
|
|
@@ -37,7 +36,7 @@ async function prepare(context, options, payload) {
|
|
|
37
36
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
38
37
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
39
38
|
context.config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
|
|
40
|
-
context.filters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
|
|
39
|
+
context.filters = (0, functionsDeployHelper_1.getEndpointFilters)(options, context.config);
|
|
41
40
|
const codebases = (0, functionsDeployHelper_1.targetCodebases)(context.config, context.filters);
|
|
42
41
|
if (codebases.length === 0) {
|
|
43
42
|
throw new error_1.FirebaseError("No function matches given --only filters. Aborting deployment.");
|
|
@@ -55,8 +54,8 @@ async function prepare(context, options, payload) {
|
|
|
55
54
|
context.firebaseConfig = firebaseConfig;
|
|
56
55
|
context.codebaseDeployEvents = {};
|
|
57
56
|
let runtimeConfig = { firebase: firebaseConfig };
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
57
|
+
const targetedCodebaseConfigs = context.config.filter((cfg) => codebases.includes(cfg.codebase));
|
|
58
|
+
if (checkAPIsEnabled[1] && targetedCodebaseConfigs.some(projectConfig_1.shouldUseRuntimeConfig)) {
|
|
60
59
|
runtimeConfig = Object.assign(Object.assign({}, runtimeConfig), (await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId)));
|
|
61
60
|
}
|
|
62
61
|
context.hasRuntimeConfig = Object.keys(runtimeConfig).some((k) => k !== "firebase");
|
|
@@ -157,7 +156,8 @@ async function prepare(context, options, payload) {
|
|
|
157
156
|
source.functionsSourceV2Hash = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.hash;
|
|
158
157
|
}
|
|
159
158
|
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
|
|
160
|
-
const
|
|
159
|
+
const configForUpload = (0, projectConfig_1.shouldUseRuntimeConfig)(localCfg) ? runtimeConfig : undefined;
|
|
160
|
+
const packagedSource = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, localCfg, configForUpload);
|
|
161
161
|
source.functionsSourceV1 = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.pathToSource;
|
|
162
162
|
source.functionsSourceV1Hash = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.hash;
|
|
163
163
|
}
|
|
@@ -314,7 +314,10 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
|
|
|
314
314
|
await runtimeDelegate.build();
|
|
315
315
|
const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
|
|
316
316
|
(0, utils_1.logLabeledBullet)("functions", `Loading and analyzing source code for codebase ${codebase} to determine what to deploy`);
|
|
317
|
-
const
|
|
317
|
+
const codebaseRuntimeConfig = (0, projectConfig_1.shouldUseRuntimeConfig)(codebaseConfig)
|
|
318
|
+
? runtimeConfig
|
|
319
|
+
: { firebase: firebaseConfig };
|
|
320
|
+
const discoveredBuild = await runtimeDelegate.discoverBuild(codebaseRuntimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
|
|
318
321
|
discoveredBuild.runtime = codebaseConfig.runtime;
|
|
319
322
|
build.applyPrefix(discoveredBuild, codebaseConfig.prefix || "");
|
|
320
323
|
wantBuilds[codebase] = discoveredBuild;
|
package/lib/detectProjectRoot.js
CHANGED
|
@@ -19,7 +19,7 @@ function detectProjectRoot(options) {
|
|
|
19
19
|
while (!(0, fsutils_1.fileExistsSync)((0, path_1.resolve)(projectRootDir, "./firebase.json"))) {
|
|
20
20
|
const parentDir = (0, path_1.dirname)(projectRootDir);
|
|
21
21
|
if (parentDir === projectRootDir) {
|
|
22
|
-
return
|
|
22
|
+
return undefined;
|
|
23
23
|
}
|
|
24
24
|
projectRootDir = parentDir;
|
|
25
25
|
}
|