firebase-tools 15.16.0 → 15.18.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 +1 -1
- package/lib/apphosting/localbuilds.js +116 -5
- package/lib/apphosting/universalMakerDownload.js +72 -0
- package/lib/apphosting/universalMakerInfo.json +16 -0
- package/lib/archiveDirectory.js +1 -1
- package/lib/deploy/apphosting/release.js +27 -13
- package/lib/deploy/apphosting/util.js +4 -19
- package/lib/deploy/functions/backend.js +2 -1
- package/lib/deploy/functions/prepare.js +140 -35
- package/lib/deploy/functions/prepareFunctionsUpload.js +1 -1
- package/lib/deploy/functions/services/ailogic.js +17 -0
- package/lib/deploy/functions/services/database.js +16 -0
- package/lib/deploy/functions/services/firestore.js +1 -0
- package/lib/deploy/functions/services/storage.js +15 -1
- package/lib/deploy/functions/triggerRegionHelper.js +111 -2
- package/lib/deploy/functions/validate.js +1 -1
- package/lib/downloadUtils.js +24 -0
- package/lib/emulator/download.js +2 -24
- package/lib/emulator/downloadableEmulatorInfo.json +31 -31
- package/lib/emulator/functionsEmulatorShared.js +2 -1
- package/lib/env.js +5 -1
- package/lib/firestore/api-sort.js +22 -0
- package/lib/firestore/api-types.js +11 -1
- package/lib/firestore/api.js +21 -1
- package/lib/firestore/fsConfig.js +8 -0
- package/lib/firestore/pretty-print.js +26 -8
- package/lib/frameworks/next/index.js +1 -1
- package/lib/fsAsync.js +53 -10
- package/lib/mcp/apps/deploy/mcp-app.js +120 -0
- package/lib/mcp/apps/deploy/vite.config.js +16 -0
- package/lib/mcp/apps/init/mcp-app.js +230 -0
- package/lib/mcp/apps/init/vite.config.js +16 -0
- package/lib/mcp/apps/update_environment/mcp-app.js +38 -36
- package/lib/mcp/apps/update_environment/vite.config.js +16 -0
- package/lib/mcp/index.js +16 -5
- package/lib/mcp/resources/deploy_ui.js +31 -0
- package/lib/mcp/resources/index.js +4 -0
- package/lib/mcp/resources/init_ui.js +31 -0
- package/lib/mcp/resources/update_environment_ui.js +3 -3
- package/lib/mcp/tools/auth/get_users.js +1 -1
- package/lib/mcp/tools/core/deploy.js +87 -0
- package/lib/mcp/tools/core/deploy_status.js +32 -0
- package/lib/mcp/tools/core/index.js +4 -0
- package/lib/mcp/tools/core/init.js +3 -0
- package/lib/mcp/tools/core/update_environment.js +3 -0
- package/lib/mcp/tools/firestore/query_collection.js +1 -1
- package/lib/mcp/tools/functions/list_functions.js +2 -2
- package/lib/mcp/util/jobs.js +31 -0
- package/lib/mcp/util.js +5 -4
- package/lib/tsconfig.compile.tsbuildinfo +1 -1
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/templates/init/functions/dart/pubspec.yaml +1 -1
- package/templates/init/functions/dart/server.dart +2 -2
package/lib/api.js
CHANGED
|
@@ -66,7 +66,7 @@ const functionsV2Origin = () => utils.envOverride("FIREBASE_FUNCTIONS_V2_URL", "
|
|
|
66
66
|
exports.functionsV2Origin = functionsV2Origin;
|
|
67
67
|
const runOrigin = () => utils.envOverride("CLOUD_RUN_URL", "https://run.googleapis.com");
|
|
68
68
|
exports.runOrigin = runOrigin;
|
|
69
|
-
const functionsDefaultRegion = () => utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "
|
|
69
|
+
const functionsDefaultRegion = () => utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "REGION_TBD");
|
|
70
70
|
exports.functionsDefaultRegion = functionsDefaultRegion;
|
|
71
71
|
const cloudbuildOrigin = () => utils.envOverride("FIREBASE_CLOUDBUILD_URL", "https://cloudbuild.googleapis.com");
|
|
72
72
|
exports.cloudbuildOrigin = cloudbuildOrigin;
|
|
@@ -1,10 +1,121 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runUniversalMaker = runUniversalMaker;
|
|
3
4
|
exports.localBuild = localBuild;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
5
|
+
const childProcess = require("child_process");
|
|
6
|
+
const fs = require("fs-extra");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const index_1 = require("./secrets/index");
|
|
6
9
|
const prompt_1 = require("../prompt");
|
|
7
10
|
const error_1 = require("../error");
|
|
11
|
+
const logger_1 = require("../logger");
|
|
12
|
+
const utils_1 = require("../utils");
|
|
13
|
+
const universalMakerDownload_1 = require("./universalMakerDownload");
|
|
14
|
+
async function runUniversalMaker(projectRoot, framework) {
|
|
15
|
+
const universalMakerBinary = await (0, universalMakerDownload_1.getOrDownloadUniversalMaker)();
|
|
16
|
+
executeUniversalMakerBinary(universalMakerBinary, projectRoot);
|
|
17
|
+
return processUniversalMakerOutput(projectRoot, framework);
|
|
18
|
+
}
|
|
19
|
+
function executeUniversalMakerBinary(universalMakerBinary, projectRoot) {
|
|
20
|
+
try {
|
|
21
|
+
const bundleOutput = path.join(projectRoot, "bundle_output");
|
|
22
|
+
fs.removeSync(bundleOutput);
|
|
23
|
+
fs.ensureDirSync(bundleOutput);
|
|
24
|
+
const res = childProcess.spawnSync(universalMakerBinary, ["-application_dir", projectRoot, "-output_dir", projectRoot, "-output_format", "json"], {
|
|
25
|
+
env: {
|
|
26
|
+
...process.env,
|
|
27
|
+
X_GOOGLE_TARGET_PLATFORM: "fah",
|
|
28
|
+
FIREBASE_OUTPUT_BUNDLE_DIR: bundleOutput,
|
|
29
|
+
},
|
|
30
|
+
stdio: "pipe",
|
|
31
|
+
});
|
|
32
|
+
if (res.stdout) {
|
|
33
|
+
logger_1.logger.debug("[Universal Maker stdout]:\n" + res.stdout.toString());
|
|
34
|
+
}
|
|
35
|
+
if (res.stderr) {
|
|
36
|
+
logger_1.logger.debug("[Universal Maker stderr]:\n" + res.stderr.toString());
|
|
37
|
+
}
|
|
38
|
+
if (res.error) {
|
|
39
|
+
throw res.error;
|
|
40
|
+
}
|
|
41
|
+
if (res.status !== 0) {
|
|
42
|
+
throw new error_1.FirebaseError(`Universal Maker failed with exit code ${res.status ?? "unknown"}.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
if (e && typeof e === "object" && "code" in e && e.code === "EACCES") {
|
|
47
|
+
throw new error_1.FirebaseError(`Failed to execute the Universal Maker binary at ${universalMakerBinary} due to permission constraints. Please assure you have set execution permissions (e.g., chmod +x) on the file.`);
|
|
48
|
+
}
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function parseBundleYaml(projectRoot, defaultRunCommand) {
|
|
53
|
+
const bundleYamlPath = path.join(projectRoot, ".apphosting", "bundle.yaml");
|
|
54
|
+
if (!fs.existsSync(bundleYamlPath)) {
|
|
55
|
+
throw new error_1.FirebaseError("Failed to resolve build artifacts. Ensure Universal Maker produced a valid bundle.yaml with outputFiles.");
|
|
56
|
+
}
|
|
57
|
+
const bundleRaw = fs.readFileSync(bundleYamlPath, "utf-8");
|
|
58
|
+
const bundleData = (0, utils_1.wrappedSafeLoad)(bundleRaw);
|
|
59
|
+
const runCommand = bundleData?.runConfig?.runCommand ?? defaultRunCommand;
|
|
60
|
+
const outputFiles = bundleData?.outputFiles?.serverApp?.include;
|
|
61
|
+
if (!outputFiles) {
|
|
62
|
+
throw new error_1.FirebaseError("Failed to resolve build artifacts. Ensure Universal Maker produced a valid bundle.yaml with outputFiles.");
|
|
63
|
+
}
|
|
64
|
+
return { runCommand, outputFiles };
|
|
65
|
+
}
|
|
66
|
+
function processUniversalMakerOutput(projectRoot, framework) {
|
|
67
|
+
const outputFilePath = path.join(projectRoot, "build_output.json");
|
|
68
|
+
if (!fs.existsSync(outputFilePath)) {
|
|
69
|
+
throw new error_1.FirebaseError(`Universal Maker did not produce the expected output file at ${outputFilePath}`);
|
|
70
|
+
}
|
|
71
|
+
const outputRaw = fs.readFileSync(outputFilePath, "utf-8");
|
|
72
|
+
fs.unlinkSync(outputFilePath);
|
|
73
|
+
const bundleOutput = path.join(projectRoot, "bundle_output");
|
|
74
|
+
const targetAppHosting = path.join(projectRoot, ".apphosting");
|
|
75
|
+
if (fs.existsSync(bundleOutput)) {
|
|
76
|
+
fs.ensureDirSync(targetAppHosting);
|
|
77
|
+
const files = fs.readdirSync(bundleOutput);
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
const dest = path.join(targetAppHosting, file);
|
|
80
|
+
if (fs.existsSync(dest)) {
|
|
81
|
+
fs.removeSync(dest);
|
|
82
|
+
}
|
|
83
|
+
fs.moveSync(path.join(bundleOutput, file), dest);
|
|
84
|
+
}
|
|
85
|
+
fs.removeSync(bundleOutput);
|
|
86
|
+
}
|
|
87
|
+
let umOutput;
|
|
88
|
+
try {
|
|
89
|
+
umOutput = JSON.parse(outputRaw);
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
throw new error_1.FirebaseError(`Failed to parse build_output.json: ${(0, error_1.getErrMsg)(e)}`);
|
|
93
|
+
}
|
|
94
|
+
const defaultRunCommand = `${umOutput.command} ${umOutput.args.join(" ")}`;
|
|
95
|
+
const { runCommand: finalRunCommand, outputFiles: finalOutputFiles } = parseBundleYaml(projectRoot, defaultRunCommand);
|
|
96
|
+
return {
|
|
97
|
+
metadata: {
|
|
98
|
+
language: umOutput.language,
|
|
99
|
+
runtime: umOutput.runtime,
|
|
100
|
+
framework: framework || "nextjs",
|
|
101
|
+
},
|
|
102
|
+
runConfig: {
|
|
103
|
+
runCommand: finalRunCommand,
|
|
104
|
+
environmentVariables: Object.entries(umOutput.envVars || {})
|
|
105
|
+
.filter(([k]) => k !== "FIREBASE_OUTPUT_BUNDLE_DIR")
|
|
106
|
+
.map(([k, v]) => ({
|
|
107
|
+
variable: k,
|
|
108
|
+
value: String(v),
|
|
109
|
+
availability: ["RUNTIME"],
|
|
110
|
+
})),
|
|
111
|
+
},
|
|
112
|
+
outputFiles: {
|
|
113
|
+
serverApp: {
|
|
114
|
+
include: finalOutputFiles,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
8
119
|
async function localBuild(projectId, projectRoot, framework, env = {}, options) {
|
|
9
120
|
const hasBuildAvailableSecrets = Object.values(env).some((v) => v.secret && (!v.availability || v.availability.includes("BUILD")));
|
|
10
121
|
if (hasBuildAvailableSecrets && !options?.allowLocalBuildSecrets) {
|
|
@@ -25,7 +136,7 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
|
|
|
25
136
|
}
|
|
26
137
|
let apphostingBuildOutput;
|
|
27
138
|
try {
|
|
28
|
-
apphostingBuildOutput = await (
|
|
139
|
+
apphostingBuildOutput = await runUniversalMaker(projectRoot, framework);
|
|
29
140
|
}
|
|
30
141
|
finally {
|
|
31
142
|
for (const key in process.env) {
|
|
@@ -41,7 +152,7 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
|
|
|
41
152
|
const discoveredEnv = apphostingBuildOutput.runConfig.environmentVariables?.map(({ variable, value, availability }) => ({
|
|
42
153
|
variable,
|
|
43
154
|
value,
|
|
44
|
-
availability,
|
|
155
|
+
availability: availability,
|
|
45
156
|
}));
|
|
46
157
|
return {
|
|
47
158
|
outputFiles: apphostingBuildOutput.outputFiles?.serverApp.include ?? [],
|
|
@@ -58,7 +169,7 @@ async function toProcessEnv(projectId, env) {
|
|
|
58
169
|
return null;
|
|
59
170
|
}
|
|
60
171
|
if (value.secret) {
|
|
61
|
-
const resolvedValue = await (0,
|
|
172
|
+
const resolvedValue = await (0, index_1.loadSecret)(projectId, value.secret);
|
|
62
173
|
return [key, resolvedValue];
|
|
63
174
|
}
|
|
64
175
|
else {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getOrDownloadUniversalMaker = getOrDownloadUniversalMaker;
|
|
4
|
+
const fs = require("fs-extra");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
const downloadUtils = require("../downloadUtils");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
const universalMakerInfo = require("./universalMakerInfo.json");
|
|
11
|
+
const CACHE_DIR = path.join(os.homedir(), ".cache", "firebase", "universal-maker");
|
|
12
|
+
const UNIVERSAL_MAKER_UPDATE_DETAILS = universalMakerInfo;
|
|
13
|
+
function getPlatformInfo() {
|
|
14
|
+
let platformKey = "";
|
|
15
|
+
if (process.platform === "darwin") {
|
|
16
|
+
if (process.arch === "arm64") {
|
|
17
|
+
platformKey = "darwin_arm64";
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
throw new error_1.FirebaseError("macOS Intel (darwin_x64) is not currently supported for Universal Maker.");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else if (process.platform === "linux") {
|
|
24
|
+
if (process.arch === "x64") {
|
|
25
|
+
platformKey = "linux_x64";
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
throw new error_1.FirebaseError("Linux ARM (linux_arm64) is not currently supported for Universal Maker.");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (process.platform === "win32") {
|
|
32
|
+
throw new error_1.FirebaseError("Windows (win32) is not currently supported for Universal Maker.");
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
throw new error_1.FirebaseError(`Unsupported platform for Universal Maker: ${process.platform} ${process.arch}`);
|
|
36
|
+
}
|
|
37
|
+
const details = UNIVERSAL_MAKER_UPDATE_DETAILS[platformKey];
|
|
38
|
+
if (!details) {
|
|
39
|
+
throw new error_1.FirebaseError(`Could not find download details for platform: ${platformKey}`);
|
|
40
|
+
}
|
|
41
|
+
return details;
|
|
42
|
+
}
|
|
43
|
+
async function getOrDownloadUniversalMaker() {
|
|
44
|
+
const details = getPlatformInfo();
|
|
45
|
+
const downloadPath = path.join(CACHE_DIR, details.downloadPathRelativeToCacheDir);
|
|
46
|
+
const hasBinary = fs.existsSync(downloadPath);
|
|
47
|
+
if (hasBinary) {
|
|
48
|
+
logger_1.logger.debug(`[apphosting] Universal Maker binary found at cache: ${downloadPath}`);
|
|
49
|
+
try {
|
|
50
|
+
await downloadUtils.validateSize(downloadPath, details.expectedSize);
|
|
51
|
+
await downloadUtils.validateChecksum(downloadPath, details.expectedChecksumSHA256, "sha256");
|
|
52
|
+
return downloadPath;
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
logger_1.logger.warn(`[apphosting] Cached Universal Maker binary failed verification: ${err.message}. Proceeding to redownload...`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
logger_1.logger.info("Downloading Universal Maker, a tool required to build your App Hosting application locally...");
|
|
59
|
+
fs.ensureDirSync(CACHE_DIR);
|
|
60
|
+
let tmpfile;
|
|
61
|
+
try {
|
|
62
|
+
tmpfile = await downloadUtils.downloadToTmp(details.remoteUrl);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
throw new error_1.FirebaseError(`Failed to download Universal Maker: ${err.message}`);
|
|
66
|
+
}
|
|
67
|
+
await downloadUtils.validateSize(tmpfile, details.expectedSize);
|
|
68
|
+
await downloadUtils.validateChecksum(tmpfile, details.expectedChecksumSHA256, "sha256");
|
|
69
|
+
fs.copySync(tmpfile, downloadPath);
|
|
70
|
+
fs.chmodSync(downloadPath, 0o755);
|
|
71
|
+
return downloadPath;
|
|
72
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"darwin_arm64": {
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"expectedSize": 16111618,
|
|
5
|
+
"expectedChecksumSHA256": "4b77d02a5f80f26d9bd1428f388c293c1fb264995d75b51c7d50fec7c87bcf58",
|
|
6
|
+
"remoteUrl": "https://artifactregistry.googleapis.com/download/v1/projects/serverless-runtimes-qa/locations/us-central1/repositories/universal-maker/files/darwin-arm64%3A1.0.0%3Auniversal_maker:download?alt=media",
|
|
7
|
+
"downloadPathRelativeToCacheDir": "universal-maker-darwin-arm64-1.0.0"
|
|
8
|
+
},
|
|
9
|
+
"linux_x64": {
|
|
10
|
+
"version": "1.0.0",
|
|
11
|
+
"expectedSize": 16856277,
|
|
12
|
+
"expectedChecksumSHA256": "dfc8357b8ce23ef1897e5590d4390f166e27734f241b770f721d68273118845c",
|
|
13
|
+
"remoteUrl": "https://artifactregistry.googleapis.com/download/v1/projects/serverless-runtimes-qa/locations/us-central1/repositories/universal-maker/files/x86-64%3A1.0.0%3Auniversal_maker:download?alt=media",
|
|
14
|
+
"downloadPathRelativeToCacheDir": "universal-maker-linux-x64-1.0.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/lib/archiveDirectory.js
CHANGED
|
@@ -19,24 +19,39 @@ async function default_1(context, options) {
|
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
22
|
-
const rollouts = backendIds.map((backendId) =>
|
|
23
|
-
|
|
24
|
-
backendId
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const rollouts = backendIds.map((backendId) => {
|
|
23
|
+
const localBuild = context.backendLocalBuilds[backendId];
|
|
24
|
+
const userStorageUri = context.backendStorageUris[backendId];
|
|
25
|
+
const rootDirectory = context.backendConfigs[backendId].rootDir;
|
|
26
|
+
const source = localBuild
|
|
27
|
+
? {
|
|
28
|
+
locallyBuilt: {
|
|
29
|
+
userStorageUri,
|
|
30
|
+
rootDirectory,
|
|
31
|
+
runCommand: localBuild.buildConfig?.runCommand,
|
|
32
|
+
env: localBuild.buildConfig?.env,
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
: {
|
|
29
36
|
archive: {
|
|
30
|
-
userStorageUri
|
|
31
|
-
rootDirectory
|
|
32
|
-
locallyBuiltSource: !!context.backendLocalBuilds[backendId],
|
|
37
|
+
userStorageUri,
|
|
38
|
+
rootDirectory,
|
|
33
39
|
},
|
|
40
|
+
};
|
|
41
|
+
return (0, rollout_1.orchestrateRollout)({
|
|
42
|
+
projectId,
|
|
43
|
+
backendId,
|
|
44
|
+
location: context.backendLocations[backendId],
|
|
45
|
+
buildInput: {
|
|
46
|
+
config: localBuild?.buildConfig,
|
|
47
|
+
source,
|
|
34
48
|
},
|
|
35
|
-
}
|
|
36
|
-
})
|
|
49
|
+
});
|
|
50
|
+
});
|
|
37
51
|
(0, utils_1.logLabeledBullet)("apphosting", `You may also track the rollout(s) at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
|
|
38
52
|
const rolloutsSpinner = ora(`Starting rollout(s) for backend(s) ${backendIds.join(", ")}; this may take a few minutes. It's safe to exit now.\n`).start();
|
|
39
53
|
const results = await Promise.allSettled(rollouts);
|
|
54
|
+
rolloutsSpinner.stop();
|
|
40
55
|
let failed = false;
|
|
41
56
|
for (let i = 0; i < results.length; i++) {
|
|
42
57
|
const res = results[i];
|
|
@@ -51,7 +66,6 @@ async function default_1(context, options) {
|
|
|
51
66
|
(0, utils_1.logLabeledError)("apphosting", `${res.reason}`);
|
|
52
67
|
}
|
|
53
68
|
}
|
|
54
|
-
rolloutsSpinner.stop();
|
|
55
69
|
if (failed) {
|
|
56
70
|
throw new error_1.FirebaseError("One or more rollouts failed. Please review the errors above and try again.");
|
|
57
71
|
}
|
|
@@ -16,8 +16,8 @@ async function createLocalBuildTarArchive(config, rootDir, targetSubDir) {
|
|
|
16
16
|
const ignore = ["firebase-debug.log", "firebase-debug.*.log", ".git"];
|
|
17
17
|
const rdrFiles = await fsAsync.readdirRecursive({
|
|
18
18
|
path: targetDir,
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
ignoreStrings: ignore,
|
|
20
|
+
supportGitIgnore: true,
|
|
21
21
|
});
|
|
22
22
|
const allFiles = rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name));
|
|
23
23
|
if (targetSubDir) {
|
|
@@ -60,13 +60,11 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
|
|
|
60
60
|
const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir;
|
|
61
61
|
const ignore = config.ignore || ["node_modules", ".git"];
|
|
62
62
|
ignore.push("firebase-debug.log", "firebase-debug.*.log");
|
|
63
|
-
const gitIgnorePatterns = parseGitIgnorePatterns(targetDir);
|
|
64
|
-
ignore.push(...gitIgnorePatterns);
|
|
65
63
|
try {
|
|
66
64
|
const files = await fsAsync.readdirRecursive({
|
|
67
65
|
path: targetDir,
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
ignoreStrings: ignore,
|
|
67
|
+
supportGitIgnore: true,
|
|
70
68
|
});
|
|
71
69
|
for (const file of files) {
|
|
72
70
|
const name = path.relative(rootDir, file.name);
|
|
@@ -82,19 +80,6 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
|
|
|
82
80
|
}
|
|
83
81
|
return tmpFile;
|
|
84
82
|
}
|
|
85
|
-
function parseGitIgnorePatterns(projectRoot, gitIgnorePath = ".gitignore") {
|
|
86
|
-
const absoluteFilePath = path.resolve(projectRoot, gitIgnorePath);
|
|
87
|
-
if (!fs.existsSync(absoluteFilePath)) {
|
|
88
|
-
return [];
|
|
89
|
-
}
|
|
90
|
-
const lines = fs
|
|
91
|
-
.readFileSync(absoluteFilePath)
|
|
92
|
-
.toString()
|
|
93
|
-
.split("\n")
|
|
94
|
-
.map((line) => line.trim())
|
|
95
|
-
.filter((line) => !line.startsWith("#") && !(line === ""));
|
|
96
|
-
return lines;
|
|
97
|
-
}
|
|
98
83
|
async function pipeAsync(from, to) {
|
|
99
84
|
from.pipe(to);
|
|
100
85
|
await from.finalize();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.missingEndpoint = exports.hasEndpoint = exports.AllFunctionsPlatforms = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.AllIngressSettings = exports.AllVpcEgressSettings = void 0;
|
|
3
|
+
exports.missingEndpoint = exports.hasEndpoint = exports.AllFunctionsPlatforms = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_TIMEOUT_SECONDS = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.AllIngressSettings = exports.AllVpcEgressSettings = void 0;
|
|
4
4
|
exports.endpointTriggerType = endpointTriggerType;
|
|
5
5
|
exports.isValidMemoryOption = isValidMemoryOption;
|
|
6
6
|
exports.isValidEgressSetting = isValidEgressSetting;
|
|
@@ -116,6 +116,7 @@ function memoryToGen2Cpu(memory) {
|
|
|
116
116
|
}
|
|
117
117
|
exports.DEFAULT_CONCURRENCY = 80;
|
|
118
118
|
exports.DEFAULT_MEMORY = 256;
|
|
119
|
+
exports.DEFAULT_TIMEOUT_SECONDS = 60;
|
|
119
120
|
exports.MIN_CPU_FOR_CONCURRENCY = 1;
|
|
120
121
|
exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
|
|
121
122
|
function secretVersionName(s) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.EVENTARC_SOURCE_ENV = void 0;
|
|
3
|
+
exports.DEFAULT_FUNCTION_REGION = exports.EVENTARC_SOURCE_ENV = void 0;
|
|
4
4
|
exports.prepare = prepare;
|
|
5
|
-
exports.
|
|
6
|
-
exports.resolveDefaultRegions = resolveDefaultRegions;
|
|
5
|
+
exports.resolveDefaultRegionsForBuild = resolveDefaultRegionsForBuild;
|
|
7
6
|
exports.inferDetailsFromExisting = inferDetailsFromExisting;
|
|
8
7
|
exports.updateEndpointTargetedStatus = updateEndpointTargetedStatus;
|
|
9
8
|
exports.inferBlockingDetails = inferBlockingDetails;
|
|
10
9
|
exports.resolveCpuAndConcurrency = resolveCpuAndConcurrency;
|
|
10
|
+
exports.resolveDefaultTimeout = resolveDefaultTimeout;
|
|
11
11
|
exports.loadCodebases = loadCodebases;
|
|
12
12
|
exports.warnIfNewGenkitFunctionIsMissingSecrets = warnIfNewGenkitFunctionIsMissingSecrets;
|
|
13
13
|
exports.ensureAllRequiredAPIsEnabled = ensureAllRequiredAPIsEnabled;
|
|
@@ -23,6 +23,12 @@ const runtimes = require("./runtimes");
|
|
|
23
23
|
const supported = require("./runtimes/supported");
|
|
24
24
|
const validate = require("./validate");
|
|
25
25
|
const ensure = require("./ensure");
|
|
26
|
+
const events = require("../../functions/events/v1");
|
|
27
|
+
const firestore_1 = require("./services/firestore");
|
|
28
|
+
const storage_1 = require("./services/storage");
|
|
29
|
+
const database_1 = require("./services/database");
|
|
30
|
+
const ailogic_1 = require("./services/ailogic");
|
|
31
|
+
const names_1 = require("../../dataconnect/names");
|
|
26
32
|
const api_1 = require("../../api");
|
|
27
33
|
const functionsDeployHelper_1 = require("./functionsDeployHelper");
|
|
28
34
|
const utils_1 = require("../../utils");
|
|
@@ -43,6 +49,7 @@ const functional_1 = require("../../functional");
|
|
|
43
49
|
const prepare_1 = require("../extensions/prepare");
|
|
44
50
|
const prompt = require("../../prompt");
|
|
45
51
|
exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
|
|
52
|
+
exports.DEFAULT_FUNCTION_REGION = "us-central1";
|
|
46
53
|
async function prepare(context, options, payload) {
|
|
47
54
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
48
55
|
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
@@ -71,6 +78,7 @@ async function prepare(context, options, payload) {
|
|
|
71
78
|
}
|
|
72
79
|
context.hasRuntimeConfig = Object.keys(runtimeConfig).some((k) => k !== "firebase");
|
|
73
80
|
const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
|
|
81
|
+
const existingBackend = await backend.existingBackend(context);
|
|
74
82
|
if (Object.values(wantBuilds).some((b) => b.extensions)) {
|
|
75
83
|
const extContext = {};
|
|
76
84
|
const extPayload = {};
|
|
@@ -92,6 +100,10 @@ async function prepare(context, options, payload) {
|
|
|
92
100
|
proto.convertIfPresent(userEnvOpt, localCfg, "configDir", (cd) => options.config.path(cd));
|
|
93
101
|
const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
|
|
94
102
|
const envs = { ...userEnvs, ...firebaseEnvs };
|
|
103
|
+
const relevantEndpoints = backend
|
|
104
|
+
.allEndpoints(existingBackend)
|
|
105
|
+
.filter((e) => e.codebase === codebase || e.codebase === undefined);
|
|
106
|
+
await resolveDefaultRegionsForBuild(wantBuild, backend.of(...relevantEndpoints));
|
|
95
107
|
const { backend: wantBackend, envs: resolvedEnvs } = await build.resolveBackend({
|
|
96
108
|
build: wantBuild,
|
|
97
109
|
firebaseConfig,
|
|
@@ -187,13 +199,6 @@ async function prepare(context, options, payload) {
|
|
|
187
199
|
context.sources[codebase] = source;
|
|
188
200
|
}
|
|
189
201
|
payload.functions = {};
|
|
190
|
-
const existingBackend = await backend.existingBackend(context);
|
|
191
|
-
for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
|
|
192
|
-
const relevantEndpoints = backend
|
|
193
|
-
.allEndpoints(existingBackend)
|
|
194
|
-
.filter((e) => e.codebase === codebase || e.codebase === undefined);
|
|
195
|
-
await resolveDefaultRegions(wantBackend, backend.of(...relevantEndpoints));
|
|
196
|
-
}
|
|
197
202
|
const haveBackends = (0, functionsDeployHelper_1.groupEndpointsByCodebase)(wantBackends, backend.allEndpoints(existingBackend));
|
|
198
203
|
for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
|
|
199
204
|
const haveBackend = haveBackends[codebase] || backend.empty();
|
|
@@ -203,6 +208,7 @@ async function prepare(context, options, payload) {
|
|
|
203
208
|
inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase));
|
|
204
209
|
await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
|
|
205
210
|
resolveCpuAndConcurrency(wantBackend);
|
|
211
|
+
resolveDefaultTimeout(wantBackend);
|
|
206
212
|
validate.endpointsAreValid(wantBackend);
|
|
207
213
|
inferBlockingDetails(wantBackend);
|
|
208
214
|
}
|
|
@@ -225,40 +231,129 @@ async function prepare(context, options, payload) {
|
|
|
225
231
|
validate.checkFiltersIntegrity(wantBackends, context.filters);
|
|
226
232
|
(0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
|
|
227
233
|
}
|
|
228
|
-
function
|
|
229
|
-
endpoint.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
async function resolveDefaultRegionsForBuild(buildObj, have) {
|
|
235
|
+
for (const [id, endpoint] of Object.entries(buildObj.endpoints)) {
|
|
236
|
+
if (!endpoint.region?.length || endpoint.region.includes(build.REGION_TBD)) {
|
|
237
|
+
let resolvedRegion = exports.DEFAULT_FUNCTION_REGION;
|
|
238
|
+
let matching;
|
|
239
|
+
for (const region of Object.keys(have.endpoints)) {
|
|
240
|
+
if (have.endpoints[region][id]) {
|
|
241
|
+
if (matching) {
|
|
242
|
+
throw new error_1.FirebaseError(`Cannot resolve default region for function ${id}. It exists in multiple regions. The region must be specified to continue.`);
|
|
243
|
+
}
|
|
244
|
+
matching = have.endpoints[region][id];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (matching) {
|
|
248
|
+
resolvedRegion = matching.region;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
try {
|
|
252
|
+
const fullEndpoint = { ...endpoint, id };
|
|
253
|
+
if (build.isBlockingTriggered(endpoint)) {
|
|
254
|
+
resolvedRegion = resolveRegionForBlockingTrigger(fullEndpoint);
|
|
255
|
+
}
|
|
256
|
+
else if (build.isEventTriggered(endpoint)) {
|
|
257
|
+
resolvedRegion = await resolveRegionForEventTrigger(fullEndpoint);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
logger_1.logger.debug(`Failed to resolve region for endpoint ${id}. Defaulting to ${exports.DEFAULT_FUNCTION_REGION}.`, (0, error_1.getErrStack)(err));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
endpoint.region = [resolvedRegion];
|
|
265
|
+
}
|
|
235
266
|
}
|
|
236
267
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
268
|
+
function resolveRegionForBlockingTrigger(endpoint) {
|
|
269
|
+
const eventType = endpoint.blockingTrigger.eventType;
|
|
270
|
+
if (events.AUTH_BLOCKING_EVENTS.includes(eventType)) {
|
|
271
|
+
return "us-east1";
|
|
272
|
+
}
|
|
273
|
+
if ((0, ailogic_1.isGlobalAILogicEndpoint)(endpoint)) {
|
|
274
|
+
return "us-east1";
|
|
275
|
+
}
|
|
276
|
+
return exports.DEFAULT_FUNCTION_REGION;
|
|
277
|
+
}
|
|
278
|
+
async function resolveRegionForEventTrigger(endpoint) {
|
|
279
|
+
const eventTrigger = endpoint.eventTrigger;
|
|
280
|
+
const eventType = eventTrigger.eventType;
|
|
281
|
+
if (eventType.startsWith("google.cloud.pubsub.") ||
|
|
282
|
+
eventType.startsWith("providers/cloud.auth/eventTypes/") ||
|
|
283
|
+
eventType.startsWith("providers/firebase.auth/eventTypes/") ||
|
|
284
|
+
eventType.startsWith("google.firebase.testlab.") ||
|
|
285
|
+
eventType.startsWith("google.firebase.remoteconfig.") ||
|
|
286
|
+
eventType.startsWith("google.firebase.firebasealerts.")) {
|
|
287
|
+
return "us-east1";
|
|
288
|
+
}
|
|
289
|
+
if (eventType.startsWith("google.cloud.firestore.")) {
|
|
290
|
+
try {
|
|
291
|
+
const databaseId = eventTrigger.eventFilters?.database || "(default)";
|
|
292
|
+
const db = await (0, firestore_1.getDatabase)(endpoint.project, databaseId);
|
|
293
|
+
const locationId = db.locationId.toLowerCase();
|
|
294
|
+
if (locationId === "nam5" || locationId === "nam7")
|
|
295
|
+
return "us-central1";
|
|
296
|
+
if (locationId === "eur3")
|
|
297
|
+
return "europe-west1";
|
|
298
|
+
return locationId;
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
logger_1.logger.debug("Failed to resolve Firestore database location", (0, error_1.getErrStack)(err));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (eventType.startsWith("google.cloud.storage.")) {
|
|
305
|
+
try {
|
|
306
|
+
const bucketName = eventTrigger.eventFilters?.bucket;
|
|
307
|
+
if (bucketName) {
|
|
308
|
+
const bucket = await (0, storage_1.getBucket)(bucketName);
|
|
309
|
+
const locationId = bucket.location.toLowerCase();
|
|
310
|
+
if (locationId === "us")
|
|
311
|
+
return "us-east1";
|
|
312
|
+
if (locationId === "eu")
|
|
313
|
+
return "europe-west1";
|
|
314
|
+
if (locationId === "asia")
|
|
315
|
+
return "asia-east1";
|
|
316
|
+
return locationId;
|
|
243
317
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
logger_1.logger.debug("Failed to resolve Cloud Storage bucket location", (0, error_1.getErrStack)(err));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (eventType.startsWith("google.firebase.database.")) {
|
|
324
|
+
if (eventTrigger.region)
|
|
325
|
+
return eventTrigger.region;
|
|
326
|
+
try {
|
|
327
|
+
const instanceName = eventTrigger.eventFilters?.instance;
|
|
328
|
+
if (instanceName) {
|
|
329
|
+
const details = await (0, database_1.getDatabaseInstanceDetails)(endpoint.project, instanceName);
|
|
330
|
+
if (details.location && details.location !== "-") {
|
|
331
|
+
return details.location.toLowerCase();
|
|
247
332
|
}
|
|
248
|
-
matching = have.endpoints[region][id];
|
|
249
333
|
}
|
|
250
334
|
}
|
|
251
|
-
|
|
252
|
-
|
|
335
|
+
catch (err) {
|
|
336
|
+
logger_1.logger.debug("Failed to resolve Realtime Database instance location", (0, error_1.getErrStack)(err));
|
|
253
337
|
}
|
|
254
|
-
moveEndpointToRegion(want, wantE, matching.region);
|
|
255
338
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
339
|
+
if (eventType.startsWith("google.firebase.dataconnect.")) {
|
|
340
|
+
if (eventTrigger.region)
|
|
341
|
+
return eventTrigger.region;
|
|
342
|
+
try {
|
|
343
|
+
const service = eventTrigger.eventFilters?.service;
|
|
344
|
+
if (service) {
|
|
345
|
+
return (0, names_1.parseServiceName)(service).location;
|
|
346
|
+
}
|
|
347
|
+
const connector = eventTrigger.eventFilters?.connector;
|
|
348
|
+
if (connector) {
|
|
349
|
+
return (0, names_1.parseConnectorName)(connector).location;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
logger_1.logger.debug("Failed to resolve DataConnect location", (0, error_1.getErrStack)(err));
|
|
354
|
+
}
|
|
261
355
|
}
|
|
356
|
+
return exports.DEFAULT_FUNCTION_REGION;
|
|
262
357
|
}
|
|
263
358
|
function inferDetailsFromExisting(want, have, usedDotenv) {
|
|
264
359
|
for (const wantE of backend.allEndpoints(want)) {
|
|
@@ -279,6 +374,9 @@ function inferDetailsFromExisting(want, have, usedDotenv) {
|
|
|
279
374
|
if (typeof wantE.cpu === "undefined" && haveE.cpu) {
|
|
280
375
|
wantE.cpu = haveE.cpu;
|
|
281
376
|
}
|
|
377
|
+
if (typeof wantE.timeoutSeconds === "undefined" && haveE.timeoutSeconds) {
|
|
378
|
+
wantE.timeoutSeconds = haveE.timeoutSeconds;
|
|
379
|
+
}
|
|
282
380
|
wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS";
|
|
283
381
|
maybeCopyTriggerRegion(wantE, haveE);
|
|
284
382
|
}
|
|
@@ -344,6 +442,13 @@ function resolveCpuAndConcurrency(want) {
|
|
|
344
442
|
}
|
|
345
443
|
}
|
|
346
444
|
}
|
|
445
|
+
function resolveDefaultTimeout(want) {
|
|
446
|
+
for (const e of backend.allEndpoints(want)) {
|
|
447
|
+
if (e.platform === "run" && e.timeoutSeconds === undefined) {
|
|
448
|
+
e.timeoutSeconds = backend.DEFAULT_TIMEOUT_SECONDS;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
347
452
|
async function loadCodebases(config, options, firebaseConfig, runtimeConfig, filters) {
|
|
348
453
|
const codebases = (0, functionsDeployHelper_1.targetCodebases)(config, filters);
|
|
349
454
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
@@ -78,7 +78,7 @@ async function packageSource(projectDir, sourceDir, config, additionalSources, r
|
|
|
78
78
|
const ignore = config.ignore || ["node_modules", ".git"];
|
|
79
79
|
ignore.push("firebase-debug.log", "firebase-debug.*.log", CONFIG_DEST_FILE);
|
|
80
80
|
try {
|
|
81
|
-
const files = await fsAsync.readdirRecursive({ path: sourceDir,
|
|
81
|
+
const files = await fsAsync.readdirRecursive({ path: sourceDir, ignoreStrings: ignore });
|
|
82
82
|
hashes.push(...(await addFilesToArchive(archive, files, sourceDir, options?.executablePaths)));
|
|
83
83
|
for (const name of additionalSources) {
|
|
84
84
|
const absPath = utils.resolveWithin(projectDir, name);
|