firebase-tools 15.11.0 → 15.12.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/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 +21 -1
- package/lib/deploy/apphosting/release.js +2 -5
- package/lib/deploy/apphosting/util.js +45 -2
- package/lib/deploy/functions/prepare.js +4 -1
- 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/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.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/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)}`;
|
|
@@ -6,11 +6,15 @@ const path = require("path");
|
|
|
6
6
|
const backend_1 = require("../../apphosting/backend");
|
|
7
7
|
const apphosting_1 = require("../../gcp/apphosting");
|
|
8
8
|
const devConnect_1 = require("../../gcp/devConnect");
|
|
9
|
+
const resourceManager_1 = require("../../gcp/resourceManager");
|
|
9
10
|
const projectUtils_1 = require("../../projectUtils");
|
|
11
|
+
const getProjectNumber_1 = require("../../getProjectNumber");
|
|
10
12
|
const prompt_1 = require("../../prompt");
|
|
11
13
|
const utils_1 = require("../../utils");
|
|
12
14
|
const localbuilds_1 = require("../../apphosting/localbuilds");
|
|
13
15
|
const error_1 = require("../../error");
|
|
16
|
+
const experiments = require("../../experiments");
|
|
17
|
+
const logger_1 = require("../../logger");
|
|
14
18
|
async function default_1(context, options) {
|
|
15
19
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
16
20
|
await (0, apphosting_1.ensureApiEnabled)(options);
|
|
@@ -21,6 +25,10 @@ async function default_1(context, options) {
|
|
|
21
25
|
context.backendStorageUris = {};
|
|
22
26
|
context.backendLocalBuilds = {};
|
|
23
27
|
const configs = getBackendConfigs(options);
|
|
28
|
+
if (configs.some((cfg) => cfg.localBuild) && experiments.isEnabled("apphostinglocalbuilds")) {
|
|
29
|
+
const projectNumber = await (0, getProjectNumber_1.getProjectNumber)(options);
|
|
30
|
+
await ensureAppHostingServiceAgentRoles(projectId, projectNumber);
|
|
31
|
+
}
|
|
24
32
|
const { backends } = await (0, apphosting_1.listBackends)(projectId, "-");
|
|
25
33
|
const foundBackends = [];
|
|
26
34
|
const notFoundBackends = [];
|
|
@@ -112,6 +120,7 @@ async function default_1(context, options) {
|
|
|
112
120
|
if (!cfg.localBuild) {
|
|
113
121
|
continue;
|
|
114
122
|
}
|
|
123
|
+
experiments.assertEnabled("apphostinglocalbuilds", "locally build App Hosting backends");
|
|
115
124
|
(0, utils_1.logLabeledBullet)("apphosting", `Starting local build for backend ${cfg.backendId}`);
|
|
116
125
|
try {
|
|
117
126
|
const { outputFiles, annotations, buildConfig } = await (0, localbuilds_1.localBuild)(options.projectRoot || "./", "nextjs");
|
|
@@ -125,7 +134,8 @@ async function default_1(context, options) {
|
|
|
125
134
|
};
|
|
126
135
|
}
|
|
127
136
|
catch (e) {
|
|
128
|
-
|
|
137
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
138
|
+
throw new error_1.FirebaseError(`Local Build for backend ${cfg.backendId} failed: ${errorMsg}`);
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
141
|
}
|
|
@@ -157,3 +167,13 @@ function getBackendConfigs(options) {
|
|
|
157
167
|
}
|
|
158
168
|
return backendConfigs.filter((cfg) => backendIds.includes(cfg.backendId));
|
|
159
169
|
}
|
|
170
|
+
async function ensureAppHostingServiceAgentRoles(projectId, projectNumber) {
|
|
171
|
+
const p4saEmail = (0, apphosting_1.serviceAgentEmail)(projectNumber);
|
|
172
|
+
try {
|
|
173
|
+
await (0, resourceManager_1.addServiceAccountToRoles)(projectId, p4saEmail, ["roles/storage.objectViewer"], true);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
logger_1.logger.debug(`Failed to grant storage.objectViewer to ${p4saEmail}: ${String(err)}`);
|
|
177
|
+
(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.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -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
|
},
|
|
@@ -1,13 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.createLocalBuildTarArchive = createLocalBuildTarArchive;
|
|
4
|
+
exports.createSourceDeployArchive = createSourceDeployArchive;
|
|
4
5
|
const archiver = require("archiver");
|
|
5
6
|
const fs = require("fs");
|
|
6
7
|
const path = require("path");
|
|
8
|
+
const tar = require("tar");
|
|
7
9
|
const tmp = require("tmp");
|
|
8
10
|
const error_1 = require("../../error");
|
|
9
11
|
const fsAsync = require("../../fsAsync");
|
|
10
|
-
|
|
12
|
+
const config_1 = require("../../apphosting/config");
|
|
13
|
+
async function createLocalBuildTarArchive(config, rootDir, targetSubDir) {
|
|
14
|
+
const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".tar.gz" }).name;
|
|
15
|
+
const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir;
|
|
16
|
+
const ignore = ["firebase-debug.log", "firebase-debug.*.log", ".git"];
|
|
17
|
+
const rdrFiles = await fsAsync.readdirRecursive({
|
|
18
|
+
path: targetDir,
|
|
19
|
+
ignore: ignore,
|
|
20
|
+
isGitIgnore: true,
|
|
21
|
+
});
|
|
22
|
+
const allFiles = rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name));
|
|
23
|
+
if (targetSubDir) {
|
|
24
|
+
const defaultFiles = fs.readdirSync(rootDir).filter((file) => {
|
|
25
|
+
return config_1.APPHOSTING_YAML_FILE_REGEX.test(file);
|
|
26
|
+
});
|
|
27
|
+
for (const file of defaultFiles) {
|
|
28
|
+
if (!allFiles.includes(file)) {
|
|
29
|
+
allFiles.push(file);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
fs.statSync(rootDir);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
38
|
+
throw new error_1.FirebaseError(`Could not read directory "${rootDir}"`);
|
|
39
|
+
}
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
if (!allFiles.length) {
|
|
43
|
+
throw new error_1.FirebaseError(`Cannot create a tar archive with 0 files from directory "${rootDir}"`);
|
|
44
|
+
}
|
|
45
|
+
await tar.create({
|
|
46
|
+
gzip: true,
|
|
47
|
+
file: tmpFile,
|
|
48
|
+
cwd: rootDir,
|
|
49
|
+
portable: true,
|
|
50
|
+
}, allFiles);
|
|
51
|
+
return tmpFile;
|
|
52
|
+
}
|
|
53
|
+
async function createSourceDeployArchive(config, rootDir, targetSubDir) {
|
|
11
54
|
const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".zip" }).name;
|
|
12
55
|
const fileStream = fs.createWriteStream(tmpFile, {
|
|
13
56
|
flags: "w",
|
|
@@ -13,6 +13,7 @@ const clc = require("colorette");
|
|
|
13
13
|
const proto = require("../../gcp/proto");
|
|
14
14
|
const backend = require("./backend");
|
|
15
15
|
const build = require("./build");
|
|
16
|
+
const experiments = require("../../experiments");
|
|
16
17
|
const ensureApiEnabled = require("../../ensureApiEnabled");
|
|
17
18
|
const functionsConfig = require("../../functionsConfig");
|
|
18
19
|
const functionsEnv = require("../../functions/env");
|
|
@@ -322,7 +323,9 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
|
|
|
322
323
|
}
|
|
323
324
|
const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext);
|
|
324
325
|
logger_1.logger.debug(`Validating ${runtimeDelegate.language} source`);
|
|
325
|
-
|
|
326
|
+
if (!experiments.isEnabled("bypassfunctionsdeprecationcheck")) {
|
|
327
|
+
supported.guardVersionSupport(runtimeDelegate.runtime);
|
|
328
|
+
}
|
|
326
329
|
await runtimeDelegate.validate();
|
|
327
330
|
logger_1.logger.debug(`Building ${runtimeDelegate.language} source`);
|
|
328
331
|
await runtimeDelegate.build();
|
|
@@ -57,6 +57,7 @@ class Fabricator {
|
|
|
57
57
|
constructor(args) {
|
|
58
58
|
this.executor = args.executor;
|
|
59
59
|
this.functionExecutor = args.functionExecutor;
|
|
60
|
+
this.runFunctionExecutor = args.runFunctionExecutor;
|
|
60
61
|
this.sources = args.sources;
|
|
61
62
|
this.appEngineLocation = args.appEngineLocation;
|
|
62
63
|
this.projectNumber = args.projectNumber;
|
|
@@ -521,7 +522,7 @@ class Fabricator {
|
|
|
521
522
|
generation: storageSource.generation ? String(storageSource.generation) : undefined,
|
|
522
523
|
},
|
|
523
524
|
};
|
|
524
|
-
await this.
|
|
525
|
+
await this.runFunctionExecutor
|
|
525
526
|
.run(async () => {
|
|
526
527
|
const op = await runV2.createService(endpoint.project, endpoint.region, endpoint.id, service);
|
|
527
528
|
endpoint.uri = op.uri;
|
|
@@ -546,7 +547,7 @@ class Fabricator {
|
|
|
546
547
|
generation: storageSource.generation ? String(storageSource.generation) : undefined,
|
|
547
548
|
},
|
|
548
549
|
};
|
|
549
|
-
await this.
|
|
550
|
+
await this.runFunctionExecutor
|
|
550
551
|
.run(async () => {
|
|
551
552
|
const op = await runV2.updateService(service);
|
|
552
553
|
endpoint.uri = op.uri;
|
|
@@ -556,7 +557,7 @@ class Fabricator {
|
|
|
556
557
|
await this.setInvoker(endpoint);
|
|
557
558
|
}
|
|
558
559
|
async deleteRunFunction(endpoint) {
|
|
559
|
-
await this.
|
|
560
|
+
await this.runFunctionExecutor
|
|
560
561
|
.run(async () => {
|
|
561
562
|
try {
|
|
562
563
|
await runV2.deleteService(endpoint.project, endpoint.region, endpoint.id);
|
|
@@ -70,9 +70,14 @@ async function release(context, options, payload) {
|
|
|
70
70
|
concurrency: 40,
|
|
71
71
|
maxBackoff: 100000,
|
|
72
72
|
};
|
|
73
|
+
const runThrottlerOptions = {
|
|
74
|
+
...throttlerOptions,
|
|
75
|
+
concurrency: 2,
|
|
76
|
+
};
|
|
73
77
|
const projectNumber = options.projectNumber || (await (0, getProjectNumber_1.getProjectNumber)(context.projectId));
|
|
74
78
|
const fab = new fabricator.Fabricator({
|
|
75
79
|
functionExecutor: new executor.QueueExecutor(throttlerOptions),
|
|
80
|
+
runFunctionExecutor: new executor.QueueExecutor(runThrottlerOptions),
|
|
76
81
|
executor: new executor.QueueExecutor(throttlerOptions),
|
|
77
82
|
sources: context.sources,
|
|
78
83
|
appEngineLocation: (0, functionsConfig_1.getAppEngineLocation)(context.firebaseConfig),
|