Skip to content

Commit d5cd2e7

Browse files
authored
Merge branch 'master' into genkit_telemetry
2 parents 9e5e9e4 + b6dd0f3 commit d5cd2e7

38 files changed

+864
-167
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
- Fixed issue where `firebase init firestore` would raise an error due to rules/indexes file path being undefined. (#8518)
1+
- Changed artifact registry cleanup policy error to warn for CI/CD workloads #8513
2+
- Enhance firebase init apphosting to support local source deploys. (#8479)

firebase-vscode/src/data-connect/service.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import {
2020
} from "../../../src/dataconnect/dataplaneClient";
2121
import {
2222
ExecuteGraphqlRequest,
23-
ExecuteGraphqlResponse,
24-
ExecuteGraphqlResponseError,
23+
GraphqlResponse,
24+
GraphqlResponseError,
2525
Impersonation,
2626
} from "../dataconnect/types";
2727
import { Client, ClientResponse } from "../../../src/apiv2";
@@ -83,33 +83,33 @@ export class DataConnectService {
8383
}
8484
private async handleProdResponse(
8585
response: ClientResponse<
86-
ExecuteGraphqlResponse | ExecuteGraphqlResponseError
86+
GraphqlResponse | GraphqlResponseError
8787
>,
8888
): Promise<ExecutionResult> {
8989
if (!(response.status >= 200 && response.status < 300)) {
9090
const errorResponse =
91-
response as ClientResponse<ExecuteGraphqlResponseError>;
91+
response as ClientResponse<GraphqlResponseError>;
9292
throw new DataConnectError(
9393
`Prod Request failed with status ${response.status}\nError Response: ${JSON.stringify(errorResponse?.body)}`,
9494
);
9595
}
96-
const successResponse = response as ClientResponse<ExecuteGraphqlResponse>;
96+
const successResponse = response as ClientResponse<GraphqlResponse>;
9797
return successResponse.body;
9898
}
9999

100100
private async handleEmulatorResponse(
101101
response: ClientResponse<
102-
ExecuteGraphqlResponse | ExecuteGraphqlResponseError
102+
GraphqlResponse | GraphqlResponseError
103103
>,
104104
): Promise<ExecutionResult> {
105105
if (!(response.status >= 200 && response.status < 300)) {
106106
const errorResponse =
107-
response as ClientResponse<ExecuteGraphqlResponseError>;
107+
response as ClientResponse<GraphqlResponseError>;
108108
throw new DataConnectError(
109109
`Emulator Request failed with status ${response.status}\nError Response: ${JSON.stringify(errorResponse?.body)}`,
110110
);
111111
}
112-
const successResponse = response as ClientResponse<ExecuteGraphqlResponse>;
112+
const successResponse = response as ClientResponse<GraphqlResponse>;
113113
return successResponse.body;
114114
}
115115

firebase-vscode/src/logger-wrapper.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import { transports, format } from "winston";
66
import Transport from "winston-transport";
77
import { stripVTControlCharacters } from "node:util";
88
import { SPLAT } from "triple-beam";
9-
import { logger as cliLogger } from "../../src/logger";
10-
import { setupLoggers, tryStringify } from "../../src/utils";
9+
import { logger as cliLogger, useConsoleLoggers, useFileLogger } from "../../src/logger";
1110
import { setInquirerLogger } from "./stubs/inquirer-stub";
1211
import { getRootFolders } from "./core/config";
1312

@@ -40,7 +39,7 @@ for (const logLevel in pluginLogger) {
4039
export function logSetup() {
4140
// Log to console (use built in CLI functionality)
4241
process.env.DEBUG = "true";
43-
setupLoggers();
42+
useConsoleLoggers();
4443

4544
// Log to file
4645
// Only log to file if firebase.debug extension setting is true.
@@ -62,18 +61,7 @@ export function logSetup() {
6261
filePath = path.join(rootFolders[0], ".firebase", "logs", "vsce-debug.log");
6362
}
6463
pluginLogger.info("Logging to path", filePath);
65-
cliLogger.add(
66-
new transports.File({
67-
level: "debug",
68-
filename: filePath,
69-
format: format.printf((info) => {
70-
const segments = [info.message, ...(info[SPLAT] || [])].map(
71-
tryStringify,
72-
);
73-
return `[${info.level}] ${stripVTControlCharacters(segments.join(" "))}`;
74-
}),
75-
}),
76-
);
64+
useFileLogger(filePath);
7765
cliLogger.add(new VSCodeOutputTransport({ level: "info" }));
7866
}
7967

npm-shrinkwrap.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firebase-tools",
3-
"version": "14.3.0",
3+
"version": "14.3.1",
44
"description": "Command-Line Interface for Firebase",
55
"main": "./lib/index.js",
66
"bin": {

schema/firebase-config.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,65 @@
983983
"format": "uri",
984984
"type": "string"
985985
},
986+
"apphosting": {
987+
"anyOf": [
988+
{
989+
"additionalProperties": false,
990+
"properties": {
991+
"alwaysDeployFromSource": {
992+
"type": "boolean"
993+
},
994+
"backendId": {
995+
"type": "string"
996+
},
997+
"ignore": {
998+
"items": {
999+
"type": "string"
1000+
},
1001+
"type": "array"
1002+
},
1003+
"rootDir": {
1004+
"type": "string"
1005+
}
1006+
},
1007+
"required": [
1008+
"backendId",
1009+
"ignore",
1010+
"rootDir"
1011+
],
1012+
"type": "object"
1013+
},
1014+
{
1015+
"items": {
1016+
"additionalProperties": false,
1017+
"properties": {
1018+
"alwaysDeployFromSource": {
1019+
"type": "boolean"
1020+
},
1021+
"backendId": {
1022+
"type": "string"
1023+
},
1024+
"ignore": {
1025+
"items": {
1026+
"type": "string"
1027+
},
1028+
"type": "array"
1029+
},
1030+
"rootDir": {
1031+
"type": "string"
1032+
}
1033+
},
1034+
"required": [
1035+
"backendId",
1036+
"ignore",
1037+
"rootDir"
1038+
],
1039+
"type": "object"
1040+
},
1041+
"type": "array"
1042+
}
1043+
]
1044+
},
9861045
"database": {
9871046
"anyOf": [
9881047
{

src/apphosting/backend.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ describe("apphosting setup functions", () => {
106106
projectId,
107107
location,
108108
backendId,
109-
cloudBuildConnRepo,
110109
"custom-service-account",
110+
cloudBuildConnRepo,
111111
webAppId,
112112
);
113113

src/apphosting/backend.ts

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Backend, BackendOutputOnlyFields, API_VERSION } from "../gcp/apphosting
1717
import { addServiceAccountToRoles } from "../gcp/resourceManager";
1818
import * as iam from "../gcp/iam";
1919
import { FirebaseError, getErrStatus, getError } from "../error";
20-
import { input, confirm, select, checkbox } from "../prompt";
20+
import { input, confirm, select, checkbox, search, Choice } from "../prompt";
2121
import { DEFAULT_LOCATION } from "./constants";
2222
import { ensure } from "../ensureApiEnabled";
2323
import * as deploymentTool from "../deploymentTool";
@@ -27,6 +27,7 @@ import { GitRepositoryLink } from "../gcp/devConnect";
2727
import * as ora from "ora";
2828
import fetch from "node-fetch";
2929
import { orchestrateRollout } from "./rollout";
30+
import * as fuzzy from "fuzzy";
3031

3132
const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
3233

@@ -126,8 +127,8 @@ export async function doSetup(
126127
projectId,
127128
location,
128129
backendId,
129-
gitRepositoryLink,
130130
serviceAccount,
131+
gitRepositoryLink,
131132
webApp?.id,
132133
rootDir,
133134
);
@@ -246,7 +247,7 @@ export async function ensureAppHostingComputeServiceAccount(
246247
/**
247248
* Prompts the user for a backend id and verifies that it doesn't match a pre-existing backend.
248249
*/
249-
async function promptNewBackendId(projectId: string, location: string): Promise<string> {
250+
export async function promptNewBackendId(projectId: string, location: string): Promise<string> {
250251
while (true) {
251252
const backendId = await input({
252253
default: "my-web-app",
@@ -280,18 +281,20 @@ export async function createBackend(
280281
projectId: string,
281282
location: string,
282283
backendId: string,
283-
repository: GitRepositoryLink,
284284
serviceAccount: string | null,
285+
repository: GitRepositoryLink | undefined,
285286
webAppId: string | undefined,
286287
rootDir = "/",
287288
): Promise<Backend> {
288289
const defaultServiceAccount = defaultComputeServiceAccountEmail(projectId);
289290
const backendReqBody: Omit<Backend, BackendOutputOnlyFields> = {
290291
servingLocality: "GLOBAL_ACCESS",
291-
codebase: {
292-
repository: `${repository.name}`,
293-
rootDirectory: rootDir,
294-
},
292+
codebase: repository
293+
? {
294+
repository: `${repository.name}`,
295+
rootDirectory: rootDir,
296+
}
297+
: undefined,
295298
labels: deploymentTool.labels(),
296299
serviceAccount: serviceAccount || defaultServiceAccount,
297300
appId: webAppId,
@@ -385,11 +388,11 @@ export async function promptLocation(
385388
return allowedLocations[0];
386389
}
387390

388-
const location = (await select<string>({
391+
const location = await select<string>({
389392
default: DEFAULT_LOCATION,
390393
message: prompt,
391394
choices: allowedLocations,
392-
})) as string;
395+
});
393396

394397
logSuccess(`Location set to ${location}.\n`);
395398

@@ -413,6 +416,39 @@ export async function getBackendForLocation(
413416
}
414417
}
415418

419+
/**
420+
* Prompts users to select an existing backend.
421+
* @param projectId the user's project ID
422+
* @param promptMessage prompt message to display to the user
423+
* @return the selected backend ID
424+
*/
425+
export async function promptExistingBackend(
426+
projectId: string,
427+
promptMessage: string,
428+
): Promise<string> {
429+
const { backends } = await apphosting.listBackends(projectId, "-");
430+
const backendId: string = await search({
431+
message: promptMessage,
432+
source: (input = ""): Promise<Choice<string>[]> => {
433+
return new Promise((resolve) =>
434+
resolve([
435+
...fuzzy
436+
.filter(input, backends, {
437+
extract: (backend) => apphosting.parseBackendName(backend.name).id,
438+
})
439+
.map((result) => {
440+
return {
441+
name: apphosting.parseBackendName(result.original.name).id,
442+
value: apphosting.parseBackendName(result.original.name).id,
443+
};
444+
}),
445+
]),
446+
);
447+
},
448+
});
449+
return backendId;
450+
}
451+
416452
/**
417453
* Fetches backends of the given backendId and lets the user choose if more than one is found.
418454
*/

src/apphosting/rollout.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ export async function createRollout(
3737
): Promise<void> {
3838
const backend = await getBackend(projectId, backendId);
3939

40-
if (!backend.codebase.repository) {
40+
if (!backend.codebase || !backend.codebase.repository) {
4141
throw new FirebaseError(
42-
`Backend ${backendId} is misconfigured due to missing a connected repository. You can delete and recreate your backend using 'firebase apphosting:backends:delete' and 'firebase apphosting:backends:create'.`,
42+
`Backend ${backendId} is missing a connected repository. If you would like to deploy from a branch or commit of a GitHub repository, you can connect one through the Firebase Console. If you would like to deploy from local source, run 'firebase deploy'.`,
4343
);
4444
}
4545

src/bin/cli.ts

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,15 @@ import { marked } from "marked";
55
marked.use(markedTerminal() as any);
66

77
import { CommanderStatic } from "commander";
8-
import { join } from "node:path";
9-
import { SPLAT } from "triple-beam";
10-
import { stripVTControlCharacters } from "node:util";
118
import * as fs from "node:fs";
129

1310
import { configstore } from "../configstore";
1411
import { errorOut } from "../errorOut";
1512
import { handlePreviewToggles } from "../handlePreviewToggles";
16-
import { logger } from "../logger";
13+
import { logger, useFileLogger } from "../logger";
1714
import * as client from "..";
1815
import * as fsutils from "../fsutils";
1916
import * as utils from "../utils";
20-
import * as winston from "winston";
2117

2218
import { enableExperimentsFromCliEnvVariable } from "../experiments";
2319
import { fetchMOTD } from "../fetchMOTD";
@@ -28,51 +24,13 @@ export function cli(pkg: any) {
2824
const args = process.argv.slice(2);
2925
let cmd: CommanderStatic;
3026

31-
function findAvailableLogFile(): string {
32-
const candidates = ["firebase-debug.log"];
33-
for (let i = 1; i < 10; i++) {
34-
candidates.push(`firebase-debug.${i}.log`);
35-
}
36-
37-
for (const c of candidates) {
38-
const logFilename = join(process.cwd(), c);
39-
40-
try {
41-
const fd = fs.openSync(logFilename, "r+");
42-
fs.closeSync(fd);
43-
return logFilename;
44-
} catch (e: any) {
45-
if (e.code === "ENOENT") {
46-
// File does not exist, which is fine
47-
return logFilename;
48-
}
49-
50-
// Any other error (EPERM, etc) means we won't be able to log to
51-
// this file so we skip it.
52-
}
53-
}
54-
55-
throw new Error("Unable to obtain permissions for firebase-debug.log");
56-
}
57-
58-
const logFilename = findAvailableLogFile();
59-
6027
if (!process.env.DEBUG && args.includes("--debug")) {
6128
process.env.DEBUG = "true";
6229
}
6330

6431
process.env.IS_FIREBASE_CLI = "true";
6532

66-
logger.add(
67-
new winston.transports.File({
68-
level: "debug",
69-
filename: logFilename,
70-
format: winston.format.printf((info) => {
71-
const segments = [info.message, ...(info[SPLAT] || [])].map(utils.tryStringify);
72-
return `[${info.level}] ${stripVTControlCharacters(segments.join(" "))}`;
73-
}),
74-
}),
75-
);
33+
const logFilename = useFileLogger();
7634

7735
logger.debug("-".repeat(70));
7836
logger.debug("Command: ", process.argv.join(" "));

0 commit comments

Comments
 (0)