firebase-tools 14.3.1 → 14.4.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/api.js +10 -2
- package/lib/apphosting/backend.js +72 -18
- package/lib/apphosting/rollout.js +2 -2
- package/lib/apphosting/secrets/dialogs.js +4 -4
- package/lib/apphosting/secrets/index.js +2 -2
- package/lib/auth.js +22 -2
- package/lib/bin/cli.js +1 -33
- package/lib/bin/mcp.js +12 -2
- package/lib/checkValidTargetFilters.js +1 -0
- package/lib/command.js +3 -2
- package/lib/commands/apphosting-secrets-grantaccess.js +1 -1
- package/lib/commands/deploy.js +1 -0
- package/lib/commands/init.js +1 -1
- package/lib/commands/login-use.js +2 -11
- package/lib/commands/use.js +9 -8
- package/lib/config.js +1 -0
- package/lib/crashlytics/listTopIssues.js +44 -0
- package/lib/dataconnect/cloudAICompanionClient.js +67 -0
- package/lib/deploy/apphosting/args.js +2 -0
- package/lib/deploy/apphosting/deploy.js +74 -0
- package/lib/deploy/apphosting/index.js +9 -0
- package/lib/deploy/apphosting/prepare.js +141 -0
- package/lib/deploy/apphosting/release.js +53 -0
- package/lib/deploy/apphosting/util.js +65 -0
- package/lib/deploy/extensions/v2FunctionHelper.js +2 -1
- package/lib/deploy/functions/checkIam.js +3 -3
- package/lib/deploy/functions/ensure.js +2 -1
- package/lib/deploy/functions/prepare.js +23 -16
- package/lib/deploy/functions/release/fabricator.js +4 -4
- package/lib/deploy/functions/release/index.js +1 -1
- package/lib/deploy/functions/runtimes/python/index.js +3 -0
- package/lib/deploy/functions/runtimes/supported/types.js +17 -11
- package/lib/deploy/index.js +2 -0
- package/lib/emulator/apphosting/index.js +1 -0
- package/lib/emulator/apphosting/serve.js +77 -3
- package/lib/emulator/auth/widget_ui.js +2 -1
- package/lib/emulator/controller.js +18 -4
- package/lib/emulator/dataconnectEmulator.js +9 -2
- package/lib/emulator/downloadableEmulatorInfo.json +60 -0
- package/lib/emulator/downloadableEmulators.js +25 -61
- package/lib/experiments.js +1 -1
- package/lib/extensions/manifest.js +2 -2
- package/lib/fsAsync.js +9 -2
- package/lib/gcp/auth.js +32 -2
- package/lib/gcp/cloudbilling.js +12 -1
- package/lib/gcp/cloudfunctions.js +1 -2
- package/lib/gcp/cloudfunctionsv2.js +1 -2
- package/lib/gcp/cloudscheduler.js +2 -2
- package/lib/gcp/computeEngine.js +19 -2
- package/lib/gcp/devConnect.js +6 -1
- package/lib/gcp/firestore.js +24 -1
- package/lib/gcp/iam.js +1 -5
- package/lib/gcp/storage.js +25 -1
- package/lib/index.js +1 -2
- package/lib/init/features/apphosting.js +84 -6
- package/lib/init/features/database.js +64 -45
- package/lib/init/features/dataconnect/index.js +51 -53
- package/lib/init/features/emulators.js +8 -4
- package/lib/init/features/firestore/index.js +54 -23
- package/lib/init/features/firestore/indexes.js +23 -24
- package/lib/init/features/firestore/rules.js +35 -41
- package/lib/init/features/functions/index.js +2 -0
- package/lib/init/features/functions/javascript.js +3 -2
- package/lib/init/features/functions/typescript.js +3 -2
- package/lib/init/features/genkit/index.js +2 -1
- package/lib/init/features/hosting/github.js +3 -2
- package/lib/init/features/index.js +8 -4
- package/lib/init/features/remoteconfig.js +3 -2
- package/lib/init/index.js +76 -24
- package/lib/logger.js +71 -7
- package/lib/management/projects.js +24 -1
- package/lib/mcp/errors.js +1 -1
- package/lib/mcp/index.js +135 -43
- package/lib/mcp/tools/auth/get_user.js +16 -7
- package/lib/mcp/tools/auth/index.js +8 -1
- package/lib/mcp/tools/auth/list_users.js +47 -0
- package/lib/mcp/tools/auth/set_claims.js +20 -11
- package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
- package/lib/mcp/tools/core/consult_assistant.js +1 -1
- package/lib/mcp/tools/core/create_android_sha.js +40 -0
- package/lib/mcp/tools/core/create_app.js +90 -0
- package/lib/mcp/tools/core/create_project.js +68 -0
- package/lib/mcp/tools/core/get_admin_sdk_config.js +26 -0
- package/lib/mcp/tools/core/get_environment.js +51 -0
- package/lib/mcp/tools/core/get_sdk_config.js +6 -3
- package/lib/mcp/tools/core/index.js +19 -2
- package/lib/mcp/tools/core/init.js +129 -0
- package/lib/mcp/tools/core/update_environment.js +55 -0
- package/lib/mcp/tools/crashlytics/index.js +5 -0
- package/lib/mcp/tools/crashlytics/list_top_issues.js +34 -0
- package/lib/mcp/tools/dataconnect/converter.js +13 -1
- package/lib/mcp/tools/dataconnect/emulator.js +32 -0
- package/lib/mcp/tools/dataconnect/execute_graphql.js +24 -8
- package/lib/mcp/tools/dataconnect/execute_graphql_read.js +24 -8
- package/lib/mcp/tools/dataconnect/execute_mutation.js +27 -15
- package/lib/mcp/tools/dataconnect/execute_query.js +26 -14
- package/lib/mcp/tools/dataconnect/generate_operation.js +6 -6
- package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
- package/lib/mcp/tools/dataconnect/get_connector.js +7 -7
- package/lib/mcp/tools/dataconnect/get_schema.js +5 -5
- package/lib/mcp/tools/dataconnect/index.js +1 -5
- package/lib/mcp/tools/dataconnect/list_services.js +1 -1
- package/lib/mcp/tools/firestore/converter.js +47 -1
- package/lib/mcp/tools/firestore/delete_document.js +37 -0
- package/lib/mcp/tools/firestore/index.js +12 -2
- package/lib/mcp/tools/firestore/list_collections.js +1 -6
- package/lib/mcp/tools/firestore/query_collection.js +116 -0
- package/lib/mcp/tools/index.js +35 -16
- package/lib/mcp/tools/messaging/index.js +5 -0
- package/lib/mcp/tools/messaging/send_message.js +42 -0
- package/lib/mcp/tools/remoteconfig/get_template.js +27 -0
- package/lib/mcp/tools/remoteconfig/index.js +7 -0
- package/lib/mcp/tools/remoteconfig/publish_template.js +34 -0
- package/lib/mcp/tools/remoteconfig/rollback_template.js +29 -0
- package/lib/mcp/tools/rules/get_rules.js +29 -0
- package/lib/mcp/tools/rules/validate_rules.js +98 -0
- package/lib/mcp/tools/storage/get_download_url.js +8 -5
- package/lib/mcp/tools/storage/index.js +7 -2
- package/lib/mcp/types.js +9 -1
- package/lib/mcp/util.js +29 -2
- package/lib/messaging/interfaces.js +2 -0
- package/lib/messaging/sendMessage.js +48 -0
- package/lib/remoteconfig/publish.js +39 -0
- package/lib/requireAuth.js +2 -2
- package/lib/utils.js +2 -37
- package/package.json +2 -1
- package/schema/firebase-config.json +62 -10
- package/templates/init/functions/javascript/package.lint.json +1 -1
- package/templates/init/functions/javascript/package.nolint.json +1 -1
- package/templates/init/functions/typescript/package.lint.json +1 -1
- package/templates/init/functions/typescript/package.nolint.json +2 -2
- package/lib/mcp/tools/directory/get_project_directory.js +0 -20
- package/lib/mcp/tools/directory/index.js +0 -6
- package/lib/mcp/tools/directory/set_project_directory.js +0 -33
- package/lib/mcp/tools/firestore/get_rules.js +0 -26
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.callCloudAICompanion = exports.cloudAICompationClient = void 0;
|
|
4
|
+
const apiv2_1 = require("../apiv2");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const CLOUD_AI_COMPANION_VERSION = "v1";
|
|
7
|
+
const CLIENT_CONTEXT_NAME_IDENTIFIER = "firebase_vscode";
|
|
8
|
+
const FIREBASE_CHAT_REQUEST_CONTEXT_TYPE_NAME = "type.googleapis.com/google.cloud.cloudaicompanion.v1main.FirebaseChatRequestContext";
|
|
9
|
+
const FDC_SCHEMA_EXPERIENCE_CONTEXT = "/appeco/firebase/fdc-schema-generator";
|
|
10
|
+
const FDC_OPERATION_EXPERIENCE_CONTEXT = "/appeco/firebase/fdc-query-generator";
|
|
11
|
+
const USER_AUTHOR = "USER";
|
|
12
|
+
function cloudAICompationClient() {
|
|
13
|
+
return new apiv2_1.Client({
|
|
14
|
+
urlPrefix: (0, api_1.cloudAiCompanionOrigin)(),
|
|
15
|
+
apiVersion: CLOUD_AI_COMPANION_VERSION,
|
|
16
|
+
auth: true,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
exports.cloudAICompationClient = cloudAICompationClient;
|
|
20
|
+
async function callCloudAICompanion(client, vscodeRequest, type) {
|
|
21
|
+
const request = buildRequest(vscodeRequest, type);
|
|
22
|
+
const { projectId } = getServiceParts(vscodeRequest.servicePath);
|
|
23
|
+
const instance = toChatResourceName(projectId);
|
|
24
|
+
const res = await client.post(`${instance}:completeTask`, request);
|
|
25
|
+
return res;
|
|
26
|
+
}
|
|
27
|
+
exports.callCloudAICompanion = callCloudAICompanion;
|
|
28
|
+
function buildRequest({ servicePath, naturalLanguageQuery, chatHistory }, type) {
|
|
29
|
+
const { serviceId } = getServiceParts(servicePath);
|
|
30
|
+
const input = {
|
|
31
|
+
messages: [
|
|
32
|
+
...chatHistory,
|
|
33
|
+
{
|
|
34
|
+
author: USER_AUTHOR,
|
|
35
|
+
content: naturalLanguageQuery,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
const clientContext = {
|
|
40
|
+
name: CLIENT_CONTEXT_NAME_IDENTIFIER,
|
|
41
|
+
additionalContext: {
|
|
42
|
+
"@type": FIREBASE_CHAT_REQUEST_CONTEXT_TYPE_NAME,
|
|
43
|
+
fdcInfo: {
|
|
44
|
+
serviceId,
|
|
45
|
+
fdcServiceName: servicePath,
|
|
46
|
+
requiresQuery: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
input,
|
|
52
|
+
clientContext,
|
|
53
|
+
experienceContext: {
|
|
54
|
+
experience: type === "schema" ? FDC_SCHEMA_EXPERIENCE_CONTEXT : FDC_OPERATION_EXPERIENCE_CONTEXT,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function toChatResourceName(projectId) {
|
|
59
|
+
return `projects/${projectId}/locations/global/instances/default`;
|
|
60
|
+
}
|
|
61
|
+
function getServiceParts(name) {
|
|
62
|
+
const match = name.match(/projects\/([^/]*)\/locations\/([^/]*)\/services\/([^/]*)/);
|
|
63
|
+
if (!match) {
|
|
64
|
+
throw new Error(`Invalid service name: ${name}`);
|
|
65
|
+
}
|
|
66
|
+
return { projectId: match[1], locationId: match[2], serviceId: match[3] };
|
|
67
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const error_1 = require("../../error");
|
|
6
|
+
const gcs = require("../../gcp/storage");
|
|
7
|
+
const getProjectNumber_1 = require("../../getProjectNumber");
|
|
8
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
9
|
+
const utils_1 = require("../../utils");
|
|
10
|
+
const util_1 = require("./util");
|
|
11
|
+
async function default_1(context, options) {
|
|
12
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
13
|
+
options.projectNumber = await (0, getProjectNumber_1.getProjectNumber)(options);
|
|
14
|
+
if (!context.backendConfigs) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (const loc of context.backendLocations.values()) {
|
|
18
|
+
const bucketName = `firebaseapphosting-sources-${options.projectNumber}-${loc.toLowerCase()}`;
|
|
19
|
+
try {
|
|
20
|
+
await gcs.getBucket(bucketName);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
const errStatus = (0, error_1.getErrStatus)(err.original);
|
|
24
|
+
if (errStatus === 403 || errStatus === 404) {
|
|
25
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Creating Cloud Storage bucket in ${loc} to store App Hosting source code uploads at ${bucketName}...`);
|
|
26
|
+
try {
|
|
27
|
+
await gcs.createBucket(projectId, {
|
|
28
|
+
name: bucketName,
|
|
29
|
+
location: loc,
|
|
30
|
+
lifecycle: {
|
|
31
|
+
rule: [
|
|
32
|
+
{
|
|
33
|
+
action: {
|
|
34
|
+
type: "Delete",
|
|
35
|
+
},
|
|
36
|
+
condition: {
|
|
37
|
+
age: 30,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if ((0, error_1.getErrStatus)(err.original) === 403) {
|
|
46
|
+
(0, utils_1.logLabeledWarning)("apphosting", "Failed to create Cloud Storage bucket because user does not have sufficient permissions. " +
|
|
47
|
+
"See https://cloud.google.com/storage/docs/access-control/iam-roles for more details on " +
|
|
48
|
+
"IAM roles that are able to create a Cloud Storage bucket, and ask your project administrator " +
|
|
49
|
+
"to grant you one of those roles.");
|
|
50
|
+
throw err.original;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const cfg of context.backendConfigs.values()) {
|
|
60
|
+
const { projectSourcePath, zippedSourcePath } = await (0, util_1.createArchive)(cfg, options.projectRoot);
|
|
61
|
+
const backendLocation = context.backendLocations.get(cfg.backendId);
|
|
62
|
+
if (!backendLocation) {
|
|
63
|
+
throw new error_1.FirebaseError(`Failed to find location for backend ${cfg.backendId}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
|
|
64
|
+
}
|
|
65
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Uploading source code at ${projectSourcePath} for backend ${cfg.backendId}...`);
|
|
66
|
+
const { bucket, object } = await gcs.uploadObject({
|
|
67
|
+
file: zippedSourcePath,
|
|
68
|
+
stream: fs.createReadStream(zippedSourcePath),
|
|
69
|
+
}, `firebaseapphosting-sources-${options.projectNumber}-${backendLocation.toLowerCase()}`);
|
|
70
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Source code uploaded at gs://${bucket}/${object}`);
|
|
71
|
+
context.backendStorageUris.set(cfg.backendId, `gs://firebaseapphosting-sources-${options.projectNumber}-${backendLocation.toLowerCase()}/${path.basename(zippedSourcePath)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.default = default_1;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.release = exports.deploy = exports.prepare = void 0;
|
|
4
|
+
const prepare_1 = require("./prepare");
|
|
5
|
+
exports.prepare = prepare_1.default;
|
|
6
|
+
const deploy_1 = require("./deploy");
|
|
7
|
+
exports.deploy = deploy_1.default;
|
|
8
|
+
const release_1 = require("./release");
|
|
9
|
+
exports.release = release_1.default;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getBackendConfigs = void 0;
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const backend_1 = require("../../apphosting/backend");
|
|
6
|
+
const apphosting_1 = require("../../gcp/apphosting");
|
|
7
|
+
const devConnect_1 = require("../../gcp/devConnect");
|
|
8
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
9
|
+
const prompt_1 = require("../../prompt");
|
|
10
|
+
const utils_1 = require("../../utils");
|
|
11
|
+
async function default_1(context, options) {
|
|
12
|
+
var _a;
|
|
13
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
14
|
+
await (0, apphosting_1.ensureApiEnabled)(options);
|
|
15
|
+
await (0, backend_1.ensureRequiredApisEnabled)(projectId);
|
|
16
|
+
try {
|
|
17
|
+
await (0, backend_1.ensureAppHostingComputeServiceAccount)(projectId, "", true);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err.status === 400) {
|
|
21
|
+
(0, utils_1.logLabeledWarning)("apphosting", "Your App Hosting compute service account is still being provisioned. Please try again in a few moments.");
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
context.backendConfigs = new Map();
|
|
26
|
+
context.backendLocations = new Map();
|
|
27
|
+
context.backendStorageUris = new Map();
|
|
28
|
+
const configs = getBackendConfigs(options);
|
|
29
|
+
const { backends } = await (0, apphosting_1.listBackends)(projectId, "-");
|
|
30
|
+
const foundBackends = [];
|
|
31
|
+
const notFoundBackends = [];
|
|
32
|
+
const ambiguousBackends = [];
|
|
33
|
+
for (const cfg of configs) {
|
|
34
|
+
const filteredBackends = backends.filter((backend) => (0, apphosting_1.parseBackendName)(backend.name).id === cfg.backendId);
|
|
35
|
+
if (filteredBackends.length === 0) {
|
|
36
|
+
notFoundBackends.push(cfg);
|
|
37
|
+
}
|
|
38
|
+
else if (filteredBackends.length === 1) {
|
|
39
|
+
foundBackends.push(cfg);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
ambiguousBackends.push(cfg);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const cfg of ambiguousBackends) {
|
|
46
|
+
const filteredBackends = backends.filter((backend) => (0, apphosting_1.parseBackendName)(backend.name).id === cfg.backendId);
|
|
47
|
+
const locations = filteredBackends.map((b) => (0, apphosting_1.parseBackendName)(b.name).location);
|
|
48
|
+
(0, utils_1.logLabeledWarning)("apphosting", `You have multiple backends with the same ${cfg.backendId} ID in regions: ${locations.join(", ")}. This is not allowed until we can support more locations. ` +
|
|
49
|
+
"Please delete and recreate any backends that share an ID with another backend.");
|
|
50
|
+
}
|
|
51
|
+
if (foundBackends.length > 0) {
|
|
52
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Found backend(s) ${foundBackends.map((cfg) => cfg.backendId).join(", ")}`);
|
|
53
|
+
}
|
|
54
|
+
for (const cfg of foundBackends) {
|
|
55
|
+
const filteredBackends = backends.filter((backend) => (0, apphosting_1.parseBackendName)(backend.name).id === cfg.backendId);
|
|
56
|
+
if (cfg.alwaysDeployFromSource === false) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const backend = filteredBackends[0];
|
|
60
|
+
const { location } = (0, apphosting_1.parseBackendName)(backend.name);
|
|
61
|
+
if (cfg.alwaysDeployFromSource === undefined && ((_a = backend.codebase) === null || _a === void 0 ? void 0 : _a.repository)) {
|
|
62
|
+
const { connectionName, id } = (0, devConnect_1.parseGitRepositoryLinkName)(backend.codebase.repository);
|
|
63
|
+
const gitRepositoryLink = await (0, devConnect_1.getGitRepositoryLink)(projectId, location, connectionName, id);
|
|
64
|
+
if (!options.force) {
|
|
65
|
+
const confirmDeploy = await (0, prompt_1.confirm)({
|
|
66
|
+
default: true,
|
|
67
|
+
message: `${cfg.backendId} is linked to the remote repository at ${gitRepositoryLink.cloneUri}. Are you sure you want to deploy your local source?`,
|
|
68
|
+
});
|
|
69
|
+
cfg.alwaysDeployFromSource = confirmDeploy;
|
|
70
|
+
const configPath = path.join(options.projectRoot || "", "firebase.json");
|
|
71
|
+
options.config.writeProjectFile(configPath, options.config.src);
|
|
72
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Your deployment preferences have been saved to firebase.json. On future invocations of "firebase deploy", your local source will be deployed to ${cfg.backendId}. You can edit this setting in your firebase.json at any time.`);
|
|
73
|
+
if (!confirmDeploy) {
|
|
74
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Skipping deployment of backend ${cfg.backendId}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
context.backendConfigs.set(cfg.backendId, cfg);
|
|
80
|
+
context.backendLocations.set(cfg.backendId, location);
|
|
81
|
+
}
|
|
82
|
+
if (notFoundBackends.length === 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (options.force) {
|
|
86
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Skipping deployments of backend(s) ${notFoundBackends.map((cfg) => cfg.backendId).join(", ")}; ` +
|
|
87
|
+
"the backend(s) do not exist yet and we cannot create them for you because you must choose primary regions for each one. " +
|
|
88
|
+
"Please run 'firebase deploy' without the --force flag, or 'firebase apphosting:backends:create' to create the backend, " +
|
|
89
|
+
"then retry deployment.");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const confirmCreate = await (0, prompt_1.confirm)({
|
|
93
|
+
default: true,
|
|
94
|
+
message: `Did not find backend(s) ${notFoundBackends.map((cfg) => cfg.backendId).join(", ")}. Do you want to create them (you'll have the option to select which to create in the next step)?`,
|
|
95
|
+
});
|
|
96
|
+
if (!confirmCreate) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const selected = await (0, prompt_1.checkbox)({
|
|
100
|
+
message: "Which backends do you want to create and deploy to?",
|
|
101
|
+
choices: notFoundBackends.map((cfg) => cfg.backendId),
|
|
102
|
+
});
|
|
103
|
+
const selectedBackends = selected.map((id) => notFoundBackends.find((backend) => backend.backendId === id));
|
|
104
|
+
for (const cfg of selectedBackends) {
|
|
105
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Creating a new backend ${cfg.backendId}...`);
|
|
106
|
+
const { location } = await (0, backend_1.doSetupSourceDeploy)(projectId, cfg.backendId);
|
|
107
|
+
context.backendConfigs.set(cfg.backendId, cfg);
|
|
108
|
+
context.backendLocations.set(cfg.backendId, location);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
exports.default = default_1;
|
|
113
|
+
function getBackendConfigs(options) {
|
|
114
|
+
if (!options.config.src.apphosting) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const backendConfigs = Array.isArray(options.config.src.apphosting)
|
|
118
|
+
? options.config.src.apphosting
|
|
119
|
+
: [options.config.src.apphosting];
|
|
120
|
+
if (!options.only) {
|
|
121
|
+
return backendConfigs;
|
|
122
|
+
}
|
|
123
|
+
const selectors = options.only.split(",");
|
|
124
|
+
const backendIds = [];
|
|
125
|
+
for (const selector of selectors) {
|
|
126
|
+
if (selector === "apphosting") {
|
|
127
|
+
return backendConfigs;
|
|
128
|
+
}
|
|
129
|
+
if (selector.startsWith("apphosting:")) {
|
|
130
|
+
const backendId = selector.replace("apphosting:", "");
|
|
131
|
+
if (backendId.length > 0) {
|
|
132
|
+
backendIds.push(backendId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (backendIds.length === 0) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
return backendConfigs.filter((cfg) => backendIds.includes(cfg.backendId));
|
|
140
|
+
}
|
|
141
|
+
exports.getBackendConfigs = getBackendConfigs;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ora = require("ora");
|
|
4
|
+
const api_1 = require("../../api");
|
|
5
|
+
const backend_1 = require("../../apphosting/backend");
|
|
6
|
+
const rollout_1 = require("../../apphosting/rollout");
|
|
7
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
8
|
+
const utils_1 = require("../../utils");
|
|
9
|
+
async function default_1(context, options) {
|
|
10
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
11
|
+
const rollouts = [];
|
|
12
|
+
const backendIds = [];
|
|
13
|
+
for (const backendId of context.backendConfigs.keys()) {
|
|
14
|
+
const config = context.backendConfigs.get(backendId);
|
|
15
|
+
const location = context.backendLocations.get(backendId);
|
|
16
|
+
const storageUri = context.backendStorageUris.get(backendId);
|
|
17
|
+
if (!config || !location || !storageUri) {
|
|
18
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Failed to find metadata for backend ${backendId}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
backendIds.push(backendId);
|
|
22
|
+
rollouts.push((0, rollout_1.orchestrateRollout)({
|
|
23
|
+
projectId,
|
|
24
|
+
location,
|
|
25
|
+
backendId,
|
|
26
|
+
buildInput: {
|
|
27
|
+
source: {
|
|
28
|
+
archive: {
|
|
29
|
+
userStorageUri: storageUri,
|
|
30
|
+
rootDirectory: config.rootDir,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
(0, utils_1.logLabeledBullet)("apphosting", `You may also track the rollout(s) at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
|
|
37
|
+
const rolloutsSpinner = ora(`Starting rollout(s) for backend(s) ${Array.from(context.backendConfigs.keys()).join(", ")}; this may take a few minutes. It's safe to exit now.\n`).start();
|
|
38
|
+
const results = await Promise.allSettled(rollouts);
|
|
39
|
+
for (let i = 0; i < results.length; i++) {
|
|
40
|
+
const res = results[i];
|
|
41
|
+
if (res.status === "fulfilled") {
|
|
42
|
+
const backend = await (0, backend_1.getBackend)(projectId, backendIds[i]);
|
|
43
|
+
(0, utils_1.logLabeledSuccess)("apphosting", `Rollout for backend ${backendIds[i]} complete!`);
|
|
44
|
+
(0, utils_1.logLabeledSuccess)("apphosting", `Your backend is now deployed at:\n\thttps://${backend.uri}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Rollout for backend ${backendIds[i]} failed.`);
|
|
48
|
+
(0, utils_1.logLabeledError)("apphosting", res.reason);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
rolloutsSpinner.stop();
|
|
52
|
+
}
|
|
53
|
+
exports.default = default_1;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createArchive = void 0;
|
|
4
|
+
const archiver = require("archiver");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const tmp = require("tmp");
|
|
8
|
+
const error_1 = require("../../error");
|
|
9
|
+
const fsAsync = require("../../fsAsync");
|
|
10
|
+
async function createArchive(config, projectRoot) {
|
|
11
|
+
const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".zip" }).name;
|
|
12
|
+
const fileStream = fs.createWriteStream(tmpFile, {
|
|
13
|
+
flags: "w",
|
|
14
|
+
encoding: "binary",
|
|
15
|
+
});
|
|
16
|
+
const archive = archiver("zip");
|
|
17
|
+
if (!projectRoot) {
|
|
18
|
+
projectRoot = process.cwd();
|
|
19
|
+
}
|
|
20
|
+
const ignore = config.ignore || ["node_modules", ".git"];
|
|
21
|
+
ignore.push("firebase-debug.log", "firebase-debug.*.log");
|
|
22
|
+
const gitIgnorePatterns = parseGitIgnorePatterns(projectRoot);
|
|
23
|
+
ignore.push(...gitIgnorePatterns);
|
|
24
|
+
try {
|
|
25
|
+
const files = await fsAsync.readdirRecursive({
|
|
26
|
+
path: projectRoot,
|
|
27
|
+
ignore: ignore,
|
|
28
|
+
isGitIgnore: true,
|
|
29
|
+
});
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
const name = path.relative(projectRoot, file.name);
|
|
32
|
+
archive.file(file.name, {
|
|
33
|
+
name,
|
|
34
|
+
mode: file.mode,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
await pipeAsync(archive, fileStream);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
throw new error_1.FirebaseError("Could not read source directory. Remove links and shortcuts and try again.", { original: err, exit: 1 });
|
|
41
|
+
}
|
|
42
|
+
return { projectSourcePath: projectRoot, zippedSourcePath: tmpFile };
|
|
43
|
+
}
|
|
44
|
+
exports.createArchive = createArchive;
|
|
45
|
+
function parseGitIgnorePatterns(projectRoot, gitIgnorePath = ".gitignore") {
|
|
46
|
+
const absoluteFilePath = path.resolve(projectRoot, gitIgnorePath);
|
|
47
|
+
if (!fs.existsSync(absoluteFilePath)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const lines = fs
|
|
51
|
+
.readFileSync(absoluteFilePath)
|
|
52
|
+
.toString()
|
|
53
|
+
.split("\n")
|
|
54
|
+
.map((line) => line.trim())
|
|
55
|
+
.filter((line) => !line.startsWith("#") && !(line === ""));
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
async function pipeAsync(from, to) {
|
|
59
|
+
from.pipe(to);
|
|
60
|
+
await from.finalize();
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
to.on("finish", resolve);
|
|
63
|
+
to.on("error", reject);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -9,6 +9,7 @@ const ensureApiEnabled_1 = require("../../ensureApiEnabled");
|
|
|
9
9
|
const planner = require("./planner");
|
|
10
10
|
const projectUtils_1 = require("../../projectUtils");
|
|
11
11
|
const api_1 = require("../../api");
|
|
12
|
+
const computeEngine_1 = require("../../gcp/computeEngine");
|
|
12
13
|
const SERVICE_AGENT_ROLE = "roles/eventarc.eventReceiver";
|
|
13
14
|
async function checkSpecForV2Functions(i) {
|
|
14
15
|
const extensionSpec = await planner.getExtensionSpec(i);
|
|
@@ -23,7 +24,7 @@ async function ensureNecessaryV2ApisAndRoles(options) {
|
|
|
23
24
|
exports.ensureNecessaryV2ApisAndRoles = ensureNecessaryV2ApisAndRoles;
|
|
24
25
|
async function ensureComputeP4SARole(projectId) {
|
|
25
26
|
const projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
|
|
26
|
-
const saEmail =
|
|
27
|
+
const saEmail = await (0, computeEngine_1.getDefaultServiceAccount)(projectNumber);
|
|
27
28
|
let policy;
|
|
28
29
|
try {
|
|
29
30
|
policy = await resourceManager.getIamPolicy(projectId);
|
|
@@ -89,8 +89,8 @@ function obtainPubSubServiceAgentBindings(projectNumber) {
|
|
|
89
89
|
return [serviceAccountTokenCreatorBinding];
|
|
90
90
|
}
|
|
91
91
|
exports.obtainPubSubServiceAgentBindings = obtainPubSubServiceAgentBindings;
|
|
92
|
-
function obtainDefaultComputeServiceAgentBindings(projectNumber) {
|
|
93
|
-
const defaultComputeServiceAgent = `serviceAccount:${gce.getDefaultServiceAccount(projectNumber)}`;
|
|
92
|
+
async function obtainDefaultComputeServiceAgentBindings(projectNumber) {
|
|
93
|
+
const defaultComputeServiceAgent = `serviceAccount:${await gce.getDefaultServiceAccount(projectNumber)}`;
|
|
94
94
|
const runInvokerBinding = {
|
|
95
95
|
role: exports.RUN_INVOKER_ROLE,
|
|
96
96
|
members: [defaultComputeServiceAgent],
|
|
@@ -117,7 +117,7 @@ async function ensureServiceAgentRoles(projectId, projectNumber, want, have, dry
|
|
|
117
117
|
const requiredBindings = [...(0, functional_1.flattenArray)(nestedRequiredBindings)];
|
|
118
118
|
if (haveServices.length === 0) {
|
|
119
119
|
requiredBindings.push(...obtainPubSubServiceAgentBindings(projectNumber));
|
|
120
|
-
requiredBindings.push(...obtainDefaultComputeServiceAgentBindings(projectNumber));
|
|
120
|
+
requiredBindings.push(...(await obtainDefaultComputeServiceAgentBindings(projectNumber)));
|
|
121
121
|
}
|
|
122
122
|
if (requiredBindings.length === 0) {
|
|
123
123
|
return;
|
|
@@ -10,6 +10,7 @@ const projects_1 = require("../../management/projects");
|
|
|
10
10
|
const functional_1 = require("../../functional");
|
|
11
11
|
const api_1 = require("../../api");
|
|
12
12
|
const backend = require("./backend");
|
|
13
|
+
const computeEngine_1 = require("../../gcp/computeEngine");
|
|
13
14
|
const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime";
|
|
14
15
|
const metadataCallCache = new Map();
|
|
15
16
|
async function defaultServiceAccount(e) {
|
|
@@ -23,7 +24,7 @@ async function defaultServiceAccount(e) {
|
|
|
23
24
|
return `${metadata.projectId}@appspot.gserviceaccount.com`;
|
|
24
25
|
}
|
|
25
26
|
else if (e.platform === "gcfv2") {
|
|
26
|
-
return
|
|
27
|
+
return await (0, computeEngine_1.getDefaultServiceAccount)(metadata.projectNumber);
|
|
27
28
|
}
|
|
28
29
|
(0, functional_1.assertExhaustive)(e.platform);
|
|
29
30
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.warnIfNewGenkitFunctionIsMissingSecrets = exports.loadCodebases = exports.resolveCpuAndConcurrency = exports.inferBlockingDetails = exports.updateEndpointTargetedStatus = exports.inferDetailsFromExisting = exports.prepare = exports.EVENTARC_SOURCE_ENV = void 0;
|
|
3
|
+
exports.ensureAllRequiredAPIsEnabled = exports.warnIfNewGenkitFunctionIsMissingSecrets = exports.loadCodebases = exports.resolveCpuAndConcurrency = exports.inferBlockingDetails = exports.updateEndpointTargetedStatus = exports.inferDetailsFromExisting = exports.prepare = exports.EVENTARC_SOURCE_ENV = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const backend = require("./backend");
|
|
6
6
|
const build = require("./build");
|
|
@@ -171,22 +171,8 @@ async function prepare(context, options, payload) {
|
|
|
171
171
|
}
|
|
172
172
|
const wantBackend = backend.merge(...Object.values(wantBackends));
|
|
173
173
|
const haveBackend = backend.merge(...Object.values(haveBackends));
|
|
174
|
+
await ensureAllRequiredAPIsEnabled(projectNumber, wantBackend);
|
|
174
175
|
await warnIfNewGenkitFunctionIsMissingSecrets(wantBackend, haveBackend, options);
|
|
175
|
-
await Promise.all(Object.values(wantBackend.requiredAPIs).map(({ api }) => {
|
|
176
|
-
return ensureApiEnabled.ensure(projectId, api, "functions", false);
|
|
177
|
-
}));
|
|
178
|
-
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
|
|
179
|
-
const V2_APIS = [(0, api_1.cloudRunApiOrigin)(), (0, api_1.eventarcOrigin)(), (0, api_1.pubsubOrigin)(), (0, api_1.storageOrigin)()];
|
|
180
|
-
const enablements = V2_APIS.map((api) => {
|
|
181
|
-
return ensureApiEnabled.ensure(context.projectId, api, "functions");
|
|
182
|
-
});
|
|
183
|
-
await Promise.all(enablements);
|
|
184
|
-
const services = ["pubsub.googleapis.com", "eventarc.googleapis.com"];
|
|
185
|
-
const generateServiceAccounts = services.map((service) => {
|
|
186
|
-
return (0, serviceusage_1.generateServiceIdentity)(projectNumber, service, "functions");
|
|
187
|
-
});
|
|
188
|
-
await Promise.all(generateServiceAccounts);
|
|
189
|
-
}
|
|
190
176
|
const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => {
|
|
191
177
|
return (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, context.filters);
|
|
192
178
|
});
|
|
@@ -350,3 +336,24 @@ async function warnIfNewGenkitFunctionIsMissingSecrets(have, want, options) {
|
|
|
350
336
|
}
|
|
351
337
|
}
|
|
352
338
|
exports.warnIfNewGenkitFunctionIsMissingSecrets = warnIfNewGenkitFunctionIsMissingSecrets;
|
|
339
|
+
async function ensureAllRequiredAPIsEnabled(projectNumber, wantBackend) {
|
|
340
|
+
await Promise.all(Object.values(wantBackend.requiredAPIs).map(({ api }) => {
|
|
341
|
+
return ensureApiEnabled.ensure(projectNumber, api, "functions", false);
|
|
342
|
+
}));
|
|
343
|
+
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
|
|
344
|
+
const V2_APIS = [(0, api_1.cloudRunApiOrigin)(), (0, api_1.eventarcOrigin)(), (0, api_1.pubsubOrigin)(), (0, api_1.storageOrigin)()];
|
|
345
|
+
const enablements = V2_APIS.map((api) => {
|
|
346
|
+
return ensureApiEnabled.ensure(projectNumber, api, "functions");
|
|
347
|
+
});
|
|
348
|
+
await Promise.all(enablements);
|
|
349
|
+
const services = ["pubsub.googleapis.com", "eventarc.googleapis.com"];
|
|
350
|
+
const generateServiceAccounts = services.map((service) => {
|
|
351
|
+
return (0, serviceusage_1.generateServiceIdentity)(projectNumber, service, "functions");
|
|
352
|
+
});
|
|
353
|
+
await Promise.all(generateServiceAccounts);
|
|
354
|
+
}
|
|
355
|
+
if (backend.someEndpoint(wantBackend, (e) => !!(e.secretEnvironmentVariables && e.secretEnvironmentVariables.length > 0))) {
|
|
356
|
+
await ensureApiEnabled.ensure(projectNumber, (0, api_1.secretManagerOrigin)(), "functions", false);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
exports.ensureAllRequiredAPIsEnabled = ensureAllRequiredAPIsEnabled;
|
|
@@ -335,7 +335,7 @@ class Fabricator {
|
|
|
335
335
|
else if (backend.isScheduleTriggered(endpoint)) {
|
|
336
336
|
const invoker = endpoint.serviceAccount
|
|
337
337
|
? [endpoint.serviceAccount]
|
|
338
|
-
: [gce.getDefaultServiceAccount(this.projectNumber)];
|
|
338
|
+
: [await gce.getDefaultServiceAccount(this.projectNumber)];
|
|
339
339
|
await this.executor
|
|
340
340
|
.run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker))
|
|
341
341
|
.catch(rethrowAs(endpoint, "set invoker"));
|
|
@@ -420,7 +420,7 @@ class Fabricator {
|
|
|
420
420
|
else if (backend.isScheduleTriggered(endpoint)) {
|
|
421
421
|
invoker = endpoint.serviceAccount
|
|
422
422
|
? [endpoint.serviceAccount]
|
|
423
|
-
: [gce.getDefaultServiceAccount(this.projectNumber)];
|
|
423
|
+
: [await gce.getDefaultServiceAccount(this.projectNumber)];
|
|
424
424
|
}
|
|
425
425
|
if (invoker) {
|
|
426
426
|
await this.executor
|
|
@@ -509,13 +509,13 @@ class Fabricator {
|
|
|
509
509
|
}
|
|
510
510
|
}
|
|
511
511
|
async upsertScheduleV1(endpoint) {
|
|
512
|
-
const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation, this.projectNumber);
|
|
512
|
+
const job = await scheduler.jobFromEndpoint(endpoint, this.appEngineLocation, this.projectNumber);
|
|
513
513
|
await this.executor
|
|
514
514
|
.run(() => scheduler.createOrReplaceJob(job))
|
|
515
515
|
.catch(rethrowAs(endpoint, "upsert schedule"));
|
|
516
516
|
}
|
|
517
517
|
async upsertScheduleV2(endpoint) {
|
|
518
|
-
const job = scheduler.jobFromEndpoint(endpoint, endpoint.region, this.projectNumber);
|
|
518
|
+
const job = await scheduler.jobFromEndpoint(endpoint, endpoint.region, this.projectNumber);
|
|
519
519
|
await this.executor
|
|
520
520
|
.run(() => scheduler.createOrReplaceJob(job))
|
|
521
521
|
.catch(rethrowAs(endpoint, "upsert schedule"));
|
|
@@ -122,7 +122,7 @@ async function setupArtifactCleanupPolicies(options, projectId, locations) {
|
|
|
122
122
|
utils.logLabeledWarning("functions", `Failed to set up cleanup policy for repositories in ${locationsWithErrors.length > 1 ? "regions" : "region"} ` +
|
|
123
123
|
`${locationsWithErrors.join(", ")}.` +
|
|
124
124
|
"This could result in a small monthly bill as container images accumulate over time.");
|
|
125
|
-
|
|
125
|
+
utils.logLabeledWarning("functions", `Functions successfully deployed but could not set up cleanup policy in ` +
|
|
126
126
|
`${locationsWithErrors.length > 1 ? "regions" : "region"} ${locationsWithErrors.join(", ")}. ` +
|
|
127
127
|
`Pass the --force option to automatically set up a cleanup policy or ` +
|
|
128
128
|
"run 'firebase functions:artifacts:setpolicy' to set up a cleanup policy to automatically delete old images.");
|
|
@@ -42,6 +42,9 @@ function getPythonBinary(runtime) {
|
|
|
42
42
|
else if (runtime === "python312") {
|
|
43
43
|
return "python3.12";
|
|
44
44
|
}
|
|
45
|
+
else if (runtime === "python313") {
|
|
46
|
+
return "python3.13";
|
|
47
|
+
}
|
|
45
48
|
(0, functional_1.assertExhaustive)(runtime, `Unhandled python runtime ${runtime}`);
|
|
46
49
|
}
|
|
47
50
|
exports.getPythonBinary = getPythonBinary;
|