Skip to content

Commit

Permalink
Inlined.web frameworks label (#5176)
Browse files Browse the repository at this point in the history
* Move HostingResolved out of firebaseConfig

* Move metric to deploy code and add label

* Add some smoke tests to the hosting pepare library
  • Loading branch information
inlined committed Oct 27, 2022
1 parent 793253f commit 74c1b19
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 29 deletions.
2 changes: 1 addition & 1 deletion src/deploy/hosting/context.ts
@@ -1,4 +1,4 @@
import { HostingResolved } from "../../firebaseConfig";
import { HostingResolved } from "../../hosting/config";
import { Context as FunctionsContext } from "../functions/args";

export interface HostingDeploy {
Expand Down
23 changes: 18 additions & 5 deletions src/deploy/hosting/prepare.ts
Expand Up @@ -6,6 +6,7 @@ import { Context } from "./context";
import { Options } from "../../options";
import { HostingOptions } from "../../hosting/options";
import { zipIn } from "../../functional";
import { track } from "../../track";

/**
* Prepare creates versions for each Hosting site to be deployed.
Expand All @@ -25,12 +26,24 @@ export async function prepare(context: Context, options: HostingOptions & Option
return Promise.resolve();
}

const version: Omit<api.Version, api.VERSION_OUTPUT_FIELDS> = {
status: "CREATED",
labels: deploymentTool.labels(),
};
const versions = await Promise.all(
configs.map((config) => api.createVersion(config.site, version))
configs.map(async (config) => {
const labels: Record<string, string> = {
...deploymentTool.labels(),
};
if (config.webFramework) {
labels["firebase-web-framework"] = config.webFramework;
}
const version: Omit<api.Version, api.VERSION_OUTPUT_FIELDS> = {
status: "CREATED",
labels,
};
const [, versionName] = await Promise.all([
track("hosting_deploy", config.webFramework || "classic"),
api.createVersion(config.site, version),
]);
return versionName;
})
);
context.hosting = {
deploys: [],
Expand Down
7 changes: 1 addition & 6 deletions src/deploy/index.ts
Expand Up @@ -61,11 +61,10 @@ export const deploy = async function (

if (targetNames.includes("hosting")) {
const config = options.config.get("hosting");
let deployedFrameworks: string[] = [];
if (Array.isArray(config) ? config.some((it) => it.source) : config.source) {
experiments.assertEnabled("webframeworks", "deploy a web framework to hosting");
const usedToTargetFunctions = targetNames.includes("functions");
deployedFrameworks = await prepareFrameworks(targetNames, context, options);
await prepareFrameworks(targetNames, context, options);
const nowTargetsFunctions = targetNames.includes("functions");
if (nowTargetsFunctions && !usedToTargetFunctions) {
if (context.hostingChannel && !experiments.isEnabled("pintags")) {
Expand All @@ -75,11 +74,7 @@ export const deploy = async function (
}
await requirePermissions(TARGET_PERMISSIONS["functions"]);
}
} else {
const count = Array.isArray(config) ? config.length : 1;
deployedFrameworks = Array<string>(count).fill("classic");
}
await Promise.all(deployedFrameworks.map((framework) => track("hosting_deploy", framework)));
}

for (const targetName of targetNames) {
Expand Down
11 changes: 2 additions & 9 deletions src/firebaseConfig.ts
Expand Up @@ -10,7 +10,7 @@ import { RequireAtLeastOne } from "./metaprogramming";
// should be sourced from - https://github.com/firebase/firebase-tools/blob/master/src/deploy/functions/runtimes/index.ts#L15
type CloudFunctionRuntimes = "nodejs10" | "nodejs12" | "nodejs14" | "nodejs16";

type Deployable = {
export type Deployable = {
predeploy?: string | string[];
postdeploy?: string | string[];
};
Expand Down Expand Up @@ -67,7 +67,7 @@ export type HostingHeaders = HostingSource & {
}[];
};

type HostingBase = {
export type HostingBase = {
public?: string;
source?: string;
ignore?: string[];
Expand Down Expand Up @@ -101,13 +101,6 @@ export type HostingMultiple = (HostingBase &
}> &
Deployable)[];

// After validating a HostingMultiple and resolving targets, we will instead
// have a HostingResolved.
export type HostingResolved = HostingBase & {
site: string;
target?: string;
} & Deployable;

type StorageSingle = {
rules: string;
target?: string;
Expand Down
9 changes: 3 additions & 6 deletions src/frameworks/index.ts
Expand Up @@ -248,7 +248,7 @@ export async function prepareFrameworks(
context: any,
options: any,
emulators: EmulatorInfo[] = []
): Promise<string[]> {
): Promise<void> {
// `firebase-frameworks` requires Node >= 16. We must check for this to avoid horrible errors.
const nodeVersion = process.version;
if (!semver.satisfies(nodeVersion, ">=16.0.0")) {
Expand All @@ -257,7 +257,6 @@ export async function prepareFrameworks(
);
}

const deployedFrameworks: string[] = [];
const project = needProjectId(context);
const { projectRoot } = options;
const account = getProjectDefaultAccount(projectRoot);
Expand All @@ -284,12 +283,11 @@ export async function prepareFrameworks(
const configs = hostingConfig(options);
let firebaseDefaults: FirebaseDefaults | undefined = undefined;
if (configs.length === 0) {
return deployedFrameworks;
return;
}
for (const config of configs) {
const { source, site, public: publicDir } = config;
if (!source) {
deployedFrameworks.push("classic");
continue;
}
config.rewrites ||= [];
Expand Down Expand Up @@ -411,7 +409,7 @@ export async function prepareFrameworks(
config.public = relative(projectRoot, hostingDist);
if (wantsBackend) codegenFunctionsDirectory = codegenProdModeFunctionsDirectory;
}
deployedFrameworks.push(`${framework}${codegenFunctionsDirectory ? "_ssr" : ""}`);
config.webFramework = `${framework}${codegenFunctionsDirectory ? "_ssr" : ""}`;
if (codegenFunctionsDirectory) {
if (firebaseDefaults) firebaseDefaults._authTokenSyncURL = "/__session";

Expand Down Expand Up @@ -555,7 +553,6 @@ exports.ssr = onRequest((req, res) => server.then(it => it.handle(req, res)));
});
}
}
return deployedFrameworks;
}

function codegenDevModeFunctionsDirectory() {
Expand Down
11 changes: 10 additions & 1 deletion src/hosting/config.ts
Expand Up @@ -5,7 +5,8 @@ import { FirebaseError } from "../error";
import {
HostingMultiple,
HostingSingle,
HostingResolved,
HostingBase,
Deployable,
HostingRewrites,
FunctionsRewrite,
LegacyFunctionsRewrite,
Expand All @@ -20,6 +21,14 @@ import * as path from "node:path";
import * as experiments from "../experiments";
import { logger } from "../logger";

// After validating a HostingMultiple and resolving targets, we will instead
// have a HostingResolved.
export type HostingResolved = HostingBase & {
site: string;
target?: string;
webFramework?: string;
} & Deployable;

// assertMatches allows us to throw when an --only flag doesn't match a target
// but an --except flag doesn't. Is this desirable behavior?
function matchingConfigs(
Expand Down
3 changes: 2 additions & 1 deletion src/hosting/options.ts
@@ -1,6 +1,7 @@
import { FirebaseConfig, HostingResolved } from "../firebaseConfig";
import { FirebaseConfig } from "../firebaseConfig";
import { Implements } from "../metaprogramming";
import { Options } from "../options";
import { HostingResolved } from "./config";

/**
* The set of fields that the Hosting codebase needs from Options.
Expand Down
118 changes: 118 additions & 0 deletions src/test/deploy/hosting/prepare.spec.ts
@@ -0,0 +1,118 @@
import { expect } from "chai";
import * as sinon from "sinon";

import { FirebaseConfig } from "../../../firebaseConfig";
import { HostingOptions } from "../../../hosting/options";
import { Context } from "../../../deploy/hosting/context";
import { Options } from "../../../options";
import * as hostingApi from "../../../hosting/api";
import * as tracking from "../../../track";
import * as deploymentTool from "../../../deploymentTool";
import * as config from "../../../hosting/config";
import { prepare } from "../../../deploy/hosting";

describe("hosting prepare", () => {
let hostingStub: sinon.SinonStubbedInstance<typeof hostingApi>;
let trackingStub: sinon.SinonStubbedInstance<typeof tracking>;
let siteConfig: config.HostingResolved;
let firebaseJson: FirebaseConfig;
let options: HostingOptions & Options;

beforeEach(() => {
hostingStub = sinon.stub(hostingApi);
trackingStub = sinon.stub(tracking);

// We're intentionally using pointer references so that editing site
// edits the results of hostingConfig() and changes firebase.json
siteConfig = {
site: "site",
public: ".",
};
firebaseJson = {
hosting: siteConfig,
};
options = {
cwd: ".",
configPath: ".",
only: "",
except: "",
filteredTargets: ["HOSTING"],
force: false,
json: false,
nonInteractive: false,
interactive: true,
debug: false,
config: {
src: firebaseJson,
} as any,
rc: null as any,

// Forces caching behavior of hostingConfig call
normalizedHostingConfig: [siteConfig],
};
});

afterEach(() => {
sinon.verifyAndRestore();
});

it("passes a smoke test with web framework", async () => {
siteConfig.webFramework = "fake-framework";

// Edit the in-memory config to add a web framework
hostingStub.createVersion.callsFake((siteId, version) => {
expect(siteId).to.equal(siteConfig.site);
expect(version.status).to.equal("CREATED");
expect(version.labels).to.deep.equal({
...deploymentTool.labels(),
"firebase-web-framework": "fake-framework",
});
return Promise.resolve("version");
});

const context: Context = {
projectId: "project",
};
await prepare(context, options);

expect(trackingStub.track).to.have.been.calledOnceWith("hosting_deploy", "fake-framework");
expect(hostingStub.createVersion).to.have.been.calledOnce;
expect(context.hosting).to.deep.equal({
deploys: [
{
config: siteConfig,
version: "version",
},
],
});
});

it("passes a smoke test without web framework", async () => {
// Do not set a web framework on siteConfig

// Edit the in-memory config to add a web framework
hostingStub.createVersion.callsFake((siteId, version) => {
expect(siteId).to.equal(siteConfig.site);
expect(version.status).to.equal("CREATED");
// Note: we're missing the web framework label
expect(version.labels).to.deep.equal(deploymentTool.labels());
return Promise.resolve("version");
});

const context: Context = {
projectId: "project",
};
await prepare(context, options);

expect(trackingStub.track).to.have.been.calledOnceWith("hosting_deploy", "classic");
expect(hostingStub.createVersion).to.have.been.calledOnce;
expect(context.hosting).to.deep.equal({
deploys: [
{
config: siteConfig,
version: "version",
},
],
});
});
});

0 comments on commit 74c1b19

Please sign in to comment.