firebase-tools 15.17.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/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 +26 -12
- package/lib/deploy/apphosting/util.js +4 -19
- package/lib/deploy/functions/backend.js +2 -1
- package/lib/deploy/functions/prepare.js +45 -50
- package/lib/deploy/functions/prepareFunctionsUpload.js +1 -1
- package/lib/deploy/functions/services/ailogic.js +10 -0
- 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/fsAsync.js +53 -10
- package/lib/tsconfig.compile.tsbuildinfo +1 -1
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -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,21 +19,35 @@ 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);
|
|
@@ -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) {
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
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;
|
|
@@ -78,6 +78,7 @@ async function prepare(context, options, payload) {
|
|
|
78
78
|
}
|
|
79
79
|
context.hasRuntimeConfig = Object.keys(runtimeConfig).some((k) => k !== "firebase");
|
|
80
80
|
const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
|
|
81
|
+
const existingBackend = await backend.existingBackend(context);
|
|
81
82
|
if (Object.values(wantBuilds).some((b) => b.extensions)) {
|
|
82
83
|
const extContext = {};
|
|
83
84
|
const extPayload = {};
|
|
@@ -99,6 +100,10 @@ async function prepare(context, options, payload) {
|
|
|
99
100
|
proto.convertIfPresent(userEnvOpt, localCfg, "configDir", (cd) => options.config.path(cd));
|
|
100
101
|
const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
|
|
101
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));
|
|
102
107
|
const { backend: wantBackend, envs: resolvedEnvs } = await build.resolveBackend({
|
|
103
108
|
build: wantBuild,
|
|
104
109
|
firebaseConfig,
|
|
@@ -194,13 +199,6 @@ async function prepare(context, options, payload) {
|
|
|
194
199
|
context.sources[codebase] = source;
|
|
195
200
|
}
|
|
196
201
|
payload.functions = {};
|
|
197
|
-
const existingBackend = await backend.existingBackend(context);
|
|
198
|
-
for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
|
|
199
|
-
const relevantEndpoints = backend
|
|
200
|
-
.allEndpoints(existingBackend)
|
|
201
|
-
.filter((e) => e.codebase === codebase || e.codebase === undefined);
|
|
202
|
-
await resolveDefaultRegions(wantBackend, backend.of(...relevantEndpoints));
|
|
203
|
-
}
|
|
204
202
|
const haveBackends = (0, functionsDeployHelper_1.groupEndpointsByCodebase)(wantBackends, backend.allEndpoints(existingBackend));
|
|
205
203
|
for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
|
|
206
204
|
const haveBackend = haveBackends[codebase] || backend.empty();
|
|
@@ -210,6 +208,7 @@ async function prepare(context, options, payload) {
|
|
|
210
208
|
inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase));
|
|
211
209
|
await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
|
|
212
210
|
resolveCpuAndConcurrency(wantBackend);
|
|
211
|
+
resolveDefaultTimeout(wantBackend);
|
|
213
212
|
validate.endpointsAreValid(wantBackend);
|
|
214
213
|
inferBlockingDetails(wantBackend);
|
|
215
214
|
}
|
|
@@ -232,52 +231,38 @@ async function prepare(context, options, payload) {
|
|
|
232
231
|
validate.checkFiltersIntegrity(wantBackends, context.filters);
|
|
233
232
|
(0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
|
|
234
233
|
}
|
|
235
|
-
function
|
|
236
|
-
endpoint.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
let matching;
|
|
247
|
-
for (const region of Object.keys(have.endpoints)) {
|
|
248
|
-
if (region === build.REGION_TBD) {
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
if (have.endpoints[region][id]) {
|
|
252
|
-
if (matching) {
|
|
253
|
-
throw new error_1.FirebaseError(`Cannot resolve default region for function ${id}. It exists in multiple regions. The region must be specified to continue.`);
|
|
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];
|
|
254
245
|
}
|
|
255
|
-
matching = have.endpoints[region][id];
|
|
256
246
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
moveEndpointToRegion(want, wantE, matching.region);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
async function resolveDefaultRegions(want, have) {
|
|
265
|
-
matchRegionsForExisting(want, have);
|
|
266
|
-
const endpoints = Object.values(want.endpoints[build.REGION_TBD] || {});
|
|
267
|
-
for (const endpoint of endpoints) {
|
|
268
|
-
let resolvedRegion = "us-central1";
|
|
269
|
-
try {
|
|
270
|
-
if (backend.isBlockingTriggered(endpoint)) {
|
|
271
|
-
resolvedRegion = resolveRegionForBlockingTrigger(endpoint);
|
|
247
|
+
if (matching) {
|
|
248
|
+
resolvedRegion = matching.region;
|
|
272
249
|
}
|
|
273
|
-
else
|
|
274
|
-
|
|
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
|
+
}
|
|
275
263
|
}
|
|
264
|
+
endpoint.region = [resolvedRegion];
|
|
276
265
|
}
|
|
277
|
-
catch (err) {
|
|
278
|
-
logger_1.logger.debug(`Failed to resolve region for endpoint ${endpoint.id}. Defaulting to us-central1.`, (0, error_1.getErrStack)(err));
|
|
279
|
-
}
|
|
280
|
-
moveEndpointToRegion(want, endpoint, resolvedRegion);
|
|
281
266
|
}
|
|
282
267
|
}
|
|
283
268
|
function resolveRegionForBlockingTrigger(endpoint) {
|
|
@@ -389,6 +374,9 @@ function inferDetailsFromExisting(want, have, usedDotenv) {
|
|
|
389
374
|
if (typeof wantE.cpu === "undefined" && haveE.cpu) {
|
|
390
375
|
wantE.cpu = haveE.cpu;
|
|
391
376
|
}
|
|
377
|
+
if (typeof wantE.timeoutSeconds === "undefined" && haveE.timeoutSeconds) {
|
|
378
|
+
wantE.timeoutSeconds = haveE.timeoutSeconds;
|
|
379
|
+
}
|
|
392
380
|
wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS";
|
|
393
381
|
maybeCopyTriggerRegion(wantE, haveE);
|
|
394
382
|
}
|
|
@@ -454,6 +442,13 @@ function resolveCpuAndConcurrency(want) {
|
|
|
454
442
|
}
|
|
455
443
|
}
|
|
456
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
|
+
}
|
|
457
452
|
async function loadCodebases(config, options, firebaseConfig, runtimeConfig, filters) {
|
|
458
453
|
const codebases = (0, functionsDeployHelper_1.targetCodebases)(config, filters);
|
|
459
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);
|
|
@@ -32,6 +32,16 @@ class AILogicService {
|
|
|
32
32
|
this.name = "ailogic";
|
|
33
33
|
this.api = "firebasevertexai.googleapis.com";
|
|
34
34
|
}
|
|
35
|
+
async requiredProjectBindings(projectNumber) {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
role: "roles/run.invoker",
|
|
39
|
+
members: [
|
|
40
|
+
`serviceAccount:service-${projectNumber}@gcp-sa-firebasevertexai.iam.gserviceaccount.com`,
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
}
|
|
35
45
|
validateTrigger(endpoint, wantBackend) {
|
|
36
46
|
if (!isAILogicEvent(endpoint)) {
|
|
37
47
|
return;
|
|
@@ -221,7 +221,7 @@ async function secretsAreValid(projectId, wantBackend) {
|
|
|
221
221
|
validatePlatformTargets(endpoints);
|
|
222
222
|
await validateSecretVersions(projectId, endpoints);
|
|
223
223
|
}
|
|
224
|
-
const secretsSupportedPlatforms =
|
|
224
|
+
const secretsSupportedPlatforms = backend.AllFunctionsPlatforms;
|
|
225
225
|
function validatePlatformTargets(endpoints) {
|
|
226
226
|
const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform));
|
|
227
227
|
if (unsupported.length > 0) {
|
package/lib/downloadUtils.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.downloadToTmp = downloadToTmp;
|
|
4
|
+
exports.validateSize = validateSize;
|
|
5
|
+
exports.validateChecksum = validateChecksum;
|
|
4
6
|
const url_1 = require("url");
|
|
7
|
+
const crypto = require("crypto");
|
|
5
8
|
const fs = require("fs-extra");
|
|
6
9
|
const ProgressBar = require("progress");
|
|
7
10
|
const tmp = require("tmp");
|
|
@@ -37,3 +40,24 @@ async function downloadToTmp(remoteUrl, auth = false) {
|
|
|
37
40
|
});
|
|
38
41
|
return tmpfile.name;
|
|
39
42
|
}
|
|
43
|
+
function validateSize(filepath, expectedSize) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const stat = fs.statSync(filepath);
|
|
46
|
+
return stat.size === expectedSize
|
|
47
|
+
? resolve()
|
|
48
|
+
: reject(new error_1.FirebaseError(`download failed, expected ${expectedSize} bytes but got ${stat.size}`, { exit: 1 }));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function validateChecksum(filepath, expectedChecksum, algorithm = "md5") {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const hash = crypto.createHash(algorithm);
|
|
54
|
+
const stream = fs.createReadStream(filepath);
|
|
55
|
+
stream.on("data", (data) => hash.update(data));
|
|
56
|
+
stream.on("end", () => {
|
|
57
|
+
const checksum = hash.digest("hex");
|
|
58
|
+
return checksum === expectedChecksum
|
|
59
|
+
? resolve()
|
|
60
|
+
: reject(new error_1.FirebaseError(`download failed, expected checksum ${expectedChecksum} but got ${checksum}`, { exit: 1 }));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
package/lib/emulator/download.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.downloadEmulator = downloadEmulator;
|
|
4
4
|
exports.downloadExtensionVersion = downloadExtensionVersion;
|
|
5
|
-
const crypto = require("crypto");
|
|
6
5
|
const fs = require("fs-extra");
|
|
7
6
|
const path = require("path");
|
|
8
7
|
const tmp = require("tmp");
|
|
@@ -36,8 +35,8 @@ async function downloadEmulator(name) {
|
|
|
36
35
|
throw err;
|
|
37
36
|
}
|
|
38
37
|
if (!emulator.opts.skipChecksumAndSize) {
|
|
39
|
-
await validateSize(tmpfile, emulator.opts.expectedSize);
|
|
40
|
-
await validateChecksum(tmpfile, emulator.opts.expectedChecksum);
|
|
38
|
+
await downloadUtils.validateSize(tmpfile, emulator.opts.expectedSize);
|
|
39
|
+
await downloadUtils.validateChecksum(tmpfile, emulator.opts.expectedChecksum, "md5");
|
|
41
40
|
}
|
|
42
41
|
if (emulator.opts.skipCache) {
|
|
43
42
|
removeOldFiles(name, emulator, true);
|
|
@@ -82,24 +81,3 @@ function removeOldFiles(name, emulator, removeAllVersions = false) {
|
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
|
-
function validateSize(filepath, expectedSize) {
|
|
86
|
-
return new Promise((resolve, reject) => {
|
|
87
|
-
const stat = fs.statSync(filepath);
|
|
88
|
-
return stat.size === expectedSize
|
|
89
|
-
? resolve()
|
|
90
|
-
: reject(new error_1.FirebaseError(`download failed, expected ${expectedSize} bytes but got ${stat.size}`, { exit: 1 }));
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
function validateChecksum(filepath, expectedChecksum) {
|
|
94
|
-
return new Promise((resolve, reject) => {
|
|
95
|
-
const hash = crypto.createHash("md5");
|
|
96
|
-
const stream = fs.createReadStream(filepath);
|
|
97
|
-
stream.on("data", (data) => hash.update(data));
|
|
98
|
-
stream.on("end", () => {
|
|
99
|
-
const checksum = hash.digest("hex");
|
|
100
|
-
return checksum === expectedChecksum
|
|
101
|
-
? resolve()
|
|
102
|
-
: reject(new error_1.FirebaseError(`download failed, expected checksum ${expectedChecksum} but got ${checksum}`, { exit: 1 }));
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
}
|