Skip to content

Commit 83feafa

Browse files
[MAJOR] Final review (first official release) (#10)
Co-authored-by: Hugo MARTIN <hugo.martin@epitech.eu> Co-authored-by: Hugo MARTIN <hugo.martin.69@gmail.com>
1 parent a61b762 commit 83feafa

39 files changed

+7305
-271
lines changed

.github/workflows/run-example-deployment.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ jobs:
1010
runs-on: ubuntu-18.04
1111
steps:
1212
- uses: actions/checkout@v2
13-
# with:
14-
# path: "github-action-deploy-on-vercel" # Act doesn't work if not specifying the path. See https://github.com/nektos/act/issues/228
1513
- uses: ./
1614
with:
1715
command: "yarn deploy:example --token ${{ secrets.VERCEL_TOKEN }}"

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v14.16.1

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,38 @@ jobs:
3232
- run: "echo \"Found deployment url: ${{ env.VERCEL_DEPLOYMENT_URL }}\""
3333
```
3434
35+
> [Source](https://github.com/UnlyEd/github-action-deploy-on-vercel/blob/review/.github/workflows/run-example-deployment.yml)
36+
3537
## What does this GitHub Action do?
3638
3739
You can use this action to deploy a Vercel project online through a GitHub action.
3840
3941
The action will return the url of the Vercel deployment _(and store it as environment variable, too)_, it will also apply domain aliases if there are any configured in the Vercel config file (`vercel.config` by default).
4042

41-
> It works quite differently compared to [`vercel-action`](https://github.com/marketplace/actions/vercel-action).
43+
## Differences between `github-action-deploy-on-vercel` and `vercel-action`
44+
45+
This action works quite differently compared to [`vercel-action`](https://github.com/marketplace/actions/vercel-action).
46+
47+
> TL;DR: `vercel-action` is great if you don't need a lot of flexibility over the `vercel deploy` command.
48+
> `github-action-deploy-on-vercel` is great if you need to run a custom command, such as a `npm/yarn` script.
49+
50+
`vercel-action` hides the `vercel deploy` command from you, and acts as a wrapper by providing its own API on top of it.
51+
52+
They simplify the `vercel` command by doing so. Unfortunately, they also reduce the flexibility available to the consumer (you).
53+
54+
In our case, we are dealing with multiple customers (B2B) which are **all sharing the same code base**.
55+
The `vercel-action` was too limited and would have complicated our setup, because it requires additional information such as `project_id`/`org_id`.
56+
57+
For most project, we believe using `vercel-action` is enough, and we encourage you to use it, if you don't need to run a special `vercel deploy` command.
4258

4359
## Why/when should you use it?
4460

45-
You want to run a custom command that (amongst other things) performs a Vercel deployment.
61+
You want to run a custom command that (amongst other things) performs a Vercel deployment and returns the URL of the Vercel deployment.
62+
63+
The URL of the deployment is often necessary to run follow-up actions, such as:
64+
- Running End-to-End tests on the deployed site
65+
- Running LightHouse tests on the deployed site
66+
- Etc.
4667

4768
### Action's API
4869

@@ -69,6 +90,7 @@ The below variables are available as outputs, but are also **injected as environ
6990
Here are a few community-powered examples, those are usually advanced use-cases!
7091

7192
- [Next Right Now](https://github.com/UnlyEd/next-right-now) _(Disclosure: We're the author!)_
93+
- [PR](https://github.com/UnlyEd/next-right-now/pull/296) - "Using this action helped us reduce a lot of **bash** code which was hardly testable." - _Next Right Now core contributors_
7294

7395
---
7496

__tests__/main.test.ts

Lines changed: 11 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import * as cp from 'child_process';
2-
import * as path from 'path';
3-
import * as process from 'process';
4-
import { BUILD_DIR, BUILD_MAIN_FILENAME } from '../src/config';
1+
import { execCommand } from "../src/vercel";
2+
import { ExecCommandOutput } from "../src/types";
53

64
/**
75
* Enhance the Node.js environment "global" variable to add our own types
@@ -18,57 +16,19 @@ declare global {
1816
}
1917
}
2018

21-
/**
22-
* Executes the compiled version of the Action's main file. (.js)
23-
*
24-
* The goal is to test the file that is actually executed by GitHub Action.
25-
* Additionally, we could also test the TS files, but we didn't do it to avoid over-complicating things (didn't seem necessary).
26-
*
27-
* @param options
28-
*/
29-
function exec_lib(options: cp.ExecFileSyncOptions): string {
30-
/**
31-
* Path of the node.js binary being used.
32-
*
33-
* @example/usr/local/Cellar/node/14.3.0/bin/node
34-
*/
35-
const nodeBinaryPath = process.execPath;
36-
37-
/**
38-
* Path of the compiled version of the Action file entrypoint.
39-
*
40-
* @example .../github-action-await-vercel/lib/main.js
41-
*/
42-
const mainFilePath = path.join(__dirname, '..', BUILD_DIR, BUILD_MAIN_FILENAME);
43-
44-
try {
45-
// console.debug(`Running command "${nodeBinaryPath} ${mainFilePath}"`);
46-
return cp.execFileSync(nodeBinaryPath, [mainFilePath], options).toString();
47-
} catch (e) {
48-
console.error(e?.output?.toString());
49-
console.error(e);
50-
throw e;
51-
}
52-
}
53-
54-
describe('Functional test', () => {
55-
/*describe('should pass when', () => {
19+
describe('Unit test', () => {
20+
describe('should pass when', () => {
5621
beforeEach(() => {
5722
global.console = global.unmuteConsole();
5823
});
5924

60-
describe('using special delimiter', () => {
61-
const options: cp.ExecFileSyncOptions = {
62-
env: {
63-
INPUT_VARIABLES: 'VAR=TEST,OTHER_VAR=OTHER_TEST,RETRIEVE',
64-
INPUT_DELIMITER: ',',
65-
},
66-
};
67-
const filteredContent = exec_lib(options);
68-
test('test', () => {
69-
expect(filteredContent.includes(',')).toBe(true);
70-
console.log(filteredContent);
25+
describe('using our tool', () => {
26+
test('with command "vercel --version" to make sure Vercel binary is installed', async () => {
27+
const execOutput: ExecCommandOutput = await execCommand("vercel --version");
28+
expect(execOutput.stderr.includes('Vercel CLI'), 'Vercel binary might not have been installed, try installing it globally using "yarn global add vercel".').toBe(true);
7129
});
7230
});
73-
});*/
31+
});
7432
});
33+
34+
export default {};

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = {
99
},
1010
verbose: true,
1111
setupFilesAfterEnv: [
12+
'jest-expect-message', // Allows to add additional message when test fails - See https://github.com/mattphillips/jest-expect-message
1213
'./jest.setup.js',
1314
],
1415
};

lib/main.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ const run = () => __awaiter(void 0, void 0, void 0, function* () {
6767
});
6868
runConfigChecks();
6969
run()
70-
.then((actionReturn) => { })
70+
.then((actionReturn) => {
71+
core.debug(`Execution done successfully.`);
72+
})
7173
.catch((error) => {
7274
core.setFailed(error);
7375
});

lib/vercel.js

Lines changed: 52 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -31,83 +31,83 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3131
return (mod && mod.__esModule) ? mod : { "default": mod };
3232
};
3333
Object.defineProperty(exports, "__esModule", { value: true });
34+
exports.execCommand = void 0;
3435
const core = __importStar(require("@actions/core"));
3536
const fs_1 = __importDefault(require("fs"));
3637
const node_fetch_1 = __importDefault(require("node-fetch"));
3738
const config_1 = require("./config");
38-
const exec = require('@actions/exec');
39-
const glob = require('@actions/glob');
40-
const generate_alias_promises = (deploymentId, teamId, aliases) => {
41-
let aliasCreationPromises = [];
39+
// Must use "require", not compatible with "import"
40+
const exec = require('@actions/exec'); // eslint-disable-line @typescript-eslint/no-var-requires
41+
const glob = require('@actions/glob'); // eslint-disable-line @typescript-eslint/no-var-requires
42+
const generateAliasPromises = (deploymentId, teamId, aliases) => {
43+
const aliasCreationPromises = [];
4244
for (const alias of aliases) {
43-
console.log(`Creating alias ${alias}`);
45+
core.debug(`Creating alias ${alias}`);
4446
aliasCreationPromises.push(node_fetch_1.default(`https://api.vercel.com/v2/now/deployments/${deploymentId}/aliases?teamId=${teamId}`, {
4547
headers: {
46-
Authorization: `Bearer ${process.env.VERCEL_TOKEN}`,
47-
"Content-Type": "application/json"
48+
'Authorization': `Bearer ${process.env.VERCEL_TOKEN}`,
49+
'Content-Type': 'application/json',
4850
},
4951
body: JSON.stringify({
50-
alias: alias
52+
alias: alias,
5153
}),
52-
method: 'POST'
53-
}).then(data => data.json()));
54+
method: 'POST',
55+
})
56+
.then((data) => data.json())
57+
.catch((e) => core.warning(e)));
5458
}
5559
return aliasCreationPromises;
5660
};
57-
const exec_command = (command) => __awaiter(void 0, void 0, void 0, function* () {
58-
/**
59-
* When we execute a program, it writes on two outputs : standard and error.
60-
* Initalizing empty variables to receive these outputs
61-
*/
61+
exports.execCommand = (command) => __awaiter(void 0, void 0, void 0, function* () {
62+
const options = {};
6263
let stdout = '';
6364
let stderr = '';
64-
const options = {};
6565
/**
66-
* Defining actions for both outputs
66+
* Listening to both events to store logs and reuse them later
6767
*/
6868
options.listeners = {
6969
stdout: (data) => {
7070
stdout += data.toString();
7171
},
7272
stderr: (data) => {
7373
stderr += data.toString();
74-
}
74+
},
7575
};
7676
yield exec.exec(command, [], options);
77-
return stdout;
77+
return { stdout, stderr };
7878
});
79-
const create_aliases = (deploymentUrl, customDeploymentFile, failIfAliasNotLinked) => __awaiter(void 0, void 0, void 0, function* () {
79+
const createAliases = (deploymentUrl, customDeploymentFile, failIfAliasNotLinked) => __awaiter(void 0, void 0, void 0, function* () {
8080
core.debug(`Starting to link aliases`);
81-
/**
82-
* Globber is a github action tool https://github.com/actions/toolkit/tree/master/packages/glob
83-
* It helps us to find the absolute path for a file. Indeed, because we don't know where the action will be run and we need to find this file, wherever it is.
84-
*/
81+
// Globber is a github action tool https://github.com/actions/toolkit/tree/master/packages/glob
82+
// It helps us to find the absolute path for a file. Indeed, because we don't know where the action will be run and we need to find this file, wherever it is.
8583
const globber = yield glob.create(customDeploymentFile);
8684
const vercelConfigFile = (yield globber.glob())[0];
8785
if (vercelConfigFile && fs_1.default.existsSync(vercelConfigFile)) {
8886
core.debug(`Found custom config file: ${vercelConfigFile}`);
8987
core.debug(`Found real path: ${vercelConfigFile}`);
9088
const vercelConfig = JSON.parse(fs_1.default.readFileSync(vercelConfigFile, 'utf8'));
9189
if (vercelConfig.alias) {
92-
const { id, ownerId } = (yield node_fetch_1.default(`https://api.vercel.com/v11/now/deployments/get?url=${deploymentUrl.replace("https://", "")}`, {
90+
const { id, ownerId, } = yield node_fetch_1.default(`https://api.vercel.com/v11/now/deployments/get?url=${deploymentUrl.replace('https://', '')}`, {
9391
headers: {
94-
Authorization: `Bearer ${process.env.VERCEL_TOKEN}`
92+
Authorization: `Bearer ${process.env.VERCEL_TOKEN}`,
9593
},
96-
method: 'GET'
97-
}).then(data => data.json()));
98-
const aliasCreationPromises = generate_alias_promises(id, ownerId, vercelConfig.alias);
94+
method: 'GET',
95+
})
96+
.then((data) => data.json())
97+
.catch((error) => core.warning(`Did not receive JSON from Vercel API while creating aliases. Message: ${error === null || error === void 0 ? void 0 : error.message}`));
98+
const aliasCreationPromises = generateAliasPromises(id, ownerId, vercelConfig.alias);
9999
core.debug(`Resolving alias promises`);
100100
const aliasesResponse = yield Promise.all(aliasCreationPromises);
101-
console.log(`Alias creation response: ${JSON.stringify(aliasesResponse)}`);
102-
if (aliasesResponse.filter(response => response.error).length > 0) {
101+
core.debug(`Alias creation response: ${JSON.stringify(aliasesResponse)}`);
102+
if (aliasesResponse.filter((response) => response.error).length > 0) {
103103
const failedAliases = aliasesResponse.filter((response) => response.error).map((response) => response.error);
104104
const message = `Got following errors: ${JSON.stringify(failedAliases)}`;
105105
failIfAliasNotLinked ? core.setFailed(message) : core.warning(message);
106-
console.log(`Exporting this error...`);
106+
core.debug(`Exporting this error...`);
107107
core.setOutput('VERCEL_ALIASES_ERROR', failedAliases);
108108
}
109-
for (const alias of aliasesResponse.filter(response => !response.error)) {
110-
console.log(`Created alias ${alias}`);
109+
for (const alias of aliasesResponse.filter((response) => !response.error)) {
110+
core.debug(`Created alias ${alias}`);
111111
}
112112
}
113113
else {
@@ -125,44 +125,47 @@ const deploy = (command, deployAlias, failIfAliasNotLinked) => __awaiter(void 0,
125125
*
126126
* Running "exec_command" displays the output in the console.
127127
*/
128-
const stdout = yield exec_command(command);
128+
const commandOutput = yield exports.execCommand(command);
129+
const stdout = commandOutput.stdout;
129130
/**
130131
* Parsing this huge output by using Regex match.
131132
* match function return strings matching with the pattern.
133+
*
132134
* Pattern "/https?:\/\/[^ ]+.vercel.app/gi"
133135
* "/https?\/\/:" start matching when we find http:// or https://
134136
* "[^ ]+.vercel.app" will catch everything as a vercel subdomain, so "*.vercel.app"
135137
* "/g" allows us to have many matchess
136138
* "i" make the regex case insensitive. It will match for "https://subDomainApp.vercel.app" and "https://subdomainapp.vercel.app"
137-
* shift returns the first occurence
139+
* "shift" returns the first occurence
138140
*/
139141
const deploymentUrl = (_a = stdout.match(/https?:\/\/[^ ]+.vercel.app/gi)) === null || _a === void 0 ? void 0 : _a.shift();
140142
/**
141143
* Locating any custom config file for Vercel (if you are using one file per customer or deployment for the same app)
142144
* match function return strings matching with the pattern.
145+
*
143146
* Pattern "/--local-config=.[^$]+?.json/gs"
144147
* "/--local-config=" starts matching on finding the argument local-config
145148
* "[^$]+?.json" with a json file provided as value
146149
* "/g" allows us to have many matchess
147150
* "s" reduce match scope on the same line
148-
* shift returns the first occurence
149-
* split isolates the json file
150-
* find automatically finds the matching json file
151+
* "shift" returns the first occurence
152+
* "split" isolates the json file
153+
* "find" automatically finds the matching json file
151154
*/
152-
const customDeploymentFile = ((_c = (_b = stdout.match(/--local-config=.[^$]+?.json/gs)) === null || _b === void 0 ? void 0 : _b.shift()) === null || _c === void 0 ? void 0 : _c.split("=").find(el => el.endsWith(".json"))) || config_1.VERCEL_CONFIG_FILE;
155+
const customDeploymentFile = ((_c = (_b = stdout.match(/--local-config=.[^$]+?.json/gs)) === null || _b === void 0 ? void 0 : _b.shift()) === null || _c === void 0 ? void 0 : _c.split('=').find((el) => el.endsWith('.json'))) || config_1.VERCEL_CONFIG_FILE;
153156
core.debug(`Command: ${command}`);
154157
core.debug(`Custom deploy file: ${customDeploymentFile}`);
155158
if (deploymentUrl) {
156-
const deploymentDomain = deploymentUrl.replace("https://", "");
157-
console.log(`Found url deployment. Exporting it...`);
158-
console.log(`VERCEL_DEPLOYMENT_URL=${deploymentUrl}`);
159-
core.exportVariable("VERCEL_DEPLOYMENT_URL", deploymentUrl);
160-
core.setOutput("VERCEL_DEPLOYMENT_URL", deploymentUrl);
161-
console.log(`VERCEL_DEPLOYMENT_DOMAIN=${deploymentDomain}`);
162-
core.exportVariable("VERCEL_DEPLOYMENT_DOMAIN", deploymentDomain);
163-
core.setOutput("VERCEL_DEPLOYMENT_DOMAIN", deploymentDomain);
159+
const deploymentDomain = deploymentUrl.replace('https://', '');
160+
core.debug(`Found url deployment. Exporting it...`);
161+
core.debug(`VERCEL_DEPLOYMENT_URL=${deploymentUrl}`);
162+
core.exportVariable('VERCEL_DEPLOYMENT_URL', deploymentUrl);
163+
core.setOutput('VERCEL_DEPLOYMENT_URL', deploymentUrl);
164+
core.debug(`VERCEL_DEPLOYMENT_DOMAIN=${deploymentDomain}`);
165+
core.exportVariable('VERCEL_DEPLOYMENT_DOMAIN', deploymentDomain);
166+
core.setOutput('VERCEL_DEPLOYMENT_DOMAIN', deploymentDomain);
164167
if (deployAlias) {
165-
yield create_aliases(deploymentUrl, customDeploymentFile, failIfAliasNotLinked);
168+
yield createAliases(deploymentUrl, customDeploymentFile, failIfAliasNotLinked);
166169
}
167170
}
168171
else {

node_modules/.yarn-integrity

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

0 commit comments

Comments
 (0)