Skip to content

Commit

Permalink
More firebase-frameworks work (#4463)
Browse files Browse the repository at this point in the history
* Work with emulators:start

* Add dev mode flag

* Dev flag not actually needed

* Move entry into firebase-tools

* Cleanup

* Bump dep

* fix missing import

Co-authored-by: Bryan Kendall <[email protected]>
  • Loading branch information
jamesdaniels and bkendall authored May 10, 2022
1 parent 5c76831 commit 745e167
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 14 deletions.
108 changes: 101 additions & 7 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"exit-code": "^1.0.2",
"express": "^4.16.4",
"filesize": "^6.1.0",
"firebase-frameworks": "^0.3.0",
"firebase-frameworks": "^0.4.0",
"fs-extra": "^5.0.0",
"glob": "^7.1.2",
"google-auth-library": "^7.11.0",
Expand Down
3 changes: 2 additions & 1 deletion src/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as FunctionsTarget from "./functions";
import * as StorageTarget from "./storage";
import * as RemoteConfigTarget from "./remoteconfig";
import * as ExtensionsTarget from "./extensions";
import { prepareFrameworks } from "../frameworks";

const TARGETS = {
hosting: HostingTarget,
Expand Down Expand Up @@ -58,7 +59,7 @@ export const deploy = async function (
if (previews.frameworkawareness && targetNames.includes("hosting")) {
const config = options.config.get("hosting");
if (Array.isArray(config) ? config.some((it) => it.source) : config.source) {
await require("firebase-frameworks").prepare(targetNames, context, options);
await prepareFrameworks(targetNames, context, options);
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/emulator/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import { ParsedTriggerDefinition } from "./functionsEmulatorShared";
import { ExtensionsEmulator } from "./extensionsEmulator";
import { normalizeAndValidate } from "../functions/projectConfig";
import { requiresJava } from "./downloadableEmulators";
import { prepareFrameworks } from "../frameworks";
import { previews } from "../previews";

const START_LOGGING_EMULATOR = utils.envOverride(
"START_LOGGING_EMULATOR",
Expand Down Expand Up @@ -402,6 +404,13 @@ export async function startAll(
}
}

if (previews.frameworkawareness) {
const config = options.config.get("hosting");
if (Array.isArray(config) ? config.some((it) => it.source) : config.source) {
await prepareFrameworks(targets, options, options);
}
}

if (shouldStart(options, Emulators.HUB)) {
const hubAddr = await getAndCheckAddress(Emulators.HUB, options);
const hub = new EmulatorHub({ projectId, ...hubAddr });
Expand Down
123 changes: 123 additions & 0 deletions src/frameworks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { join } from "path";
import { exit } from "process";

import { needProjectId } from "../projectUtils";
import { normalizedHostingConfigs } from "../hosting/normalizedHostingConfigs";
import { listSites, Site } from "../hosting/api";
import { getAppConfig, AppPlatform } from "../management/apps";
import { promises as fsPromises } from "fs";
import { promptOnce } from "../prompt";

const { writeFile } = fsPromises;

export const shortSiteName = (site?: Site) => site?.name && site.name.split("/").pop();

export const prepareFrameworks = async (targetNames: string[], context: any, options: any) => {
const project = needProjectId(context);
// options.site is not present when emulated. We could call requireHostingSite but IAM permissions haven't
// been booted up (at this point) and we may be offline, so just use projectId. Most of the time
// the default site is named the same as the project & for frameworks this is only used for naming the
// function... unless you're using authenticated server-context TODO explore the implication here.
const configs = normalizedHostingConfigs({ site: project, ...options }, { resolveTargets: true });
options.normalizedHostingConfigs = configs;
if (configs.length === 0) return;
for (const config of configs) {
const { source, site, public: publicDir } = config;
if (!source) continue;
const dist = join(".firebase", site);
const hostingDist = join(dist, "hosting");
const functionsDist = join(dist, "functions");
if (publicDir)
throw new Error(`hosting.public and hosting.source cannot both be set in firebase.json`);
const getProjectPath = (...args: string[]) => join(process.cwd(), source, ...args);
const functionName = `ssr${site.replace(/-/g, "")}`;
const { build } = require("firebase-frameworks/tools");
const { usingCloudFunctions, rewrites, redirects, headers, usesFirebaseConfig } = await build(
{
dist,
project,
site,
function: {
name: functionName,
region: "us-central1",
},
},
getProjectPath
);
config.public = hostingDist;
if (usingCloudFunctions) {
if (context.hostingChannel) {
// TODO move to prompts
const message =
"Cannot preview changes to the backend, you will only see changes to the static content on this channel.";
if (!options.nonInteractive) {
const continueDeploy = await promptOnce({
type: "confirm",
default: true,
message: `${message} Would you like to continue with the deploy?`,
});
if (!continueDeploy) exit(1);
} else {
console.error(message);
}
} else {
const functionConfig = {
source: functionsDist,
codebase: `firebase-frameworks-${site}`,
};
if (targetNames.includes("functions")) {
const combinedFunctionsConfig = [functionConfig].concat(
options.config.get("functions") || []
);
options.config.set("functions", combinedFunctionsConfig);
} else {
targetNames.unshift("functions");
options.config.set("functions", functionConfig);
}
}

config.rewrites = [
...(config.rewrites || []),
...rewrites,
{
source: "**",
function: functionName,
},
];

let firebaseProjectConfig = null;
if (usesFirebaseConfig) {
const sites = await listSites(project);
const selectedSite = sites.find((it) => shortSiteName(it) === site);
if (selectedSite) {
const { appId } = selectedSite;
if (appId) {
firebaseProjectConfig = await getAppConfig(appId, AppPlatform.WEB);
} else {
console.warn(
`No Firebase app associated with site ${site}, unable to provide authenticated server context`
);
}
}
}
writeFile(
join(functionsDist, ".env"),
`FRAMEWORKS_FIREBASE_PROJECT_CONFIG="${JSON.stringify(firebaseProjectConfig).replace(
/"/g,
'\\"'
)}"`
);
} else {
config.rewrites = [
...(config.rewrites || []),
...rewrites,
{
source: "**",
destination: "/index.html",
},
];
}
config.redirects = [...(config.redirects || []), ...redirects];
config.headers = [...(config.headers || []), ...headers];
}
};
8 changes: 8 additions & 0 deletions src/hosting/normalizedHostingConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { cloneDeep } from "lodash";
import { FirebaseError } from "../error";

interface HostingConfig {
source?: string;
public?: string;
site: string;
target: string;
rewrites?: any[];
redirects?: any[];
headers?: any[];
}

function filterOnly(configs: HostingConfig[], onlyString: string): HostingConfig[] {
Expand Down Expand Up @@ -90,6 +95,9 @@ export function normalizedHostingConfigs(
cmdOptions: any, // eslint-disable-line @typescript-eslint/no-explicit-any
options: { resolveTargets?: boolean } = {}
): HostingConfig[] {
// First see if there's a momoized copy on the options, from frameworks
const normalizedHostingConfigs = cmdOptions.normalizedHostingConfigs;
if (normalizedHostingConfigs) return normalizedHostingConfigs;
let configs = cloneDeep(cmdOptions.config.get("hosting"));
if (!configs) {
return [];
Expand Down
7 changes: 2 additions & 5 deletions src/serve/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EmulatorServer } from "../emulator/emulatorServer";
import * as _ from "lodash";
import { logger } from "../logger";
import { prepareFrameworks } from "../frameworks";
import { previews } from "../previews";

const { FunctionsServer } = require("./functions");
Expand All @@ -26,11 +27,7 @@ export async function serve(options: any): Promise<void> {
targetNames.includes("hosting") &&
[].concat(options.config.get("hosting")).some((it: any) => it.source)
) {
await require("firebase-frameworks").prepare(
targetNames,
{ project: options.projectId },
options
);
await prepareFrameworks(targetNames, options, options);
}
await Promise.all(
_.map(targetNames, (targetName: string) => {
Expand Down

0 comments on commit 745e167

Please sign in to comment.