Make script throw on fail

main
Johannes Andersen 4 years ago
parent afbcc17ab9
commit 3265a60a0d
No known key found for this signature in database
GPG Key ID: 5D8D41426C95C066

@ -6,174 +6,167 @@ const { technologies, categories } = JSON.parse(
fs.readFileSync('./src/technologies.json') fs.readFileSync('./src/technologies.json')
) )
try { Object.keys(technologies).forEach((name) => {
Object.keys(technologies).forEach((name) => { const technology = technologies[name]
const technology = technologies[name]
// Validate regular expressions
;['url', 'html', 'meta', 'headers', 'cookies', 'script', 'js'].forEach(
(type) => {
if (technology[type]) {
const keyed =
typeof technology[type] === 'string' ||
Array.isArray(technology[type])
? { _: technology[type] }
: technology[type]
Object.keys(keyed).forEach((key) => {
const patterns = Array.isArray(keyed[key])
? keyed[key]
: [keyed[key]]
patterns.forEach((pattern, index) => {
const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]`
const [regex, ...flags] = pattern.split('\\;')
let maxGroups = 0
flags.forEach((flag) => {
const [key, value] = flag.split(':')
if (key === 'version') {
const refs = value.match(/\\(\d+)/g)
if (refs) {
maxGroups = refs.reduce((max, ref) =>
Math.max(max, parseInt(refs[1] || 0))
)
}
} else if (key === 'confidence') {
if (
!/^\d+$/.test(value) ||
parseInt(value, 10) < 0 ||
parseInt(value, 10) > 99
) {
throw new Error(
`Confidence value must a number between 0 and 99: ${value} (${id})`
)
}
} else {
throw new Error(`Invalid flag: ${key} (${id})`)
}
})
// Validate regular expression
try {
// eslint-disable-next-line no-new
new RegExp(regex)
} catch (error) {
throw new Error(`${error.message} (${id})`)
}
// Count capture groups
const groups = new RegExp(`${regex}|`).exec('').length - 1
if (groups > maxGroups) { // Validate regular expressions
throw new Error( ;['url', 'html', 'meta', 'headers', 'cookies', 'script', 'js'].forEach(
`Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})` (type) => {
) if (technology[type]) {
} const keyed =
typeof technology[type] === 'string' ||
Array.isArray(technology[type])
? { _: technology[type] }
: technology[type]
if (type === 'html' && !/[<>]/.test(regex)) { Object.keys(keyed).forEach((key) => {
throw new Error( const patterns = Array.isArray(keyed[key]) ? keyed[key] : [keyed[key]]
`HTML pattern must include < or >: ${regex} (${id})`
)
}
})
})
}
}
)
// Validate categories patterns.forEach((pattern, index) => {
technology.cats.forEach((id) => { const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]`
if (!categories[id]) {
throw new Error(`No such category: ${id} (${name})`)
}
})
// Validate icons const [regex, ...flags] = pattern.split('\\;')
if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) {
throw new Error(`No such icon: ${technology.icon} (${name})`)
}
// Validate website URLs let maxGroups = 0
try {
// eslint-disable-next-line no-new
const { protocol } = new URL(technology.website)
if (protocol !== 'http:' && protocol !== 'https:') { flags.forEach((flag) => {
throw new Error('Invalid protocol') const [key, value] = flag.split(':')
}
} catch (error) {
throw new Error(`Invalid website URL: ${technology.website} (${name})`)
}
// Validate implies and excludes if (key === 'version') {
const { implies, excludes } = technology const refs = value.match(/\\(\d+)/g)
if (implies) { if (refs) {
;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => { maxGroups = refs.reduce((max, ref) =>
const [_name, ...flags] = implied.split('\\;') Math.max(max, parseInt(refs[1] || 0))
)
}
} else if (key === 'confidence') {
if (
!/^\d+$/.test(value) ||
parseInt(value, 10) < 0 ||
parseInt(value, 10) > 99
) {
throw new Error(
`Confidence value must a number between 0 and 99: ${value} (${id})`
)
}
} else {
throw new Error(`Invalid flag: ${key} (${id})`)
}
})
const id = `${name}: implies[${implied}]` // Validate regular expression
try {
// eslint-disable-next-line no-new
new RegExp(regex)
} catch (error) {
throw new Error(`${error.message} (${id})`)
}
if (!technologies[_name]) { // Count capture groups
throw new Error(`Implied technology does not exist: ${_name} (${id})`) const groups = new RegExp(`${regex}|`).exec('').length - 1
}
flags.forEach((flag) => { if (groups > maxGroups) {
const [key, value] = flag.split(':') throw new Error(
`Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})`
)
}
if (key === 'confidence') { if (type === 'html' && !/[<>]/.test(regex)) {
if (
!/^\d+$/.test(value) ||
parseInt(value, 10) < 0 ||
parseInt(value, 10) > 99
) {
throw new Error( throw new Error(
`Confidence value must a number between 0 and 99: ${value} (${id})` `HTML pattern must include < or >: ${regex} (${id})`
) )
} }
} else { })
throw new Error(`Invalid flag: ${key} (${id})`)
}
}) })
}) }
} }
)
if (excludes) { // Validate categories
;(Array.isArray(excludes) ? excludes : [excludes]).forEach((excluded) => { technology.cats.forEach((id) => {
const id = `${name}: excludes[${excluded}]` if (!categories[id]) {
throw new Error(`No such category: ${id} (${name})`)
if (!technologies[excluded]) {
throw new Error(
`Excluded technology does not exist: ${excluded} (${id})`
)
}
})
} }
}) })
// Validate icons // Validate icons
fs.readdirSync(iconPath).forEach((file) => { if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) {
const filePath = `${iconPath}/${file}` throw new Error(`No such icon: ${technology.icon} (${name})`)
}
// Validate website URLs
try {
// eslint-disable-next-line no-new
const { protocol } = new URL(technology.website)
if (protocol !== 'http:' && protocol !== 'https:') {
throw new Error('Invalid protocol')
}
} catch (error) {
throw new Error(`Invalid website URL: ${technology.website} (${name})`)
}
// Validate implies and excludes
const { implies, excludes } = technology
if (implies) {
;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => {
const [_name, ...flags] = implied.split('\\;')
const id = `${name}: implies[${implied}]`
if (fs.statSync(filePath).isFile() && !file.startsWith('.')) { if (!technologies[_name]) {
if (!/^(png|svg)$/i.test(file.split('.').pop())) { throw new Error(`Implied technology does not exist: ${_name} (${id})`)
throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`)
} }
if ( flags.forEach((flag) => {
!Object.values(technologies).some(({ icon }) => icon === file) && const [key, value] = flag.split(':')
file !== 'default.svg'
) { if (key === 'confidence') {
throw new Error(`Extraneous file: ${filePath}}`) if (
!/^\d+$/.test(value) ||
parseInt(value, 10) < 0 ||
parseInt(value, 10) > 99
) {
throw new Error(
`Confidence value must a number between 0 and 99: ${value} (${id})`
)
}
} else {
throw new Error(`Invalid flag: ${key} (${id})`)
}
})
})
}
if (excludes) {
;(Array.isArray(excludes) ? excludes : [excludes]).forEach((excluded) => {
const id = `${name}: excludes[${excluded}]`
if (!technologies[excluded]) {
throw new Error(
`Excluded technology does not exist: ${excluded} (${id})`
)
} }
})
}
})
// Validate icons
fs.readdirSync(iconPath).forEach((file) => {
const filePath = `${iconPath}/${file}`
if (fs.statSync(filePath).isFile() && !file.startsWith('.')) {
if (!/^(png|svg)$/i.test(file.split('.').pop())) {
throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`)
} }
})
} catch (error) { if (
// eslint-disable-next-line no-console !Object.values(technologies).some(({ icon }) => icon === file) &&
console.error(error.message) file !== 'default.svg'
} ) {
throw new Error(`Extraneous file: ${filePath}}`)
}
}
})

@ -1513,7 +1513,7 @@
1 1
], ],
"html": "<[^>]+data-apos-refreshable[^>]", "html": "<[^>]+data-apos-refreshable[^>]",
"icon": "ApostropheCMS.svg", "icon": "apostrophecms.svg",
"implies": "Node.js", "implies": "Node.js",
"website": "http://apostrophecms.org" "website": "http://apostrophecms.org"
}, },