firebase-tools 15.5.0 → 15.6.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/apiv2.js +11 -5
- package/lib/apphosting/backend.js +1 -1
- package/lib/commands/deploy.js +3 -0
- package/lib/commands/init.js +5 -0
- package/lib/config.js +1 -0
- package/lib/dataconnect/client.js +2 -2
- package/lib/dataconnect/prompts.js +14 -0
- package/lib/deploy/auth/deploy.js +63 -0
- package/lib/deploy/auth/index.js +9 -0
- package/lib/deploy/auth/prepare.js +26 -0
- package/lib/deploy/auth/release.js +5 -0
- package/lib/deploy/dataconnect/release.js +11 -0
- package/lib/deploy/functions/backend.js +10 -0
- package/lib/deploy/functions/build.js +2 -2
- package/lib/deploy/functions/checkIam.js +3 -2
- package/lib/deploy/functions/deploy.js +4 -2
- package/lib/deploy/functions/prepare.js +5 -2
- package/lib/deploy/functions/prepareFunctionsUpload.js +7 -5
- package/lib/deploy/functions/release/fabricator.js +96 -15
- package/lib/deploy/functions/release/index.js +2 -1
- package/lib/deploy/functions/runtimes/dart.js +42 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +4 -1
- package/lib/deploy/functions/runtimes/index.js +9 -1
- package/lib/deploy/functions/runtimes/supported/types.js +6 -0
- package/lib/deploy/index.js +2 -0
- package/lib/emulator/auth/index.js +23 -17
- package/lib/emulator/auth/operations.js +9 -1
- package/lib/emulator/downloadableEmulatorInfo.json +31 -31
- package/lib/emulator/hubExport.js +15 -1
- package/lib/ensureApiEnabled.js +2 -2
- package/lib/gcp/cloudbilling.js +6 -3
- package/lib/gcp/iam.js +1 -1
- package/lib/gcp/runv2.js +75 -3
- package/lib/gcp/serviceusage.js +2 -2
- package/lib/gemini/fdcExperience.js +0 -11
- package/lib/init/features/auth.js +72 -0
- package/lib/init/features/dataconnect/resolver.js +17 -3
- package/lib/init/features/functions/index.js +22 -14
- package/lib/init/features/functions/javascript.js +18 -3
- package/lib/init/features/functions/typescript.js +20 -3
- package/lib/init/features/functions/utils.js +10 -0
- package/lib/init/features/genkit/index.js +3 -2
- package/lib/init/features/index.js +6 -2
- package/lib/init/index.js +11 -1
- package/lib/management/provisioning/provision.js +3 -0
- package/lib/management/provisioning/types.js +7 -0
- package/lib/mcp/resources/guides/init_auth.js +19 -2
- package/lib/mcp/tools/apptesting/tests.js +6 -4
- package/lib/mcp/tools/core/init.js +31 -0
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +41 -0
- package/templates/init/apptesting/smoke_test.yaml +1 -1
- package/templates/init/dataconnect/secondary_schema.gql +8 -0
- package/templates/init/functions/javascript/index-ongraphrequest.js +36 -0
- package/templates/init/functions/javascript/package-ongraphrequest.lint.json +28 -0
- package/templates/init/functions/javascript/package-ongraphrequest.nolint.json +25 -0
- package/templates/init/functions/typescript/index-ongraphrequest.ts +44 -0
- package/templates/init/functions/typescript/package-ongraphrequest.lint.json +33 -0
- package/templates/init/functions/typescript/package-ongraphrequest.nolint.json +27 -0
- package/lib/mcp/util.test.js +0 -468
package/lib/apiv2.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Client = exports.CLI_OAUTH_PROJECT_NUMBER = exports.STANDARD_HEADERS = void 0;
|
|
3
|
+
exports.Client = exports.CLI_OAUTH_PROJECT_NUMBER = exports.GOOG_USER_PROJECT_HEADER = exports.STANDARD_HEADERS = void 0;
|
|
4
4
|
exports.setRefreshToken = setRefreshToken;
|
|
5
5
|
exports.setAccessToken = setAccessToken;
|
|
6
6
|
exports.getAccessToken = getAccessToken;
|
|
@@ -29,7 +29,7 @@ exports.STANDARD_HEADERS = {
|
|
|
29
29
|
"X-Client-Version": clientVersion,
|
|
30
30
|
};
|
|
31
31
|
const GOOG_QUOTA_USER_HEADER = "x-goog-quota-user";
|
|
32
|
-
|
|
32
|
+
exports.GOOG_USER_PROJECT_HEADER = "x-goog-user-project";
|
|
33
33
|
const GOOGLE_CLOUD_QUOTA_PROJECT = process.env.GOOGLE_CLOUD_QUOTA_PROJECT;
|
|
34
34
|
exports.CLI_OAUTH_PROJECT_NUMBER = "563584335869";
|
|
35
35
|
let accessToken = "";
|
|
@@ -163,7 +163,7 @@ class Client {
|
|
|
163
163
|
if (!reqOptions.ignoreQuotaProject &&
|
|
164
164
|
GOOGLE_CLOUD_QUOTA_PROJECT &&
|
|
165
165
|
GOOGLE_CLOUD_QUOTA_PROJECT !== "") {
|
|
166
|
-
reqOptions.headers.set(GOOG_USER_PROJECT_HEADER, GOOGLE_CLOUD_QUOTA_PROJECT);
|
|
166
|
+
reqOptions.headers.set(exports.GOOG_USER_PROJECT_HEADER, GOOGLE_CLOUD_QUOTA_PROJECT);
|
|
167
167
|
}
|
|
168
168
|
return reqOptions;
|
|
169
169
|
}
|
|
@@ -352,8 +352,14 @@ class Client {
|
|
|
352
352
|
const logURL = this.requestURL(options);
|
|
353
353
|
logger_1.logger.debug(`>>> [apiv2][query] ${options.method} ${logURL} ${queryParamsLog}`);
|
|
354
354
|
const headers = options.headers;
|
|
355
|
-
if (headers && headers.has(GOOG_QUOTA_USER_HEADER)) {
|
|
356
|
-
|
|
355
|
+
if (headers && (headers.has(GOOG_QUOTA_USER_HEADER) || headers.has(exports.GOOG_USER_PROJECT_HEADER))) {
|
|
356
|
+
const userHeader = headers.has(GOOG_QUOTA_USER_HEADER)
|
|
357
|
+
? `${GOOG_QUOTA_USER_HEADER}=${headers.get(GOOG_QUOTA_USER_HEADER)}`
|
|
358
|
+
: "";
|
|
359
|
+
const projectHeader = headers.has(exports.GOOG_USER_PROJECT_HEADER)
|
|
360
|
+
? `${exports.GOOG_USER_PROJECT_HEADER}=${headers.get(exports.GOOG_USER_PROJECT_HEADER)}`
|
|
361
|
+
: "";
|
|
362
|
+
logger_1.logger.debug(`>>> [apiv2][(partial)header] ${options.method} ${logURL} ${userHeader} ${projectHeader}`);
|
|
357
363
|
}
|
|
358
364
|
if (options.body !== undefined) {
|
|
359
365
|
let logBody = "[omitted]";
|
|
@@ -190,7 +190,7 @@ async function ensureAppHostingComputeServiceAccount(projectId, serviceAccount)
|
|
|
190
190
|
const sa = serviceAccount || defaultComputeServiceAccountEmail(projectId);
|
|
191
191
|
const name = `projects/${projectId}/serviceAccounts/${sa}`;
|
|
192
192
|
try {
|
|
193
|
-
await iam.testResourceIamPermissions((0, api_1.iamOrigin)(), "v1", name, ["iam.serviceAccounts.actAs"],
|
|
193
|
+
await iam.testResourceIamPermissions((0, api_1.iamOrigin)(), "v1", name, ["iam.serviceAccounts.actAs"], `${projectId}`);
|
|
194
194
|
}
|
|
195
195
|
catch (err) {
|
|
196
196
|
if (!(err instanceof error_1.FirebaseError)) {
|
package/lib/commands/deploy.js
CHANGED
|
@@ -26,6 +26,7 @@ exports.VALID_DEPLOY_TARGETS = [
|
|
|
26
26
|
"extensions",
|
|
27
27
|
"dataconnect",
|
|
28
28
|
"apphosting",
|
|
29
|
+
"auth",
|
|
29
30
|
];
|
|
30
31
|
exports.TARGET_PERMISSIONS = {
|
|
31
32
|
database: ["firebasedatabase.instances.update"],
|
|
@@ -73,6 +74,8 @@ exports.TARGET_PERMISSIONS = {
|
|
|
73
74
|
"firebasedataconnect.schemas.list",
|
|
74
75
|
"firebasedataconnect.schemas.update",
|
|
75
76
|
],
|
|
77
|
+
apphosting: [],
|
|
78
|
+
auth: ["firebase.projects.update", "firebaseauth.configs.update"],
|
|
76
79
|
};
|
|
77
80
|
exports.command = new command_1.Command("deploy")
|
|
78
81
|
.description("deploy code and assets to your Firebase project")
|
package/lib/commands/init.js
CHANGED
package/lib/config.js
CHANGED
|
@@ -117,8 +117,8 @@ async function upsertSchema(schema, validateOnly = false, async = false) {
|
|
|
117
117
|
masterTimeout: 60000,
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
|
-
async function deleteSchema(
|
|
121
|
-
const op = await dataconnectClient().delete(
|
|
120
|
+
async function deleteSchema(name) {
|
|
121
|
+
const op = await dataconnectClient().delete(name);
|
|
122
122
|
await operationPoller.pollOperation({
|
|
123
123
|
apiOrigin: (0, api_1.dataconnectOrigin)(),
|
|
124
124
|
apiVersion: DATACONNECT_API_VERSION,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.promptDeleteConnector = promptDeleteConnector;
|
|
4
|
+
exports.promptDeleteSchema = promptDeleteSchema;
|
|
4
5
|
const prompt_1 = require("../prompt");
|
|
5
6
|
const utils = require("../utils");
|
|
6
7
|
const client_1 = require("./client");
|
|
@@ -17,3 +18,16 @@ async function promptDeleteConnector(options, connectorName) {
|
|
|
17
18
|
utils.logLabeledSuccess("dataconnect", `Connector ${connectorName} deleted`);
|
|
18
19
|
}
|
|
19
20
|
}
|
|
21
|
+
async function promptDeleteSchema(options, schemaName) {
|
|
22
|
+
utils.logLabeledWarning("dataconnect", `Schema ${schemaName} exists but is not listed in dataconnect.yaml.`);
|
|
23
|
+
const confirmDeletion = await (0, prompt_1.confirm)({
|
|
24
|
+
default: false,
|
|
25
|
+
message: `Do you want to delete the schema ${schemaName}?`,
|
|
26
|
+
force: options.force,
|
|
27
|
+
nonInteractive: options.nonInteractive,
|
|
28
|
+
});
|
|
29
|
+
if (confirmDeletion) {
|
|
30
|
+
await (0, client_1.deleteSchema)(schemaName);
|
|
31
|
+
utils.logLabeledSuccess("dataconnect", `Schema ${schemaName} deleted`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deploy = deploy;
|
|
4
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
5
|
+
const provision_1 = require("../../management/provisioning/provision");
|
|
6
|
+
const apps_1 = require("../../management/apps");
|
|
7
|
+
const types_1 = require("../../management/provisioning/types");
|
|
8
|
+
const logger_1 = require("../../logger");
|
|
9
|
+
const utils_1 = require("../../utils");
|
|
10
|
+
async function deploy(context, options) {
|
|
11
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
12
|
+
const config = options.config.src.auth;
|
|
13
|
+
if (!config) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const appId = context.auth?.appId;
|
|
17
|
+
if (!appId) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const authInput = {};
|
|
21
|
+
const providers = config.providers;
|
|
22
|
+
const logMsg = [];
|
|
23
|
+
if (providers) {
|
|
24
|
+
if (providers.anonymous === true) {
|
|
25
|
+
logMsg.push("anonymous");
|
|
26
|
+
authInput.anonymousAuthProviderMode = types_1.ProviderMode.PROVIDER_ENABLED;
|
|
27
|
+
}
|
|
28
|
+
if (providers.emailPassword === true) {
|
|
29
|
+
logMsg.push("email/password");
|
|
30
|
+
authInput.emailAuthProviderMode = types_1.ProviderMode.PROVIDER_ENABLED;
|
|
31
|
+
}
|
|
32
|
+
if (providers.googleSignIn) {
|
|
33
|
+
logMsg.push("Google sign-in");
|
|
34
|
+
authInput.googleSigninProviderMode = types_1.ProviderMode.PROVIDER_ENABLED;
|
|
35
|
+
authInput.googleSigninProviderConfig = {
|
|
36
|
+
publicDisplayName: providers.googleSignIn.oAuthBrandDisplayName,
|
|
37
|
+
customerSupportEmail: providers.googleSignIn.supportEmail,
|
|
38
|
+
oauthRedirectUris: providers.googleSignIn.authorizedRedirectUris,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (Object.keys(authInput).length === 0) {
|
|
43
|
+
logger_1.logger.debug("[auth] No auth providers configured to enable.");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
logger_1.logger.info(`Enabling auth providers: ${logMsg.join(", ")}...`);
|
|
47
|
+
await (0, provision_1.provisionFirebaseApp)({
|
|
48
|
+
project: {
|
|
49
|
+
parent: {
|
|
50
|
+
type: "existing_project",
|
|
51
|
+
projectId: projectId,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
app: {
|
|
55
|
+
platform: apps_1.AppPlatform.WEB,
|
|
56
|
+
appId: appId,
|
|
57
|
+
},
|
|
58
|
+
features: {
|
|
59
|
+
firebaseAuthInput: authInput,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
(0, utils_1.logSuccess)(`Auth providers enabled: ${logMsg.join(", ")}`);
|
|
63
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.release = exports.deploy = exports.prepare = void 0;
|
|
4
|
+
var prepare_1 = require("./prepare");
|
|
5
|
+
Object.defineProperty(exports, "prepare", { enumerable: true, get: function () { return prepare_1.prepare; } });
|
|
6
|
+
var deploy_1 = require("./deploy");
|
|
7
|
+
Object.defineProperty(exports, "deploy", { enumerable: true, get: function () { return deploy_1.deploy; } });
|
|
8
|
+
var release_1 = require("./release");
|
|
9
|
+
Object.defineProperty(exports, "release", { enumerable: true, get: function () { return release_1.release; } });
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepare = prepare;
|
|
4
|
+
const apps_1 = require("../../management/apps");
|
|
5
|
+
const logger_1 = require("../../logger");
|
|
6
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
7
|
+
async function prepare(context, options) {
|
|
8
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
9
|
+
const config = options.config.src.auth;
|
|
10
|
+
if (!config) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const apps = await (0, apps_1.listFirebaseApps)(projectId, apps_1.AppPlatform.WEB);
|
|
14
|
+
let app = apps.find((a) => a.displayName === "Default Web App");
|
|
15
|
+
if (!app && apps.length > 0) {
|
|
16
|
+
app = apps[0];
|
|
17
|
+
}
|
|
18
|
+
if (!app) {
|
|
19
|
+
logger_1.logger.info("No Firebase Web App found. Creating 'Default Web App' for Auth provisioning...");
|
|
20
|
+
app = await (0, apps_1.createWebApp)(projectId, {
|
|
21
|
+
displayName: "Default Web App",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
context.auth = context.auth || {};
|
|
25
|
+
context.auth.appId = app.appId;
|
|
26
|
+
}
|
|
@@ -88,6 +88,13 @@ async function default_1(context, options) {
|
|
|
88
88
|
for (const c of connectorsToDelete) {
|
|
89
89
|
await (0, prompts_1.promptDeleteConnector)(options, c.name);
|
|
90
90
|
}
|
|
91
|
+
const allSchemas = await deployedSchemas(serviceInfos);
|
|
92
|
+
const schemasToDelete = filters
|
|
93
|
+
? []
|
|
94
|
+
: allSchemas.filter((s) => !wantSecondarySchemas.some((w) => w.name === s.name) && !(0, types_1.isMainSchema)(s));
|
|
95
|
+
for (const s of schemasToDelete) {
|
|
96
|
+
await (0, prompts_1.promptDeleteSchema)(options, s.name);
|
|
97
|
+
}
|
|
91
98
|
let consolePath = "/dataconnect";
|
|
92
99
|
if (serviceInfos.length === 1) {
|
|
93
100
|
const sn = (0, names_1.parseServiceName)(serviceInfos[0].serviceName);
|
|
@@ -106,3 +113,7 @@ async function deployedConnectors(serviceInfos) {
|
|
|
106
113
|
}
|
|
107
114
|
return connectors;
|
|
108
115
|
}
|
|
116
|
+
async function deployedSchemas(serviceInfos) {
|
|
117
|
+
const schemasPerService = await Promise.all(serviceInfos.map((si) => (0, client_1.listSchemas)(si.serviceName)));
|
|
118
|
+
return schemasPerService.flat();
|
|
119
|
+
}
|
|
@@ -29,6 +29,7 @@ exports.findEndpoint = findEndpoint;
|
|
|
29
29
|
exports.matchingBackend = matchingBackend;
|
|
30
30
|
exports.regionalEndpoints = regionalEndpoints;
|
|
31
31
|
exports.compareFunctions = compareFunctions;
|
|
32
|
+
exports.maybeDeterministicCloudRunUri = maybeDeterministicCloudRunUri;
|
|
32
33
|
const gcf = require("../../gcp/cloudfunctions");
|
|
33
34
|
const gcfV2 = require("../../gcp/cloudfunctionsv2");
|
|
34
35
|
const run = require("../../gcp/runv2");
|
|
@@ -340,3 +341,12 @@ function compareFunctions(left, right) {
|
|
|
340
341
|
}
|
|
341
342
|
return 0;
|
|
342
343
|
}
|
|
344
|
+
function maybeDeterministicCloudRunUri(httpsFunc, projectNumber) {
|
|
345
|
+
const serviceName = httpsFunc.id.toLowerCase().replaceAll("_", "-");
|
|
346
|
+
const dnsSegment = `${serviceName}-${projectNumber}`;
|
|
347
|
+
if (dnsSegment.length > 63) {
|
|
348
|
+
logger_1.logger.info(`Function name ${httpsFunc.id} is too long to have a deterministic Cloud Run URI. Printing the non-deterministic URI instead.`);
|
|
349
|
+
return httpsFunc.uri;
|
|
350
|
+
}
|
|
351
|
+
return `https://${serviceName}-${projectNumber}.${httpsFunc.region}.run.app`;
|
|
352
|
+
}
|
|
@@ -55,7 +55,7 @@ function isBlockingTriggered(triggered) {
|
|
|
55
55
|
return {}.hasOwnProperty.call(triggered, "blockingTrigger");
|
|
56
56
|
}
|
|
57
57
|
const allMemoryOptions = [128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768];
|
|
58
|
-
exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2"];
|
|
58
|
+
exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2", "run"];
|
|
59
59
|
exports.AllVpcEgressSettings = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"];
|
|
60
60
|
exports.AllIngressSettings = [
|
|
61
61
|
"ALLOW_ALL",
|
|
@@ -207,7 +207,7 @@ function toBackend(build, paramValues) {
|
|
|
207
207
|
runtime: bdEndpoint.runtime,
|
|
208
208
|
...trigger,
|
|
209
209
|
};
|
|
210
|
-
proto.copyIfPresent(bkEndpoint, bdEndpoint, "environmentVariables", "labels", "secretEnvironmentVariables");
|
|
210
|
+
proto.copyIfPresent(bkEndpoint, bdEndpoint, "environmentVariables", "labels", "secretEnvironmentVariables", "baseImageUri", "command", "args");
|
|
211
211
|
r.resolveStrings(bkEndpoint, bdEndpoint, "serviceAccount");
|
|
212
212
|
proto.convertIfPresent(bkEndpoint, bdEndpoint, "ingressSettings", (from) => {
|
|
213
213
|
if (from !== null && !backend.AllIngressSettings.includes(from)) {
|
|
@@ -52,8 +52,9 @@ async function checkHttpIam(context, options, payload) {
|
|
|
52
52
|
const filters = context.filters || (0, functionsDeployHelper_1.getEndpointFilters)(options, context.config);
|
|
53
53
|
const wantBackends = Object.values(payload.functions).map(({ wantBackend }) => wantBackend);
|
|
54
54
|
const httpEndpoints = [...(0, functional_1.flattenArray)(wantBackends.map((b) => backend.allEndpoints(b)))]
|
|
55
|
-
.filter(backend.isHttpsTriggered || backend.isDataConnectGraphqlTriggered)
|
|
56
|
-
.filter((f) => (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(f, filters))
|
|
55
|
+
.filter((f) => backend.isHttpsTriggered(f) || backend.isDataConnectGraphqlTriggered(f))
|
|
56
|
+
.filter((f) => (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(f, filters))
|
|
57
|
+
.filter((f) => f.platform !== "run");
|
|
57
58
|
const existing = await backend.existingBackend(context);
|
|
58
59
|
const newHttpsEndpoints = httpEndpoints.filter(backend.missingEndpoint(existing));
|
|
59
60
|
if (newHttpsEndpoints.length === 0) {
|
|
@@ -18,6 +18,7 @@ const experiments = require("../../experiments");
|
|
|
18
18
|
const backend_1 = require("./backend");
|
|
19
19
|
const extensions_1 = require("../extensions");
|
|
20
20
|
const getProjectNumber_1 = require("../../getProjectNumber");
|
|
21
|
+
const path = require("path");
|
|
21
22
|
(0, tmp_1.setGracefulCleanup)();
|
|
22
23
|
async function uploadSourceV1(projectId, source, wantBackend) {
|
|
23
24
|
const v1Endpoints = backend.allEndpoints(wantBackend).filter((e) => e.platform === "gcfv1");
|
|
@@ -53,7 +54,8 @@ async function uploadSourceV2(projectId, projectNumber, source, wantBackend) {
|
|
|
53
54
|
file: source.functionsSourceV2,
|
|
54
55
|
stream: exports.createReadStream(source.functionsSourceV2),
|
|
55
56
|
};
|
|
56
|
-
if (!experiments.isEnabled("
|
|
57
|
+
if (!experiments.isEnabled("functionsrunapionly") &&
|
|
58
|
+
!v2Endpoints.some((e) => e.platform === "run")) {
|
|
57
59
|
if (process.env.GOOGLE_CLOUD_QUOTA_PROJECT) {
|
|
58
60
|
(0, utils_1.logLabeledWarning)("functions", "GOOGLE_CLOUD_QUOTA_PROJECT is not usable when uploading source for Cloud Functions.");
|
|
59
61
|
}
|
|
@@ -80,7 +82,7 @@ async function uploadSourceV2(projectId, projectNumber, source, wantBackend) {
|
|
|
80
82
|
},
|
|
81
83
|
},
|
|
82
84
|
});
|
|
83
|
-
const objectPath = `${source.functionsSourceV2Hash}.
|
|
85
|
+
const objectPath = `${source.functionsSourceV2Hash}${path.extname(source.functionsSourceV2)}`;
|
|
84
86
|
await gcs.upload(uploadOpts, `${bucketName}/${objectPath}`, undefined, true);
|
|
85
87
|
return {
|
|
86
88
|
bucket: bucketName,
|
|
@@ -157,7 +157,7 @@ async function prepare(context, options, payload) {
|
|
|
157
157
|
if (backend.someEndpoint(wantBackend, () => true)) {
|
|
158
158
|
(0, utils_1.logLabeledBullet)("functions", `preparing ${clc.bold(sourceDirName)} directory for uploading...`);
|
|
159
159
|
}
|
|
160
|
-
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
|
|
160
|
+
if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2" || e.platform === "run")) {
|
|
161
161
|
const schPathSet = new Set();
|
|
162
162
|
for (const e of backend.allEndpoints(wantBackend)) {
|
|
163
163
|
if (backend.isDataConnectGraphqlTriggered(e) &&
|
|
@@ -165,7 +165,10 @@ async function prepare(context, options, payload) {
|
|
|
165
165
|
schPathSet.add(e.dataConnectGraphqlTrigger.schemaFilePath);
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
-
const
|
|
168
|
+
const exportType = backend.someEndpoint(wantBackend, (e) => e.platform === "run")
|
|
169
|
+
? "tar.gz"
|
|
170
|
+
: "zip";
|
|
171
|
+
const packagedSource = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(options.config.projectDir, sourceDir, localCfg, [...schPathSet], undefined, { exportType });
|
|
169
172
|
source.functionsSourceV2 = packagedSource?.pathToSource;
|
|
170
173
|
source.functionsSourceV2Hash = packagedSource?.hash;
|
|
171
174
|
}
|
|
@@ -44,13 +44,15 @@ async function pipeAsync(from, to) {
|
|
|
44
44
|
to.on("error", reject);
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
-
async function packageSource(projectDir, sourceDir, config, additionalSources, runtimeConfig) {
|
|
48
|
-
const
|
|
47
|
+
async function packageSource(projectDir, sourceDir, config, additionalSources, runtimeConfig, options) {
|
|
48
|
+
const exportType = options?.exportType || "zip";
|
|
49
|
+
const postfix = `.${exportType}`;
|
|
50
|
+
const tmpFile = tmp.fileSync({ prefix: "firebase-functions-", postfix }).name;
|
|
49
51
|
const fileStream = fs.createWriteStream(tmpFile, {
|
|
50
52
|
flags: "w",
|
|
51
53
|
encoding: "binary",
|
|
52
54
|
});
|
|
53
|
-
const archive = archiver("zip");
|
|
55
|
+
const archive = exportType === "tar.gz" ? archiver("tar", { gzip: true }) : archiver("zip");
|
|
54
56
|
const hashes = [];
|
|
55
57
|
const ignore = config.ignore || ["node_modules", ".git"];
|
|
56
58
|
ignore.push("firebase-debug.log", "firebase-debug.*.log", CONFIG_DEST_FILE);
|
|
@@ -110,8 +112,8 @@ async function packageSource(projectDir, sourceDir, config, additionalSources, r
|
|
|
110
112
|
const hash = hashes.join(".");
|
|
111
113
|
return { pathToSource: tmpFile, hash };
|
|
112
114
|
}
|
|
113
|
-
async function prepareFunctionsUpload(projectDir, sourceDir, config, additionalSources, runtimeConfig) {
|
|
114
|
-
return packageSource(projectDir, sourceDir, config, additionalSources, runtimeConfig);
|
|
115
|
+
async function prepareFunctionsUpload(projectDir, sourceDir, config, additionalSources, runtimeConfig, options) {
|
|
116
|
+
return packageSource(projectDir, sourceDir, config, additionalSources, runtimeConfig, options);
|
|
115
117
|
}
|
|
116
118
|
function convertToSortedKeyValueArray(config) {
|
|
117
119
|
if (typeof config !== "object" || config === null)
|
|
@@ -22,6 +22,7 @@ const poller = require("../../../operation-poller");
|
|
|
22
22
|
const pubsub = require("../../../gcp/pubsub");
|
|
23
23
|
const reporter = require("./reporter");
|
|
24
24
|
const run = require("../../../gcp/run");
|
|
25
|
+
const runV2 = require("../../../gcp/runv2");
|
|
25
26
|
const scheduler = require("../../../gcp/cloudscheduler");
|
|
26
27
|
const utils = require("../../../utils");
|
|
27
28
|
const services = require("../services");
|
|
@@ -138,9 +139,7 @@ class Fabricator {
|
|
|
138
139
|
await this.createV2Function(endpoint, scraperV2);
|
|
139
140
|
}
|
|
140
141
|
else if (endpoint.platform === "run") {
|
|
141
|
-
|
|
142
|
-
exit: 1,
|
|
143
|
-
});
|
|
142
|
+
await this.createRunFunction(endpoint);
|
|
144
143
|
}
|
|
145
144
|
else {
|
|
146
145
|
(0, functional_1.assertExhaustive)(endpoint.platform);
|
|
@@ -161,7 +160,7 @@ class Fabricator {
|
|
|
161
160
|
await this.updateV2Function(update.endpoint, scraperV2);
|
|
162
161
|
}
|
|
163
162
|
else if (update.endpoint.platform === "run") {
|
|
164
|
-
|
|
163
|
+
await this.updateRunFunction(update);
|
|
165
164
|
}
|
|
166
165
|
else {
|
|
167
166
|
(0, functional_1.assertExhaustive)(update.endpoint.platform);
|
|
@@ -177,7 +176,7 @@ class Fabricator {
|
|
|
177
176
|
return this.deleteV2Function(endpoint);
|
|
178
177
|
}
|
|
179
178
|
else if (endpoint.platform === "run") {
|
|
180
|
-
|
|
179
|
+
return this.deleteRunFunction(endpoint);
|
|
181
180
|
}
|
|
182
181
|
(0, functional_1.assertExhaustive)(endpoint.platform);
|
|
183
182
|
}
|
|
@@ -507,6 +506,80 @@ class Fabricator {
|
|
|
507
506
|
}, { retryCodes: [...executor_1.DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] })
|
|
508
507
|
.catch(rethrowAs(endpoint, "delete"));
|
|
509
508
|
}
|
|
509
|
+
async createRunFunction(endpoint) {
|
|
510
|
+
const storageSource = this.sources[endpoint.codebase]?.storage;
|
|
511
|
+
if (!storageSource) {
|
|
512
|
+
logger_1.logger.debug("Precondition failed. Cannot create a Cloud Run function without storage");
|
|
513
|
+
throw new Error("Precondition failed");
|
|
514
|
+
}
|
|
515
|
+
const service = runV2.serviceFromEndpoint(endpoint, "scratch");
|
|
516
|
+
const container = service.template.containers[0];
|
|
517
|
+
container.sourceCode = {
|
|
518
|
+
cloudStorageSource: {
|
|
519
|
+
bucket: storageSource.bucket,
|
|
520
|
+
object: storageSource.object,
|
|
521
|
+
generation: storageSource.generation ? String(storageSource.generation) : undefined,
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
await this.executor
|
|
525
|
+
.run(async () => {
|
|
526
|
+
const op = await runV2.createService(endpoint.project, endpoint.region, endpoint.id, service);
|
|
527
|
+
endpoint.uri = op.uri;
|
|
528
|
+
endpoint.runServiceId = endpoint.id;
|
|
529
|
+
})
|
|
530
|
+
.catch(rethrowAs(endpoint, "create"));
|
|
531
|
+
await this.setInvoker(endpoint);
|
|
532
|
+
}
|
|
533
|
+
async updateRunFunction(update) {
|
|
534
|
+
const endpoint = update.endpoint;
|
|
535
|
+
const storageSource = this.sources[endpoint.codebase]?.storage;
|
|
536
|
+
if (!storageSource) {
|
|
537
|
+
logger_1.logger.debug("Precondition failed. Cannot update a Cloud Run function without storage");
|
|
538
|
+
throw new Error("Precondition failed");
|
|
539
|
+
}
|
|
540
|
+
const service = runV2.serviceFromEndpoint(endpoint, "scratch");
|
|
541
|
+
const container = service.template.containers[0];
|
|
542
|
+
container.sourceCode = {
|
|
543
|
+
cloudStorageSource: {
|
|
544
|
+
bucket: storageSource.bucket,
|
|
545
|
+
object: storageSource.object,
|
|
546
|
+
generation: storageSource.generation ? String(storageSource.generation) : undefined,
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
await this.executor
|
|
550
|
+
.run(async () => {
|
|
551
|
+
const op = await runV2.updateService(service);
|
|
552
|
+
endpoint.uri = op.uri;
|
|
553
|
+
endpoint.runServiceId = endpoint.id;
|
|
554
|
+
})
|
|
555
|
+
.catch(rethrowAs(endpoint, "update"));
|
|
556
|
+
await this.setInvoker(endpoint);
|
|
557
|
+
}
|
|
558
|
+
async deleteRunFunction(endpoint) {
|
|
559
|
+
await this.executor
|
|
560
|
+
.run(async () => {
|
|
561
|
+
try {
|
|
562
|
+
await runV2.deleteService(endpoint.project, endpoint.region, endpoint.id);
|
|
563
|
+
}
|
|
564
|
+
catch (err) {
|
|
565
|
+
if (err.status === 404) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
throw err;
|
|
569
|
+
}
|
|
570
|
+
})
|
|
571
|
+
.catch(rethrowAs(endpoint, "delete"));
|
|
572
|
+
}
|
|
573
|
+
async setInvoker(endpoint) {
|
|
574
|
+
if (backend.isHttpsTriggered(endpoint)) {
|
|
575
|
+
const invoker = endpoint.httpsTrigger.invoker || ["public"];
|
|
576
|
+
if (!invoker.includes("private")) {
|
|
577
|
+
await this.executor
|
|
578
|
+
.run(() => run.setInvokerUpdate(endpoint.project, `projects/${endpoint.project}/locations/${endpoint.region}/services/${endpoint.runServiceId}`, invoker))
|
|
579
|
+
.catch(rethrowAs(endpoint, "set invoker"));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
510
583
|
async setRunTraits(serviceName, endpoint) {
|
|
511
584
|
await this.functionExecutor
|
|
512
585
|
.run(async () => {
|
|
@@ -530,11 +603,6 @@ class Fabricator {
|
|
|
530
603
|
.catch(rethrowAs(endpoint, "set concurrency"));
|
|
531
604
|
}
|
|
532
605
|
async setTrigger(endpoint) {
|
|
533
|
-
if (endpoint.platform === "run") {
|
|
534
|
-
throw new error_1.FirebaseError("Setting triggers for Cloud Run functions is not supported yet.", {
|
|
535
|
-
exit: 1,
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
606
|
if (backend.isScheduleTriggered(endpoint)) {
|
|
539
607
|
if (endpoint.platform === "gcfv1") {
|
|
540
608
|
await this.upsertScheduleV1(endpoint);
|
|
@@ -544,21 +612,25 @@ class Fabricator {
|
|
|
544
612
|
await this.upsertScheduleV2(endpoint);
|
|
545
613
|
return;
|
|
546
614
|
}
|
|
615
|
+
else if (endpoint.platform === "run") {
|
|
616
|
+
throw new error_1.FirebaseError("Schedule triggers for Cloud Run functions are not supported yet.");
|
|
617
|
+
}
|
|
547
618
|
(0, functional_1.assertExhaustive)(endpoint.platform);
|
|
548
619
|
}
|
|
549
620
|
else if (backend.isTaskQueueTriggered(endpoint)) {
|
|
621
|
+
if (endpoint.platform === "run") {
|
|
622
|
+
throw new error_1.FirebaseError("Task Queue triggers for Cloud Run functions are not supported yet.");
|
|
623
|
+
}
|
|
550
624
|
await this.upsertTaskQueue(endpoint);
|
|
551
625
|
}
|
|
552
626
|
else if (backend.isBlockingTriggered(endpoint)) {
|
|
627
|
+
if (endpoint.platform === "run") {
|
|
628
|
+
throw new error_1.FirebaseError("Blocking triggers for Cloud Run functions are not supported yet.");
|
|
629
|
+
}
|
|
553
630
|
await this.registerBlockingTrigger(endpoint);
|
|
554
631
|
}
|
|
555
632
|
}
|
|
556
633
|
async deleteTrigger(endpoint) {
|
|
557
|
-
if (endpoint.platform === "run") {
|
|
558
|
-
throw new error_1.FirebaseError("Deleting triggers for Cloud Run functions is not supported yet.", {
|
|
559
|
-
exit: 1,
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
634
|
if (backend.isScheduleTriggered(endpoint)) {
|
|
563
635
|
if (endpoint.platform === "gcfv1") {
|
|
564
636
|
await this.deleteScheduleV1(endpoint);
|
|
@@ -568,12 +640,21 @@ class Fabricator {
|
|
|
568
640
|
await this.deleteScheduleV2(endpoint);
|
|
569
641
|
return;
|
|
570
642
|
}
|
|
643
|
+
else if (endpoint.platform === "run") {
|
|
644
|
+
throw new error_1.FirebaseError("Schedule triggers for Cloud Run functions are not supported yet.");
|
|
645
|
+
}
|
|
571
646
|
(0, functional_1.assertExhaustive)(endpoint.platform);
|
|
572
647
|
}
|
|
573
648
|
else if (backend.isTaskQueueTriggered(endpoint)) {
|
|
649
|
+
if (endpoint.platform === "run") {
|
|
650
|
+
throw new error_1.FirebaseError("Task Queue triggers for Cloud Run functions are not supported yet.");
|
|
651
|
+
}
|
|
574
652
|
await this.disableTaskQueue(endpoint);
|
|
575
653
|
}
|
|
576
654
|
else if (backend.isBlockingTriggered(endpoint)) {
|
|
655
|
+
if (endpoint.platform === "run") {
|
|
656
|
+
throw new error_1.FirebaseError("Blocking triggers for Cloud Run functions are not supported yet.");
|
|
657
|
+
}
|
|
577
658
|
await this.unregisterBlockingTrigger(endpoint);
|
|
578
659
|
}
|
|
579
660
|
}
|
|
@@ -107,7 +107,8 @@ function printTriggerUrls(results, projectNumber) {
|
|
|
107
107
|
continue;
|
|
108
108
|
}
|
|
109
109
|
if (backend.isDataConnectGraphqlTriggered(httpsFunc)) {
|
|
110
|
-
|
|
110
|
+
const uri = backend.maybeDeterministicCloudRunUri(httpsFunc, projectNumber);
|
|
111
|
+
logger_1.logger.info(clc.bold("Function URL"), `(${(0, functionsDeployHelper_1.getFunctionLabel)(httpsFunc)}):`, uri);
|
|
111
112
|
continue;
|
|
112
113
|
}
|
|
113
114
|
logger_1.logger.info(clc.bold("Function URL"), `(${(0, functionsDeployHelper_1.getFunctionLabel)(httpsFunc)}):`, httpsFunc.uri);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryCreateDelegate = tryCreateDelegate;
|
|
4
|
+
const fs = require("fs-extra");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const yaml = require("js-yaml");
|
|
7
|
+
const discovery = require("./discovery");
|
|
8
|
+
async function tryCreateDelegate(context) {
|
|
9
|
+
const yamlPath = path.join(context.sourceDir, "functions.yaml");
|
|
10
|
+
if (!(await fs.pathExists(yamlPath))) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const runtime = context.runtime || "dart3";
|
|
14
|
+
return {
|
|
15
|
+
language: "dart",
|
|
16
|
+
runtime: runtime,
|
|
17
|
+
bin: "",
|
|
18
|
+
validate: async () => {
|
|
19
|
+
try {
|
|
20
|
+
const content = await fs.readFile(yamlPath, "utf8");
|
|
21
|
+
yaml.load(content);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
throw new Error(`Failed to parse functions.yaml: ${e.message}`);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
build: async () => {
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
},
|
|
30
|
+
watch: async () => {
|
|
31
|
+
return Promise.resolve(async () => {
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
discoverBuild: async () => {
|
|
35
|
+
const build = await discovery.detectFromYaml(context.sourceDir, context.projectId, runtime);
|
|
36
|
+
if (!build) {
|
|
37
|
+
throw new Error("Could not find functions.yaml");
|
|
38
|
+
}
|
|
39
|
+
return build;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -82,6 +82,9 @@ function assertBuildEndpoint(ep, id) {
|
|
|
82
82
|
taskQueueTrigger: "object",
|
|
83
83
|
blockingTrigger: "object",
|
|
84
84
|
cpu: (cpu) => cpu === null || isCEL(cpu) || cpu === "gcf_gen1" || typeof cpu === "number",
|
|
85
|
+
baseImageUri: "string?",
|
|
86
|
+
command: "array?",
|
|
87
|
+
args: "array?",
|
|
85
88
|
});
|
|
86
89
|
if (ep.vpc) {
|
|
87
90
|
(0, parsing_1.assertKeyTypes)(prefix + ".vpc", ep.vpc, {
|
|
@@ -294,7 +297,7 @@ function parseEndpointForBuild(id, ep, project, defaultRegion, runtime) {
|
|
|
294
297
|
if ("serviceAccountEmail" in ep) {
|
|
295
298
|
parsed.serviceAccount = ep.serviceAccountEmail;
|
|
296
299
|
}
|
|
297
|
-
(0, proto_1.copyIfPresent)(parsed, ep, "omit", "availableMemoryMb", "cpu", "maxInstances", "minInstances", "concurrency", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "serviceAccount");
|
|
300
|
+
(0, proto_1.copyIfPresent)(parsed, ep, "omit", "availableMemoryMb", "cpu", "maxInstances", "minInstances", "concurrency", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "serviceAccount", "baseImageUri", "command", "args");
|
|
298
301
|
(0, proto_1.convertIfPresent)(parsed, ep, "secretEnvironmentVariables", (senvs) => {
|
|
299
302
|
if (!senvs) {
|
|
300
303
|
return null;
|