Skip to content
Merged
21 changes: 8 additions & 13 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,16 @@ inputs:
default: "7d"
required: false
projectId:
description:
"The project to deploy to. If you leave this blank, make sure you check in
a .firebaserc file"
description: "The project to deploy to. If you leave this blank, make sure you check in a .firebaserc file"
required: false
channelId:
description: "The ID of the channel to deploy to. If you leave this blank,
a preview channel and its ID will be auto-generated per branch or PR."
description: "The ID of the channel to deploy to. If you leave this blank, a preview channel and its ID will be auto-generated per branch or PR."
required: false
target:
description:
"The target name of the Hosting site to deploy to. If you leave this blank,
the default target or all targets defined in the .firebaserc will be deployed to.
Refer to the Hosting docs about [multiple sites](https://firebase.google.com/docs/hosting/multisites)
for more information about deploy targets."
description: "The target name of the Hosting site to deploy to. If you leave this blank, the default target or all targets defined in the .firebaserc will be deployed to. Refer to the Hosting docs about [multiple sites](https://firebase.google.com/docs/hosting/multisites) for more information about deploy targets."
required: false
entryPoint:
description:
"The location of your firebase.json file, relative to the root of your
directory"
description: "The location of your firebase.json file, relative to the root of your directory"
default: "."
required: false
firebaseToolsVersion:
Expand All @@ -64,6 +55,10 @@ inputs:
Disable auto-commenting with the preview channel URL to the pull request
default: "false"
required: false
force:
description: "Pass 'true' to use the --force flag with firebase deploy. This will automatically accept any warning or prompts during deploy. Use with caution."
default: "false"
required: false
outputs:
urls:
description: The url(s) deployed to
Expand Down
25 changes: 17 additions & 8 deletions bin/action.min.js
Original file line number Diff line number Diff line change
Expand Up @@ -92946,8 +92946,9 @@ async function execWithCredentials(args, projectId, gacFilename, opts) {
let deployOutputBuf = [];
const debug = opts.debug || false;
const firebaseToolsVersion = opts.firebaseToolsVersion || "latest";
const force = opts.force;
try {
await exec_1.exec(`npx firebase-tools@${firebaseToolsVersion}`, [...args, ...(projectId ? ["--project", projectId] : []), debug ? "--debug" // gives a more thorough error message
await exec_1.exec(`npx firebase-tools@${firebaseToolsVersion}`, [...args, ...(projectId ? ["--project", projectId] : []), ...(force ? ["--force"] : []), debug ? "--debug" // gives a more thorough error message
: "--json" // allows us to easily parse the output
], {
listeners: {
Expand All @@ -92968,7 +92969,8 @@ async function execWithCredentials(args, projectId, gacFilename, opts) {
console.log("Retrying deploy with the --debug flag for better error output");
await execWithCredentials(args, projectId, gacFilename, {
debug: true,
firebaseToolsVersion
firebaseToolsVersion,
force
});
} else {
throw e;
Expand All @@ -92982,10 +92984,12 @@ async function deployPreview(gacFilename, deployConfig) {
channelId,
target,
expires,
firebaseToolsVersion
firebaseToolsVersion,
force
} = deployConfig;
const deploymentText = await execWithCredentials(["hosting:channel:deploy", channelId, ...(target ? ["--only", target] : []), ...(expires ? ["--expires", expires] : [])], projectId, gacFilename, {
firebaseToolsVersion
firebaseToolsVersion,
force
});
const deploymentResult = JSON.parse(deploymentText.trim());
return deploymentResult;
Expand All @@ -92994,10 +92998,12 @@ async function deployProductionSite(gacFilename, productionDeployConfig) {
const {
projectId,
target,
firebaseToolsVersion
firebaseToolsVersion,
force
} = productionDeployConfig;
const deploymentText = await execWithCredentials(["deploy", "--only", `hosting${target ? ":" + target : ""}`], projectId, gacFilename, {
firebaseToolsVersion
firebaseToolsVersion,
force
});
const deploymentResult = JSON.parse(deploymentText);
return deploymentResult;
Expand Down Expand Up @@ -93181,6 +93187,7 @@ const entryPoint = core.getInput("entryPoint");
const target = core.getInput("target");
const firebaseToolsVersion = core.getInput("firebaseToolsVersion");
const disableComment = core.getInput("disableComment");
const force = core.getInput("force") === "true";
async function run() {
const isPullRequest = !!github.context.payload.pull_request;
let finish = details => console.log(details);
Expand Down Expand Up @@ -93212,7 +93219,8 @@ async function run() {
const deployment = await deployProductionSite(gacFilename, {
projectId,
target,
firebaseToolsVersion
firebaseToolsVersion,
force
});
if (deployment.status === "error") {
throw Error(deployment.error);
Expand All @@ -93237,7 +93245,8 @@ async function run() {
expires,
channelId,
target,
firebaseToolsVersion
firebaseToolsVersion,
force
});
if (deployment.status === "error") {
throw Error(deployment.error);
Expand Down
20 changes: 14 additions & 6 deletions src/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,18 @@ type DeployConfig = {
target?: string;
// Optional version specification for firebase-tools. Defaults to `latest`.
firebaseToolsVersion?: string;
force?: boolean;
};

export type ChannelDeployConfig = DeployConfig & {
expires: string;
channelId: string;
force?: boolean;
};

export type ProductionDeployConfig = DeployConfig & {};
export type ProductionDeployConfig = DeployConfig & {
force?: boolean;
};

export function interpretChannelDeployResult(
deployResult: ChannelSuccessResult
Expand All @@ -74,18 +78,20 @@ async function execWithCredentials(
args: string[],
projectId,
gacFilename,
opts: { debug?: boolean; firebaseToolsVersion?: string }
opts: { debug?: boolean; firebaseToolsVersion?: string; force?: boolean }
) {
let deployOutputBuf: Buffer[] = [];
const debug = opts.debug || false;
const firebaseToolsVersion = opts.firebaseToolsVersion || "latest";
const force = opts.force;

try {
await exec(
`npx firebase-tools@${firebaseToolsVersion}`,
[
...args,
...(projectId ? ["--project", projectId] : []),
...(force ? ["--force"] : []),
debug
? "--debug" // gives a more thorough error message
: "--json", // allows us to easily parse the output
Expand Down Expand Up @@ -114,6 +120,7 @@ async function execWithCredentials(
await execWithCredentials(args, projectId, gacFilename, {
debug: true,
firebaseToolsVersion,
force,
});
} else {
throw e;
Expand All @@ -129,7 +136,7 @@ export async function deployPreview(
gacFilename: string,
deployConfig: ChannelDeployConfig
) {
const { projectId, channelId, target, expires, firebaseToolsVersion } =
const { projectId, channelId, target, expires, firebaseToolsVersion, force } =
deployConfig;

const deploymentText = await execWithCredentials(
Expand All @@ -141,7 +148,7 @@ export async function deployPreview(
],
projectId,
gacFilename,
{ firebaseToolsVersion }
{ firebaseToolsVersion, force }
);

const deploymentResult = JSON.parse(deploymentText.trim()) as
Expand All @@ -155,13 +162,14 @@ export async function deployProductionSite(
gacFilename,
productionDeployConfig: ProductionDeployConfig
) {
const { projectId, target, firebaseToolsVersion } = productionDeployConfig;
const { projectId, target, firebaseToolsVersion, force } =
productionDeployConfig;

const deploymentText = await execWithCredentials(
["deploy", "--only", `hosting${target ? ":" + target : ""}`],
projectId,
gacFilename,
{ firebaseToolsVersion }
{ firebaseToolsVersion, force }
);

const deploymentResult = JSON.parse(deploymentText) as
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const entryPoint = getInput("entryPoint");
const target = getInput("target");
const firebaseToolsVersion = getInput("firebaseToolsVersion");
const disableComment = getInput("disableComment");
const force = getInput("force") === "true";

async function run() {
const isPullRequest = !!context.payload.pull_request;
Expand Down Expand Up @@ -94,6 +95,7 @@ async function run() {
projectId,
target,
firebaseToolsVersion,
force,
});
if (deployment.status === "error") {
throw Error((deployment as ErrorResult).error);
Expand Down Expand Up @@ -122,6 +124,7 @@ async function run() {
channelId,
target,
firebaseToolsVersion,
force,
});

if (deployment.status === "error") {
Expand Down
79 changes: 79 additions & 0 deletions test/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ const baseLiveDeployConfig: ProductionDeployConfig = {
projectId: "my-project",
};

const forceProductionDeployConfig: ProductionDeployConfig = {
projectId: "my-project",
force: true,
};

const forcePreviewDeployConfig: ChannelDeployConfig = {
projectId: "my-project",
channelId: "my-channel",
expires: undefined,
force: true,
};

async function fakeExecFail(
mainCommand: string,
args: string[],
Expand Down Expand Up @@ -128,6 +140,48 @@ describe("deploy", () => {
});
});

describe("deploy to preview channel with force flag", () => {
it("calls exec and interprets the output, including the --force flag when force is true", async () => {
// @ts-ignore read-only property
exec.exec = jest.fn(fakeExec);

const deployOutput: ChannelSuccessResult = (await deployPreview(
"my-file",
forcePreviewDeployConfig
)) as ChannelSuccessResult;

expect(exec.exec).toBeCalled();
expect(deployOutput).toEqual(channelSingleSiteSuccess);

// Check the arguments that exec was called with
// @ts-ignore Jest adds a magic "mock" property
const args = exec.exec.mock.calls;
const deployFlags = args[0][1];
expect(deployFlags).toContain("hosting:channel:deploy");
expect(deployFlags).toContain("--force");
});

it("specifies a target when one is provided", async () => {
// @ts-ignore read-only property
exec.exec = jest.fn(fakeExec);

const config: ChannelDeployConfig = {
...forcePreviewDeployConfig,
target: "my-second-site",
};

await deployPreview("my-file", config);

// Check the arguments that exec was called with
// @ts-ignore Jest adds a magic "mock" property
const args = exec.exec.mock.calls;
const deployFlags = args[0][1];
expect(deployFlags).toContain("--only");
expect(deployFlags).toContain("my-second-site");
expect(deployFlags).toContain("--force");
});
});

describe("deploy to live channel", () => {
it("calls exec and interprets the output", async () => {
// @ts-ignore read-only property
Expand All @@ -150,4 +204,29 @@ describe("deploy", () => {
expect(deployFlags).toContain("hosting");
});
});

describe("deploy to live channel with force flag", () => {
it("includes --force flag when force is true for deploy", async () => {
// @ts-ignore read-only property
exec.exec = jest.fn(fakeExec);

const forceDeployOutput: ProductionSuccessResult =
(await deployProductionSite(
"my-file",
forceProductionDeployConfig
)) as ProductionSuccessResult;

expect(exec.exec).toBeCalled();
expect(forceDeployOutput).toEqual(liveDeploySingleSiteSuccess);

// Check the arguments that exec was called with
// @ts-ignore Jest adds a magic "mock" property
const args = exec.exec.mock.calls;
const deployFlags = args[0][1];
expect(deployFlags).toContain("deploy");
expect(deployFlags).toContain("--only");
expect(deployFlags).toContain("hosting");
expect(deployFlags).toContain("--force");
});
});
});