firebase-tools 15.18.0 → 15.19.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/accountExporter.js +13 -6
- package/lib/accountImporter.js +18 -1
- package/lib/apphosting/constants.js +2 -1
- package/lib/apphosting/localbuilds.js +23 -66
- package/lib/apphosting/universalMakerInfo.json +4 -4
- package/lib/commands/dataconnect-sql-shell.js +21 -3
- package/lib/deploy/apphosting/deploy.js +39 -23
- package/lib/deploy/apphosting/prepare.js +28 -6
- package/lib/deploy/apphosting/util.js +34 -21
- package/lib/deploy/functions/prepare.js +8 -10
- package/lib/deploy/functions/services/ailogic.js +4 -6
- package/lib/emulator/downloadableEmulatorInfo.json +31 -31
- package/lib/gcp/cloudsql/connect.js +49 -25
- package/lib/tsconfig.compile.tsbuildinfo +1 -1
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +19 -4
- package/templates/init/functions/dart/_gitignore +1 -9
package/lib/accountExporter.js
CHANGED
|
@@ -40,10 +40,16 @@ const PROVIDER_ID_INDEX_MAP = new Map([
|
|
|
40
40
|
["facebook.com", 11],
|
|
41
41
|
["twitter.com", 15],
|
|
42
42
|
["github.com", 19],
|
|
43
|
+
["apple.com", 28],
|
|
44
|
+
["microsoft.com", 32],
|
|
45
|
+
["gc.apple.com", 36],
|
|
46
|
+
["playgames.google.com", 40],
|
|
47
|
+
["linkedin.com", 44],
|
|
48
|
+
["yahoo.com", 48],
|
|
43
49
|
]);
|
|
44
|
-
function
|
|
45
|
-
if (str.includes(",")) {
|
|
46
|
-
return `"${str}"`;
|
|
50
|
+
function escapeCsv(str) {
|
|
51
|
+
if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
|
|
52
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
47
53
|
}
|
|
48
54
|
return str;
|
|
49
55
|
}
|
|
@@ -53,17 +59,18 @@ function convertToNormalBase64(data) {
|
|
|
53
59
|
function addProviderUserInfo(providerInfo, arr, startPos) {
|
|
54
60
|
arr[startPos] = providerInfo.rawId;
|
|
55
61
|
arr[startPos + 1] = providerInfo.email || "";
|
|
56
|
-
arr[startPos + 2] =
|
|
62
|
+
arr[startPos + 2] = escapeCsv(providerInfo.displayName || "");
|
|
57
63
|
arr[startPos + 3] = providerInfo.photoUrl || "";
|
|
58
64
|
}
|
|
59
65
|
function transUserToArray(user) {
|
|
60
|
-
const
|
|
66
|
+
const arrLength = Math.max(...PROVIDER_ID_INDEX_MAP.values()) + 4;
|
|
67
|
+
const arr = Array(arrLength).fill("");
|
|
61
68
|
arr[0] = user.localId;
|
|
62
69
|
arr[1] = user.email || "";
|
|
63
70
|
arr[2] = user.emailVerified || false;
|
|
64
71
|
arr[3] = convertToNormalBase64(user.passwordHash || "");
|
|
65
72
|
arr[4] = convertToNormalBase64(user.salt || "");
|
|
66
|
-
arr[5] =
|
|
73
|
+
arr[5] = escapeCsv(user.displayName || "");
|
|
67
74
|
arr[6] = user.photoUrl || "";
|
|
68
75
|
for (let i = 0; i < (!user.providerUserInfo ? 0 : user.providerUserInfo.length); i++) {
|
|
69
76
|
const providerInfo = user.providerUserInfo[i];
|
package/lib/accountImporter.js
CHANGED
|
@@ -32,7 +32,18 @@ const ALLOWED_JSON_KEYS_RENAMING = {
|
|
|
32
32
|
lastSignedInAt: "lastLoginAt",
|
|
33
33
|
};
|
|
34
34
|
const ALLOWED_PROVIDER_USER_INFO_KEYS = ["providerId", "rawId", "email", "displayName", "photoUrl"];
|
|
35
|
-
const ALLOWED_PROVIDER_IDS = [
|
|
35
|
+
const ALLOWED_PROVIDER_IDS = [
|
|
36
|
+
"google.com",
|
|
37
|
+
"facebook.com",
|
|
38
|
+
"twitter.com",
|
|
39
|
+
"github.com",
|
|
40
|
+
"apple.com",
|
|
41
|
+
"microsoft.com",
|
|
42
|
+
"gc.apple.com",
|
|
43
|
+
"playgames.google.com",
|
|
44
|
+
"linkedin.com",
|
|
45
|
+
"yahoo.com",
|
|
46
|
+
];
|
|
36
47
|
function isValidBase64(str) {
|
|
37
48
|
const expected = Buffer.from(str, "base64").toString("base64");
|
|
38
49
|
if (str.length < expected.length && !str.endsWith("=")) {
|
|
@@ -125,6 +136,12 @@ function transArrayToUser(arr) {
|
|
|
125
136
|
addProviderUserInfo(user, "facebook.com", arr.slice(11, 15));
|
|
126
137
|
addProviderUserInfo(user, "twitter.com", arr.slice(15, 19));
|
|
127
138
|
addProviderUserInfo(user, "github.com", arr.slice(19, 23));
|
|
139
|
+
addProviderUserInfo(user, "apple.com", arr.slice(28, 32));
|
|
140
|
+
addProviderUserInfo(user, "microsoft.com", arr.slice(32, 36));
|
|
141
|
+
addProviderUserInfo(user, "gc.apple.com", arr.slice(36, 40));
|
|
142
|
+
addProviderUserInfo(user, "playgames.google.com", arr.slice(40, 44));
|
|
143
|
+
addProviderUserInfo(user, "linkedin.com", arr.slice(44, 48));
|
|
144
|
+
addProviderUserInfo(user, "yahoo.com", arr.slice(48, 52));
|
|
128
145
|
if (user.passwordHash && !isValidBase64(user.passwordHash)) {
|
|
129
146
|
return {
|
|
130
147
|
error: "Password hash should be base64 encoded.",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ALLOWED_DEPLOY_METHODS = exports.DEFAULT_DEPLOY_METHOD = exports.DEFAULT_LOCATION = void 0;
|
|
3
|
+
exports.LOCAL_BUILD_DIR_NAME = exports.ALLOWED_DEPLOY_METHODS = exports.DEFAULT_DEPLOY_METHOD = exports.DEFAULT_LOCATION = void 0;
|
|
4
4
|
exports.DEFAULT_LOCATION = "us-east4";
|
|
5
5
|
exports.DEFAULT_DEPLOY_METHOD = "github";
|
|
6
6
|
exports.ALLOWED_DEPLOY_METHODS = [{ name: "Deploy using github", value: "github" }];
|
|
7
|
+
exports.LOCAL_BUILD_DIR_NAME = ".local_build";
|
|
@@ -11,21 +11,23 @@ const error_1 = require("../error");
|
|
|
11
11
|
const logger_1 = require("../logger");
|
|
12
12
|
const utils_1 = require("../utils");
|
|
13
13
|
const universalMakerDownload_1 = require("./universalMakerDownload");
|
|
14
|
-
async function runUniversalMaker(projectRoot,
|
|
14
|
+
async function runUniversalMaker(projectRoot, addedEnv) {
|
|
15
15
|
const universalMakerBinary = await (0, universalMakerDownload_1.getOrDownloadUniversalMaker)();
|
|
16
|
-
executeUniversalMakerBinary(universalMakerBinary, projectRoot);
|
|
17
|
-
return processUniversalMakerOutput(projectRoot
|
|
16
|
+
executeUniversalMakerBinary(universalMakerBinary, projectRoot, addedEnv);
|
|
17
|
+
return processUniversalMakerOutput(projectRoot);
|
|
18
18
|
}
|
|
19
|
-
function executeUniversalMakerBinary(universalMakerBinary, projectRoot) {
|
|
19
|
+
function executeUniversalMakerBinary(universalMakerBinary, projectRoot, addedEnv) {
|
|
20
20
|
try {
|
|
21
|
-
const
|
|
22
|
-
fs.removeSync(
|
|
23
|
-
fs.ensureDirSync(
|
|
21
|
+
const targetAppHosting = path.join(projectRoot, ".apphosting");
|
|
22
|
+
fs.removeSync(targetAppHosting);
|
|
23
|
+
fs.ensureDirSync(targetAppHosting);
|
|
24
24
|
const res = childProcess.spawnSync(universalMakerBinary, ["-application_dir", projectRoot, "-output_dir", projectRoot, "-output_format", "json"], {
|
|
25
|
+
cwd: projectRoot,
|
|
25
26
|
env: {
|
|
26
27
|
...process.env,
|
|
28
|
+
...addedEnv,
|
|
27
29
|
X_GOOGLE_TARGET_PLATFORM: "fah",
|
|
28
|
-
FIREBASE_OUTPUT_BUNDLE_DIR:
|
|
30
|
+
FIREBASE_OUTPUT_BUNDLE_DIR: targetAppHosting,
|
|
29
31
|
},
|
|
30
32
|
stdio: "pipe",
|
|
31
33
|
});
|
|
@@ -57,33 +59,16 @@ function parseBundleYaml(projectRoot, defaultRunCommand) {
|
|
|
57
59
|
const bundleRaw = fs.readFileSync(bundleYamlPath, "utf-8");
|
|
58
60
|
const bundleData = (0, utils_1.wrappedSafeLoad)(bundleRaw);
|
|
59
61
|
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
|
-
}
|
|
62
|
+
const outputFiles = bundleData?.outputFiles?.serverApp?.include ?? [];
|
|
64
63
|
return { runCommand, outputFiles };
|
|
65
64
|
}
|
|
66
|
-
function processUniversalMakerOutput(projectRoot
|
|
65
|
+
function processUniversalMakerOutput(projectRoot) {
|
|
67
66
|
const outputFilePath = path.join(projectRoot, "build_output.json");
|
|
68
67
|
if (!fs.existsSync(outputFilePath)) {
|
|
69
68
|
throw new error_1.FirebaseError(`Universal Maker did not produce the expected output file at ${outputFilePath}`);
|
|
70
69
|
}
|
|
71
70
|
const outputRaw = fs.readFileSync(outputFilePath, "utf-8");
|
|
72
71
|
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
72
|
let umOutput;
|
|
88
73
|
try {
|
|
89
74
|
umOutput = JSON.parse(outputRaw);
|
|
@@ -94,11 +79,6 @@ function processUniversalMakerOutput(projectRoot, framework) {
|
|
|
94
79
|
const defaultRunCommand = `${umOutput.command} ${umOutput.args.join(" ")}`;
|
|
95
80
|
const { runCommand: finalRunCommand, outputFiles: finalOutputFiles } = parseBundleYaml(projectRoot, defaultRunCommand);
|
|
96
81
|
return {
|
|
97
|
-
metadata: {
|
|
98
|
-
language: umOutput.language,
|
|
99
|
-
runtime: umOutput.runtime,
|
|
100
|
-
framework: framework || "nextjs",
|
|
101
|
-
},
|
|
102
82
|
runConfig: {
|
|
103
83
|
runCommand: finalRunCommand,
|
|
104
84
|
environmentVariables: Object.entries(umOutput.envVars || {})
|
|
@@ -116,7 +96,7 @@ function processUniversalMakerOutput(projectRoot, framework) {
|
|
|
116
96
|
},
|
|
117
97
|
};
|
|
118
98
|
}
|
|
119
|
-
async function localBuild(projectId, projectRoot,
|
|
99
|
+
async function localBuild(projectId, projectRoot, env = {}, options) {
|
|
120
100
|
const hasBuildAvailableSecrets = Object.values(env).some((v) => v.secret && (!v.availability || v.availability.includes("BUILD")));
|
|
121
101
|
if (hasBuildAvailableSecrets && !options?.allowLocalBuildSecrets) {
|
|
122
102
|
if (options?.nonInteractive) {
|
|
@@ -129,26 +109,8 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
|
|
|
129
109
|
throw new error_1.FirebaseError("Cancelled local build due to BUILD-available secrets.");
|
|
130
110
|
}
|
|
131
111
|
}
|
|
132
|
-
const originalEnv = { ...process.env };
|
|
133
112
|
const addedEnv = await toProcessEnv(projectId, env);
|
|
134
|
-
|
|
135
|
-
process.env[key] = value;
|
|
136
|
-
}
|
|
137
|
-
let apphostingBuildOutput;
|
|
138
|
-
try {
|
|
139
|
-
apphostingBuildOutput = await runUniversalMaker(projectRoot, framework);
|
|
140
|
-
}
|
|
141
|
-
finally {
|
|
142
|
-
for (const key in process.env) {
|
|
143
|
-
if (!(key in originalEnv)) {
|
|
144
|
-
delete process.env[key];
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
for (const [key, value] of Object.entries(originalEnv)) {
|
|
148
|
-
process.env[key] = value;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
const annotations = Object.fromEntries(Object.entries(apphostingBuildOutput.metadata).map(([key, value]) => [key, String(value)]));
|
|
113
|
+
const apphostingBuildOutput = await runUniversalMaker(projectRoot, addedEnv);
|
|
152
114
|
const discoveredEnv = apphostingBuildOutput.runConfig.environmentVariables?.map(({ variable, value, availability }) => ({
|
|
153
115
|
variable,
|
|
154
116
|
value,
|
|
@@ -156,7 +118,6 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
|
|
|
156
118
|
}));
|
|
157
119
|
return {
|
|
158
120
|
outputFiles: apphostingBuildOutput.outputFiles?.serverApp.include ?? [],
|
|
159
|
-
annotations,
|
|
160
121
|
buildConfig: {
|
|
161
122
|
runCommand: apphostingBuildOutput.runConfig.runCommand,
|
|
162
123
|
env: discoveredEnv ?? [],
|
|
@@ -164,18 +125,14 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
|
|
|
164
125
|
};
|
|
165
126
|
}
|
|
166
127
|
async function toProcessEnv(projectId, env) {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
else {
|
|
176
|
-
return [key, value.value || ""];
|
|
177
|
-
}
|
|
128
|
+
const buildVars = Object.entries(env).filter(([, value]) => {
|
|
129
|
+
return !value.availability || value.availability.includes("BUILD");
|
|
130
|
+
});
|
|
131
|
+
const resolvedEntries = await Promise.all(buildVars.map(async ([key, value]) => {
|
|
132
|
+
const resolvedValue = value.secret
|
|
133
|
+
? await (0, index_1.loadSecret)(projectId, value.secret)
|
|
134
|
+
: value.value || "";
|
|
135
|
+
return [key, resolvedValue];
|
|
178
136
|
}));
|
|
179
|
-
|
|
180
|
-
return Object.fromEntries(filteredEntries);
|
|
137
|
+
return Object.fromEntries(resolvedEntries);
|
|
181
138
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"darwin_arm64": {
|
|
3
3
|
"version": "1.0.0",
|
|
4
|
-
"expectedSize":
|
|
5
|
-
"expectedChecksumSHA256": "
|
|
4
|
+
"expectedSize": 16266914,
|
|
5
|
+
"expectedChecksumSHA256": "72dae4eeefedc096649134f6ee592248c17dba5cdaec5bf679f367b2093d41cc",
|
|
6
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
7
|
"downloadPathRelativeToCacheDir": "universal-maker-darwin-arm64-1.0.0"
|
|
8
8
|
},
|
|
9
9
|
"linux_x64": {
|
|
10
10
|
"version": "1.0.0",
|
|
11
|
-
"expectedSize":
|
|
12
|
-
"expectedChecksumSHA256": "
|
|
11
|
+
"expectedSize": 17023802,
|
|
12
|
+
"expectedChecksumSHA256": "ba74d2d2ce61d1b773f5b9eefeb09ef48a6890f2767581b57f9871869693823d",
|
|
13
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
14
|
"downloadPathRelativeToCacheDir": "universal-maker-linux-x64-1.0.0"
|
|
15
15
|
}
|
|
@@ -103,6 +103,9 @@ exports.command = new command_1.Command("dataconnect:sql:shell")
|
|
|
103
103
|
user: username,
|
|
104
104
|
database: databaseId,
|
|
105
105
|
});
|
|
106
|
+
pool.on("error", (err) => {
|
|
107
|
+
logger_1.logger.debug("PostgreSQL pool error:", err);
|
|
108
|
+
});
|
|
106
109
|
const conn = await pool.connect();
|
|
107
110
|
await conn.query(`SET search_path TO "${schemaName}"`);
|
|
108
111
|
logger_1.logger.info(`Logged in as ${username}`);
|
|
@@ -110,8 +113,23 @@ exports.command = new command_1.Command("dataconnect:sql:shell")
|
|
|
110
113
|
logger_1.logger.info(clc.gray("Type your your SQL query or '.exit' to quit, queries should end with ';' or add empty line to execute."));
|
|
111
114
|
await mainShellLoop(conn);
|
|
112
115
|
logger_1.logger.info(clc.yellow("Exiting shell..."));
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
try {
|
|
117
|
+
conn.release();
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
logger_1.logger.debug("Error releasing pg connection:", err);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
connector.close();
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
logger_1.logger.debug("Error closing Cloud SQL connector:", err);
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
await pool.end();
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
logger_1.logger.debug("Error ending pg pool:", err);
|
|
133
|
+
}
|
|
116
134
|
return { projectId };
|
|
117
135
|
});
|
|
@@ -10,6 +10,7 @@ const projectUtils_1 = require("../../projectUtils");
|
|
|
10
10
|
const utils_1 = require("../../utils");
|
|
11
11
|
const util = require("./util");
|
|
12
12
|
const experiments = require("../../experiments");
|
|
13
|
+
const logger_1 = require("../../logger");
|
|
13
14
|
async function default_1(context, options) {
|
|
14
15
|
if (Object.entries(context.backendConfigs).length === 0) {
|
|
15
16
|
return;
|
|
@@ -52,31 +53,46 @@ async function default_1(context, options) {
|
|
|
52
53
|
}));
|
|
53
54
|
await Promise.all(Object.values(context.backendConfigs).map(async (cfg) => {
|
|
54
55
|
const rootDir = options.projectRoot ?? process.cwd();
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
let localBuildScratchDir;
|
|
57
|
+
try {
|
|
58
|
+
const isLocalBuild = cfg.localBuild;
|
|
59
|
+
let outputFiles;
|
|
60
|
+
if (isLocalBuild) {
|
|
61
|
+
experiments.assertEnabled("apphostinglocalbuilds", "App Hosting local builds");
|
|
62
|
+
const localBuild = context.backendLocalBuilds[cfg.backendId];
|
|
63
|
+
outputFiles = localBuild?.outputFiles;
|
|
64
|
+
localBuildScratchDir = localBuild?.localBuildScratchDir;
|
|
65
|
+
if (!outputFiles || !localBuildScratchDir) {
|
|
66
|
+
throw new error_1.FirebaseError(`No local build output files found for ${cfg.backendId}`);
|
|
67
|
+
}
|
|
62
68
|
}
|
|
69
|
+
const zippedSourcePath = isLocalBuild
|
|
70
|
+
? await util.createLocalBuildTarArchive(cfg, localBuildScratchDir, outputFiles ?? [])
|
|
71
|
+
: await util.createSourceDeployArchive(cfg, rootDir);
|
|
72
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Zipped ${isLocalBuild ? "built app" : "source"} for backend ${cfg.backendId}`);
|
|
73
|
+
const backendLocation = context.backendLocations[cfg.backendId];
|
|
74
|
+
if (!backendLocation) {
|
|
75
|
+
throw new error_1.FirebaseError(`Failed to find location for backend ${cfg.backendId}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
|
|
76
|
+
}
|
|
77
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Uploading ${isLocalBuild ? "built app" : "source"} for backend ${cfg.backendId}...`);
|
|
78
|
+
const bucketName = bucketsPerLocation[backendLocation];
|
|
79
|
+
const { bucket, object } = await gcs.uploadObject({
|
|
80
|
+
file: zippedSourcePath,
|
|
81
|
+
stream: fs.createReadStream(zippedSourcePath),
|
|
82
|
+
}, bucketName, isLocalBuild ? gcs.ContentType.TAR : gcs.ContentType.ZIP);
|
|
83
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Uploaded at gs://${bucket}/${object}`);
|
|
84
|
+
context.backendStorageUris[cfg.backendId] =
|
|
85
|
+
`gs://${bucketName}/${path.basename(zippedSourcePath)}`;
|
|
63
86
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
finally {
|
|
88
|
+
if (localBuildScratchDir && fs.existsSync(localBuildScratchDir)) {
|
|
89
|
+
try {
|
|
90
|
+
fs.rmSync(localBuildScratchDir, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
logger_1.logger.debug(`Failed to clean up local build directory ${localBuildScratchDir}: ${err}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
71
96
|
}
|
|
72
|
-
(0, utils_1.logLabeledBullet)("apphosting", `Uploading ${isLocalBuild ? "built app" : "source"} for backend ${cfg.backendId}...`);
|
|
73
|
-
const bucketName = bucketsPerLocation[backendLocation];
|
|
74
|
-
const { bucket, object } = await gcs.uploadObject({
|
|
75
|
-
file: zippedSourcePath,
|
|
76
|
-
stream: fs.createReadStream(zippedSourcePath),
|
|
77
|
-
}, bucketName, isLocalBuild ? gcs.ContentType.TAR : gcs.ContentType.ZIP);
|
|
78
|
-
(0, utils_1.logLabeledBullet)("apphosting", `Uploaded at gs://${bucket}/${object}`);
|
|
79
|
-
context.backendStorageUris[cfg.backendId] =
|
|
80
|
-
`gs://${bucketName}/${path.basename(zippedSourcePath)}`;
|
|
81
97
|
}));
|
|
82
98
|
}
|
|
@@ -4,7 +4,10 @@ exports.default = default_1;
|
|
|
4
4
|
exports.injectEnvVarsFromApphostingConfig = injectEnvVarsFromApphostingConfig;
|
|
5
5
|
exports.injectAutoInitEnvVars = injectAutoInitEnvVars;
|
|
6
6
|
exports.getBackendConfigs = getBackendConfigs;
|
|
7
|
+
const fs = require("fs");
|
|
7
8
|
const path = require("path");
|
|
9
|
+
const fsAsync = require("../../fsAsync");
|
|
10
|
+
const util_1 = require("./util");
|
|
8
11
|
const backend_1 = require("../../apphosting/backend");
|
|
9
12
|
const apphosting_1 = require("../../gcp/apphosting");
|
|
10
13
|
const yaml_1 = require("../../apphosting/yaml");
|
|
@@ -16,6 +19,7 @@ const getProjectNumber_1 = require("../../getProjectNumber");
|
|
|
16
19
|
const prompt_1 = require("../../prompt");
|
|
17
20
|
const utils_1 = require("../../utils");
|
|
18
21
|
const localbuilds_1 = require("../../apphosting/localbuilds");
|
|
22
|
+
const constants_1 = require("../../apphosting/constants");
|
|
19
23
|
const error_1 = require("../../error");
|
|
20
24
|
const managementApps = require("../../management/apps");
|
|
21
25
|
const utils_2 = require("../../apphosting/utils");
|
|
@@ -132,21 +136,21 @@ async function default_1(context, options) {
|
|
|
132
136
|
(0, utils_1.logLabeledBullet)("apphosting", `Starting local build for backend ${cfg.backendId}`);
|
|
133
137
|
await injectEnvVarsFromApphostingConfig(configs.filter((c) => c.backendId === cfg.backendId), options, buildEnv, runtimeEnv);
|
|
134
138
|
await injectAutoInitEnvVars(cfg, backends, buildEnv, runtimeEnv);
|
|
139
|
+
const rootDir = options.projectRoot || process.cwd();
|
|
140
|
+
const localBuildScratchDir = path.join(rootDir, `${constants_1.LOCAL_BUILD_DIR_NAME}_${cfg.backendId}`);
|
|
135
141
|
try {
|
|
136
|
-
|
|
142
|
+
await prepareLocalBuildScratchDirectory(rootDir, localBuildScratchDir, cfg);
|
|
143
|
+
const { outputFiles, buildConfig } = await (0, localbuilds_1.localBuild)(projectId, localBuildScratchDir, buildEnv[cfg.backendId] || {}, {
|
|
137
144
|
nonInteractive: options.nonInteractive,
|
|
138
145
|
allowLocalBuildSecrets: !!options.allowLocalBuildSecrets,
|
|
139
146
|
});
|
|
140
|
-
if (outputFiles.length !== 1) {
|
|
141
|
-
throw new error_1.FirebaseError(`Local build for backend ${cfg.backendId} failed: No output files found.`);
|
|
142
|
-
}
|
|
143
147
|
context.backendLocalBuilds[cfg.backendId] = {
|
|
144
|
-
|
|
148
|
+
outputFiles,
|
|
149
|
+
localBuildScratchDir,
|
|
145
150
|
buildConfig: {
|
|
146
151
|
...buildConfig,
|
|
147
152
|
env: mergeEnvVars(buildConfig.env || [], runtimeEnv[cfg.backendId] || {}),
|
|
148
153
|
},
|
|
149
|
-
annotations,
|
|
150
154
|
};
|
|
151
155
|
}
|
|
152
156
|
catch (e) {
|
|
@@ -244,3 +248,21 @@ async function ensureAppHostingServiceAgentRoles(projectId, projectNumber) {
|
|
|
244
248
|
(0, utils_1.logLabeledWarning)("apphosting", `Unable to verify App Hosting service agent permissions for ${p4saEmail}. If you encounter a PERMISSION_DENIED error during rollout, please ensure the service agent has the "Storage Object Viewer" role.`);
|
|
245
249
|
}
|
|
246
250
|
}
|
|
251
|
+
async function prepareLocalBuildScratchDirectory(rootDir, localBuildScratchDir, cfg) {
|
|
252
|
+
const ignore = (0, util_1.resolveIgnorePatterns)(cfg);
|
|
253
|
+
if (fs.existsSync(localBuildScratchDir)) {
|
|
254
|
+
throw new error_1.FirebaseError(`The local build scratch directory '${localBuildScratchDir}' already exists. Please delete it and try again.`);
|
|
255
|
+
}
|
|
256
|
+
fs.mkdirSync(localBuildScratchDir, { recursive: true });
|
|
257
|
+
const filesToCopy = await fsAsync.readdirRecursive({
|
|
258
|
+
path: rootDir,
|
|
259
|
+
ignoreStrings: ignore,
|
|
260
|
+
supportGitIgnore: true,
|
|
261
|
+
});
|
|
262
|
+
for (const file of filesToCopy) {
|
|
263
|
+
const relativePath = path.relative(rootDir, file.name);
|
|
264
|
+
const destPath = path.join(localBuildScratchDir, relativePath);
|
|
265
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
266
|
+
fs.copyFileSync(file.name, destPath);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createLocalBuildTarArchive = createLocalBuildTarArchive;
|
|
4
4
|
exports.createSourceDeployArchive = createSourceDeployArchive;
|
|
5
|
+
exports.resolveIgnorePatterns = resolveIgnorePatterns;
|
|
5
6
|
const archiver = require("archiver");
|
|
6
7
|
const fs = require("fs");
|
|
7
8
|
const path = require("path");
|
|
@@ -9,25 +10,28 @@ const tar = require("tar");
|
|
|
9
10
|
const tmp = require("tmp");
|
|
10
11
|
const error_1 = require("../../error");
|
|
11
12
|
const fsAsync = require("../../fsAsync");
|
|
12
|
-
const
|
|
13
|
-
async function createLocalBuildTarArchive(config, rootDir,
|
|
13
|
+
const utils_1 = require("../../utils");
|
|
14
|
+
async function createLocalBuildTarArchive(config, rootDir, outputFiles) {
|
|
14
15
|
const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".tar.gz" }).name;
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
path
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
const filesToPackage = outputFiles.length > 0 ? outputFiles : ["."];
|
|
17
|
+
const allFiles = [];
|
|
18
|
+
for (const fileOrDir of filesToPackage) {
|
|
19
|
+
const absolutePath = path.join(rootDir, fileOrDir);
|
|
20
|
+
if (!fs.existsSync(absolutePath)) {
|
|
21
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Expected build output file or directory not found: ${fileOrDir}`);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const stat = fs.statSync(absolutePath);
|
|
25
|
+
if (stat.isDirectory()) {
|
|
26
|
+
const rdrFiles = await fsAsync.readdirRecursive({
|
|
27
|
+
path: absolutePath,
|
|
28
|
+
ignoreStrings: ["firebase-debug.log", "firebase-debug.*.log"],
|
|
29
|
+
supportGitIgnore: false,
|
|
30
|
+
});
|
|
31
|
+
allFiles.push(...rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name)));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
allFiles.push(path.relative(rootDir, absolutePath));
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
try {
|
|
@@ -58,8 +62,7 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
|
|
|
58
62
|
});
|
|
59
63
|
const archive = archiver("zip");
|
|
60
64
|
const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir;
|
|
61
|
-
const ignore = config
|
|
62
|
-
ignore.push("firebase-debug.log", "firebase-debug.*.log");
|
|
65
|
+
const ignore = resolveIgnorePatterns(config);
|
|
63
66
|
try {
|
|
64
67
|
const files = await fsAsync.readdirRecursive({
|
|
65
68
|
path: targetDir,
|
|
@@ -76,10 +79,20 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
|
|
|
76
79
|
await pipeAsync(archive, fileStream);
|
|
77
80
|
}
|
|
78
81
|
catch (err) {
|
|
79
|
-
throw new error_1.FirebaseError(`Could not read source directory. Remove links and shortcuts and try again. Original: ${err}`, { original: err, exit: 1 });
|
|
82
|
+
throw new error_1.FirebaseError(`Could not read source directory. Remove links and shortcuts and try again. Original: ${String(err)}`, { original: err, exit: 1 });
|
|
80
83
|
}
|
|
81
84
|
return tmpFile;
|
|
82
85
|
}
|
|
86
|
+
function resolveIgnorePatterns(config, skipDefaultNodeModules = false) {
|
|
87
|
+
const ignore = config.ignore
|
|
88
|
+
? [...config.ignore]
|
|
89
|
+
: skipDefaultNodeModules
|
|
90
|
+
? [".git"]
|
|
91
|
+
: ["node_modules", ".git"];
|
|
92
|
+
ignore.push("firebase-debug.log", "firebase-debug.*.log");
|
|
93
|
+
ignore.push(".local_build_*");
|
|
94
|
+
return ignore;
|
|
95
|
+
}
|
|
83
96
|
async function pipeAsync(from, to) {
|
|
84
97
|
from.pipe(to);
|
|
85
98
|
await from.finalize();
|
|
@@ -249,12 +249,11 @@ async function resolveDefaultRegionsForBuild(buildObj, have) {
|
|
|
249
249
|
}
|
|
250
250
|
else {
|
|
251
251
|
try {
|
|
252
|
-
const fullEndpoint = { ...endpoint, id };
|
|
253
252
|
if (build.isBlockingTriggered(endpoint)) {
|
|
254
|
-
resolvedRegion = resolveRegionForBlockingTrigger(
|
|
253
|
+
resolvedRegion = resolveRegionForBlockingTrigger(endpoint.blockingTrigger);
|
|
255
254
|
}
|
|
256
255
|
else if (build.isEventTriggered(endpoint)) {
|
|
257
|
-
resolvedRegion = await resolveRegionForEventTrigger(
|
|
256
|
+
resolvedRegion = await resolveRegionForEventTrigger(endpoint.project, endpoint.eventTrigger);
|
|
258
257
|
}
|
|
259
258
|
}
|
|
260
259
|
catch (err) {
|
|
@@ -265,18 +264,17 @@ async function resolveDefaultRegionsForBuild(buildObj, have) {
|
|
|
265
264
|
}
|
|
266
265
|
}
|
|
267
266
|
}
|
|
268
|
-
function resolveRegionForBlockingTrigger(
|
|
269
|
-
const eventType =
|
|
267
|
+
function resolveRegionForBlockingTrigger(blockingTrigger) {
|
|
268
|
+
const eventType = blockingTrigger.eventType;
|
|
270
269
|
if (events.AUTH_BLOCKING_EVENTS.includes(eventType)) {
|
|
271
270
|
return "us-east1";
|
|
272
271
|
}
|
|
273
|
-
if ((0, ailogic_1.
|
|
272
|
+
if ((0, ailogic_1.isGlobalAILogicTrigger)(blockingTrigger)) {
|
|
274
273
|
return "us-east1";
|
|
275
274
|
}
|
|
276
275
|
return exports.DEFAULT_FUNCTION_REGION;
|
|
277
276
|
}
|
|
278
|
-
async function resolveRegionForEventTrigger(
|
|
279
|
-
const eventTrigger = endpoint.eventTrigger;
|
|
277
|
+
async function resolveRegionForEventTrigger(project, eventTrigger) {
|
|
280
278
|
const eventType = eventTrigger.eventType;
|
|
281
279
|
if (eventType.startsWith("google.cloud.pubsub.") ||
|
|
282
280
|
eventType.startsWith("providers/cloud.auth/eventTypes/") ||
|
|
@@ -289,7 +287,7 @@ async function resolveRegionForEventTrigger(endpoint) {
|
|
|
289
287
|
if (eventType.startsWith("google.cloud.firestore.")) {
|
|
290
288
|
try {
|
|
291
289
|
const databaseId = eventTrigger.eventFilters?.database || "(default)";
|
|
292
|
-
const db = await (0, firestore_1.getDatabase)(
|
|
290
|
+
const db = await (0, firestore_1.getDatabase)(project, databaseId);
|
|
293
291
|
const locationId = db.locationId.toLowerCase();
|
|
294
292
|
if (locationId === "nam5" || locationId === "nam7")
|
|
295
293
|
return "us-central1";
|
|
@@ -326,7 +324,7 @@ async function resolveRegionForEventTrigger(endpoint) {
|
|
|
326
324
|
try {
|
|
327
325
|
const instanceName = eventTrigger.eventFilters?.instance;
|
|
328
326
|
if (instanceName) {
|
|
329
|
-
const details = await (0, database_1.getDatabaseInstanceDetails)(
|
|
327
|
+
const details = await (0, database_1.getDatabaseInstanceDetails)(project, instanceName);
|
|
330
328
|
if (details.location && details.location !== "-") {
|
|
331
329
|
return details.location.toLowerCase();
|
|
332
330
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AILogicService = exports.AI_LOGIC_EVENTS = exports.AI_LOGIC_AFTER_GENERATE_CONTENT = exports.AI_LOGIC_BEFORE_GENERATE_CONTENT = void 0;
|
|
4
4
|
exports.isAILogicEvent = isAILogicEvent;
|
|
5
|
-
exports.
|
|
5
|
+
exports.isGlobalAILogicTrigger = isGlobalAILogicTrigger;
|
|
6
6
|
const backend = require("../backend");
|
|
7
7
|
const error_1 = require("../../../error");
|
|
8
8
|
const ailogicApi = require("../../../gcp/ailogic");
|
|
@@ -20,11 +20,9 @@ function isAILogicEvent(endpoint) {
|
|
|
20
20
|
}
|
|
21
21
|
return exports.AI_LOGIC_EVENTS.includes(endpoint.blockingTrigger.eventType);
|
|
22
22
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
return !endpoint.blockingTrigger.options?.regionalWebhook;
|
|
23
|
+
function isGlobalAILogicTrigger(blockingTrigger) {
|
|
24
|
+
return (exports.AI_LOGIC_EVENTS.includes(blockingTrigger.eventType) &&
|
|
25
|
+
!blockingTrigger.options?.regionalWebhook);
|
|
28
26
|
}
|
|
29
27
|
class AILogicService {
|
|
30
28
|
constructor() {
|