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/Ochanoko.svg b/src/drivers/webextension/images/icons/Ochanoko.svg new file mode 100644 index 000000000..2d878dd2c --- /dev/null +++ b/src/drivers/webextension/images/icons/Ochanoko.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/drivers/webextension/images/icons/Oxatis.svg b/src/drivers/webextension/images/icons/Oxatis.svg new file mode 100644 index 000000000..f999989fa --- /dev/null +++ b/src/drivers/webextension/images/icons/Oxatis.svg @@ -0,0 +1,9 @@ + + + + + + + + + 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/Profitwell.svg b/src/drivers/webextension/images/icons/Profitwell.svg new file mode 100644 index 000000000..379810e3d --- /dev/null +++ b/src/drivers/webextension/images/icons/Profitwell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/drivers/webextension/images/icons/ThriveCart.svg b/src/drivers/webextension/images/icons/ThriveCart.svg new file mode 100644 index 000000000..48852f2c3 --- /dev/null +++ b/src/drivers/webextension/images/icons/ThriveCart.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/drivers/webextension/images/icons/Visualsoft.svg b/src/drivers/webextension/images/icons/Visualsoft.svg new file mode 100644 index 000000000..5096b2fdb --- /dev/null +++ b/src/drivers/webextension/images/icons/Visualsoft.svg @@ -0,0 +1,9 @@ + + + + + + + + + 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/drivers/webextension/js/driver.js b/src/drivers/webextension/js/driver.js index e1eb75ab9..81557cddc 100644 --- a/src/drivers/webextension/js/driver.js +++ b/src/drivers/webextension/js/driver.js @@ -54,7 +54,7 @@ const Driver = { ), tabs: {}, robots: await getOption('robots', {}), - ads: {}, + ads: [], } chrome.browserAction.setBadgeBackgroundColor({ color: '#6B39BD' }, () => {}) diff --git a/src/technologies.json b/src/technologies.json index 139de686a..1e8a78b3b 100644 --- a/src/technologies.json +++ b/src/technologies.json @@ -806,7 +806,8 @@ 18 ], "cookies": { - "cookie_name": "adonis-session" + "adonis-session": "", + "adonis-session-values": "" }, "icon": "AdonisJS.png", "implies": "Node.js", @@ -2547,7 +2548,10 @@ "_bsap": "", "_bsap_serving_callback": "" }, - "scripts": "^https?://s\\d\\.buysellads\\.com/", + "scripts": [ + "^https?://s\\d\\.buysellads\\.com/", + "servedby-buysellads\\.com/monetization(?:\\.[\\w\\d]+)?\\.js" + ], "website": "http://buysellads.com" }, "CCV Shop": { @@ -6683,6 +6687,15 @@ }, "website": "https://www.hubspot.com" }, + "HubSpot Analytics": { + "cats": [ + 10 + ], + "description": "HubSpot is a marketing and sales software that helps companies attract visitors, convert leads, and close customers.", + "icon": "HubSpot.png", + "scripts": "js\\.hs-analytics\\.net/analytics", + "website": "https://www.hubspot.com/products/marketing/analytics" + }, "Hugo": { "cats": [ 57 @@ -9961,6 +9974,18 @@ }, "website": "https://en.oxid-esales.com/en/home.html" }, + "Ochanoko": { + "cats": [ + 6 + ], + "description": "Ochanoko is a ecommerce online shopping cart solutions, ecommerce web site hosting.", + "icon": "Ochanoko.svg", + "js": { + "ocnkProducts": "" + }, + "scripts": "ocnk-min\\.js", + "website": "https://www.ocnk.com" + }, "October CMS": { "cats": [ 1 @@ -10454,6 +10479,17 @@ "url": "/owa/auth/log(?:on|off)\\.aspx", "website": "http://help.outlook.com" }, + "Oxatis": { + "cats": [ + 6 + ], + "description": "Oxatis is a cloud-based ecommerce solution which enables users to create and manage their own online store websites.", + "icon": "Oxatis.svg", + "meta": { + "generator": "^Oxatis\\s\\(www\\.oxatis\\.com\\)$" + }, + "website": "https://www.oxatis.com/" + }, "Oxygen": { "cats": [ 51 @@ -11258,6 +11294,16 @@ "scripts": "prism\\.js", "website": "http://prismjs.com" }, + "Profitwell": { + "cats": [ + 10 + ], + "icon": "Profitwell.svg", + "scripts": [ + "public\\.profitwell\\.com/js/profitwell\\.js" + ], + "website": "https://www.profitwell.com/" + }, "Project Wonderful": { "cats": [ 36 @@ -13373,7 +13419,7 @@ "SMARTSTORE.VISITOR": "" }, "html": "", - "icon": "smartstore.png", + "icon": "Smartstore.png", "implies": "Microsoft ASP.NET", "meta": { "generator": "^SmartStore.NET (.+)$\\;version:\\1" @@ -14522,6 +14568,18 @@ "implies": "PHP", "website": "http://www.thinkphp.cn" }, + "ThriveCart": { + "cats": [ + 6 + ], + "description": "ThriveCart is a sales cart solution that lets you create checkout pages for your online products and services.", + "icon": "ThriveCart.svg", + "js": { + "ThriveCart": "" + }, + "scripts": "thrivecart\\.js", + "website": "https://thrivecart.com" + }, "Ticimax": { "cats": [ 6 @@ -15347,6 +15405,21 @@ "scripts": "secure\\.checkout\\.visa\\.com", "website": "https://checkout.visa.com" }, + "Visualsoft": { + "cats": [ + 6 + ], + "description": "Visualsoft is an ecommerce agency that delivers web design, development and marketing services to online retailers.", + "icon": "Visualsoft.svg", + "cookies": { + "vscommerce": "" + }, + "meta": { + "vs_status_checker_version": "\\d+", + "vsvatprices": "" + }, + "website": "https://www.visualsoft.co.uk/" + }, "Visual Website Optimizer": { "cats": [ 10