Skip to content

Commit 276388d

Browse files
authored
Add tests (#46)
1 parent 8660c48 commit 276388d

File tree

14 files changed

+5342
-372
lines changed

14 files changed

+5342
-372
lines changed

.github/workflows/deploy-preview.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ jobs:
2525
run: npm ci
2626
- name: Check that Prettier was run
2727
run: npm run format:check
28+
- name: Test
29+
run: npm run test
2830
- name: Deploy to Firebase Hosting preview channel
2931
id: firebase_hosting_preview
3032
uses: ./

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
.firebase/*
3-
**/*.d.ts
3+
**/*.d.ts
4+
demo/.firebase/hosting.*.cache

babel.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// babel config for jest tests
2+
// https://jestjs.io/docs/en/getting-started#using-typescript
3+
module.exports = {
4+
presets: [
5+
["@babel/preset-env", { targets: { node: "current" } }],
6+
"@babel/preset-typescript",
7+
],
8+
};

bin/action.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jest.config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* For a detailed explanation regarding each configuration property and type check, visit:
3+
* https://jestjs.io/docs/en/configuration.html
4+
*/
5+
6+
export default {
7+
// Automatically clear mock calls and instances between every test
8+
clearMocks: true,
9+
10+
// A preset that is used as a base for Jest's configuration
11+
preset: "ts-jest",
12+
13+
// The test environment that will be used for testing
14+
testEnvironment: "node",
15+
};

package-lock.json

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

package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,29 @@
77
"@actions/core": "^1.2.6",
88
"@actions/exec": "^1.0.3",
99
"@actions/github": "^2.1.1",
10+
"@babel/core": "^7.12.3",
11+
"@babel/preset-env": "^7.12.1",
12+
"@babel/preset-typescript": "^7.12.1",
1013
"@tsconfig/node12": "^1.0.7",
14+
"@types/jest": "^26.0.15",
1115
"@types/tmp": "^0.2.0",
16+
"babel-jest": "^26.6.0",
1217
"husky": "^4.2.5",
18+
"jest": "^26.6.0",
1319
"microbundle": "^0.12.3",
1420
"prettier": "^2.1.2",
1521
"pretty-quick": "^3.0.2",
1622
"tmp": "^0.2.1",
23+
"ts-jest": "^26.4.1",
24+
"ts-node": "^9.0.0",
1725
"typescript": "^4.0.3"
1826
},
1927
"scripts": {
2028
"format:check": "prettier . --list-different",
2129
"format": "prettier --write .",
2230
"build": "microbundle -f cjs --target node --compress --no-sourcemap src/index.ts",
23-
"build:watch": "microbundle watch -f cjs --target node --compress --no-sourcemap src/index.ts"
31+
"build:watch": "microbundle watch -f cjs --target node --compress --no-sourcemap src/index.ts",
32+
"test": "jest"
2433
},
2534
"husky": {
2635
"hooks": {

src/deploy.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { exec } from "@actions/exec";
1818

1919
export type SiteDeploy = {
2020
site: string;
21-
target: string | undefined;
21+
target?: string;
2222
url: string;
2323
expireTime: string;
2424
};
@@ -36,33 +36,47 @@ export type ChannelSuccessResult = {
3636
export type ProductionSuccessResult = {
3737
status: "success";
3838
result: {
39-
hosting: string;
39+
hosting: string | string[];
4040
};
4141
};
4242

4343
export type DeployConfig = {
4444
projectId: string;
4545
expires: string;
4646
channelId: string;
47-
target: string;
47+
target?: string;
4848
};
4949

50-
export type productionDeployConfig = {
50+
export type ProductionDeployConfig = {
5151
projectId: string;
5252
target?: string;
5353
};
5454

55+
export function interpretChannelDeployResult(
56+
deployResult: ChannelSuccessResult
57+
): { expireTime: string; urls: string[] } {
58+
const allSiteResults = Object.values(deployResult.result);
59+
60+
const expireTime = allSiteResults[0].expireTime;
61+
const urls = allSiteResults.map((siteResult) => siteResult.url);
62+
63+
return {
64+
expireTime,
65+
urls,
66+
};
67+
}
68+
5569
async function execWithCredentials(
56-
firebase,
5770
args: string[],
5871
projectId,
5972
gacFilename,
6073
debug: boolean = false
6174
) {
6275
let deployOutputBuf: Buffer[] = [];
76+
6377
try {
6478
await exec(
65-
firebase,
79+
"npx firebase-tools",
6680
[
6781
...args,
6882
...(projectId ? ["--project", projectId] : []),
@@ -91,7 +105,7 @@ async function execWithCredentials(
91105
console.log(
92106
"Retrying deploy with the --debug flag for better error output"
93107
);
94-
return execWithCredentials(firebase, args, projectId, gacFilename, true);
108+
await execWithCredentials(args, projectId, gacFilename, true);
95109
} else {
96110
throw e;
97111
}
@@ -102,11 +116,13 @@ async function execWithCredentials(
102116
: ""; // output from the CLI
103117
}
104118

105-
export async function deploy(gacFilename: string, deployConfig: DeployConfig) {
106-
const { projectId, expires, channelId, target } = deployConfig;
119+
export async function deployPreview(
120+
gacFilename: string,
121+
deployConfig: DeployConfig
122+
) {
123+
const { projectId, channelId, target, expires } = deployConfig;
107124

108125
const deploymentText = await execWithCredentials(
109-
"npx firebase-tools",
110126
[
111127
"hosting:channel:deploy",
112128
channelId,
@@ -117,7 +133,7 @@ export async function deploy(gacFilename: string, deployConfig: DeployConfig) {
117133
gacFilename
118134
);
119135

120-
const deploymentResult = JSON.parse(deploymentText) as
136+
const deploymentResult = JSON.parse(deploymentText.trim()) as
121137
| ChannelSuccessResult
122138
| ErrorResult;
123139

@@ -126,12 +142,11 @@ export async function deploy(gacFilename: string, deployConfig: DeployConfig) {
126142

127143
export async function deployProductionSite(
128144
gacFilename,
129-
productionDeployConfig: productionDeployConfig
145+
productionDeployConfig: ProductionDeployConfig
130146
) {
131147
const { projectId, target } = productionDeployConfig;
132148

133149
const deploymentText = await execWithCredentials(
134-
"npx firebase-tools",
135150
["deploy", "--only", `hosting${target ? ":" + target : ""}`],
136151
projectId,
137152
gacFilename

src/index.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,17 @@ import { context, GitHub } from "@actions/github";
2525
import { existsSync } from "fs";
2626
import { createCheck } from "./createCheck";
2727
import { createGacFile } from "./createGACFile";
28-
import { deploy, deployProductionSite, ErrorResult } from "./deploy";
28+
import {
29+
deployPreview,
30+
deployProductionSite,
31+
ErrorResult,
32+
interpretChannelDeployResult,
33+
} from "./deploy";
2934
import { getChannelId } from "./getChannelId";
30-
import { postOrUpdateComment } from "./postOrUpdateComment";
35+
import {
36+
getURLsMarkdownFromChannelDeployResult,
37+
postChannelSuccessComment,
38+
} from "./postOrUpdateComment";
3139

3240
// Inputs defined in action.yml
3341
const expires = getInput("expires");
@@ -104,21 +112,19 @@ async function run() {
104112
const channelId = getChannelId(configuredChannelId, context);
105113

106114
startGroup(`Deploying to Firebase preview channel ${channelId}`);
107-
const deployment = await deploy(gacFilename, {
115+
const deployment = await deployPreview(gacFilename, {
108116
projectId,
109117
expires,
110118
channelId,
111119
target,
112120
});
113-
endGroup();
114121

115122
if (deployment.status === "error") {
116123
throw Error((deployment as ErrorResult).error);
117124
}
125+
endGroup();
118126

119-
const allSiteResults = Object.values(deployment.result);
120-
const expireTime = allSiteResults[0].expireTime;
121-
const urls = allSiteResults.map((siteResult) => siteResult.url);
127+
const { expireTime, urls } = interpretChannelDeployResult(deployment);
122128

123129
setOutput("urls", urls);
124130
setOutput("expire_time", expireTime);
@@ -132,24 +138,15 @@ async function run() {
132138
if (token && isPullRequest) {
133139
const commitId = context.payload.pull_request?.head.sha.substring(0, 7);
134140

135-
await postOrUpdateComment(
136-
github,
137-
context,
138-
`
139-
Visit the preview URL for this PR (updated for commit ${commitId}):
140-
141-
${urlsListMarkdown}
142-
143-
<sub>(expires ${new Date(expireTime).toUTCString()})</sub>`.trim()
144-
);
141+
await postChannelSuccessComment(github, context, deployment, commitId);
145142
}
146143

147144
await finish({
148145
details_url: urls[0],
149146
conclusion: "success",
150147
output: {
151148
title: `Deploy preview succeeded`,
152-
summary: urlsListMarkdown,
149+
summary: getURLsMarkdownFromChannelDeployResult(deployment),
153150
},
154151
});
155152
} catch (e) {

src/postOrUpdateComment.ts

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,59 @@
1717
import { endGroup, startGroup } from "@actions/core";
1818
import { GitHub } from "@actions/github";
1919
import { Context } from "@actions/github/lib/context";
20+
import {
21+
ChannelSuccessResult,
22+
interpretChannelDeployResult,
23+
ErrorResult,
24+
} from "./deploy";
25+
26+
const BOT_SIGNATURE =
27+
"<sub>🔥 via [Firebase Hosting GitHub Action](https://github.com/marketplace/actions/deploy-to-firebase-hosting) 🌎</sub>";
28+
29+
export function isCommentByBot(comment): boolean {
30+
return comment.user.type === "Bot" && comment.body.includes(BOT_SIGNATURE);
31+
}
32+
33+
export function getURLsMarkdownFromChannelDeployResult(
34+
result: ChannelSuccessResult
35+
): string {
36+
const { urls } = interpretChannelDeployResult(result);
37+
38+
return urls.length === 1
39+
? `[${urls[0]}](${urls[0]})`
40+
: urls.map((url) => `- [${url}](${url})`).join("\n");
41+
}
42+
43+
export function getChannelDeploySuccessComment(
44+
result: ChannelSuccessResult,
45+
commit: string
46+
) {
47+
const urlList = getURLsMarkdownFromChannelDeployResult(result);
48+
const { expireTime } = interpretChannelDeployResult(result);
49+
50+
return `
51+
Visit the preview URL for this PR (updated for commit ${commit}):
52+
53+
${urlList}
54+
55+
<sub>(expires ${new Date(expireTime).toUTCString()})</sub>
56+
57+
${BOT_SIGNATURE}`.trim();
58+
}
59+
60+
export async function postChannelSuccessComment(
61+
github: GitHub | undefined,
62+
context: Context,
63+
result: ChannelSuccessResult,
64+
commit: string
65+
) {
66+
const commentMarkdown = getChannelDeploySuccessComment(result, commit);
67+
68+
return postOrUpdateComment(github, context, commentMarkdown);
69+
}
2070

2171
// create a PR comment, or update one if it already exists
22-
export async function postOrUpdateComment(
72+
async function postOrUpdateComment(
2373
github: GitHub | undefined,
2474
context: Context,
2575
commentMarkdown: string
@@ -36,21 +86,16 @@ export async function postOrUpdateComment(
3686

3787
const comment = {
3888
...commentInfo,
39-
body:
40-
commentMarkdown +
41-
"\n\n<sub>🔥 via [Firebase Hosting GitHub Action](https://github.com/marketplace/actions/deploy-to-firebase-hosting) 🌎</sub>",
89+
body: commentMarkdown,
4290
};
4391

44-
startGroup(`Updating PR comment`);
92+
startGroup(`Commenting on PR`);
4593
let commentId;
4694
try {
4795
const comments = (await github.issues.listComments(commentInfo)).data;
4896
for (let i = comments.length; i--; ) {
4997
const c = comments[i];
50-
if (
51-
c.user.type === "Bot" &&
52-
/<sub>[\s\n]*🔥 via \[Firebase Hosting GitHub Action/.test(c.body)
53-
) {
98+
if (isCommentByBot(c)) {
5499
commentId = c.id;
55100
break;
56101
}

0 commit comments

Comments
 (0)