From da8e5fd342a57713dcc91d70bbb781f4a77719ec Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Fri, 5 Jul 2019 17:52:10 +0100 Subject: [PATCH 01/13] Add validator-query --- packages/validator-query/.gitignore | 1 + packages/validator-query/index.js | 41 +++++ .../validator-query/lib/AmpValidatorQuery.js | 143 +++++++++++++++ packages/validator-query/lib/rules/browser.js | 26 +++ packages/validator-query/lib/rules/index.js | 26 +++ packages/validator-query/lib/rules/node.js | 72 ++++++++ packages/validator-query/package.json | 27 +++ .../spec/AmpValidatorQuerySpec.js | 171 ++++++++++++++++++ 8 files changed, 507 insertions(+) create mode 100644 packages/validator-query/.gitignore create mode 100644 packages/validator-query/index.js create mode 100644 packages/validator-query/lib/AmpValidatorQuery.js create mode 100644 packages/validator-query/lib/rules/browser.js create mode 100644 packages/validator-query/lib/rules/index.js create mode 100644 packages/validator-query/lib/rules/node.js create mode 100644 packages/validator-query/package.json create mode 100644 packages/validator-query/spec/AmpValidatorQuerySpec.js diff --git a/packages/validator-query/.gitignore b/packages/validator-query/.gitignore new file mode 100644 index 000000000..6b37003e2 --- /dev/null +++ b/packages/validator-query/.gitignore @@ -0,0 +1 @@ +validator.json diff --git a/packages/validator-query/index.js b/packages/validator-query/index.js new file mode 100644 index 000000000..854a96b9a --- /dev/null +++ b/packages/validator-query/index.js @@ -0,0 +1,41 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const loadRules = require('./lib/rules'); +const AmpValidatorQuery = require('./lib/AmpValidatorQuery'); + +let cached = null; + +async function load(opt) { + opt = opt || {}; + if (!opt.noCache && cached) { + return cached; + } + + let rules = opt.rules; + delete opt.rules; + + if (!rules) { + rules = await loadRules(opt); + } + + cached = new AmpValidatorQuery(rules); + return cached; +} + +module.exports = load; diff --git a/packages/validator-query/lib/AmpValidatorQuery.js b/packages/validator-query/lib/AmpValidatorQuery.js new file mode 100644 index 000000000..87ef983d4 --- /dev/null +++ b/packages/validator-query/lib/AmpValidatorQuery.js @@ -0,0 +1,143 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class AmpValidatorQuery { + constructor(rules) { + this.initRules_(rules); + } + + getTagsForFormat(format, transformed) { + format = format.toLowerCase(); + return this.tags + .filter( + tag => + tag.htmlFormat.includes(format.toUpperCase()) && + this.checkEntityFormat_(tag, format) && + this.checkEntityTransformed_(tag, transformed) + ) + .map(tag => { + tag = Object.assign({}, tag); + tag.attrs = tag.attrs.filter( + attr => + this.checkEntityFormat_(attr, format) && + this.checkEntityTransformed_(attr, transformed) + ); + return tag; + }); + } + + getExtensionsForFormat(format) { + format = format.toUpperCase(); + return this.extensions + .filter(extension => extension.htmlFormat.includes(format)) + .reduce((result, extension) => { + result[extension.name] = Object.assign({}, extension); + delete result[extension.name].name; + return result; + }, {}); + } + + checkEntityTransformed_(entity, transformed) { + if (transformed) { + return this.checkEntityFormat_(entity, 'transformed'); + } + if (entity.enabledBy && entity.enabledBy.includes('transformed')) { + return false; + } + if (entity.disabledBy && !entity.disabledBy.includes('transformed')) { + return false; + } + return true; + } + + checkEntityFormat_(entity, format) { + format = format.toLowerCase(); + if (entity.enabledBy && !entity.enabledBy.includes(format)) { + return false; + } + if (entity.disabledBy && entity.disabledBy.includes(format)) { + return false; + } + return true; + } + + initRules_(rules) { + this.initErrors_(rules); + this.initAttrLists_(rules); + this.initTags_(rules); + this.initExtensions_(rules); + } + + initErrors_(rules) { + this.errors = {}; + for (const errorFormat of rules.errorFormats) { + const error = this.errors[errorFormat.code] || {}; + error.format = errorFormat.format; + this.errors[errorFormat.code] = error; + } + for (const errorSpecificity of rules.errorSpecificity) { + const error = this.errors[errorSpecificity.code] || {}; + error.specificity = errorSpecificity.specificity; + this.errors[errorSpecificity.code] = error; + } + } + + initAttrLists_(rules) { + this.attrLists = {}; + this.specialAttrLists = {}; + for (const { name, attrs } of rules.attrLists) { + if (name.startsWith('$')) { + this.specialAttrLists[name] = attrs; + } else { + this.attrLists[name] = attrs; + } + } + this.specialAttrLists.$AMP_LAYOUT_ATTRS.forEach( + attr => (attr.layout = true) + ); + this.specialAttrLists.$GLOBAL_ATTRS.forEach(attr => (attr.global = true)); + } + + initTags_(rules) { + this.tags = rules.tags + .filter(tag => !tag.extensionSpec) + .map(tag => { + tag.attrs = tag.attrs || []; + if (tag.attrLists) { + for (const attrList of tag.attrLists) { + tag.attrs.push(...this.attrLists[attrList]); + } + delete tag.attrLists; + } + if (tag.ampLayout) { + tag.attrs.push(...this.specialAttrLists.$AMP_LAYOUT_ATTRS); + } + tag.attrs.push(...this.specialAttrLists.$GLOBAL_ATTRS); + + return tag; + }); + } + + initExtensions_(rules) { + this.extensions = rules.tags + .filter(tag => tag.extensionSpec) + .map(tag => + Object.assign({}, tag.extensionSpec, { htmlFormat: tag.htmlFormat }) + ); + } +} + +module.exports = AmpValidatorQuery; diff --git a/packages/validator-query/lib/rules/browser.js b/packages/validator-query/lib/rules/browser.js new file mode 100644 index 000000000..783a6b06e --- /dev/null +++ b/packages/validator-query/lib/rules/browser.js @@ -0,0 +1,26 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +async function load({ source, url }) { + if (source === 'local') { + throw new Error('Local loading is not supported in browsers'); + } + + const req = await fetch(url); + return req.json(); +} + +module.exports = load; diff --git a/packages/validator-query/lib/rules/index.js b/packages/validator-query/lib/rules/index.js new file mode 100644 index 000000000..4496468b2 --- /dev/null +++ b/packages/validator-query/lib/rules/index.js @@ -0,0 +1,26 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const VALIDATOR_RULES_URL = 'https://cdn.ampproject.org/v0/validator.json'; + +module.exports = function(opt) { + opt = opt || {}; + opt.url = opt.url || VALIDATOR_RULES_URL; + if (typeof process !== 'undefined') { + return require('./node')(opt); + } + return require('./browser')(opt); +}; diff --git a/packages/validator-query/lib/rules/node.js b/packages/validator-query/lib/rules/node.js new file mode 100644 index 000000000..cb295a2db --- /dev/null +++ b/packages/validator-query/lib/rules/node.js @@ -0,0 +1,72 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const https = require('https'); +const { promisify } = require('util'); +const fs = require('fs'); +const path = require('path'); +const readFileAsync = promisify(fs.readFile); + +const VALIDATOR_RULES_LOCAL = path.join(__dirname, '../../validator.json'); + +function fetch(url) { + return new Promise((resolve, reject) => { + https + .get(url, res => { + if (res.statusCode != 200) { + reject(new Error(`Got status code ${res.statusCode}`)); + return; + } + + let data = ''; + res.on('data', buffer => { + data += buffer; + }); + res.on('end', () => { + resolve(data); + }); + }) + .on('error', err => { + reject(err); + }); + }); +} + +async function loadRemote(url) { + const data = await fetch(url); + return JSON.parse(data); +} + +async function loadLocal() { + const data = await readFileAsync(VALIDATOR_RULES_LOCAL); + return JSON.parse(data); +} + +async function load({ source, url }) { + switch (source) { + case 'local': + return loadLocal(); + case 'remote': + return loadRemote(url); + default: + if (fs.existsSync(VALIDATOR_RULES_LOCAL)) { + return loadLocal(); + } + return loadRemote(); + } +} + +module.exports = load; diff --git a/packages/validator-query/package.json b/packages/validator-query/package.json new file mode 100644 index 000000000..71f4fdbc4 --- /dev/null +++ b/packages/validator-query/package.json @@ -0,0 +1,27 @@ +{ + "name": "@ampproject/validator-query", + "version": "1.0.0-beta.3", + "description": "A library that helps query AMP Validator rules", + "main": "index.js", + "keywords": [ + "amp" + ], + "scripts": { + "fetchRules": "curl https://cdn.ampproject.org/v0/validator.json --output validator.json" + }, + "files": [ + "index.js", + "lib" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/ampproject/amp-toolbox.git" + }, + "author": "AMPHTML Team", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/ampproject/amp-toolbox/issues" + }, + "homepage": "https://github.com/ampproject/amp-toolbox/tree/master/packages/validator-query", + "gitHead": "cb496f081c871a25dc73113ae1e1fccfb59b2732" +} diff --git a/packages/validator-query/spec/AmpValidatorQuerySpec.js b/packages/validator-query/spec/AmpValidatorQuerySpec.js new file mode 100644 index 000000000..274561740 --- /dev/null +++ b/packages/validator-query/spec/AmpValidatorQuerySpec.js @@ -0,0 +1,171 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const AmpValidatorQuery = require('../lib/AmpValidatorQuery'); + +describe('AmpValidatorQuery', () => { + it('Loads errors', () => { + const query = makeQuery({ + errorFormats: [ + { + code: 'TEST', + format: '%s error' + } + ], + errorSpecificity: [ + { + code: 'TEST', + specificity: 1 + } + ] + }); + expect(query.errors).toEqual({ + TEST: { + format: '%s error', + specificity: 1 + } + }); + }); + + it('Loads extensions', () => { + const query = makeQuery({ + tags: [ + { + extensionSpec: { + name: 'amp-some-component', + version: ['0.1', 'latest'] + }, + htmlFormat: ['AMP'], + tagName: 'SCRIPT' + } + ] + }); + expect(query.tags).toEqual([]); + expect(query.extensions).toEqual([ + { + name: 'amp-some-component', + version: ['0.1', 'latest'], + htmlFormat: ['AMP'] + } + ]); + expect(query.getExtensionsForFormat('AMP')).toEqual({ + 'amp-some-component': { + version: ['0.1', 'latest'], + htmlFormat: ['AMP'] + } + }); + expect(query.getExtensionsForFormat('AMP4EMAIL')).toEqual({}); + }); + + it('Loads tags', () => { + const query = makeQuery({ + attrLists: [ + { + name: '$GLOBAL_ATTRS', + attrs: [{ name: 'global' }] + }, + { + name: '$AMP_LAYOUT_ATTRS', + attrs: [{ name: 'layoutattr' }] + }, + { + name: 'some-list', + attrs: [{ name: 'test' }] + } + ], + tags: [ + { + htmlFormat: ['AMP', 'AMP4EMAIL'], + attrs: [{ name: 'align' }], + attrLists: ['some-list'], + tagName: 'DIV' + }, + { + htmlFormat: ['AMP', 'AMP4EMAIL'], + attrs: [{ name: 'align' }], + disabledBy: ['transformed'], + ampLayout: { + supportedLayouts: ['FIXED', 'FIXED_HEIGHT'] + }, + tagName: 'AMP-IMG' + } + ] + }); + + const tags = [ + { + htmlFormat: ['AMP', 'AMP4EMAIL'], + attrs: [ + { name: 'align' }, + { name: 'test' }, + { + name: 'global', + global: true + } + ], + tagName: 'DIV' + }, + { + htmlFormat: ['AMP', 'AMP4EMAIL'], + attrs: [ + { name: 'align' }, + { + name: 'layoutattr', + layout: true + }, + { + name: 'global', + global: true + } + ], + disabledBy: ['transformed'], + ampLayout: { + supportedLayouts: ['FIXED', 'FIXED_HEIGHT'] + }, + tagName: 'AMP-IMG' + } + ]; + expect(query.tags).toEqual(tags); + expect(query.extensions).toEqual([]); + expect(query.getTagsForFormat('AMP4EMAIL')).toEqual(tags); + expect(query.getTagsForFormat('AMP', true)).toEqual([tags[0]]); + }); +}); + +function makeQuery(rules) { + return new AmpValidatorQuery( + Object.assign( + { + errorFormats: [], + errorSpecificity: [], + attrLists: [ + { + name: '$AMP_LAYOUT_ATTRS', + attrs: [] + }, + { + name: '$GLOBAL_ATTRS', + attrs: [] + } + ], + tags: [] + }, + rules + ) + ); +} From c7ab5971e75ffe8e1d9e2ce25f0ecefb096d52c5 Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Fri, 5 Jul 2019 17:52:44 +0100 Subject: [PATCH 02/13] lint --- .../validator-query/lib/AmpValidatorQuery.js | 78 ++++++------- packages/validator-query/lib/rules/browser.js | 2 +- packages/validator-query/lib/rules/node.js | 34 +++--- .../spec/AmpValidatorQuerySpec.js | 108 +++++++++--------- 4 files changed, 111 insertions(+), 111 deletions(-) diff --git a/packages/validator-query/lib/AmpValidatorQuery.js b/packages/validator-query/lib/AmpValidatorQuery.js index 87ef983d4..e05cd2f40 100644 --- a/packages/validator-query/lib/AmpValidatorQuery.js +++ b/packages/validator-query/lib/AmpValidatorQuery.js @@ -22,32 +22,32 @@ class AmpValidatorQuery { getTagsForFormat(format, transformed) { format = format.toLowerCase(); return this.tags - .filter( - tag => - tag.htmlFormat.includes(format.toUpperCase()) && + .filter( + (tag) => + tag.htmlFormat.includes(format.toUpperCase()) && this.checkEntityFormat_(tag, format) && this.checkEntityTransformed_(tag, transformed) - ) - .map(tag => { - tag = Object.assign({}, tag); - tag.attrs = tag.attrs.filter( - attr => - this.checkEntityFormat_(attr, format) && + ) + .map((tag) => { + tag = Object.assign({}, tag); + tag.attrs = tag.attrs.filter( + (attr) => + this.checkEntityFormat_(attr, format) && this.checkEntityTransformed_(attr, transformed) - ); - return tag; - }); + ); + return tag; + }); } getExtensionsForFormat(format) { format = format.toUpperCase(); return this.extensions - .filter(extension => extension.htmlFormat.includes(format)) - .reduce((result, extension) => { - result[extension.name] = Object.assign({}, extension); - delete result[extension.name].name; - return result; - }, {}); + .filter((extension) => extension.htmlFormat.includes(format)) + .reduce((result, extension) => { + result[extension.name] = Object.assign({}, extension); + delete result[extension.name].name; + return result; + }, {}); } checkEntityTransformed_(entity, transformed) { @@ -98,7 +98,7 @@ class AmpValidatorQuery { initAttrLists_(rules) { this.attrLists = {}; this.specialAttrLists = {}; - for (const { name, attrs } of rules.attrLists) { + for (const {name, attrs} of rules.attrLists) { if (name.startsWith('$')) { this.specialAttrLists[name] = attrs; } else { @@ -106,37 +106,37 @@ class AmpValidatorQuery { } } this.specialAttrLists.$AMP_LAYOUT_ATTRS.forEach( - attr => (attr.layout = true) + (attr) => (attr.layout = true) ); - this.specialAttrLists.$GLOBAL_ATTRS.forEach(attr => (attr.global = true)); + this.specialAttrLists.$GLOBAL_ATTRS.forEach((attr) => (attr.global = true)); } initTags_(rules) { this.tags = rules.tags - .filter(tag => !tag.extensionSpec) - .map(tag => { - tag.attrs = tag.attrs || []; - if (tag.attrLists) { - for (const attrList of tag.attrLists) { - tag.attrs.push(...this.attrLists[attrList]); + .filter((tag) => !tag.extensionSpec) + .map((tag) => { + tag.attrs = tag.attrs || []; + if (tag.attrLists) { + for (const attrList of tag.attrLists) { + tag.attrs.push(...this.attrLists[attrList]); + } + delete tag.attrLists; + } + if (tag.ampLayout) { + tag.attrs.push(...this.specialAttrLists.$AMP_LAYOUT_ATTRS); } - delete tag.attrLists; - } - if (tag.ampLayout) { - tag.attrs.push(...this.specialAttrLists.$AMP_LAYOUT_ATTRS); - } - tag.attrs.push(...this.specialAttrLists.$GLOBAL_ATTRS); + tag.attrs.push(...this.specialAttrLists.$GLOBAL_ATTRS); - return tag; - }); + return tag; + }); } initExtensions_(rules) { this.extensions = rules.tags - .filter(tag => tag.extensionSpec) - .map(tag => - Object.assign({}, tag.extensionSpec, { htmlFormat: tag.htmlFormat }) - ); + .filter((tag) => tag.extensionSpec) + .map((tag) => + Object.assign({}, tag.extensionSpec, {htmlFormat: tag.htmlFormat}) + ); } } diff --git a/packages/validator-query/lib/rules/browser.js b/packages/validator-query/lib/rules/browser.js index 783a6b06e..c1325e862 100644 --- a/packages/validator-query/lib/rules/browser.js +++ b/packages/validator-query/lib/rules/browser.js @@ -14,7 +14,7 @@ * limitations under the License. */ -async function load({ source, url }) { +async function load({source, url}) { if (source === 'local') { throw new Error('Local loading is not supported in browsers'); } diff --git a/packages/validator-query/lib/rules/node.js b/packages/validator-query/lib/rules/node.js index cb295a2db..4154d3cd8 100644 --- a/packages/validator-query/lib/rules/node.js +++ b/packages/validator-query/lib/rules/node.js @@ -15,7 +15,7 @@ */ const https = require('https'); -const { promisify } = require('util'); +const {promisify} = require('util'); const fs = require('fs'); const path = require('path'); const readFileAsync = promisify(fs.readFile); @@ -25,23 +25,23 @@ const VALIDATOR_RULES_LOCAL = path.join(__dirname, '../../validator.json'); function fetch(url) { return new Promise((resolve, reject) => { https - .get(url, res => { - if (res.statusCode != 200) { - reject(new Error(`Got status code ${res.statusCode}`)); - return; - } + .get(url, (res) => { + if (res.statusCode != 200) { + reject(new Error(`Got status code ${res.statusCode}`)); + return; + } - let data = ''; - res.on('data', buffer => { - data += buffer; + let data = ''; + res.on('data', (buffer) => { + data += buffer; + }); + res.on('end', () => { + resolve(data); + }); + }) + .on('error', (err) => { + reject(err); }); - res.on('end', () => { - resolve(data); - }); - }) - .on('error', err => { - reject(err); - }); }); } @@ -55,7 +55,7 @@ async function loadLocal() { return JSON.parse(data); } -async function load({ source, url }) { +async function load({source, url}) { switch (source) { case 'local': return loadLocal(); diff --git a/packages/validator-query/spec/AmpValidatorQuerySpec.js b/packages/validator-query/spec/AmpValidatorQuerySpec.js index 274561740..0cc087a44 100644 --- a/packages/validator-query/spec/AmpValidatorQuerySpec.js +++ b/packages/validator-query/spec/AmpValidatorQuerySpec.js @@ -24,21 +24,21 @@ describe('AmpValidatorQuery', () => { errorFormats: [ { code: 'TEST', - format: '%s error' - } + format: '%s error', + }, ], errorSpecificity: [ { code: 'TEST', - specificity: 1 - } - ] + specificity: 1, + }, + ], }); expect(query.errors).toEqual({ TEST: { format: '%s error', - specificity: 1 - } + specificity: 1, + }, }); }); @@ -48,26 +48,26 @@ describe('AmpValidatorQuery', () => { { extensionSpec: { name: 'amp-some-component', - version: ['0.1', 'latest'] + version: ['0.1', 'latest'], }, htmlFormat: ['AMP'], - tagName: 'SCRIPT' - } - ] + tagName: 'SCRIPT', + }, + ], }); expect(query.tags).toEqual([]); expect(query.extensions).toEqual([ { name: 'amp-some-component', version: ['0.1', 'latest'], - htmlFormat: ['AMP'] - } + htmlFormat: ['AMP'], + }, ]); expect(query.getExtensionsForFormat('AMP')).toEqual({ 'amp-some-component': { version: ['0.1', 'latest'], - htmlFormat: ['AMP'] - } + htmlFormat: ['AMP'], + }, }); expect(query.getExtensionsForFormat('AMP4EMAIL')).toEqual({}); }); @@ -77,68 +77,68 @@ describe('AmpValidatorQuery', () => { attrLists: [ { name: '$GLOBAL_ATTRS', - attrs: [{ name: 'global' }] + attrs: [{name: 'global'}], }, { name: '$AMP_LAYOUT_ATTRS', - attrs: [{ name: 'layoutattr' }] + attrs: [{name: 'layoutattr'}], }, { name: 'some-list', - attrs: [{ name: 'test' }] - } + attrs: [{name: 'test'}], + }, ], tags: [ { htmlFormat: ['AMP', 'AMP4EMAIL'], - attrs: [{ name: 'align' }], + attrs: [{name: 'align'}], attrLists: ['some-list'], - tagName: 'DIV' + tagName: 'DIV', }, { htmlFormat: ['AMP', 'AMP4EMAIL'], - attrs: [{ name: 'align' }], + attrs: [{name: 'align'}], disabledBy: ['transformed'], ampLayout: { - supportedLayouts: ['FIXED', 'FIXED_HEIGHT'] + supportedLayouts: ['FIXED', 'FIXED_HEIGHT'], }, - tagName: 'AMP-IMG' - } - ] + tagName: 'AMP-IMG', + }, + ], }); const tags = [ { htmlFormat: ['AMP', 'AMP4EMAIL'], attrs: [ - { name: 'align' }, - { name: 'test' }, + {name: 'align'}, + {name: 'test'}, { name: 'global', - global: true - } + global: true, + }, ], - tagName: 'DIV' + tagName: 'DIV', }, { htmlFormat: ['AMP', 'AMP4EMAIL'], attrs: [ - { name: 'align' }, + {name: 'align'}, { name: 'layoutattr', - layout: true + layout: true, }, { name: 'global', - global: true - } + global: true, + }, ], disabledBy: ['transformed'], ampLayout: { - supportedLayouts: ['FIXED', 'FIXED_HEIGHT'] + supportedLayouts: ['FIXED', 'FIXED_HEIGHT'], }, - tagName: 'AMP-IMG' - } + tagName: 'AMP-IMG', + }, ]; expect(query.tags).toEqual(tags); expect(query.extensions).toEqual([]); @@ -149,23 +149,23 @@ describe('AmpValidatorQuery', () => { function makeQuery(rules) { return new AmpValidatorQuery( - Object.assign( - { - errorFormats: [], - errorSpecificity: [], - attrLists: [ + Object.assign( { - name: '$AMP_LAYOUT_ATTRS', - attrs: [] + errorFormats: [], + errorSpecificity: [], + attrLists: [ + { + name: '$AMP_LAYOUT_ATTRS', + attrs: [], + }, + { + name: '$GLOBAL_ATTRS', + attrs: [], + }, + ], + tags: [], }, - { - name: '$GLOBAL_ATTRS', - attrs: [] - } - ], - tags: [] - }, - rules - ) + rules + ) ); } From f5214a81d97a13317d7f85d3ed0485d2b8253b24 Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Fri, 5 Jul 2019 18:14:13 +0100 Subject: [PATCH 03/13] add readme --- README.md | 3 +- packages/validator-query/README.md | 57 ++++++++++++++++++++++++++++++ packages/validator-query/index.js | 2 +- 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 packages/validator-query/README.md diff --git a/README.md b/README.md index ae20ca36e..2676fed68 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ A collection of AMP tools making it easier to publish and host AMP pages. The fo - **[amp-runtime-version](./packages/runtime-version):** a javascript library for querying the current AMP runtime version. - **[amp-update-cache](./packages/update-cache):** a javascript library for updating AMP documents in AMP Caches. - **[amp-update-linter](./packages/linter):** a javascript library for linting AMP documents (includes CLI mode). +- **[amp-validator-query](./packages/validator-query):** a javascript library for querying AMP validator rules. ## Development @@ -43,7 +44,7 @@ git clone https://github.com/your-fork/amp-toolbox.git # step into local repo cd amp-toolbox -# install dependencies +# install dependencies npm install # run tests diff --git a/packages/validator-query/README.md b/packages/validator-query/README.md new file mode 100644 index 000000000..e1dc2595a --- /dev/null +++ b/packages/validator-query/README.md @@ -0,0 +1,57 @@ +# AMP-Toolbox Validator Query + +Makes it easier to query published AMP Validator rules and extract information +about required markup and attributes for all AMP formats. + +## Usage + +Install via: + +``` +$ npm install @ampproject/toolbox-validator-query@canary +``` + +### Including the Module + +#### ES Module (Browser) + +```javascript +import { load } from 'amp-toolbox-validator-query'; +``` + +#### CommonJs (Node) + +```javascript +const { load } = require('amp-toolbox-validator-query'); +``` + +### Using the module + +```javascript + // Loads the validator rules remotely with default options + const query = load(); + + // Get all tag names used in AMP for Email + const names = query.getTagsForFormat('AMP4EMAIL').map(tag => tag.tagName); +``` + +### Options + +`load` optionally accepts an options object allowing you to customize its +behaviour. + +The following options are supported: + + * `noCache`: true to always fetch latest rules (by default, subsequent calls to `load` reuse the same result). + * `rules`: object to use locally specified rules instead of fetching them from the AMP CDN. + * `url`: override the URL where validator rules are fetched from. + * `source`: one of `'local'` (load rules from local file named "validator.json"), `'remote'` (fetch rules from CDN) or `'auto'` which is the default (tries looking for the local file first, then tries to fetch from CDN). + +Example: + +``` +load({ + noCache: true, + source: 'remote' +}); +``` diff --git a/packages/validator-query/index.js b/packages/validator-query/index.js index 854a96b9a..51c5db3f8 100644 --- a/packages/validator-query/index.js +++ b/packages/validator-query/index.js @@ -38,4 +38,4 @@ async function load(opt) { return cached; } -module.exports = load; +module.exports = { load }; From a4cf89e574c2fd10c247c04977e48766e3c7955e Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Mon, 8 Jul 2019 13:18:01 +0100 Subject: [PATCH 04/13] fix lint and package name --- packages/validator-query/README.md | 4 ++-- packages/validator-query/index.js | 2 +- packages/validator-query/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/validator-query/README.md b/packages/validator-query/README.md index e1dc2595a..70377cc5f 100644 --- a/packages/validator-query/README.md +++ b/packages/validator-query/README.md @@ -16,13 +16,13 @@ $ npm install @ampproject/toolbox-validator-query@canary #### ES Module (Browser) ```javascript -import { load } from 'amp-toolbox-validator-query'; +import { load } from '@ampproject/toolbox-validator-query'; ``` #### CommonJs (Node) ```javascript -const { load } = require('amp-toolbox-validator-query'); +const { load } = require('@ampproject/toolbox-validator-query'); ``` ### Using the module diff --git a/packages/validator-query/index.js b/packages/validator-query/index.js index 51c5db3f8..50da9b668 100644 --- a/packages/validator-query/index.js +++ b/packages/validator-query/index.js @@ -38,4 +38,4 @@ async function load(opt) { return cached; } -module.exports = { load }; +module.exports = {load}; diff --git a/packages/validator-query/package.json b/packages/validator-query/package.json index 71f4fdbc4..73ee77235 100644 --- a/packages/validator-query/package.json +++ b/packages/validator-query/package.json @@ -1,5 +1,5 @@ { - "name": "@ampproject/validator-query", + "name": "@ampproject/toolbox-validator-query", "version": "1.0.0-beta.3", "description": "A library that helps query AMP Validator rules", "main": "index.js", From 66757f0c9fa75211345f50c09501757a8d9df579 Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Mon, 8 Jul 2019 16:41:19 +0100 Subject: [PATCH 05/13] address comments --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c8eb39f5..27acad6af 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "@ampproject/toolbox-optimizer": "file:packages/optimizer", "@ampproject/toolbox-optimizer-express": "file:packages/optimizer-express", "@ampproject/toolbox-runtime-version": "file:packages/runtime-version", - "@ampproject/toolbox-update-cache": "file:packages/update-cache" + "@ampproject/toolbox-update-cache": "file:packages/update-cache", + "@ampproject/validator-query": "file:packages/validator-query" } } From e02708d2aec9c1bd9770acb47f02a5dfaddf0bd9 Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Mon, 8 Jul 2019 17:03:32 +0100 Subject: [PATCH 06/13] use oneBehindFetch in rules --- packages/validator-query/lib/rules.js | 70 ++++++++++++++++++ packages/validator-query/lib/rules/browser.js | 26 ------- packages/validator-query/lib/rules/index.js | 26 ------- packages/validator-query/lib/rules/node.js | 72 ------------------- packages/validator-query/package.json | 3 + 5 files changed, 73 insertions(+), 124 deletions(-) create mode 100644 packages/validator-query/lib/rules.js delete mode 100644 packages/validator-query/lib/rules/browser.js delete mode 100644 packages/validator-query/lib/rules/index.js delete mode 100644 packages/validator-query/lib/rules/node.js diff --git a/packages/validator-query/lib/rules.js b/packages/validator-query/lib/rules.js new file mode 100644 index 000000000..893be502b --- /dev/null +++ b/packages/validator-query/lib/rules.js @@ -0,0 +1,70 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const {oneBehindFetch} = require('@ampproject/toolbox-core'); + +const VALIDATOR_RULES_URL = 'https://cdn.ampproject.org/v0/validator.json'; +const VALIDATOR_RULES_LOCAL = '../../validator.json'; + +const isNodeJs = typeof process !== 'undefined'; + +async function loadRemote(url) { + const data = await oneBehindFetch(url); + return req.json(); +} + +async function loadLocal() { + if (!isNodeJs) { + throw new Error('Local loading is not supported in browsers'); + } + + const {promisify} = require('util'); + const readFileAsync = promisify(require('fs').readFile); + + const data = await readFileAsync(getLocalPath()); + return JSON.parse(data); +} + +async function loadDefault(url) { + if (!isNodeJs) { + return loadRemote(url); + } + + const {existsSync} = require('fs'); + + if (existsSync(getLocalPath())) { + return loadLocal(); + } + return loadRemote(url); +} + +function getLocalPath() { + const path = require('path'); + return path.join(__dirname, VALIDATOR_RULES_LOCAL); +} + +async function load({source, url}) { + switch (source) { + case 'local': + return loadLocal(); + case 'remote': + return loadRemote(url); + default: + return loadDefault(url); + } +} + +module.exports = load; diff --git a/packages/validator-query/lib/rules/browser.js b/packages/validator-query/lib/rules/browser.js deleted file mode 100644 index c1325e862..000000000 --- a/packages/validator-query/lib/rules/browser.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 2019 The AMP HTML Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -async function load({source, url}) { - if (source === 'local') { - throw new Error('Local loading is not supported in browsers'); - } - - const req = await fetch(url); - return req.json(); -} - -module.exports = load; diff --git a/packages/validator-query/lib/rules/index.js b/packages/validator-query/lib/rules/index.js deleted file mode 100644 index 4496468b2..000000000 --- a/packages/validator-query/lib/rules/index.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 2019 The AMP HTML Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const VALIDATOR_RULES_URL = 'https://cdn.ampproject.org/v0/validator.json'; - -module.exports = function(opt) { - opt = opt || {}; - opt.url = opt.url || VALIDATOR_RULES_URL; - if (typeof process !== 'undefined') { - return require('./node')(opt); - } - return require('./browser')(opt); -}; diff --git a/packages/validator-query/lib/rules/node.js b/packages/validator-query/lib/rules/node.js deleted file mode 100644 index 4154d3cd8..000000000 --- a/packages/validator-query/lib/rules/node.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright 2019 The AMP HTML Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const https = require('https'); -const {promisify} = require('util'); -const fs = require('fs'); -const path = require('path'); -const readFileAsync = promisify(fs.readFile); - -const VALIDATOR_RULES_LOCAL = path.join(__dirname, '../../validator.json'); - -function fetch(url) { - return new Promise((resolve, reject) => { - https - .get(url, (res) => { - if (res.statusCode != 200) { - reject(new Error(`Got status code ${res.statusCode}`)); - return; - } - - let data = ''; - res.on('data', (buffer) => { - data += buffer; - }); - res.on('end', () => { - resolve(data); - }); - }) - .on('error', (err) => { - reject(err); - }); - }); -} - -async function loadRemote(url) { - const data = await fetch(url); - return JSON.parse(data); -} - -async function loadLocal() { - const data = await readFileAsync(VALIDATOR_RULES_LOCAL); - return JSON.parse(data); -} - -async function load({source, url}) { - switch (source) { - case 'local': - return loadLocal(); - case 'remote': - return loadRemote(url); - default: - if (fs.existsSync(VALIDATOR_RULES_LOCAL)) { - return loadLocal(); - } - return loadRemote(); - } -} - -module.exports = load; diff --git a/packages/validator-query/package.json b/packages/validator-query/package.json index 73ee77235..268c8d91b 100644 --- a/packages/validator-query/package.json +++ b/packages/validator-query/package.json @@ -22,6 +22,9 @@ "bugs": { "url": "https://github.com/ampproject/amp-toolbox/issues" }, + "dependencies": { + "@ampproject/toolbox-core": "^1.0.0-beta.3" + }, "homepage": "https://github.com/ampproject/amp-toolbox/tree/master/packages/validator-query", "gitHead": "cb496f081c871a25dc73113ae1e1fccfb59b2732" } From 84bc72d63bbe45233c951b4f2a789bedec1370a1 Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Mon, 8 Jul 2019 17:18:49 +0100 Subject: [PATCH 07/13] rename from query to rules --- README.md | 2 +- package.json | 2 +- .../.gitignore | 0 .../README.md | 16 +++++----- .../index.js | 6 ++-- .../lib/AmpValidatorRules.js} | 4 +-- .../lib/loadRules.js} | 7 ++-- .../package.json | 4 +-- .../spec/AmpValidatorRulesSpec.js} | 32 +++++++++---------- 9 files changed, 37 insertions(+), 36 deletions(-) rename packages/{validator-query => validator-rules}/.gitignore (100%) rename packages/{validator-query => validator-rules}/README.md (69%) rename packages/{validator-query => validator-rules}/index.js (85%) rename packages/{validator-query/lib/AmpValidatorQuery.js => validator-rules/lib/AmpValidatorRules.js} (98%) rename packages/{validator-query/lib/rules.js => validator-rules/lib/loadRules.js} (92%) rename packages/{validator-query => validator-rules}/package.json (90%) rename packages/{validator-query/spec/AmpValidatorQuerySpec.js => validator-rules/spec/AmpValidatorRulesSpec.js} (83%) diff --git a/README.md b/README.md index 2676fed68..4059bee0f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ A collection of AMP tools making it easier to publish and host AMP pages. The fo - **[amp-runtime-version](./packages/runtime-version):** a javascript library for querying the current AMP runtime version. - **[amp-update-cache](./packages/update-cache):** a javascript library for updating AMP documents in AMP Caches. - **[amp-update-linter](./packages/linter):** a javascript library for linting AMP documents (includes CLI mode). -- **[amp-validator-query](./packages/validator-query):** a javascript library for querying AMP validator rules. +- **[amp-validator-rules](./packages/validator-rules):** a javascript library for querying AMP validator rules. ## Development diff --git a/package.json b/package.json index 27acad6af..bbc6e06f4 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,6 @@ "@ampproject/toolbox-optimizer-express": "file:packages/optimizer-express", "@ampproject/toolbox-runtime-version": "file:packages/runtime-version", "@ampproject/toolbox-update-cache": "file:packages/update-cache", - "@ampproject/validator-query": "file:packages/validator-query" + "@ampproject/toolbox-validator-rules": "file:packages/validator-rules" } } diff --git a/packages/validator-query/.gitignore b/packages/validator-rules/.gitignore similarity index 100% rename from packages/validator-query/.gitignore rename to packages/validator-rules/.gitignore diff --git a/packages/validator-query/README.md b/packages/validator-rules/README.md similarity index 69% rename from packages/validator-query/README.md rename to packages/validator-rules/README.md index 70377cc5f..94ad14700 100644 --- a/packages/validator-query/README.md +++ b/packages/validator-rules/README.md @@ -1,14 +1,14 @@ -# AMP-Toolbox Validator Query +# AMP-Toolbox Validator Rules -Makes it easier to query published AMP Validator rules and extract information -about required markup and attributes for all AMP formats. +Queries published AMP Validator rules and extracts information about required +markup and attributes for all AMP formats. ## Usage Install via: ``` -$ npm install @ampproject/toolbox-validator-query@canary +$ npm install @ampproject/toolbox-validator-rules@canary ``` ### Including the Module @@ -16,23 +16,23 @@ $ npm install @ampproject/toolbox-validator-query@canary #### ES Module (Browser) ```javascript -import { load } from '@ampproject/toolbox-validator-query'; +import { load } from '@ampproject/toolbox-validator-rules'; ``` #### CommonJs (Node) ```javascript -const { load } = require('@ampproject/toolbox-validator-query'); +const { load } = require('@ampproject/toolbox-validator-rules'); ``` ### Using the module ```javascript // Loads the validator rules remotely with default options - const query = load(); + const rules = load(); // Get all tag names used in AMP for Email - const names = query.getTagsForFormat('AMP4EMAIL').map(tag => tag.tagName); + const names = rules.getTagsForFormat('AMP4EMAIL').map(tag => tag.tagName); ``` ### Options diff --git a/packages/validator-query/index.js b/packages/validator-rules/index.js similarity index 85% rename from packages/validator-query/index.js rename to packages/validator-rules/index.js index 50da9b668..761955c84 100644 --- a/packages/validator-query/index.js +++ b/packages/validator-rules/index.js @@ -16,8 +16,8 @@ 'use strict'; -const loadRules = require('./lib/rules'); -const AmpValidatorQuery = require('./lib/AmpValidatorQuery'); +const loadRules = require('./lib/loadRules'); +const AmpValidatorRules = require('./lib/AmpValidatorRules'); let cached = null; @@ -34,7 +34,7 @@ async function load(opt) { rules = await loadRules(opt); } - cached = new AmpValidatorQuery(rules); + cached = new AmpValidatorRules(rules); return cached; } diff --git a/packages/validator-query/lib/AmpValidatorQuery.js b/packages/validator-rules/lib/AmpValidatorRules.js similarity index 98% rename from packages/validator-query/lib/AmpValidatorQuery.js rename to packages/validator-rules/lib/AmpValidatorRules.js index e05cd2f40..1f80bddec 100644 --- a/packages/validator-query/lib/AmpValidatorQuery.js +++ b/packages/validator-rules/lib/AmpValidatorRules.js @@ -14,7 +14,7 @@ * limitations under the License. */ -class AmpValidatorQuery { +class AmpValidatorRules { constructor(rules) { this.initRules_(rules); } @@ -140,4 +140,4 @@ class AmpValidatorQuery { } } -module.exports = AmpValidatorQuery; +module.exports = AmpValidatorRules; diff --git a/packages/validator-query/lib/rules.js b/packages/validator-rules/lib/loadRules.js similarity index 92% rename from packages/validator-query/lib/rules.js rename to packages/validator-rules/lib/loadRules.js index 893be502b..7cc64a13c 100644 --- a/packages/validator-query/lib/rules.js +++ b/packages/validator-rules/lib/loadRules.js @@ -22,7 +22,7 @@ const VALIDATOR_RULES_LOCAL = '../../validator.json'; const isNodeJs = typeof process !== 'undefined'; async function loadRemote(url) { - const data = await oneBehindFetch(url); + const req = await oneBehindFetch(url); return req.json(); } @@ -56,7 +56,8 @@ function getLocalPath() { return path.join(__dirname, VALIDATOR_RULES_LOCAL); } -async function load({source, url}) { +async function loadRules({source, url}) { + url = url || VALIDATOR_RULES_URL; switch (source) { case 'local': return loadLocal(); @@ -67,4 +68,4 @@ async function load({source, url}) { } } -module.exports = load; +module.exports = loadRules; diff --git a/packages/validator-query/package.json b/packages/validator-rules/package.json similarity index 90% rename from packages/validator-query/package.json rename to packages/validator-rules/package.json index 268c8d91b..5e9b756fe 100644 --- a/packages/validator-query/package.json +++ b/packages/validator-rules/package.json @@ -1,5 +1,5 @@ { - "name": "@ampproject/toolbox-validator-query", + "name": "@ampproject/toolbox-validator-rules", "version": "1.0.0-beta.3", "description": "A library that helps query AMP Validator rules", "main": "index.js", @@ -25,6 +25,6 @@ "dependencies": { "@ampproject/toolbox-core": "^1.0.0-beta.3" }, - "homepage": "https://github.com/ampproject/amp-toolbox/tree/master/packages/validator-query", + "homepage": "https://github.com/ampproject/amp-toolbox/tree/master/packages/validator-rules", "gitHead": "cb496f081c871a25dc73113ae1e1fccfb59b2732" } diff --git a/packages/validator-query/spec/AmpValidatorQuerySpec.js b/packages/validator-rules/spec/AmpValidatorRulesSpec.js similarity index 83% rename from packages/validator-query/spec/AmpValidatorQuerySpec.js rename to packages/validator-rules/spec/AmpValidatorRulesSpec.js index 0cc087a44..984df4f15 100644 --- a/packages/validator-query/spec/AmpValidatorQuerySpec.js +++ b/packages/validator-rules/spec/AmpValidatorRulesSpec.js @@ -16,11 +16,11 @@ 'use strict'; -const AmpValidatorQuery = require('../lib/AmpValidatorQuery'); +const AmpValidatorRules = require('../lib/AmpValidatorRules'); -describe('AmpValidatorQuery', () => { +describe('AmpValidatorRules', () => { it('Loads errors', () => { - const query = makeQuery({ + const rules = makeRules({ errorFormats: [ { code: 'TEST', @@ -34,7 +34,7 @@ describe('AmpValidatorQuery', () => { }, ], }); - expect(query.errors).toEqual({ + expect(rules.errors).toEqual({ TEST: { format: '%s error', specificity: 1, @@ -43,7 +43,7 @@ describe('AmpValidatorQuery', () => { }); it('Loads extensions', () => { - const query = makeQuery({ + const rules = makeRules({ tags: [ { extensionSpec: { @@ -55,25 +55,25 @@ describe('AmpValidatorQuery', () => { }, ], }); - expect(query.tags).toEqual([]); - expect(query.extensions).toEqual([ + expect(rules.tags).toEqual([]); + expect(rules.extensions).toEqual([ { name: 'amp-some-component', version: ['0.1', 'latest'], htmlFormat: ['AMP'], }, ]); - expect(query.getExtensionsForFormat('AMP')).toEqual({ + expect(rules.getExtensionsForFormat('AMP')).toEqual({ 'amp-some-component': { version: ['0.1', 'latest'], htmlFormat: ['AMP'], }, }); - expect(query.getExtensionsForFormat('AMP4EMAIL')).toEqual({}); + expect(rules.getExtensionsForFormat('AMP4EMAIL')).toEqual({}); }); it('Loads tags', () => { - const query = makeQuery({ + const rules = makeRules({ attrLists: [ { name: '$GLOBAL_ATTRS', @@ -140,15 +140,15 @@ describe('AmpValidatorQuery', () => { tagName: 'AMP-IMG', }, ]; - expect(query.tags).toEqual(tags); - expect(query.extensions).toEqual([]); - expect(query.getTagsForFormat('AMP4EMAIL')).toEqual(tags); - expect(query.getTagsForFormat('AMP', true)).toEqual([tags[0]]); + expect(rules.tags).toEqual(tags); + expect(rules.extensions).toEqual([]); + expect(rules.getTagsForFormat('AMP4EMAIL')).toEqual(tags); + expect(rules.getTagsForFormat('AMP', true)).toEqual([tags[0]]); }); }); -function makeQuery(rules) { - return new AmpValidatorQuery( +function makeRules(rules) { + return new AmpValidatorRules( Object.assign( { errorFormats: [], From a0178ceb11fe10c535676d16bfb7bdb06597c3a4 Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Tue, 9 Jul 2019 18:15:35 +0100 Subject: [PATCH 08/13] address comments --- packages/validator-rules/README.md | 44 +++++-- .../validator-rules/lib/AmpValidatorRules.js | 115 +++++++++++++----- .../spec/AmpValidatorRulesSpec.js | 11 +- 3 files changed, 125 insertions(+), 45 deletions(-) diff --git a/packages/validator-rules/README.md b/packages/validator-rules/README.md index 94ad14700..606b60a29 100644 --- a/packages/validator-rules/README.md +++ b/packages/validator-rules/README.md @@ -16,33 +16,63 @@ $ npm install @ampproject/toolbox-validator-rules@canary #### ES Module (Browser) ```javascript -import { load } from '@ampproject/toolbox-validator-rules'; +import validatorRules from '@ampproject/toolbox-validator-rules'; ``` #### CommonJs (Node) ```javascript -const { load } = require('@ampproject/toolbox-validator-rules'); +const validatorRules = require('@ampproject/toolbox-validator-rules'); ``` ### Using the module ```javascript // Loads the validator rules remotely with default options - const rules = load(); + const rules = validatorRules.fetch(); + + + // The raw unprocessed rules + console.log(rules.raw); + + // All tags, combined with their respective attribute lists + console.log(rules.tags); + + // All extensions + console.log(rules.extensions); // Get all tag names used in AMP for Email - const names = rules.getTagsForFormat('AMP4EMAIL').map(tag => tag.tagName); + const tags = rules.getTagsForFormat('AMP4EMAIL'); + + // Display their names + console.log(tags.map(tag => tag.tagName)); + + // Get information about an extension + const ext = rules.getExtensionForFormat('amp-carousel', 'AMP4EMAIL'); + + // Display supported versions + console.log(ext.versions); ``` +### Format of rules + +The rules used closely follow the proto definitions from [validator.proto](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto). + +Specifically: + +- The `raw` property is unprocessed [ValidatorRules](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto#L643) +- The result of `getTagsForFormat` and the `tags` property is a list of [TagSpec](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L463) +- The result of `getExtensionForFormat` and the `extensions` property a list of [ExtensionSpec](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L388) +- The `errors` property combines [ErrorFormat](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L874) and [ErrorSpecificity](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L869) + ### Options -`load` optionally accepts an options object allowing you to customize its +`fetch` optionally accepts an options object allowing you to customize its behaviour. The following options are supported: - * `noCache`: true to always fetch latest rules (by default, subsequent calls to `load` reuse the same result). + * `noCache`: true to always fetch latest rules (by default, subsequent calls to `fetch` reuse the same result). * `rules`: object to use locally specified rules instead of fetching them from the AMP CDN. * `url`: override the URL where validator rules are fetched from. * `source`: one of `'local'` (load rules from local file named "validator.json"), `'remote'` (fetch rules from CDN) or `'auto'` which is the default (tries looking for the local file first, then tries to fetch from CDN). @@ -50,7 +80,7 @@ The following options are supported: Example: ``` -load({ +validatorRules.fetch({ noCache: true, source: 'remote' }); diff --git a/packages/validator-rules/lib/AmpValidatorRules.js b/packages/validator-rules/lib/AmpValidatorRules.js index 1f80bddec..b2ce04853 100644 --- a/packages/validator-rules/lib/AmpValidatorRules.js +++ b/packages/validator-rules/lib/AmpValidatorRules.js @@ -15,11 +15,44 @@ */ class AmpValidatorRules { + /** + * Creates an instance of AmpValidatorRules. + * @param {Object} rules - rules imported from validator.json + */ constructor(rules) { + /** + * Unprocessed validator rules. + * @type {Object} + */ + this.raw = rules; + /** + * List of all the tags processed from rules. + * @type {Array} + */ + this.tags = []; + /** + * List of all the extensions processed from rules. + * @type {Array} + */ + this.extensions = []; + /** + * Map of errors and their associated format and specificity. + * @type {Object} + */ + this.errors = {}; + + this.extensionCache_ = {}; this.initRules_(rules); } - getTagsForFormat(format, transformed) { + /** + * Returns the list of supported tags for the given format. + * + * @param {string} format - Format to return tags for + * @param {boolean} [transformed] - Use transformed version of the format + * @return {Array} List of tags supported by the given format + */ + getTagsForFormat(format, transformed = false) { format = format.toLowerCase(); return this.tags .filter( @@ -39,39 +72,48 @@ class AmpValidatorRules { }); } - getExtensionsForFormat(format) { - format = format.toUpperCase(); - return this.extensions - .filter((extension) => extension.htmlFormat.includes(format)) - .reduce((result, extension) => { - result[extension.name] = Object.assign({}, extension); - delete result[extension.name].name; - return result; - }, {}); + /** + * Returns the AMP extension spec for the given format and name. + * + * @param {string} format - Format to filter on + * @param {string} extension - Extension name + * @return {Object} Extension spec + */ + getExtensionForFormat(format, extension) { + format = format.toLowerCase(); + extension = extension.toLowerCase(); + const key = `${format}|${extension}`; + return this.extensionCache_[key] || null; } checkEntityTransformed_(entity, transformed) { + const isEnabled = this.isEnabled_(entity, 'transformed'); + const isDisabled = this.isDisabled_(entity, 'transformed'); if (transformed) { - return this.checkEntityFormat_(entity, 'transformed'); + return isEnabled !== false && isDisabled !== true; } - if (entity.enabledBy && entity.enabledBy.includes('transformed')) { - return false; - } - if (entity.disabledBy && !entity.disabledBy.includes('transformed')) { - return false; - } - return true; + return isEnabled !== true && isDisabled !== false; } checkEntityFormat_(entity, format) { format = format.toLowerCase(); - if (entity.enabledBy && !entity.enabledBy.includes(format)) { - return false; + const isEnabled = this.isEnabled_(entity, format); + const isDisabled = this.isDisabled_(entity, format); + return isEnabled !== false && isDisabled !== true; + } + + isEnabled_(entity, format) { + if (!entity.enabledBy) { + return null; } - if (entity.disabledBy && entity.disabledBy.includes(format)) { - return false; + return entity.enabledBy.includes(format); + } + + isDisabled_(entity, format) { + if (!entity.disabledBy) { + return null; } - return true; + return entity.disabledBy.includes(format); } initRules_(rules) { @@ -96,19 +138,19 @@ class AmpValidatorRules { } initAttrLists_(rules) { - this.attrLists = {}; - this.specialAttrLists = {}; + this.attrLists_ = {}; + this.specialAttrLists_ = {}; for (const {name, attrs} of rules.attrLists) { if (name.startsWith('$')) { - this.specialAttrLists[name] = attrs; + this.specialAttrLists_[name] = attrs; } else { - this.attrLists[name] = attrs; + this.attrLists_[name] = attrs; } } - this.specialAttrLists.$AMP_LAYOUT_ATTRS.forEach( + this.specialAttrLists_.$AMP_LAYOUT_ATTRS.forEach( (attr) => (attr.layout = true) ); - this.specialAttrLists.$GLOBAL_ATTRS.forEach((attr) => (attr.global = true)); + this.specialAttrLists_.$GLOBAL_ATTRS.forEach((attr) => (attr.global = true)); } initTags_(rules) { @@ -118,14 +160,14 @@ class AmpValidatorRules { tag.attrs = tag.attrs || []; if (tag.attrLists) { for (const attrList of tag.attrLists) { - tag.attrs.push(...this.attrLists[attrList]); + tag.attrs.push(...this.attrLists_[attrList]); } delete tag.attrLists; } if (tag.ampLayout) { - tag.attrs.push(...this.specialAttrLists.$AMP_LAYOUT_ATTRS); + tag.attrs.push(...this.specialAttrLists_.$AMP_LAYOUT_ATTRS); } - tag.attrs.push(...this.specialAttrLists.$GLOBAL_ATTRS); + tag.attrs.push(...this.specialAttrLists_.$GLOBAL_ATTRS); return tag; }); @@ -137,6 +179,15 @@ class AmpValidatorRules { .map((tag) => Object.assign({}, tag.extensionSpec, {htmlFormat: tag.htmlFormat}) ); + + for (const extension of this.extensions) { + const name = extension.name.toLowerCase(); + for (let format of extension.htmlFormat) { + format = format.toLowerCase(); + const key = `${format}|${name}`; + this.extensionCache_[key] = extension; + } + } } } diff --git a/packages/validator-rules/spec/AmpValidatorRulesSpec.js b/packages/validator-rules/spec/AmpValidatorRulesSpec.js index 984df4f15..7b4af18b5 100644 --- a/packages/validator-rules/spec/AmpValidatorRulesSpec.js +++ b/packages/validator-rules/spec/AmpValidatorRulesSpec.js @@ -63,13 +63,12 @@ describe('AmpValidatorRules', () => { htmlFormat: ['AMP'], }, ]); - expect(rules.getExtensionsForFormat('AMP')).toEqual({ - 'amp-some-component': { - version: ['0.1', 'latest'], - htmlFormat: ['AMP'], - }, + expect(rules.getExtensionForFormat('AMP', 'amp-some-component')).toEqual({ + name: 'amp-some-component', + version: ['0.1', 'latest'], + htmlFormat: ['AMP'], }); - expect(rules.getExtensionsForFormat('AMP4EMAIL')).toEqual({}); + expect(rules.getExtensionForFormat('AMP4EMAIL', 'amp-some-component')).toEqual(null); }); it('Loads tags', () => { From 4807294213035ca38dace9372ab1beee12f5113f Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Tue, 9 Jul 2019 18:20:20 +0100 Subject: [PATCH 09/13] rename extensions method --- packages/validator-rules/README.md | 5 +++-- packages/validator-rules/lib/AmpValidatorRules.js | 2 +- packages/validator-rules/spec/AmpValidatorRulesSpec.js | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/validator-rules/README.md b/packages/validator-rules/README.md index 606b60a29..7be6aadce 100644 --- a/packages/validator-rules/README.md +++ b/packages/validator-rules/README.md @@ -48,7 +48,7 @@ const validatorRules = require('@ampproject/toolbox-validator-rules'); console.log(tags.map(tag => tag.tagName)); // Get information about an extension - const ext = rules.getExtensionForFormat('amp-carousel', 'AMP4EMAIL'); + const ext = rules.getExtension('amp-carousel', 'AMP4EMAIL'); // Display supported versions console.log(ext.versions); @@ -62,7 +62,8 @@ Specifically: - The `raw` property is unprocessed [ValidatorRules](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto#L643) - The result of `getTagsForFormat` and the `tags` property is a list of [TagSpec](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L463) -- The result of `getExtensionForFormat` and the `extensions` property a list of [ExtensionSpec](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L388) +- The result of `getExtension` is [ExtensionSpec](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L388) with the `htmlFormat` field from `TagSpec` +- The `extensions` property a list of [ExtensionSpec](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L388) with the `htmlFormat` field from `TagSpec` - The `errors` property combines [ErrorFormat](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L874) and [ErrorSpecificity](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L869) ### Options diff --git a/packages/validator-rules/lib/AmpValidatorRules.js b/packages/validator-rules/lib/AmpValidatorRules.js index b2ce04853..87802a089 100644 --- a/packages/validator-rules/lib/AmpValidatorRules.js +++ b/packages/validator-rules/lib/AmpValidatorRules.js @@ -79,7 +79,7 @@ class AmpValidatorRules { * @param {string} extension - Extension name * @return {Object} Extension spec */ - getExtensionForFormat(format, extension) { + getExtension(format, extension) { format = format.toLowerCase(); extension = extension.toLowerCase(); const key = `${format}|${extension}`; diff --git a/packages/validator-rules/spec/AmpValidatorRulesSpec.js b/packages/validator-rules/spec/AmpValidatorRulesSpec.js index 7b4af18b5..820b098e9 100644 --- a/packages/validator-rules/spec/AmpValidatorRulesSpec.js +++ b/packages/validator-rules/spec/AmpValidatorRulesSpec.js @@ -63,12 +63,12 @@ describe('AmpValidatorRules', () => { htmlFormat: ['AMP'], }, ]); - expect(rules.getExtensionForFormat('AMP', 'amp-some-component')).toEqual({ + expect(rules.getExtension('AMP', 'amp-some-component')).toEqual({ name: 'amp-some-component', version: ['0.1', 'latest'], htmlFormat: ['AMP'], }); - expect(rules.getExtensionForFormat('AMP4EMAIL', 'amp-some-component')).toEqual(null); + expect(rules.getExtension('AMP4EMAIL', 'amp-some-component')).toEqual(null); }); it('Loads tags', () => { From d1d9923116a4ca90a1bcaedb09f3c5d045e03703 Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Wed, 10 Jul 2019 17:30:06 +0100 Subject: [PATCH 10/13] address comments --- packages/validator-rules/README.md | 5 +++-- packages/validator-rules/index.js | 6 ++---- packages/validator-rules/lib/AmpValidatorRules.js | 6 ++++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/validator-rules/README.md b/packages/validator-rules/README.md index 7be6aadce..ec815db82 100644 --- a/packages/validator-rules/README.md +++ b/packages/validator-rules/README.md @@ -29,7 +29,7 @@ const validatorRules = require('@ampproject/toolbox-validator-rules'); ```javascript // Loads the validator rules remotely with default options - const rules = validatorRules.fetch(); + const rules = await validatorRules.fetch(); // The raw unprocessed rules @@ -42,6 +42,7 @@ const validatorRules = require('@ampproject/toolbox-validator-rules'); console.log(rules.extensions); // Get all tag names used in AMP for Email + // The supported formats are AMP, AMP4EMAIL, AMP4ADS and ACTIONS const tags = rules.getTagsForFormat('AMP4EMAIL'); // Display their names @@ -60,7 +61,7 @@ The rules used closely follow the proto definitions from [validator.proto](https Specifically: -- The `raw` property is unprocessed [ValidatorRules](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto#L643) +- The `raw` property is unprocessed [ValidatorRules](https://github.com/ampproject/amphtml/blob/master/validator/validator.proto#L643), the same format used by `https://cdn.ampproject.org/v0/validator.json` - The result of `getTagsForFormat` and the `tags` property is a list of [TagSpec](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L463) - The result of `getExtension` is [ExtensionSpec](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L388) with the `htmlFormat` field from `TagSpec` - The `extensions` property a list of [ExtensionSpec](https://github.com/ampproject/amphtml/blob/b892d81467594cab5473c803e071af5108f834a6/validator/validator.proto#L388) with the `htmlFormat` field from `TagSpec` diff --git a/packages/validator-rules/index.js b/packages/validator-rules/index.js index 761955c84..0b7812b1d 100644 --- a/packages/validator-rules/index.js +++ b/packages/validator-rules/index.js @@ -21,14 +21,12 @@ const AmpValidatorRules = require('./lib/AmpValidatorRules'); let cached = null; -async function load(opt) { - opt = opt || {}; +async function fetch(opt = {}) { if (!opt.noCache && cached) { return cached; } let rules = opt.rules; - delete opt.rules; if (!rules) { rules = await loadRules(opt); @@ -38,4 +36,4 @@ async function load(opt) { return cached; } -module.exports = {load}; +module.exports = {fetch}; diff --git a/packages/validator-rules/lib/AmpValidatorRules.js b/packages/validator-rules/lib/AmpValidatorRules.js index 87802a089..10398683e 100644 --- a/packages/validator-rules/lib/AmpValidatorRules.js +++ b/packages/validator-rules/lib/AmpValidatorRules.js @@ -158,15 +158,21 @@ class AmpValidatorRules { .filter((tag) => !tag.extensionSpec) .map((tag) => { tag.attrs = tag.attrs || []; + + // Merge global attribute lists into atrrs if (tag.attrLists) { for (const attrList of tag.attrLists) { tag.attrs.push(...this.attrLists_[attrList]); } delete tag.attrLists; } + + // $AMP_LAYOUT_ATTRS are present in all components with ampLayout if (tag.ampLayout) { tag.attrs.push(...this.specialAttrLists_.$AMP_LAYOUT_ATTRS); } + + // $GLOBAL_ATTRS are present in all components tag.attrs.push(...this.specialAttrLists_.$GLOBAL_ATTRS); return tag; From 82481fae7c19840099ac992ea7bd1cb41a4942ea Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Thu, 11 Jul 2019 11:55:19 +0100 Subject: [PATCH 11/13] address comments --- .../validator-rules/lib/AmpValidatorRules.js | 3 +- packages/validator-rules/lib/loadRules.js | 47 ++----------------- 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/packages/validator-rules/lib/AmpValidatorRules.js b/packages/validator-rules/lib/AmpValidatorRules.js index 10398683e..998645418 100644 --- a/packages/validator-rules/lib/AmpValidatorRules.js +++ b/packages/validator-rules/lib/AmpValidatorRules.js @@ -159,7 +159,8 @@ class AmpValidatorRules { .map((tag) => { tag.attrs = tag.attrs || []; - // Merge global attribute lists into atrrs + // `attrLists` contains list IDs that are looked up from the global + // attribute lists and merged into `attrs`. if (tag.attrLists) { for (const attrList of tag.attrLists) { tag.attrs.push(...this.attrLists_[attrList]); diff --git a/packages/validator-rules/lib/loadRules.js b/packages/validator-rules/lib/loadRules.js index 7cc64a13c..40f3bf917 100644 --- a/packages/validator-rules/lib/loadRules.js +++ b/packages/validator-rules/lib/loadRules.js @@ -14,58 +14,21 @@ * limitations under the License. */ +// This solution is temporary and will be replaced when +// https://github.com/ampproject/amp-toolbox/issues/378 is resolved. + const {oneBehindFetch} = require('@ampproject/toolbox-core'); const VALIDATOR_RULES_URL = 'https://cdn.ampproject.org/v0/validator.json'; -const VALIDATOR_RULES_LOCAL = '../../validator.json'; - -const isNodeJs = typeof process !== 'undefined'; async function loadRemote(url) { const req = await oneBehindFetch(url); return req.json(); } -async function loadLocal() { - if (!isNodeJs) { - throw new Error('Local loading is not supported in browsers'); - } - - const {promisify} = require('util'); - const readFileAsync = promisify(require('fs').readFile); - - const data = await readFileAsync(getLocalPath()); - return JSON.parse(data); -} - -async function loadDefault(url) { - if (!isNodeJs) { - return loadRemote(url); - } - - const {existsSync} = require('fs'); - - if (existsSync(getLocalPath())) { - return loadLocal(); - } - return loadRemote(url); -} - -function getLocalPath() { - const path = require('path'); - return path.join(__dirname, VALIDATOR_RULES_LOCAL); -} - -async function loadRules({source, url}) { +async function loadRules({url}) { url = url || VALIDATOR_RULES_URL; - switch (source) { - case 'local': - return loadLocal(); - case 'remote': - return loadRemote(url); - default: - return loadDefault(url); - } + return loadRemote(url); } module.exports = loadRules; From 9bcb263b5e588530853d09914dba74e1be0729b5 Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Thu, 11 Jul 2019 14:49:14 +0100 Subject: [PATCH 12/13] fix README --- packages/validator-rules/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/validator-rules/README.md b/packages/validator-rules/README.md index ec815db82..caf4db5b5 100644 --- a/packages/validator-rules/README.md +++ b/packages/validator-rules/README.md @@ -49,7 +49,7 @@ const validatorRules = require('@ampproject/toolbox-validator-rules'); console.log(tags.map(tag => tag.tagName)); // Get information about an extension - const ext = rules.getExtension('amp-carousel', 'AMP4EMAIL'); + const ext = rules.getExtension('AMP4EMAIL', 'amp-carousel'); // Display supported versions console.log(ext.versions); From e8e4dc1a614d6b56adc16dc1015ee4c17ffe7909 Mon Sep 17 00:00:00 2001 From: Filip Stanis Date: Thu, 11 Jul 2019 15:06:31 +0100 Subject: [PATCH 13/13] update package-lock via lerna --- package-lock.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/package-lock.json b/package-lock.json index 7f4b487c7..6e24bb66e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4418,6 +4418,22 @@ } } }, + "@ampproject/toolbox-validator-rules": { + "version": "file:packages/validator-rules", + "requires": { + "@ampproject/toolbox-core": "^1.0.0-beta.3" + }, + "dependencies": { + "@ampproject/toolbox-core": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@ampproject/toolbox-core/-/toolbox-core-1.0.0-beta.3.tgz", + "integrity": "sha512-IjZQRIRyAQDKA56PXsph2uyXvKQl+cVh+Xz1UX0Am0vIdbwXacHbx/oxYC7O0HD5Gbg6K508tHBeUTJFiUO0iA==", + "requires": { + "node-fetch": "2.6.0" + } + } + } + }, "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",