mirror of
https://github.com/BradNut/awesome-uses
synced 2025-09-08 17:40:31 +00:00
feat: change validate action to take into consideration only add… (#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 #382
This commit is contained in:
parent
26a5bd5385
commit
86b6b5d6d3
6 changed files with 155 additions and 93 deletions
18
.github/workflows/data-validate.yml
vendored
18
.github/workflows/data-validate.yml
vendored
|
|
@ -15,11 +15,15 @@ jobs:
|
|||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 13.x
|
||||
- name: Install validation libs
|
||||
run: |
|
||||
npm install -g @hapi/joi@17.0.2
|
||||
npm install -g @actions/core@1.2.0
|
||||
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
|
||||
|
|
|
|||
19
package-lock.json
generated
19
package-lock.json
generated
|
|
@ -5,9 +5,22 @@
|
|||
"requires": true,
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.0.tgz",
|
||||
"integrity": "sha512-ZKdyhlSlyz38S6YFfPnyNgCDZuAF2T0Qv5eHflNWytPS8Qjvz39bZFMry9Bb/dpSnqWcNeav5yM2CTYpJeY+Dw=="
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.1.tgz",
|
||||
"integrity": "sha512-xD+CQd9p4lU7ZfRqmUcbJpqR+Ss51rJRVeXMyOLrZQImN9/8Sy/BEUBnHO/UKD3z03R686PVTLfEPmkropGuLw=="
|
||||
},
|
||||
"@actions/exec": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz",
|
||||
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==",
|
||||
"requires": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@actions/io": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
|
||||
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.5.5",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.0",
|
||||
"@actions/core": "^1.2.1",
|
||||
"@actions/exec": "^1.0.3",
|
||||
"@hapi/joi": "^17.0.2",
|
||||
"country-emoji": "^1.5.0",
|
||||
"esm": "^3.2.25",
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
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 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;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
core.error(`Ping to "${url}" failed with error: ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
import { getMasterData, Schema, getStatusCode } from './utils.js';
|
||||
import srcData from '../src/data.js';
|
||||
|
||||
(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`
|
||||
);
|
||||
// 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));
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (process.env.CI !== 'true') {
|
||||
process.exit(failingUrls.length > 0 ? 1 : 0)
|
||||
|
||||
if (failedUrlsCount) {
|
||||
core.error(`Action failed with ${failedUrlsCount} URL fetch failures`);
|
||||
}
|
||||
|
||||
if (errors.length || failedUrlsCount) {
|
||||
core.setFailed('Action failed with errors, see logs');
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
5
scripts/masterData.js
Normal file
5
scripts/masterData.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* this is a stub file, do not edit it
|
||||
* see `scripts/utils.js` -> `getMasterData`
|
||||
*/
|
||||
export default [];
|
||||
85
scripts/utils.js
Normal file
85
scripts/utils.js
Normal file
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue