firebase-tools 15.17.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 +105 -37
- package/lib/apphosting/universalMakerDownload.js +72 -0
- package/lib/apphosting/universalMakerInfo.json +16 -0
- package/lib/archiveDirectory.js +1 -1
- 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/release.js +26 -12
- package/lib/deploy/apphosting/util.js +35 -37
- package/lib/deploy/functions/backend.js +2 -1
- package/lib/deploy/functions/prepare.js +50 -57
- package/lib/deploy/functions/prepareFunctionsUpload.js +1 -1
- package/lib/deploy/functions/services/ailogic.js +14 -6
- 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/gcp/cloudsql/connect.js +49 -25
- package/lib/tsconfig.compile.tsbuildinfo +1 -1
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +21 -6
- 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";
|
|
@@ -1,11 +1,102 @@
|
|
|
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");
|
|
8
|
-
|
|
11
|
+
const logger_1 = require("../logger");
|
|
12
|
+
const utils_1 = require("../utils");
|
|
13
|
+
const universalMakerDownload_1 = require("./universalMakerDownload");
|
|
14
|
+
async function runUniversalMaker(projectRoot, addedEnv) {
|
|
15
|
+
const universalMakerBinary = await (0, universalMakerDownload_1.getOrDownloadUniversalMaker)();
|
|
16
|
+
executeUniversalMakerBinary(universalMakerBinary, projectRoot, addedEnv);
|
|
17
|
+
return processUniversalMakerOutput(projectRoot);
|
|
18
|
+
}
|
|
19
|
+
function executeUniversalMakerBinary(universalMakerBinary, projectRoot, addedEnv) {
|
|
20
|
+
try {
|
|
21
|
+
const targetAppHosting = path.join(projectRoot, ".apphosting");
|
|
22
|
+
fs.removeSync(targetAppHosting);
|
|
23
|
+
fs.ensureDirSync(targetAppHosting);
|
|
24
|
+
const res = childProcess.spawnSync(universalMakerBinary, ["-application_dir", projectRoot, "-output_dir", projectRoot, "-output_format", "json"], {
|
|
25
|
+
cwd: projectRoot,
|
|
26
|
+
env: {
|
|
27
|
+
...process.env,
|
|
28
|
+
...addedEnv,
|
|
29
|
+
X_GOOGLE_TARGET_PLATFORM: "fah",
|
|
30
|
+
FIREBASE_OUTPUT_BUNDLE_DIR: targetAppHosting,
|
|
31
|
+
},
|
|
32
|
+
stdio: "pipe",
|
|
33
|
+
});
|
|
34
|
+
if (res.stdout) {
|
|
35
|
+
logger_1.logger.debug("[Universal Maker stdout]:\n" + res.stdout.toString());
|
|
36
|
+
}
|
|
37
|
+
if (res.stderr) {
|
|
38
|
+
logger_1.logger.debug("[Universal Maker stderr]:\n" + res.stderr.toString());
|
|
39
|
+
}
|
|
40
|
+
if (res.error) {
|
|
41
|
+
throw res.error;
|
|
42
|
+
}
|
|
43
|
+
if (res.status !== 0) {
|
|
44
|
+
throw new error_1.FirebaseError(`Universal Maker failed with exit code ${res.status ?? "unknown"}.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
if (e && typeof e === "object" && "code" in e && e.code === "EACCES") {
|
|
49
|
+
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.`);
|
|
50
|
+
}
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function parseBundleYaml(projectRoot, defaultRunCommand) {
|
|
55
|
+
const bundleYamlPath = path.join(projectRoot, ".apphosting", "bundle.yaml");
|
|
56
|
+
if (!fs.existsSync(bundleYamlPath)) {
|
|
57
|
+
throw new error_1.FirebaseError("Failed to resolve build artifacts. Ensure Universal Maker produced a valid bundle.yaml with outputFiles.");
|
|
58
|
+
}
|
|
59
|
+
const bundleRaw = fs.readFileSync(bundleYamlPath, "utf-8");
|
|
60
|
+
const bundleData = (0, utils_1.wrappedSafeLoad)(bundleRaw);
|
|
61
|
+
const runCommand = bundleData?.runConfig?.runCommand ?? defaultRunCommand;
|
|
62
|
+
const outputFiles = bundleData?.outputFiles?.serverApp?.include ?? [];
|
|
63
|
+
return { runCommand, outputFiles };
|
|
64
|
+
}
|
|
65
|
+
function processUniversalMakerOutput(projectRoot) {
|
|
66
|
+
const outputFilePath = path.join(projectRoot, "build_output.json");
|
|
67
|
+
if (!fs.existsSync(outputFilePath)) {
|
|
68
|
+
throw new error_1.FirebaseError(`Universal Maker did not produce the expected output file at ${outputFilePath}`);
|
|
69
|
+
}
|
|
70
|
+
const outputRaw = fs.readFileSync(outputFilePath, "utf-8");
|
|
71
|
+
fs.unlinkSync(outputFilePath);
|
|
72
|
+
let umOutput;
|
|
73
|
+
try {
|
|
74
|
+
umOutput = JSON.parse(outputRaw);
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
throw new error_1.FirebaseError(`Failed to parse build_output.json: ${(0, error_1.getErrMsg)(e)}`);
|
|
78
|
+
}
|
|
79
|
+
const defaultRunCommand = `${umOutput.command} ${umOutput.args.join(" ")}`;
|
|
80
|
+
const { runCommand: finalRunCommand, outputFiles: finalOutputFiles } = parseBundleYaml(projectRoot, defaultRunCommand);
|
|
81
|
+
return {
|
|
82
|
+
runConfig: {
|
|
83
|
+
runCommand: finalRunCommand,
|
|
84
|
+
environmentVariables: Object.entries(umOutput.envVars || {})
|
|
85
|
+
.filter(([k]) => k !== "FIREBASE_OUTPUT_BUNDLE_DIR")
|
|
86
|
+
.map(([k, v]) => ({
|
|
87
|
+
variable: k,
|
|
88
|
+
value: String(v),
|
|
89
|
+
availability: ["RUNTIME"],
|
|
90
|
+
})),
|
|
91
|
+
},
|
|
92
|
+
outputFiles: {
|
|
93
|
+
serverApp: {
|
|
94
|
+
include: finalOutputFiles,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function localBuild(projectId, projectRoot, env = {}, options) {
|
|
9
100
|
const hasBuildAvailableSecrets = Object.values(env).some((v) => v.secret && (!v.availability || v.availability.includes("BUILD")));
|
|
10
101
|
if (hasBuildAvailableSecrets && !options?.allowLocalBuildSecrets) {
|
|
11
102
|
if (options?.nonInteractive) {
|
|
@@ -18,34 +109,15 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
|
|
|
18
109
|
throw new error_1.FirebaseError("Cancelled local build due to BUILD-available secrets.");
|
|
19
110
|
}
|
|
20
111
|
}
|
|
21
|
-
const originalEnv = { ...process.env };
|
|
22
112
|
const addedEnv = await toProcessEnv(projectId, env);
|
|
23
|
-
|
|
24
|
-
process.env[key] = value;
|
|
25
|
-
}
|
|
26
|
-
let apphostingBuildOutput;
|
|
27
|
-
try {
|
|
28
|
-
apphostingBuildOutput = await (0, build_1.localBuild)(projectRoot, framework);
|
|
29
|
-
}
|
|
30
|
-
finally {
|
|
31
|
-
for (const key in process.env) {
|
|
32
|
-
if (!(key in originalEnv)) {
|
|
33
|
-
delete process.env[key];
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
for (const [key, value] of Object.entries(originalEnv)) {
|
|
37
|
-
process.env[key] = value;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const annotations = Object.fromEntries(Object.entries(apphostingBuildOutput.metadata).map(([key, value]) => [key, String(value)]));
|
|
113
|
+
const apphostingBuildOutput = await runUniversalMaker(projectRoot, addedEnv);
|
|
41
114
|
const discoveredEnv = apphostingBuildOutput.runConfig.environmentVariables?.map(({ variable, value, availability }) => ({
|
|
42
115
|
variable,
|
|
43
116
|
value,
|
|
44
|
-
availability,
|
|
117
|
+
availability: availability,
|
|
45
118
|
}));
|
|
46
119
|
return {
|
|
47
120
|
outputFiles: apphostingBuildOutput.outputFiles?.serverApp.include ?? [],
|
|
48
|
-
annotations,
|
|
49
121
|
buildConfig: {
|
|
50
122
|
runCommand: apphostingBuildOutput.runConfig.runCommand,
|
|
51
123
|
env: discoveredEnv ?? [],
|
|
@@ -53,18 +125,14 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
|
|
|
53
125
|
};
|
|
54
126
|
}
|
|
55
127
|
async function toProcessEnv(projectId, env) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
else {
|
|
65
|
-
return [key, value.value || ""];
|
|
66
|
-
}
|
|
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];
|
|
67
136
|
}));
|
|
68
|
-
|
|
69
|
-
return Object.fromEntries(filteredEntries);
|
|
137
|
+
return Object.fromEntries(resolvedEntries);
|
|
70
138
|
}
|
|
@@ -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": 16266914,
|
|
5
|
+
"expectedChecksumSHA256": "72dae4eeefedc096649134f6ee592248c17dba5cdaec5bf679f367b2093d41cc",
|
|
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": 17023802,
|
|
12
|
+
"expectedChecksumSHA256": "ba74d2d2ce61d1b773f5b9eefeb09ef48a6890f2767581b57f9871869693823d",
|
|
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
|
@@ -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
|
+
}
|
|
@@ -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);
|