diff --git a/.eslintrc.js b/.eslintrc.js
index 8ff448efd..f91394cce 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,19 +2,18 @@ module.exports = {
root: true,
env: {
browser: true,
- node: true
+ node: true,
},
parserOptions: {
- parser: 'babel-eslint'
+ parser: 'babel-eslint',
},
extends: [
'@nuxtjs',
'prettier',
'prettier/vue',
'plugin:prettier/recommended',
- 'plugin:nuxt/recommended'
- ],
- plugins: [
- 'prettier'
+ 'plugin:nuxt/recommended',
+ 'plugin:json/recommended',
],
+ plugins: ['prettier'],
}
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
new file mode 100644
index 000000000..e9041ab8f
--- /dev/null
+++ b/.github/workflows/validate.yml
@@ -0,0 +1,25 @@
+name: Validate
+on:
+ push:
+ pull_request:
+
+jobs:
+ validate:
+ name: Validate
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2.3.3
+ - uses: actions/setup-node@v2.1.2
+ with:
+ node-version: '14'
+ - name: Restore npm cache
+ uses: actions/cache@v2
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+ - name: Install dependencies
+ run: npm install
+ - name: Validate
+ run: npm run validate
diff --git a/bin/validate.js b/bin/validate.js
index cdf931123..7c9c9b238 100755
--- a/bin/validate.js
+++ b/bin/validate.js
@@ -6,174 +6,167 @@ const { technologies, categories } = JSON.parse(
fs.readFileSync('./src/technologies.json')
)
-try {
- Object.keys(technologies).forEach((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
+Object.keys(technologies).forEach((name) => {
+ const technology = technologies[name]
- if (groups > maxGroups) {
- throw new Error(
- `Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})`
- )
- }
+ // 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]
- if (type === 'html' && !/[<>]/.test(regex)) {
- throw new Error(
- `HTML pattern must include < or >: ${regex} (${id})`
- )
- }
- })
- })
- }
- }
- )
+ Object.keys(keyed).forEach((key) => {
+ const patterns = Array.isArray(keyed[key]) ? keyed[key] : [keyed[key]]
- // Validate categories
- technology.cats.forEach((id) => {
- if (!categories[id]) {
- throw new Error(`No such category: ${id} (${name})`)
- }
- })
+ patterns.forEach((pattern, index) => {
+ const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]`
- // Validate icons
- if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) {
- throw new Error(`No such icon: ${technology.icon} (${name})`)
- }
+ const [regex, ...flags] = pattern.split('\\;')
- // Validate website URLs
- try {
- // eslint-disable-next-line no-new
- const { protocol } = new URL(technology.website)
+ let maxGroups = 0
- if (protocol !== 'http:' && protocol !== 'https:') {
- throw new Error('Invalid protocol')
- }
- } catch (error) {
- throw new Error(`Invalid website URL: ${technology.website} (${name})`)
- }
+ flags.forEach((flag) => {
+ const [key, value] = flag.split(':')
- // Validate implies and excludes
- const { implies, excludes } = technology
+ if (key === 'version') {
+ const refs = value.match(/\\(\d+)/g)
- if (implies) {
- ;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => {
- const [_name, ...flags] = implied.split('\\;')
+ 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})`)
+ }
+ })
- 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]) {
- throw new Error(`Implied technology does not exist: ${_name} (${id})`)
- }
+ // Count capture groups
+ const groups = new RegExp(`${regex}|`).exec('').length - 1
- flags.forEach((flag) => {
- const [key, value] = flag.split(':')
+ if (groups > maxGroups) {
+ throw new Error(
+ `Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})`
+ )
+ }
- if (key === 'confidence') {
- if (
- !/^\d+$/.test(value) ||
- parseInt(value, 10) < 0 ||
- parseInt(value, 10) > 99
- ) {
+ if (type === 'html' && !/[<>]/.test(regex)) {
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) {
- ;(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 categories
+ technology.cats.forEach((id) => {
+ if (!categories[id]) {
+ throw new Error(`No such category: ${id} (${name})`)
}
})
// Validate icons
- fs.readdirSync(iconPath).forEach((file) => {
- const filePath = `${iconPath}/${file}`
+ if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) {
+ 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 (!/^(png|svg)$/i.test(file.split('.').pop())) {
- throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`)
+ if (!technologies[_name]) {
+ throw new Error(`Implied technology does not exist: ${_name} (${id})`)
}
- if (
- !Object.values(technologies).some(({ icon }) => icon === file) &&
- file !== 'default.svg'
- ) {
- throw new Error(`Extraneous file: ${filePath}}`)
+ flags.forEach((flag) => {
+ const [key, value] = flag.split(':')
+
+ 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})`)
+ }
+ })
+ })
+ }
+
+ 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) {
- // eslint-disable-next-line no-console
- console.error(error.message)
-}
+
+ if (
+ !Object.values(technologies).some(({ icon }) => icon === file) &&
+ file !== 'default.svg'
+ ) {
+ throw new Error(`Extraneous file: ${filePath}}`)
+ }
+ }
+})
diff --git a/package.json b/package.json
index c24bd1c32..6e6ff2e39 100644
--- a/package.json
+++ b/package.json
@@ -8,15 +8,17 @@
"@nuxtjs/eslint-config": "^3.1.0",
"@nuxtjs/eslint-module": "^2.0.0",
"babel-eslint": "^10.1.0",
- "eslint": "^7.7.0",
- "eslint-config-prettier": "^6.11.0",
+ "eslint": "^7.13.0",
+ "eslint-config-prettier": "^6.15.0",
+ "eslint-plugin-json": "^2.1.2",
"eslint-plugin-nuxt": "^1.0.0",
"eslint-plugin-prettier": "^3.1.4",
- "prettier": "^2.1.0"
+ "prettier": "^2.1.2"
},
"scripts": {
"link": "node ./bin/link.js",
- "lint": "eslint --fix src/**/*.js",
+ "lint": "eslint src/**/*.{js,json}",
+ "lint:fix": "eslint --fix src/**/*.{js,json}",
"validate": "yarn run lint && jsonlint -qV ./schema.json ./src/technologies.json && node ./bin/validate.js",
"convert": "cd ./src/drivers/webextension/images/icons ; cp *.svg converted ; cd converted ; convert-svg-to-png *.svg --width 32 --height 32 ; rm *.svg",
"prettify": "jsonlint -si --trim-trailing-commas --enforce-double-quotes ./src/technologies.json",
diff --git a/src/drivers/webextension/_locales/ca/messages.json b/src/drivers/webextension/_locales/ca/messages.json
index 0ea5132c7..9df2f83aa 100644
--- a/src/drivers/webextension/_locales/ca/messages.json
+++ b/src/drivers/webextension/_locales/ca/messages.json
@@ -93,6 +93,6 @@
"categoryName71": { "message": "Affiliate program" },
"categoryName72": { "message": "Appointment scheduling" },
"categoryName73": { "message": "Surveys" },
- "categoryName75": { "message": "A/B testing" },
+ "categoryName74": { "message": "A/B testing" },
"categoryName75": { "message": "Email" }
}
diff --git a/src/drivers/webextension/images/icons/Plataforma NEO.svg b/src/drivers/webextension/images/icons/Plataforma NEO.svg
deleted file mode 100644
index 5a112f6e0..000000000
--- a/src/drivers/webextension/images/icons/Plataforma NEO.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
\ No newline at end of file
diff --git a/src/drivers/webextension/images/icons/apostrophecms.svg b/src/drivers/webextension/images/icons/apostrophecms.svg
deleted file mode 100644
index 8ccef8d6c..000000000
--- a/src/drivers/webextension/images/icons/apostrophecms.svg
+++ /dev/null
@@ -1,33 +0,0 @@
-
diff --git a/src/technologies.json b/src/technologies.json
index 567f70069..9f4f4ad34 100644
--- a/src/technologies.json
+++ b/src/technologies.json
@@ -13368,7 +13368,7 @@
"SMARTSTORE.VISITOR": ""
},
"html": "",
- "icon": "smartstore.png",
+ "icon": "Smartstore.png",
"implies": "Microsoft ASP.NET",
"meta": {
"generator": "^SmartStore.NET (.+)$\\;version:\\1"