firebase-tools 15.11.0 → 15.12.1-main.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/agentSkills.js +70 -0
- package/lib/api.js +3 -1
- package/lib/apphosting/backend.js +22 -3
- package/lib/apphosting/config.js +19 -2
- package/lib/apphosting/localbuilds.js +25 -4
- package/lib/bin/mcp.js +5 -1
- package/lib/commands/apphosting-backends-create.js +19 -2
- package/lib/commands/apphosting-backends-list.js +21 -5
- package/lib/commands/functions-delete.js +1 -0
- package/lib/commands/functions-export.js +40 -0
- package/lib/commands/index.js +3 -0
- package/lib/commands/init.js +1 -0
- package/lib/deploy/apphosting/deploy.js +11 -6
- package/lib/deploy/apphosting/prepare.js +88 -4
- package/lib/deploy/apphosting/release.js +2 -5
- package/lib/deploy/apphosting/util.js +45 -2
- package/lib/deploy/functions/prepare.js +5 -1
- package/lib/deploy/functions/prepareFunctionsUpload.js +5 -2
- package/lib/deploy/functions/release/fabricator.js +4 -3
- package/lib/deploy/functions/release/index.js +5 -0
- package/lib/deploy/functions/services/ailogic.js +68 -0
- package/lib/deploy/functions/services/index.js +4 -0
- package/lib/emulator/downloadableEmulatorInfo.json +24 -24
- package/lib/emulator/storage/rules/manager.js +10 -3
- package/lib/emulator/storage/rules/runtime.js +9 -7
- package/lib/experiments.js +22 -0
- package/lib/firebase_studio/migrate.js +30 -61
- package/lib/functions/iac/export.js +36 -0
- package/lib/functions/iac/terraform.js +146 -0
- package/lib/gcp/ailogic.js +108 -0
- package/lib/gcp/cloudfunctionsv2.js +24 -0
- package/lib/gcp/cloudsql/cloudsqladmin.js +1 -1
- package/lib/init/features/agentSkills.js +26 -0
- package/lib/init/features/dataconnect/sdk.js +26 -12
- package/lib/init/features/index.js +4 -1
- package/lib/init/index.js +6 -0
- package/lib/tsconfig.compile.tsbuildinfo +1 -0
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/lib/utils.js +8 -0
- package/package.json +5 -3
- package/standalone/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.promptForAgentSkills = promptForAgentSkills;
|
|
4
|
+
exports.installAgentSkills = installAgentSkills;
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
const utils = require("./utils");
|
|
7
|
+
const prompt = require("./prompt");
|
|
8
|
+
const error_1 = require("./error");
|
|
9
|
+
async function promptForAgentSkills() {
|
|
10
|
+
return prompt.confirm({
|
|
11
|
+
message: "Would you like to install agent skills for Firebase?",
|
|
12
|
+
default: true,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async function installAgentSkills(options) {
|
|
16
|
+
if (!utils.commandExistsSync("npx")) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const args = [
|
|
20
|
+
"-y",
|
|
21
|
+
"skills",
|
|
22
|
+
"add",
|
|
23
|
+
"firebase/agent-skills",
|
|
24
|
+
"--skill",
|
|
25
|
+
"*",
|
|
26
|
+
"-y",
|
|
27
|
+
];
|
|
28
|
+
if (options.agentName) {
|
|
29
|
+
args.push("-a", options.agentName);
|
|
30
|
+
}
|
|
31
|
+
if (options.global) {
|
|
32
|
+
args.push("-g");
|
|
33
|
+
}
|
|
34
|
+
if (options.background) {
|
|
35
|
+
utils.logBullet("Installing Agent skills in the background...");
|
|
36
|
+
try {
|
|
37
|
+
const child = (0, child_process_1.spawn)("npx", args, {
|
|
38
|
+
cwd: options.cwd,
|
|
39
|
+
stdio: "ignore",
|
|
40
|
+
detached: true,
|
|
41
|
+
shell: process.platform === "win32",
|
|
42
|
+
});
|
|
43
|
+
child.unref();
|
|
44
|
+
utils.logSuccess("Agent skills installation started");
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
utils.logWarning(`Could not start Agent skills installation: ${(0, error_1.getErrMsg)(err)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
utils.logBullet("Adding Agent skills...");
|
|
52
|
+
try {
|
|
53
|
+
const result = (0, child_process_1.spawnSync)("npx", args, {
|
|
54
|
+
cwd: options.cwd,
|
|
55
|
+
stdio: "ignore",
|
|
56
|
+
shell: process.platform === "win32",
|
|
57
|
+
});
|
|
58
|
+
if (result.error) {
|
|
59
|
+
throw result.error;
|
|
60
|
+
}
|
|
61
|
+
if (result.status !== 0) {
|
|
62
|
+
throw new Error(`npx skills add exited with code ${result.status}`);
|
|
63
|
+
}
|
|
64
|
+
utils.logSuccess("Added Agent skills");
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
utils.logWarning(`Could not add Agent skills: ${(0, error_1.getErrMsg)(err)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/lib/api.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.storageOrigin = exports.runtimeconfigOrigin = exports.rulesOrigin = exports.resourceManagerOrigin = exports.crashlyticsApiOrigin = exports.messagingApiOrigin = exports.remoteConfigApiOrigin = exports.rtdbMetadataOrigin = exports.rtdbManagementOrigin = exports.realtimeOrigin = exports.extensionsTOSOrigin = exports.extensionsPublisherOrigin = exports.extensionsOrigin = exports.iamOrigin = exports.identityOrigin = exports.hostingOrigin = exports.googleOrigin = exports.pubsubOrigin = exports.cloudTasksOrigin = exports.cloudschedulerOrigin = exports.cloudbuildOrigin = exports.functionsDefaultRegion = exports.runOrigin = exports.functionsV2Origin = exports.functionsOrigin = exports.firestoreOrigin = exports.firestoreOriginOrEmulator = exports.firedataOrigin = exports.firebaseExtensionsRegistryOrigin = exports.firebaseApiOrigin = exports.eventarcOrigin = exports.dynamicLinksKey = exports.dynamicLinksOrigin = exports.consoleOrigin = exports.authManagementOrigin = exports.authOrigin = exports.apphostingGitHubAppInstallationURL = exports.apphostingP4SADomain = exports.apphostingOrigin = exports.appDistributionOrigin = exports.artifactRegistryDomain = exports.developerConnectP4SADomain = exports.developerConnectOrigin = exports.containerRegistryDomain = exports.cloudMonitoringOrigin = exports.cloudloggingOrigin = exports.cloudbillingOrigin = exports.clientSecret = exports.clientId = exports.authProxyOrigin = void 0;
|
|
4
|
-
exports.developerKnowledgeOrigin = exports.cloudTestingOrigin = exports.appTestingOrigin = exports.cloudAiCompanionOrigin = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectP4SADomain = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.studioApiOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = void 0;
|
|
4
|
+
exports.developerKnowledgeOrigin = exports.cloudTestingOrigin = exports.appTestingOrigin = exports.cloudAiCompanionOrigin = exports.aiLogicProxyOrigin = exports.vertexAIOrigin = exports.cloudSQLAdminOrigin = exports.dataConnectLocalConnString = exports.dataconnectP4SADomain = exports.dataconnectOrigin = exports.githubClientSecret = exports.githubClientId = exports.computeOrigin = exports.secretManagerOrigin = exports.githubApiOrigin = exports.githubOrigin = exports.studioApiOrigin = exports.serviceUsageOrigin = exports.cloudRunApiOrigin = exports.hostingApiOrigin = exports.firebaseStorageOrigin = void 0;
|
|
5
5
|
exports.getScopes = getScopes;
|
|
6
6
|
exports.setScopes = setScopes;
|
|
7
7
|
const constants_1 = require("./emulator/constants");
|
|
@@ -146,6 +146,8 @@ const cloudSQLAdminOrigin = () => utils.envOverride("CLOUD_SQL_URL", "https://sq
|
|
|
146
146
|
exports.cloudSQLAdminOrigin = cloudSQLAdminOrigin;
|
|
147
147
|
const vertexAIOrigin = () => utils.envOverride("VERTEX_AI_URL", "https://aiplatform.googleapis.com");
|
|
148
148
|
exports.vertexAIOrigin = vertexAIOrigin;
|
|
149
|
+
const aiLogicProxyOrigin = () => utils.envOverride("AI_LOGIC_PROXY_URL", "https://firebasevertexai.googleapis.com");
|
|
150
|
+
exports.aiLogicProxyOrigin = aiLogicProxyOrigin;
|
|
149
151
|
const cloudAiCompanionOrigin = () => utils.envOverride("CLOUD_AI_COMPANION_URL", "https://cloudaicompanion.googleapis.com");
|
|
150
152
|
exports.cloudAiCompanionOrigin = cloudAiCompanionOrigin;
|
|
151
153
|
const appTestingOrigin = () => utils.envOverride("FIREBASE_APP_TESTING_URL", "https://firebaseapptesting.googleapis.com");
|
|
@@ -34,6 +34,8 @@ const ora = require("ora");
|
|
|
34
34
|
const node_fetch_1 = require("node-fetch");
|
|
35
35
|
const rollout_1 = require("./rollout");
|
|
36
36
|
const fuzzy = require("fuzzy");
|
|
37
|
+
const experiments_1 = require("../experiments");
|
|
38
|
+
const DEFAULT_RUNTIME = "nodejs";
|
|
37
39
|
const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
|
|
38
40
|
const apphostingPollerOptions = {
|
|
39
41
|
apiOrigin: (0, api_1.apphostingOrigin)(),
|
|
@@ -64,7 +66,7 @@ async function awaitTlsReady(url) {
|
|
|
64
66
|
}
|
|
65
67
|
} while (!ready);
|
|
66
68
|
}
|
|
67
|
-
async function doSetup(projectId, nonInteractive, webAppName, backendId, serviceAccount, primaryRegion, rootDir) {
|
|
69
|
+
async function doSetup(projectId, nonInteractive, webAppName, backendId, serviceAccount, primaryRegion, rootDir, runtime, automaticBaseImageUpdatesDisabled) {
|
|
68
70
|
await ensureRequiredApisEnabled(projectId);
|
|
69
71
|
await ensureAppHostingComputeServiceAccount(projectId, serviceAccount ? serviceAccount : null);
|
|
70
72
|
let location = primaryRegion;
|
|
@@ -97,12 +99,27 @@ async function doSetup(projectId, nonInteractive, webAppName, backendId, service
|
|
|
97
99
|
if (!location || !backendId) {
|
|
98
100
|
throw new error_1.FirebaseError("Internal error: location or backendId is not defined.");
|
|
99
101
|
}
|
|
102
|
+
if (runtime === undefined && (0, experiments_1.isEnabled)("abiu")) {
|
|
103
|
+
if (nonInteractive) {
|
|
104
|
+
runtime = DEFAULT_RUNTIME;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
runtime = await (0, prompt_1.select)({
|
|
108
|
+
message: "Which runtime do you want to use?",
|
|
109
|
+
choices: [
|
|
110
|
+
{ name: "Node.js (default)", value: DEFAULT_RUNTIME },
|
|
111
|
+
{ name: "Node.js 22", value: "nodejs22" },
|
|
112
|
+
],
|
|
113
|
+
default: DEFAULT_RUNTIME,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
100
117
|
const webApp = await app_1.webApps.getOrCreateWebApp(projectId, webAppName ? webAppName : null, backendId);
|
|
101
118
|
if (!webApp) {
|
|
102
119
|
(0, utils_1.logWarning)(`Firebase web app not set`);
|
|
103
120
|
}
|
|
104
121
|
const createBackendSpinner = ora("Creating your new backend...").start();
|
|
105
|
-
const backend = await createBackend(projectId, location, backendId, serviceAccount ? serviceAccount : null, gitRepositoryLink, webApp?.id, rootDir);
|
|
122
|
+
const backend = await createBackend(projectId, location, backendId, serviceAccount ? serviceAccount : null, gitRepositoryLink, webApp?.id, rootDir, runtime, automaticBaseImageUpdatesDisabled);
|
|
106
123
|
createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);
|
|
107
124
|
if (nonInteractive) {
|
|
108
125
|
return;
|
|
@@ -227,7 +244,7 @@ async function promptNewBackendId(projectId, location) {
|
|
|
227
244
|
function defaultComputeServiceAccountEmail(projectId) {
|
|
228
245
|
return `${DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME}@${projectId}.iam.gserviceaccount.com`;
|
|
229
246
|
}
|
|
230
|
-
async function createBackend(projectId, location, backendId, serviceAccount, repository, webAppId, rootDir = "/") {
|
|
247
|
+
async function createBackend(projectId, location, backendId, serviceAccount, repository, webAppId, rootDir = "/", runtime, automaticBaseImageUpdatesDisabled) {
|
|
231
248
|
const defaultServiceAccount = defaultComputeServiceAccountEmail(projectId);
|
|
232
249
|
const backendReqBody = {
|
|
233
250
|
servingLocality: "GLOBAL_ACCESS",
|
|
@@ -240,6 +257,8 @@ async function createBackend(projectId, location, backendId, serviceAccount, rep
|
|
|
240
257
|
labels: deploymentTool.labels(),
|
|
241
258
|
serviceAccount: serviceAccount || defaultServiceAccount,
|
|
242
259
|
appId: webAppId,
|
|
260
|
+
runtime: { value: runtime ?? "" },
|
|
261
|
+
automaticBaseImageUpdatesDisabled,
|
|
243
262
|
};
|
|
244
263
|
async function createBackendAndPoll() {
|
|
245
264
|
const op = await apphosting.createBackend(projectId, location, backendReqBody, backendId);
|
package/lib/apphosting/config.js
CHANGED
|
@@ -12,6 +12,7 @@ exports.maybeAddSecretToYaml = maybeAddSecretToYaml;
|
|
|
12
12
|
exports.maybeGenerateEmulatorYaml = maybeGenerateEmulatorYaml;
|
|
13
13
|
exports.overrideChosenEnv = overrideChosenEnv;
|
|
14
14
|
exports.suggestedTestKeyName = suggestedTestKeyName;
|
|
15
|
+
exports.splitEnvVars = splitEnvVars;
|
|
15
16
|
const path_1 = require("path");
|
|
16
17
|
const fs_1 = require("fs");
|
|
17
18
|
const yaml = require("yaml");
|
|
@@ -23,7 +24,6 @@ const yaml_1 = require("./yaml");
|
|
|
23
24
|
const logger_1 = require("../logger");
|
|
24
25
|
const csm = require("../gcp/secretManager");
|
|
25
26
|
const error_1 = require("../error");
|
|
26
|
-
const path_2 = require("path");
|
|
27
27
|
exports.APPHOSTING_BASE_YAML_FILE = "apphosting.yaml";
|
|
28
28
|
exports.APPHOSTING_EMULATORS_YAML_FILE = "apphosting.emulator.yaml";
|
|
29
29
|
exports.APPHOSTING_LOCAL_YAML_FILE = "apphosting.local.yaml";
|
|
@@ -69,7 +69,7 @@ function load(yamlPath) {
|
|
|
69
69
|
const dynamicDispatch = exports;
|
|
70
70
|
async function getAppHostingConfiguration(backendDir) {
|
|
71
71
|
const appHostingConfigPaths = dynamicDispatch.listAppHostingFilesInPath(backendDir);
|
|
72
|
-
const fileNameToPathMap = Object.fromEntries(appHostingConfigPaths.map((path) => [(0,
|
|
72
|
+
const fileNameToPathMap = Object.fromEntries(appHostingConfigPaths.map((path) => [(0, path_1.basename)(path), path]));
|
|
73
73
|
const output = yaml_1.AppHostingYamlConfig.empty();
|
|
74
74
|
const baseFilePath = fileNameToPathMap[exports.APPHOSTING_BASE_YAML_FILE];
|
|
75
75
|
const emulatorsFilePath = fileNameToPathMap[exports.APPHOSTING_EMULATORS_YAML_FILE];
|
|
@@ -246,3 +246,20 @@ async function overrideChosenEnv(projectId, env) {
|
|
|
246
246
|
function suggestedTestKeyName(variable) {
|
|
247
247
|
return "test-" + variable.replace(/_/g, "-").toLowerCase();
|
|
248
248
|
}
|
|
249
|
+
function splitEnvVars(env) {
|
|
250
|
+
const build = {};
|
|
251
|
+
const runtime = {};
|
|
252
|
+
for (const [key, val] of Object.entries(env)) {
|
|
253
|
+
const envVal = { ...val };
|
|
254
|
+
if (envVal.value !== undefined) {
|
|
255
|
+
envVal.value = String(envVal.value);
|
|
256
|
+
}
|
|
257
|
+
if (val.availability?.includes("BUILD") || !val.availability) {
|
|
258
|
+
build[key] = envVal;
|
|
259
|
+
}
|
|
260
|
+
if (val.availability?.includes("RUNTIME") || !val.availability) {
|
|
261
|
+
runtime[key] = envVal;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return { build, runtime };
|
|
265
|
+
}
|
|
@@ -2,10 +2,28 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.localBuild = localBuild;
|
|
4
4
|
const build_1 = require("@apphosting/build");
|
|
5
|
-
async function localBuild(projectRoot, framework) {
|
|
6
|
-
const
|
|
5
|
+
async function localBuild(projectRoot, framework, env = {}) {
|
|
6
|
+
const originalEnv = { ...process.env };
|
|
7
|
+
const addedEnv = toProcessEnv(env);
|
|
8
|
+
for (const [key, value] of Object.entries(addedEnv)) {
|
|
9
|
+
process.env[key] = value;
|
|
10
|
+
}
|
|
11
|
+
let apphostingBuildOutput;
|
|
12
|
+
try {
|
|
13
|
+
apphostingBuildOutput = await (0, build_1.localBuild)(projectRoot, framework);
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
for (const key in process.env) {
|
|
17
|
+
if (!(key in originalEnv)) {
|
|
18
|
+
delete process.env[key];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
for (const [key, value] of Object.entries(originalEnv)) {
|
|
22
|
+
process.env[key] = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
7
25
|
const annotations = Object.fromEntries(Object.entries(apphostingBuildOutput.metadata).map(([key, value]) => [key, String(value)]));
|
|
8
|
-
const
|
|
26
|
+
const discoveredEnv = apphostingBuildOutput.runConfig.environmentVariables?.map(({ variable, value, availability }) => ({
|
|
9
27
|
variable,
|
|
10
28
|
value,
|
|
11
29
|
availability,
|
|
@@ -15,7 +33,10 @@ async function localBuild(projectRoot, framework) {
|
|
|
15
33
|
annotations,
|
|
16
34
|
buildConfig: {
|
|
17
35
|
runCommand: apphostingBuildOutput.runConfig.runCommand,
|
|
18
|
-
env:
|
|
36
|
+
env: discoveredEnv ?? [],
|
|
19
37
|
},
|
|
20
38
|
};
|
|
21
39
|
}
|
|
40
|
+
function toProcessEnv(env) {
|
|
41
|
+
return Object.fromEntries(Object.entries(env).map(([key, value]) => [key, value.value || ""]));
|
|
42
|
+
}
|
package/lib/bin/mcp.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.mcp = mcp;
|
|
5
5
|
const path_1 = require("path");
|
|
6
|
+
const promises_1 = require("fs/promises");
|
|
7
|
+
const os_1 = require("os");
|
|
6
8
|
const util_1 = require("util");
|
|
7
9
|
const logger_1 = require("../logger");
|
|
8
10
|
const index_1 = require("../mcp/index");
|
|
@@ -82,7 +84,9 @@ async function mcp() {
|
|
|
82
84
|
if (earlyExit)
|
|
83
85
|
return;
|
|
84
86
|
(0, env_1.setFirebaseMcp)(true);
|
|
85
|
-
(0,
|
|
87
|
+
const mcpLogDir = (0, path_1.join)((0, os_1.homedir)(), ".cache", "firebase");
|
|
88
|
+
await (0, promises_1.mkdir)(mcpLogDir, { recursive: true });
|
|
89
|
+
(0, logger_1.useFileLogger)((0, path_1.join)(mcpLogDir, "firebase-debug.log"));
|
|
86
90
|
const activeFeatures = (values.only || "")
|
|
87
91
|
.split(",")
|
|
88
92
|
.map((f) => f.trim())
|
|
@@ -7,6 +7,7 @@ const projectUtils_1 = require("../projectUtils");
|
|
|
7
7
|
const requireAuth_1 = require("../requireAuth");
|
|
8
8
|
const backend_1 = require("../apphosting/backend");
|
|
9
9
|
const apphosting_1 = require("../gcp/apphosting");
|
|
10
|
+
const experiments = require("../experiments");
|
|
10
11
|
const firedata_1 = require("../gcp/firedata");
|
|
11
12
|
const requireTosAcceptance_1 = require("../requireTosAcceptance");
|
|
12
13
|
exports.command = new command_1.Command("apphosting:backends:create")
|
|
@@ -15,7 +16,15 @@ exports.command = new command_1.Command("apphosting:backends:create")
|
|
|
15
16
|
.option("--backend <backend>", "specify the name of the new backend. Required with --non-interactive.")
|
|
16
17
|
.option("-s, --service-account <serviceAccount>", "specify the service account used to run the server", "")
|
|
17
18
|
.option("--primary-region <primaryRegion>", "specify the primary region for the backend. Required with --non-interactive.")
|
|
18
|
-
.option("--root-dir <rootDir>", "specify the root directory for the backend.")
|
|
19
|
+
.option("--root-dir <rootDir>", "specify the root directory for the backend.");
|
|
20
|
+
const abiuEnabled = experiments.isEnabled("abiu");
|
|
21
|
+
if (abiuEnabled) {
|
|
22
|
+
exports.command
|
|
23
|
+
.option("--runtime [runtime]", "specify the runtime for the backend (e.g., nodejs, nodejs22)")
|
|
24
|
+
.option("--automatic-base-image-updates", "enable automatic base image updates")
|
|
25
|
+
.option("--no-automatic-base-image-updates", "disable automatic base image updates");
|
|
26
|
+
}
|
|
27
|
+
exports.command
|
|
19
28
|
.before(requireAuth_1.requireAuth)
|
|
20
29
|
.before(apphosting_1.ensureApiEnabled)
|
|
21
30
|
.before((0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.APPHOSTING_TOS_ID))
|
|
@@ -24,5 +33,13 @@ exports.command = new command_1.Command("apphosting:backends:create")
|
|
|
24
33
|
if (options.nonInteractive && (options.backend == null || options.primaryRegion == null)) {
|
|
25
34
|
throw new error_1.FirebaseError(`--non-interactive option requires --backend and --primary-region`);
|
|
26
35
|
}
|
|
27
|
-
|
|
36
|
+
const abiuAllowed = experiments.isEnabled("abiu");
|
|
37
|
+
if (!abiuAllowed && (options.runtime || options.automaticBaseImageUpdates !== undefined)) {
|
|
38
|
+
throw new error_1.FirebaseError("The --runtime and --automatic-base-image-updates flags are only available when the 'abiu' experiment is enabled. To enable it, run 'firebase experiments:enable abiu'.");
|
|
39
|
+
}
|
|
40
|
+
const runtime = abiuAllowed && typeof options.runtime === "string" ? options.runtime : undefined;
|
|
41
|
+
const automaticBaseImageUpdatesDisabled = abiuAllowed
|
|
42
|
+
? options.automaticBaseImageUpdates === false
|
|
43
|
+
: undefined;
|
|
44
|
+
return (0, backend_1.doSetup)(projectId, options.nonInteractive, options.app, options.backend, options.serviceAccount, options.primaryRegion, options.rootDir, runtime, automaticBaseImageUpdatesDisabled);
|
|
28
45
|
});
|
|
@@ -9,8 +9,8 @@ const logger_1 = require("../logger");
|
|
|
9
9
|
const projectUtils_1 = require("../projectUtils");
|
|
10
10
|
const requireAuth_1 = require("../requireAuth");
|
|
11
11
|
const apphosting = require("../gcp/apphosting");
|
|
12
|
+
const experiments_1 = require("../experiments");
|
|
12
13
|
const Table = require("cli-table3");
|
|
13
|
-
const TABLE_HEAD = ["Backend", "Repository", "URL", "Primary Region", "Updated Date"];
|
|
14
14
|
exports.command = new command_1.Command("apphosting:backends:list")
|
|
15
15
|
.description("list Firebase App Hosting backends")
|
|
16
16
|
.before(requireAuth_1.requireAuth)
|
|
@@ -29,19 +29,35 @@ exports.command = new command_1.Command("apphosting:backends:list")
|
|
|
29
29
|
return backends;
|
|
30
30
|
});
|
|
31
31
|
function printBackendsTable(backends) {
|
|
32
|
+
const abiuEnabled = (0, experiments_1.isEnabled)("abiu");
|
|
33
|
+
const head = ["Backend", "Repository", "URL", "Primary Region"];
|
|
34
|
+
if (abiuEnabled) {
|
|
35
|
+
head.push("ABIU");
|
|
36
|
+
head.push("Runtime");
|
|
37
|
+
}
|
|
38
|
+
head.push("Updated Date");
|
|
32
39
|
const table = new Table({
|
|
33
|
-
head:
|
|
40
|
+
head: head,
|
|
34
41
|
style: { head: ["green"] },
|
|
35
42
|
});
|
|
36
43
|
for (const backend of backends) {
|
|
37
44
|
const { location, id } = apphosting.parseBackendName(backend.name);
|
|
38
|
-
|
|
45
|
+
const row = [
|
|
39
46
|
id,
|
|
40
47
|
backend.codebase?.repository?.split("/").pop() ?? "",
|
|
41
48
|
backend.uri.startsWith("https:") ? backend.uri : "https://" + backend.uri,
|
|
42
49
|
location,
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
];
|
|
51
|
+
if (abiuEnabled) {
|
|
52
|
+
let abiuStatus = "N/A";
|
|
53
|
+
if (backend.automaticBaseImageUpdatesDisabled !== undefined) {
|
|
54
|
+
abiuStatus = backend.automaticBaseImageUpdatesDisabled ? "Disabled" : "Enabled";
|
|
55
|
+
}
|
|
56
|
+
row.push(abiuStatus);
|
|
57
|
+
row.push(backend.runtime?.value ?? "N/A");
|
|
58
|
+
}
|
|
59
|
+
row.push((0, utils_1.datetimeString)(new Date(backend.updateTime)));
|
|
60
|
+
table.push(row);
|
|
45
61
|
}
|
|
46
62
|
logger_1.logger.info(table.toString());
|
|
47
63
|
}
|
|
@@ -75,6 +75,7 @@ exports.command = new command_1.Command("functions:delete [filters...]")
|
|
|
75
75
|
try {
|
|
76
76
|
const fab = new fabricator.Fabricator({
|
|
77
77
|
functionExecutor,
|
|
78
|
+
runFunctionExecutor: functionExecutor,
|
|
78
79
|
appEngineLocation,
|
|
79
80
|
executor: new executor.QueueExecutor({}),
|
|
80
81
|
sources: {},
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.command = void 0;
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const error_1 = require("../error");
|
|
6
|
+
const iac = require("../functions/iac/export");
|
|
7
|
+
const projectConfig_1 = require("../functions/projectConfig");
|
|
8
|
+
const clc = require("colorette");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
const EXPORTERS = {
|
|
11
|
+
internal: iac.getInternalIac,
|
|
12
|
+
};
|
|
13
|
+
exports.command = new command_1.Command("functions:export")
|
|
14
|
+
.description("export Cloud Functions code and configuration")
|
|
15
|
+
.option("--format <format>", `Format of the output. Can be ${Object.keys(EXPORTERS).join(", ")}.`)
|
|
16
|
+
.option("--codebase <codebase>", "Optional codebase to export. If not specified, exports the default or only codebase.")
|
|
17
|
+
.action(async (options) => {
|
|
18
|
+
if (!options.format || !Object.keys(EXPORTERS).includes(options.format)) {
|
|
19
|
+
throw new error_1.FirebaseError(`Must specify --format as ${Object.keys(EXPORTERS).join(", ")}.`);
|
|
20
|
+
}
|
|
21
|
+
const config = (0, projectConfig_1.normalizeAndValidate)(options.config?.src?.functions);
|
|
22
|
+
let codebaseConfig;
|
|
23
|
+
if (options.codebase) {
|
|
24
|
+
codebaseConfig = (0, projectConfig_1.configForCodebase)(config, options.codebase);
|
|
25
|
+
}
|
|
26
|
+
else if (config.length === 1) {
|
|
27
|
+
codebaseConfig = config[0];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
codebaseConfig = (0, projectConfig_1.configForCodebase)(config, "default");
|
|
31
|
+
}
|
|
32
|
+
if (!codebaseConfig.source) {
|
|
33
|
+
throw new error_1.FirebaseError("Codebase does not have a local source directory.");
|
|
34
|
+
}
|
|
35
|
+
const manifest = await EXPORTERS[options.format](options, codebaseConfig);
|
|
36
|
+
for (const [file, contents] of Object.entries(manifest)) {
|
|
37
|
+
logger_1.logger.info(`Manifest file: ${clc.bold(file)}`);
|
|
38
|
+
logger_1.logger.info(contents);
|
|
39
|
+
}
|
|
40
|
+
});
|
package/lib/commands/index.js
CHANGED
|
@@ -141,6 +141,9 @@ function load(client) {
|
|
|
141
141
|
client.functions.config.set = loadCommand("functions-config-set");
|
|
142
142
|
client.functions.config.unset = loadCommand("functions-config-unset");
|
|
143
143
|
client.functions.delete = loadCommand("functions-delete");
|
|
144
|
+
if (experiments.isEnabled("functionsiac")) {
|
|
145
|
+
client.functions.export = loadCommand("functions-export");
|
|
146
|
+
}
|
|
144
147
|
client.functions.log = loadCommand("functions-log");
|
|
145
148
|
client.functions.shell = loadCommand("functions-shell");
|
|
146
149
|
client.functions.list = loadCommand("functions-list");
|
package/lib/commands/init.js
CHANGED
|
@@ -228,6 +228,7 @@ async function initAction(feature, options) {
|
|
|
228
228
|
if (setup.features.includes("dataconnect") && setup.features.includes("dataconnect:sdk")) {
|
|
229
229
|
setup.features = setup.features.filter((f) => f !== "dataconnect:sdk");
|
|
230
230
|
}
|
|
231
|
+
setup.features.push("agentSkills");
|
|
231
232
|
await (0, init_1.init)(setup, config, options);
|
|
232
233
|
await postInitSaves(setup, config);
|
|
233
234
|
if (setup.instructions.length) {
|
|
@@ -8,7 +8,8 @@ const gcs = require("../../gcp/storage");
|
|
|
8
8
|
const getProjectNumber_1 = require("../../getProjectNumber");
|
|
9
9
|
const projectUtils_1 = require("../../projectUtils");
|
|
10
10
|
const utils_1 = require("../../utils");
|
|
11
|
-
const
|
|
11
|
+
const util = require("./util");
|
|
12
|
+
const experiments = require("../../experiments");
|
|
12
13
|
async function default_1(context, options) {
|
|
13
14
|
if (Object.entries(context.backendConfigs).length === 0) {
|
|
14
15
|
return;
|
|
@@ -52,24 +53,28 @@ async function default_1(context, options) {
|
|
|
52
53
|
await Promise.all(Object.values(context.backendConfigs).map(async (cfg) => {
|
|
53
54
|
const rootDir = options.projectRoot ?? process.cwd();
|
|
54
55
|
let builtAppDir;
|
|
55
|
-
|
|
56
|
+
const isLocalBuild = !!cfg.localBuild;
|
|
57
|
+
if (isLocalBuild) {
|
|
58
|
+
experiments.assertEnabled("apphostinglocalbuilds", "App Hosting local builds");
|
|
56
59
|
builtAppDir = context.backendLocalBuilds[cfg.backendId].buildDir;
|
|
57
60
|
if (!builtAppDir) {
|
|
58
61
|
throw new error_1.FirebaseError(`No local build dir found for ${cfg.backendId}`);
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
|
-
const zippedSourcePath =
|
|
62
|
-
|
|
64
|
+
const zippedSourcePath = isLocalBuild
|
|
65
|
+
? await util.createLocalBuildTarArchive(cfg, rootDir, builtAppDir)
|
|
66
|
+
: await util.createSourceDeployArchive(cfg, rootDir);
|
|
67
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Zipped ${isLocalBuild ? "built app" : "source"} for backend ${cfg.backendId}`);
|
|
63
68
|
const backendLocation = context.backendLocations[cfg.backendId];
|
|
64
69
|
if (!backendLocation) {
|
|
65
70
|
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.`);
|
|
66
71
|
}
|
|
67
|
-
(0, utils_1.logLabeledBullet)("apphosting", `Uploading ${
|
|
72
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Uploading ${isLocalBuild ? "built app" : "source"} for backend ${cfg.backendId}...`);
|
|
68
73
|
const bucketName = bucketsPerLocation[backendLocation];
|
|
69
74
|
const { bucket, object } = await gcs.uploadObject({
|
|
70
75
|
file: zippedSourcePath,
|
|
71
76
|
stream: fs.createReadStream(zippedSourcePath),
|
|
72
|
-
}, bucketName);
|
|
77
|
+
}, bucketName, isLocalBuild ? gcs.ContentType.TAR : gcs.ContentType.ZIP);
|
|
73
78
|
(0, utils_1.logLabeledBullet)("apphosting", `Uploaded at gs://${bucket}/${object}`);
|
|
74
79
|
context.backendStorageUris[cfg.backendId] =
|
|
75
80
|
`gs://${bucketName}/${path.basename(zippedSourcePath)}`;
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = default_1;
|
|
4
|
+
exports.injectEnvVarsFromApphostingConfig = injectEnvVarsFromApphostingConfig;
|
|
5
|
+
exports.injectAutoInitEnvVars = injectAutoInitEnvVars;
|
|
4
6
|
exports.getBackendConfigs = getBackendConfigs;
|
|
5
7
|
const path = require("path");
|
|
6
8
|
const backend_1 = require("../../apphosting/backend");
|
|
7
9
|
const apphosting_1 = require("../../gcp/apphosting");
|
|
10
|
+
const yaml_1 = require("../../apphosting/yaml");
|
|
11
|
+
const config_1 = require("../../apphosting/config");
|
|
8
12
|
const devConnect_1 = require("../../gcp/devConnect");
|
|
13
|
+
const resourceManager_1 = require("../../gcp/resourceManager");
|
|
9
14
|
const projectUtils_1 = require("../../projectUtils");
|
|
15
|
+
const getProjectNumber_1 = require("../../getProjectNumber");
|
|
10
16
|
const prompt_1 = require("../../prompt");
|
|
11
17
|
const utils_1 = require("../../utils");
|
|
12
18
|
const localbuilds_1 = require("../../apphosting/localbuilds");
|
|
13
19
|
const error_1 = require("../../error");
|
|
20
|
+
const managementApps = require("../../management/apps");
|
|
21
|
+
const utils_2 = require("../../apphosting/utils");
|
|
22
|
+
const experiments = require("../../experiments");
|
|
23
|
+
const logger_1 = require("../../logger");
|
|
14
24
|
async function default_1(context, options) {
|
|
15
25
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
16
26
|
await (0, apphosting_1.ensureApiEnabled)(options);
|
|
@@ -21,6 +31,10 @@ async function default_1(context, options) {
|
|
|
21
31
|
context.backendStorageUris = {};
|
|
22
32
|
context.backendLocalBuilds = {};
|
|
23
33
|
const configs = getBackendConfigs(options);
|
|
34
|
+
if (configs.some((cfg) => cfg.localBuild) && experiments.isEnabled("apphostinglocalbuilds")) {
|
|
35
|
+
const projectNumber = await (0, getProjectNumber_1.getProjectNumber)(options);
|
|
36
|
+
await ensureAppHostingServiceAgentRoles(projectId, projectNumber);
|
|
37
|
+
}
|
|
24
38
|
const { backends } = await (0, apphosting_1.listBackends)(projectId, "-");
|
|
25
39
|
const foundBackends = [];
|
|
26
40
|
const notFoundBackends = [];
|
|
@@ -108,24 +122,66 @@ async function default_1(context, options) {
|
|
|
108
122
|
if (skippedBackends.length > 0) {
|
|
109
123
|
(0, utils_1.logLabeledWarning)("apphosting", `Skipping deployment of backend(s) ${skippedBackends.map((cfg) => cfg.backendId).join(", ")}.`);
|
|
110
124
|
}
|
|
125
|
+
const buildEnv = {};
|
|
126
|
+
const runtimeEnv = {};
|
|
111
127
|
for (const cfg of Object.values(context.backendConfigs)) {
|
|
112
128
|
if (!cfg.localBuild) {
|
|
113
129
|
continue;
|
|
114
130
|
}
|
|
131
|
+
experiments.assertEnabled("apphostinglocalbuilds", "locally build App Hosting backends");
|
|
115
132
|
(0, utils_1.logLabeledBullet)("apphosting", `Starting local build for backend ${cfg.backendId}`);
|
|
133
|
+
await injectEnvVarsFromApphostingConfig(configs.filter((c) => c.backendId === cfg.backendId), options, buildEnv, runtimeEnv);
|
|
134
|
+
await injectAutoInitEnvVars(cfg, backends, buildEnv, runtimeEnv);
|
|
116
135
|
try {
|
|
117
|
-
const { outputFiles, annotations, buildConfig } = await (0, localbuilds_1.localBuild)(options.projectRoot || "./", "nextjs");
|
|
136
|
+
const { outputFiles, annotations, buildConfig } = await (0, localbuilds_1.localBuild)(options.projectRoot || "./", "nextjs", buildEnv[cfg.backendId] || {});
|
|
118
137
|
if (outputFiles.length !== 1) {
|
|
119
138
|
throw new error_1.FirebaseError(`Local build for backend ${cfg.backendId} failed: No output files found.`);
|
|
120
139
|
}
|
|
121
140
|
context.backendLocalBuilds[cfg.backendId] = {
|
|
122
141
|
buildDir: outputFiles[0],
|
|
123
|
-
buildConfig
|
|
142
|
+
buildConfig: {
|
|
143
|
+
...buildConfig,
|
|
144
|
+
env: mergeEnvVars(buildConfig.env || [], runtimeEnv[cfg.backendId] || {}),
|
|
145
|
+
},
|
|
124
146
|
annotations,
|
|
125
147
|
};
|
|
126
148
|
}
|
|
127
149
|
catch (e) {
|
|
128
|
-
|
|
150
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
151
|
+
throw new error_1.FirebaseError(`Local Build for backend ${cfg.backendId} failed: ${errorMsg}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function injectEnvVarsFromApphostingConfig(configs, options, buildEnv, runtimeEnv) {
|
|
156
|
+
for (const cfg of configs) {
|
|
157
|
+
const rootDir = options.projectRoot || process.cwd();
|
|
158
|
+
const appDir = path.join(rootDir, cfg.rootDir || "");
|
|
159
|
+
let yamlConfig = yaml_1.AppHostingYamlConfig.empty();
|
|
160
|
+
try {
|
|
161
|
+
yamlConfig = await (0, config_1.getAppHostingConfiguration)(appDir);
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Failed to read apphosting.yaml, may be missing environment variables and other configs`);
|
|
165
|
+
}
|
|
166
|
+
const { build, runtime } = (0, config_1.splitEnvVars)(yamlConfig.env);
|
|
167
|
+
buildEnv[cfg.backendId] = { ...buildEnv[cfg.backendId], ...build };
|
|
168
|
+
runtimeEnv[cfg.backendId] = { ...runtimeEnv[cfg.backendId], ...runtime };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function injectAutoInitEnvVars(cfg, backends, buildEnv, runtimeEnv) {
|
|
172
|
+
var _a, _b;
|
|
173
|
+
const backend = backends.find((b) => (0, apphosting_1.parseBackendName)(b.name).id === cfg.backendId);
|
|
174
|
+
if (backend?.appId) {
|
|
175
|
+
try {
|
|
176
|
+
const webappConfig = (await managementApps.getAppConfig(backend.appId, managementApps.AppPlatform.WEB));
|
|
177
|
+
const autoinitVars = (0, utils_2.getAutoinitEnvVars)(webappConfig);
|
|
178
|
+
for (const [envVarName, envVarValue] of Object.entries(autoinitVars)) {
|
|
179
|
+
(_a = buildEnv[cfg.backendId])[envVarName] ?? (_a[envVarName] = { value: envVarValue });
|
|
180
|
+
(_b = runtimeEnv[cfg.backendId])[envVarName] ?? (_b[envVarName] = { value: envVarValue });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Unable to lookup details for backend ${cfg.backendId}. Firebase SDK autoinit will not be available.`);
|
|
129
185
|
}
|
|
130
186
|
}
|
|
131
187
|
}
|
|
@@ -155,5 +211,33 @@ function getBackendConfigs(options) {
|
|
|
155
211
|
if (backendIds.length === 0) {
|
|
156
212
|
return [];
|
|
157
213
|
}
|
|
158
|
-
|
|
214
|
+
const filteredConfigs = backendConfigs.filter((cfg) => backendIds.includes(cfg.backendId));
|
|
215
|
+
const foundIds = filteredConfigs.map((cfg) => cfg.backendId);
|
|
216
|
+
const missingIds = backendIds.filter((id) => !foundIds.includes(id));
|
|
217
|
+
if (missingIds.length > 0) {
|
|
218
|
+
throw new error_1.FirebaseError(`App Hosting backend IDs ${missingIds.join(",")} not detected in firebase.json`);
|
|
219
|
+
}
|
|
220
|
+
return filteredConfigs;
|
|
221
|
+
}
|
|
222
|
+
function mergeEnvVars(base, overrides) {
|
|
223
|
+
const merged = new Map();
|
|
224
|
+
for (const env of base) {
|
|
225
|
+
if (env.variable) {
|
|
226
|
+
merged.set(env.variable, env);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const [envVarName, envVarConfig] of Object.entries(overrides)) {
|
|
230
|
+
merged.set(envVarName, { ...envVarConfig, variable: envVarName });
|
|
231
|
+
}
|
|
232
|
+
return Array.from(merged.values());
|
|
233
|
+
}
|
|
234
|
+
async function ensureAppHostingServiceAgentRoles(projectId, projectNumber) {
|
|
235
|
+
const p4saEmail = (0, apphosting_1.serviceAgentEmail)(projectNumber);
|
|
236
|
+
try {
|
|
237
|
+
await (0, resourceManager_1.addServiceAccountToRoles)(projectId, p4saEmail, ["roles/storage.objectViewer"], true);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
logger_1.logger.debug(`Failed to grant storage.objectViewer to ${p4saEmail}: ${String(err)}`);
|
|
241
|
+
(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.`);
|
|
242
|
+
}
|
|
159
243
|
}
|
|
@@ -15,11 +15,6 @@ async function default_1(context, options) {
|
|
|
15
15
|
(0, utils_1.logLabeledWarning)("apphosting", `Failed to find metadata for backend(s) ${backendIds.join(", ")}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
|
|
16
16
|
backendIds = backendIds.filter((id) => !missingBackends.includes(id));
|
|
17
17
|
}
|
|
18
|
-
const localBuildBackends = backendIds.filter((id) => context.backendLocalBuilds[id]);
|
|
19
|
-
if (localBuildBackends.length > 0) {
|
|
20
|
-
(0, utils_1.logLabeledWarning)("apphosting", `Skipping backend(s) ${localBuildBackends.join(", ")}. Local Builds are not supported yet.`);
|
|
21
|
-
backendIds = backendIds.filter((id) => !localBuildBackends.includes(id));
|
|
22
|
-
}
|
|
23
18
|
if (backendIds.length === 0) {
|
|
24
19
|
return;
|
|
25
20
|
}
|
|
@@ -29,10 +24,12 @@ async function default_1(context, options) {
|
|
|
29
24
|
backendId,
|
|
30
25
|
location: context.backendLocations[backendId],
|
|
31
26
|
buildInput: {
|
|
27
|
+
config: context.backendLocalBuilds[backendId]?.buildConfig,
|
|
32
28
|
source: {
|
|
33
29
|
archive: {
|
|
34
30
|
userStorageUri: context.backendStorageUris[backendId],
|
|
35
31
|
rootDirectory: context.backendConfigs[backendId].rootDir,
|
|
32
|
+
locallyBuiltSource: !!context.backendLocalBuilds[backendId],
|
|
36
33
|
},
|
|
37
34
|
},
|
|
38
35
|
},
|