forked from wesbos/awesome-uses
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: change validate action to take into consideration only add… (we…
…sbos#422) `node scripts/data-valdiate.js` cand also be run locally This also will fix the issue when some valid URL returned timeout Because the action wanted to make all requests at the same time `git restore` is available only from Git 2.23 https://github.blog/2019-08-16-highlights-from-git-2-23/#experimental-alternatives-for-git-checkout Closes wesbos#382
- Loading branch information
1 parent
26a5bd5
commit 86b6b5d
Showing
6 changed files
with
151 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,11 +15,15 @@ jobs: | |
- uses: actions/setup-node@v1 | ||
with: | ||
node-version: 13.x | ||
- name: Install validation libs | ||
run: | | ||
npm install -g @hapi/[email protected] | ||
npm install -g @actions/[email protected] | ||
npm link @hapi/joi | ||
npm link @actions/core | ||
|
||
- name: Cache/Restore node modules | ||
uses: actions/cache@v1 | ||
with: | ||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS | ||
key: ${{ runner.os }}-npm-${{ hashFiles('./package-lock.json') }} | ||
|
||
- name: Install Dependencies | ||
run: npm install | ||
|
||
- name: Validate data.js | ||
run: node ./scripts/data-validate.js | ||
run: node ./scripts/data-validate.js |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,88 +1,42 @@ | ||
import Joi from '@hapi/joi'; | ||
import core from '@actions/core'; | ||
import * as http from 'http'; | ||
import * as https from 'https'; | ||
import data from '../src/data.js'; | ||
import flags from './flags.js'; | ||
import { getMasterData, Schema, getStatusCode } from './utils.js'; | ||
import srcData from '../src/data.js'; | ||
|
||
if (process.env.CI !== 'true') { | ||
core.error = console.error; | ||
core.setFailed = console.error; | ||
} | ||
|
||
const schema = Joi.object({ | ||
name: Joi.string().required(), | ||
description: Joi.string().required(), | ||
url: Joi.string() | ||
.uri() | ||
.required() | ||
.pattern(/(use|uses|using|setup|environment|^https:\/\/gist.github.com\/)/), | ||
country: Joi.string() | ||
.valid(...flags) | ||
.required(), | ||
twitter: Joi.string().pattern(new RegExp(/^@?(\w){1,15}$/)), | ||
emoji: Joi.string().allow(''), | ||
computer: Joi.string().valid('apple', 'windows', 'linux'), | ||
phone: Joi.string().valid('iphone', 'android'), | ||
tags: Joi.array().items(Joi.string()), | ||
}); | ||
|
||
const errors = data | ||
.map(person => schema.validate(person)) | ||
.filter(v => v.error) | ||
.map(v => v.error); | ||
|
||
errors.forEach(e => { | ||
core.error(e._original.name); | ||
e.details.forEach(d => core.error(d.message)); | ||
}); | ||
|
||
if (errors.length) { | ||
core.setFailed('Action failed with validation errors, see logs'); | ||
} | ||
const REQUEST_TIMEOUT = 10000; | ||
|
||
function getStatusCode(url) { | ||
const client = url.startsWith('https') ? https : http; | ||
return new Promise((resolve, reject) => { | ||
setTimeout(() => reject(new Error('Request timed out')), REQUEST_TIMEOUT); | ||
client | ||
.get(url, res => { | ||
resolve(res.statusCode); | ||
}) | ||
.on('error', err => { | ||
reject(err); | ||
}); | ||
(async () => { | ||
// on master branch will be empty array | ||
const masterDataUrls = (await getMasterData()).map(d => d.url); | ||
// so here data will be an array with all users | ||
const data = srcData.filter(d => !masterDataUrls.includes(d.url)); | ||
|
||
const errors = data | ||
.map(person => Schema.validate(person)) | ||
.filter(v => v.error) | ||
.map(v => v.error); | ||
|
||
errors.forEach(e => { | ||
core.error(e._original.name || e._original.url); | ||
e.details.forEach(d => core.error(d.message)); | ||
}); | ||
} | ||
|
||
async function isWorkingUrl(url) { | ||
try { | ||
const statusCode = await getStatusCode(url); | ||
if (statusCode < 200 || statusCode >= 400) { | ||
core.error(`Ping to "${url}" failed with status: ${statusCode}`); | ||
return false; | ||
let failedUrlsCount = 0; | ||
for await (const { url } of data) { | ||
try { | ||
const statusCode = await getStatusCode(url); | ||
if (statusCode < 200 || statusCode >= 400) { | ||
core.error(`Ping to "${url}" failed with status: ${statusCode}`); | ||
failedUrlsCount += 1; | ||
} | ||
} catch (e) { | ||
core.error(`Ping to "${url}" failed with error: ${e}`); | ||
failedUrlsCount += 1; | ||
} | ||
return true; | ||
} catch (e) { | ||
core.error(`Ping to "${url}" failed with error: ${e}`); | ||
return false; | ||
} | ||
} | ||
|
||
(async () => { | ||
// TODO: we might need to batch these in sets instead of requesting 100+ URLs | ||
// at the same time | ||
const areWorkingUrls = await Promise.all( | ||
data.map(p => p.url).map(url => isWorkingUrl(url)) | ||
); | ||
const failingUrls = areWorkingUrls.filter(a => !a); | ||
if (failingUrls.length > 0) { | ||
core.setFailed( | ||
`Action failed with ${failingUrls.length} URL fetch failures, see logs` | ||
); | ||
if (failedUrlsCount) { | ||
core.error(`Action failed with ${failedUrlsCount} URL fetch failures`); | ||
} | ||
if (process.env.CI !== 'true') { | ||
process.exit(failingUrls.length > 0 ? 1 : 0) | ||
|
||
if (errors.length || failedUrlsCount) { | ||
core.setFailed('Action failed with errors, see logs'); | ||
} | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/** | ||
* this is a stub file, do not edit it | ||
* see `scripts/utils.js` -> `getMasterData` | ||
*/ | ||
export default []; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import exec from '@actions/exec'; | ||
import core from '@actions/core'; | ||
import Joi from '@hapi/joi'; | ||
import * as http from 'http'; | ||
import * as https from 'https'; | ||
import flags from './flags.js'; | ||
|
||
async function getCurrentBranchName() { | ||
let myOutput = ''; | ||
let myError = ''; | ||
|
||
const options = { | ||
silent: true, | ||
listeners: { | ||
stdout: data => (myOutput += data.toString()), | ||
stderr: data => (myError += data.toString()), | ||
}, | ||
}; | ||
|
||
await exec.exec('git rev-parse --abbrev-ref HEAD', [], options); | ||
return myOutput.trim(); | ||
} | ||
|
||
/** on master branch will return an empty array */ | ||
export async function getMasterData() { | ||
const options = { silent: true }; | ||
const curentBranchName = getCurrentBranchName(); | ||
// when on a branch/PR different from master | ||
// will populate scripts/masterData.js with src/data.js from master | ||
if (curentBranchName !== 'master') { | ||
core.info('Executing action on branch different from master'); | ||
await exec.exec('mv src/data.js src/tmpData.js', [], options); | ||
await exec.exec('git fetch origin master', [], options); | ||
await exec.exec('git restore --source=FETCH_HEAD src/data.js', [], options); | ||
await exec.exec('mv src/data.js scripts/masterData.js', [], options); | ||
await exec.exec('mv src/tmpData.js src/data.js', [], options); | ||
} else { | ||
core.info('Executing action on master branch'); | ||
} | ||
|
||
const masterData = await import('./masterData.js').then(m => m.default); | ||
|
||
// restore `scripts/masterData.js` after was loaded | ||
if (curentBranchName !== 'master') { | ||
await exec.exec('git restore scripts/masterData.js', [], options); | ||
} | ||
|
||
return masterData; | ||
} | ||
|
||
export const Schema = Joi.object({ | ||
name: Joi.string().required(), | ||
description: Joi.string().required(), | ||
url: Joi.string() | ||
.uri() | ||
.required() | ||
.pattern(/(use|uses|using|setup|environment|^https:\/\/gist.github.com\/)/), | ||
country: Joi.string() | ||
.valid(...flags) | ||
.required(), | ||
twitter: Joi.string().pattern(new RegExp(/^@?(\w){1,15}$/)), | ||
emoji: Joi.string().allow(''), | ||
computer: Joi.string().valid('apple', 'windows', 'linux'), | ||
phone: Joi.string().valid('iphone', 'android'), | ||
tags: Joi.array().items(Joi.string()), | ||
}); | ||
|
||
export function getStatusCode(url) { | ||
const client = url.startsWith('https') ? https : http; | ||
return new Promise((resolve, reject) => { | ||
const REQUEST_TIMEOUT = 10000; | ||
const timeoutId = setTimeout( | ||
reject, | ||
REQUEST_TIMEOUT, | ||
new Error('Request timed out') | ||
); | ||
|
||
client | ||
.get(url, res => { | ||
clearTimeout(timeoutId); | ||
resolve(res.statusCode); | ||
}) | ||
.on('error', err => reject(err)); | ||
}); | ||
} |