├── .travis.yml ├── LICENSE ├── README.md ├── bin └── wemf ├── libs ├── formatter.js └── manifestKeyRules.js ├── package-lock.json ├── package.json └── test ├── fixtures ├── check_must_key.json ├── check_must_key_pass.json ├── manifest.json └── unsupported_key.json └── libs └── formatter.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 7 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 pastak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebExtension Manifest Formatter 2 | 3 | [![Build Status](https://travis-ci.org/pastak/wemf.svg?branch=master)](https://travis-ci.org/pastak/wemf) 4 | 5 | [![](https://nodei.co/npm-dl/wemf.png?months=3)](https://www.npmjs.com/package/wemf) 6 | 7 | [![](https://nodei.co/npm/wemf.png?downloads=true&downloadRank=true&stars=true)](https://www.npmjs.com/package/wemf) 8 | 9 | 10 | Formatter and validator for manifest.json on Chrome Extension / Firefox WebExtension / Extension for Edge. 11 | 12 | ## Install 13 | 14 | `% npm install -g wemf` 15 | 16 | ## Usage 17 | 18 | ``` 19 | Usage: wemf [options] 20 | 21 | Options: 22 | 23 | -h, --help output usage information 24 | -V, --version output the version number 25 | --validate Only validate manifest.json 26 | -O --output Output manifest.json path 27 | -U --update Update manifest.json itself 28 | --browser Set target browser (chrome|firefox|edge) default: firefox 29 | ``` 30 | 31 | ### Formatter 32 | 33 | `% wemf /path/to/chrome-ext/manifest.json -O /path/to/firefox-ext/manifest.json` 34 | 35 | ### Validate 36 | 37 | `% wemf /path/to/firefox-ext/manifest.json --validate` 38 | 39 | if it has no problem, return nothing 40 | 41 | ### Tips 42 | 43 | #### Set custom value via `package.json` 44 | 45 | If you want to fill columns (ex: `applications`) automatically, you should write `webextension` column on project's `package.json`. 46 | 47 | Example 48 | 49 | ```js 50 | { 51 | "name": "hoge", 52 | ... 53 | "webextension": { 54 | "name": 'extension-name', 55 | "applications": { 56 | "gecko": { 57 | "id": "sample-extension@example.org", 58 | "strict_min_version": "47.0a1" 59 | } 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | #### inherit value from `package.json` 66 | 67 | When `name`, `version`, `author`, `description` or `homepage_url` is filled `inherit`, `manifest.json`'s field will be filled by same key name value from `package.json` (`homepage_url` will be filled by `homepage` in `package.json`) 68 | 69 | ## information 70 | 71 | Please check newest information 72 | 73 | - [WebExtensions - MozillaWiki](https://wiki.mozilla.org/WebExtensions) 74 | - [Are we Web Extensions yet?](http://www.arewewebextensionsyet.com/) 75 | - [Chrome incompatibilities - Mozilla | MDN](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Chrome_incompatibilities) 76 | - [manifest.json - Mozilla | MDN](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json) 77 | 78 | ## Development 79 | 80 | Welcome your Pull Request!! 81 | 82 | Please fork it and send Pull Request to this repository. 83 | 84 | ### Testing 85 | 86 | `% npm test` 87 | -------------------------------------------------------------------------------- /bin/wemf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const fs = require('fs') 5 | const path = require('path') 6 | const program = require('commander') 7 | const promptSync = require('prompt-sync')() 8 | const Formatter = require('../libs/formatter') 9 | let _package = {} 10 | try { 11 | _package = JSON.parse(fs.readFileSync(path.resolve('package.json'))) 12 | } catch (e) {} 13 | 14 | const inputKey = (formatter) => (key) => { 15 | if (key === 'applications') { 16 | while (1) { 17 | const id = promptSync('Type your extension id :') 18 | formatter.fillMustKey('applications', id) 19 | if (formatter.checkApplicationsKeyFormat()) break 20 | console.log(`Your extension id "${id}" is invalid`) 21 | console.log(`ex: addon-name@yourdomain.net`) 22 | } 23 | } else if (key === 'manifest_version') { 24 | formatter.fillMustKey('manifest_version', 2) 25 | } else { 26 | let value = _package[key] || '' 27 | value = promptSync(`Type your extension ${key} (default: ${value}) :`, _package[key]) 28 | formatter.fillMustKey(key, value) 29 | } 30 | } 31 | 32 | program 33 | .usage(' [options]') 34 | .version(require('../package.json').version) 35 | .option('--validate', 'Only validate manifest.json') 36 | .option('-O --output ', 'Output manifest.json path') 37 | .option('-U --update', 'Update manifest.json itself') 38 | .option('--browser ', 'Set target browser (chrome|firefox|edge) default: firefox') 39 | .option('--no-color', 'disable color in output') 40 | .option('--quiet', 'hide WARNING and RECOMMEND messages') 41 | .option('--data ', 'data to overwrite manifest.json') 42 | .action((packageJsonPath) => { 43 | 44 | const RESET_COLOR = program.color ? '\u001b[0m' : '' 45 | const WARNING_COLOR = program.color ? '\u001b[33m' : '' 46 | const RECOMMEND_COLOR = program.color ? '\u001b[34m' : '' 47 | const ERROR_COLOR = program.color ? '\u001b[31m' : '' 48 | 49 | const WARNING_TEXT = `${WARNING_COLOR}WARNING${RESET_COLOR}` 50 | const RECOMMEND_TEXT = `${RECOMMEND_COLOR}RECOMMEND${RESET_COLOR}` 51 | const ERROR_TEXT = `${ERROR_COLOR}ERROR${RESET_COLOR}` 52 | 53 | const browser = program.browser 54 | if (browser !== undefined && !/^(chrome|firefox|edge)$/.test(browser)) { 55 | console.error('You should choose browser from [chrome,firefox,edge]') 56 | process.exit(1) 57 | } 58 | let data = program.data 59 | try { 60 | data = JSON.parse(String(data)) 61 | } catch (e) {} 62 | const formatter = new Formatter(packageJsonPath, browser, data) 63 | if (!program.quiet && formatter.recommendMessage.length > 0) { 64 | console.warn(RECOMMEND_TEXT + ': ' + formatter.recommendMessage.join('\n' + RECOMMEND_TEXT + ': ')) 65 | } 66 | if (!program.quiet && formatter.warningMessage.length > 0) { 67 | console.warn(WARNING_TEXT + ': ' + formatter.warningMessage.join('\n' + WARNING_TEXT + ': ')) 68 | } 69 | if (program.validate) { 70 | if (!formatter.validator()) { 71 | console.error(ERROR_TEXT + ': ' + formatter.errorMessages.join('\n' + ERROR_TEXT + ': ')) 72 | process.exit(1) 73 | } 74 | return 75 | } 76 | formatter.deleteUnsupportedKey() 77 | formatter.deleteUnsupportedProps() 78 | formatter.shouldContainKeys.forEach(inputKey(formatter)) 79 | if (program.update) { 80 | program.output = packageJsonPath 81 | } 82 | if (formatter.unSupportedKeys.length > 0) { 83 | console.error(formatter.unSupportedKeys.join('\n')) 84 | process.exit(1) 85 | } else if (program.output) { 86 | try { 87 | fs.writeFileSync(path.resolve(program.output), JSON.stringify(formatter.json, null, 2)) 88 | } catch (e) { 89 | console.error(e) 90 | process.exit(1) 91 | } 92 | } else { 93 | console.log(JSON.stringify(formatter.json, null, 2)) 94 | } 95 | }) 96 | program.parse(process.argv) 97 | if (!program.args.length) { 98 | program.help() 99 | } 100 | -------------------------------------------------------------------------------- /libs/formatter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const fs = require('fs') 3 | const path = require('path') 4 | const GUID = require('guid') 5 | const keyRules = require('./manifestKeyRules') 6 | 7 | module.exports = class Formatter { 8 | constructor (_path, browser = 'firefox', data = {}) { 9 | try { 10 | this.json = JSON.parse(fs.readFileSync(path.resolve(_path))) 11 | try { 12 | const _package = JSON.parse(fs.readFileSync(path.resolve('package.json'))) 13 | const webextensionConfig = _package.webextension || {} 14 | this.json = Object.assign(webextensionConfig, this.json, data) 15 | ;['name', 'version', 'author', 'description', 'homepage_url'].forEach((key) => { 16 | if (this.json[key] === 'inherit') this.json[key] = _package[key === 'homepage_url' ? 'homepage' : key] 17 | }) 18 | } catch (e) {} 19 | } catch (e) { 20 | throw new Error(e) 21 | } 22 | this.browser = browser 23 | this.unSupportedKeys = [] 24 | this.errorMessages = [] 25 | this.warningMessage = [] 26 | this.recommendMessage = [] 27 | 28 | this.rules = {} 29 | this.validKeys = [] 30 | 31 | this.loadRules() 32 | this.validator() 33 | } 34 | 35 | loadRules () { 36 | const browser = this.browser 37 | const rules = keyRules[browser] 38 | Object.keys(rules).forEach((key) => { 39 | this.rules[key] = [] 40 | if (key === 'optional_permissions' && Array.isArray(rules['permissions'])) { 41 | this.rules[key] = [key, rules['permissions'][1]] 42 | this.validKeys.push(key) 43 | } else if (rules[key] === 'inherit') { 44 | keyRules['chrome'][key].forEach((rule) => { 45 | this.rules[key].push(rule) 46 | this.validKeys.push(Array.isArray(rule) ? rule[0] : rule) 47 | }) 48 | } else { 49 | rules[key].forEach((rule) => { 50 | this.rules[key].push(rule) 51 | this.validKeys.push(Array.isArray(rule) ? rule[0] : rule) 52 | }) 53 | } 54 | }) 55 | } 56 | 57 | validator () { 58 | this.errorMessages = [] 59 | this.checkRequiredKey() 60 | this.checkRecommendedKey() 61 | this.checkUnsupportedKey() 62 | this.checkUnsupportedProps() 63 | this.checkApplicationsKeyFormat() 64 | const result = this.errorMessages.length === 0 65 | this.isValid = result 66 | return result 67 | } 68 | 69 | checkRecommendedKey () { 70 | const recommendKeys = this.rules['recommend'] 71 | recommendKeys.forEach((key) => { 72 | if (!this.hasKey(key)) { 73 | this.recommendMessage.push(`set ${key}`) 74 | } 75 | }) 76 | } 77 | 78 | checkUnsupportedKeyOrProps () { 79 | return this.checkUnsupportedKey() && this.checkUnsupportedProps() 80 | } 81 | 82 | checkApplicationsKeyFormat () { 83 | // applicationsをサポートしていない場合はチェックしない 84 | if (this.validKeys.indexOf('applications') === -1) return true 85 | const applications = this.json.applications 86 | if (!(applications && 87 | applications.hasOwnProperty('gecko') && 88 | applications.gecko.hasOwnProperty('id') && 89 | applications.gecko.id)) return false 90 | const id = applications.gecko.id 91 | // Check Valid ID 92 | // NOTE: You can't use + 93 | if (!(/^[A-Z|a-z|0-9|\-|\.]+@[a-z|A-Z|0-9|-]+(\.[a-z-A-Z]+)+$/).test(id) || GUID.isGuid(id)) { 94 | this.errorMessages.push(`Invaid id: ${id}`) 95 | return false 96 | } 97 | return true 98 | } 99 | 100 | hasKey (rule) { 101 | const keyName = this.getKeyName(rule) 102 | return this.json[keyName] !== undefined 103 | } 104 | 105 | getKeyName (rule) { 106 | return typeof rule === 'string' ? rule : rule[0] 107 | } 108 | 109 | checkRequiredKey () { 110 | const shouldContainKeys = [] 111 | const requiredKeys = this.rules['required'] 112 | requiredKeys.forEach((rule) => { 113 | if (!this.hasKey(rule)) shouldContainKeys.push(this.getKeyName(rule)) 114 | }) 115 | const result = shouldContainKeys.length === 0 116 | if (!result) { 117 | this.errorMessages.push(`${this.browser}'s extension must have keys: ${shouldContainKeys.join(', ')}`) 118 | } 119 | this.shouldContainKeys = shouldContainKeys 120 | return result 121 | } 122 | fillMustKey (key, val) { 123 | if (val === undefined) { 124 | // Set key as props 125 | if (typeof key === 'string') { 126 | if (key === 'manifest_version') this.json.manifest_version = '2' 127 | } else { 128 | this.json = Object.assign(this.json, key) 129 | } 130 | } else { 131 | if (key === 'applications') { 132 | if (typeof val === 'string') { 133 | this.json.applications = {gecko: {id: val}} 134 | } else { 135 | if (val.hasOwnProperty('id')) { 136 | this.json.applications = {gecko: val} 137 | } else { 138 | this.json.applications = val 139 | } 140 | } 141 | } else { 142 | this.json[key] = val 143 | } 144 | } 145 | this.validator() 146 | } 147 | checkUnsupportedKey () { 148 | const containedKey = Object.keys(this.json).filter(k => this.validKeys.indexOf(k) === -1) 149 | if (containedKey.length === 0) { 150 | return true 151 | } else { 152 | this.errorMessages.push(`${this.browser}'s extension does not yet support keys: ${containedKey.join(', ')}`) 153 | return false 154 | } 155 | } 156 | 157 | searchUnsupportedProps (cb) { 158 | Object.keys(this.rules).forEach((ruleType) => { 159 | this.rules[ruleType] 160 | .filter((rule) => typeof rule !== 'string' && Array.isArray(rule)) 161 | .forEach((rule) => { 162 | let target = this.json[rule[0]] 163 | if (!target) return 164 | if (rule[1].unsupportProps) { 165 | rule[1].unsupportProps.forEach((unsupportProp) => { 166 | if (!target.hasOwnProperty(unsupportProp)) return 167 | if (cb(rule[0], unsupportProp) === 'delete') { 168 | delete target[unsupportProp] 169 | } 170 | }) 171 | } 172 | if (rule[1].supportValues) { 173 | target.filter((val) => rule[1].supportValues.indexOf(val) === -1) 174 | .forEach((unsupportVal) => { 175 | if ( 176 | (rule[0] === 'permissions' || rule[0] === 'optional_permissions') && 177 | this.checkValidHostPattern(unsupportVal) 178 | ) return 179 | 180 | if (cb(rule[0], unsupportVal) === 'delete') { 181 | target.splice(target.indexOf(unsupportVal), 1) 182 | } 183 | }) 184 | } 185 | if (rule[1].recommend) { 186 | const recommend = rule[1].recommend 187 | if (typeof recommend === 'object') { 188 | Object.keys(recommend).forEach((key) => { 189 | if (recommend[key] !== target[key]) { 190 | this.recommendMessage.push(`${recommend[key]} is better than ${target[key]} on ${key} of ${rule[0]}`) 191 | } 192 | }) 193 | } else if (recommend !== target) { 194 | this.recommendMessage.push(`${recommend} is better than ${target} on ${rule[0]}`) 195 | } 196 | } 197 | }) 198 | }) 199 | } 200 | 201 | checkValidHostPattern (val) { 202 | if (val === '') return true 203 | const result = /^(http|https|file|ftp|\*):\/\/(\*|((\*\.)?[^\/\*]+))?\/.*$/.test(val) 204 | if (!result) { 205 | this.errorMessages.push(`${val} is invalid on permissons host pattern`) 206 | } 207 | return result 208 | } 209 | 210 | checkUnsupportedProps () { 211 | let result = true 212 | this.searchUnsupportedProps((k, keyword) => { 213 | result = false 214 | this.errorMessages.push(`${k} does'nt yet support keyword '${keyword}'`) 215 | }) 216 | return result 217 | } 218 | deleteUnsupportedProps () { 219 | this.searchUnsupportedProps((k, keyword, notDeleted) => { 220 | if (notDeleted) { 221 | this.unSupportedKeys.push(`"${k}" doesn't support keyword: "${keyword}"`) 222 | } 223 | return 'delete' 224 | }) 225 | this.validator() 226 | } 227 | deleteUnsupportedKey () { 228 | Object.keys(this.json) 229 | .filter(k => this.validKeys.indexOf(k) === -1) 230 | .forEach(k => { 231 | delete this.json[k] 232 | }) 233 | this.validator() 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /libs/manifestKeyRules.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/extensions/manifest 2 | const chrome = { 3 | required: [ 4 | ['manifest_version', {value: 2}], 5 | 'name', 6 | 'version' 7 | ], 8 | optional: [ 9 | 'author', 10 | 'automation', 11 | ['background', {recommend: {persistent: false}}], 12 | 'background_page', 13 | 'chrome_settings_overrides', 14 | 'chrome_ui_overrides', 15 | 'chrome_url_overrides', 16 | ['commands', {unsupportProps: ['_execute_sidebar_action']}], 17 | 'content_capabilities', 18 | 'content_scripts', 19 | 'content_security_policy', 20 | 'converted_from_user_script', 21 | 'current_locale', 22 | 'devtools_page', 23 | 'event_rules', 24 | 'externally_connectable', 25 | 'file_browser_handlers', 26 | 'file_system_provider_capabilities', 27 | 'homepage_url', 28 | 'import', 29 | 'incognito', 30 | 'input_components', 31 | 'key', 32 | 'minimum_chrome_version', 33 | 'nacl_modules', 34 | 'oauth2', 35 | 'offline_enabled', 36 | 'omnibox', 37 | 'optional_permissions', 38 | 'options_page', 39 | 'options_ui', 40 | ['permissions', { 41 | supportValues: [ 42 | 'activeTab', 43 | 'alarms', 44 | 'background', 45 | 'bookmarks', 46 | 'browsingData', 47 | 'certificateProvider', 48 | 'clipboardRead', 49 | 'clipboardWrite', 50 | 'contentSettings', 51 | 'contextMenus', 52 | 'cookies', 53 | 'debugger', 54 | 'declarativeContent', 55 | 'declarativeWebRequest', 56 | 'desktopCapture', 57 | 'displaySource', 58 | 'dns', 59 | 'documentScan', 60 | 'downloads', 61 | 'enterprise.deviceAttributes', 62 | 'enterprise.platformKeys', 63 | 'experimental', 64 | 'fileBrowserHandler', 65 | 'fileSystemProvider', 66 | 'fontSettings', 67 | 'gcm', 68 | 'geolocation', 69 | 'history', 70 | 'identity', 71 | 'idle', 72 | 'idltest', 73 | 'management', 74 | 'nativeMessaging', 75 | 'networking.config', 76 | 'notifications', 77 | 'pageCapture', 78 | 'platformKeys', 79 | 'power', 80 | 'printerProvider', 81 | 'privacy', 82 | 'processes', 83 | 'proxy', 84 | 'sessions', 85 | 'signedInDevices', 86 | 'storage', 87 | 'system.cpu', 88 | 'system.display', 89 | 'system.memory', 90 | 'system.storage', 91 | 'tabCapture', 92 | 'tabs', 93 | 'topSites', 94 | 'tts', 95 | 'ttsEngine', 96 | 'unlimitedStorage', 97 | 'vpnProvider', 98 | 'wallpaper', 99 | 'webNavigation', 100 | 'webRequest', 101 | 'webRequestBlocking' 102 | ] 103 | }], 104 | 'platforms', 105 | 'plugins', 106 | 'requirements', 107 | 'sandbox', 108 | 'short_name', 109 | 'signature', 110 | 'spellcheck', 111 | 'storage', 112 | 'system_indicator', 113 | 'tts_engine', 114 | 'update_url', 115 | 'version_name', 116 | 'web_accessible_resources' 117 | ], 118 | recommend: ['default_locale', 'description', 'icons'], 119 | pickOneOrNone: ['browser_action', 'page_action'] 120 | } 121 | 122 | // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json 123 | const firefox = { 124 | required: [ 125 | 'version', 126 | 'name', 127 | ['manifest_version', {value: 2}] 128 | ], 129 | optional: [ 130 | 'applications', 131 | 'author', 132 | ['background', {unsupportProps: ['persistent']}], 133 | 'browser_action', 134 | 'chrome_url_overrides', 135 | 'commands', 136 | 'content_scripts', 137 | 'content_security_policy', 138 | 'default_locale', 139 | 'description', 140 | 'developer', 141 | 'homepage_url', 142 | 'icons', 143 | 'omnibox', 144 | ['options_ui', {unsupportProps: ['chrome_style']}], 145 | 'page_action', 146 | ['permissions', { 147 | supportValues: [ 148 | 'activeTab', 149 | 'alarms', 150 | 'bookmarks', 151 | 'browsingData', 152 | 'contextMenus', 153 | 'contextualIdentities', 154 | 'cookies', 155 | 'downloads', 156 | 'downloads.open', 157 | 'history', 158 | 'identity', 159 | 'idle', 160 | 'management', 161 | 'nativeMessaging', 162 | 'notifications', 163 | 'sessions', 164 | 'storage', 165 | 'tabs', 166 | 'topSites', 167 | 'webNavigation', 168 | 'webRequest', 169 | 'webRequestBlocking' 170 | ] 171 | }], 172 | 'short_name', 173 | 'sidebar_action', 174 | 'web_accessible_resources' 175 | ], 176 | recommend: 'inherit', 177 | pickOneOrNone: 'inherit' 178 | } 179 | 180 | // https://docs.microsoft.com/en-us/microsoft-edge/extensions/api-support/supported-manifest-keys 181 | 182 | const edge = { 183 | required: ['author', 'name', 'version'], 184 | optional: [ 185 | 'background', 186 | ['browser_action', { 187 | unsupportProps: ['browser_style'] 188 | }], 189 | 'content_scripts', 190 | 'content_security_policy', 191 | 'default_locale', 192 | 'minimum_edge_version', 193 | 'key', 194 | 'options_page', 195 | ['permissions', { 196 | supportValues: [ 197 | 'contextMenus', 198 | 'cookies', 199 | 'geolocation', 200 | 'idle', 201 | 'storage', 202 | 'tabs', 203 | 'unlimitedStorage', 204 | 'webNavigation', 205 | 'webRequest', 206 | 'webRequestBlocking' 207 | ] 208 | }], 209 | 'short_name', 210 | 'web_accessible_resources' 211 | ], 212 | recommend: ['description', 'manifest_version', 'icons'], 213 | pickOneOrNone: 'inherit' 214 | } 215 | 216 | module.exports = {chrome, firefox, edge} 217 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wemf", 3 | "version": "1.3.0", 4 | "description": "Format manifest.json to use on WebExtension from Chrome Extension", 5 | "bin": { 6 | "wemf": "bin/wemf" 7 | }, 8 | "scripts": { 9 | "test": "standard **/*.js && ava" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+ssh://git@github.com/pastak/wemf.git" 14 | }, 15 | "keywords": [ 16 | "WebExtension", 17 | "ChromeExtension" 18 | ], 19 | "author": "pastak ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/pastak/wemf/issues" 23 | }, 24 | "homepage": "https://github.com/pastak/wemf#readme", 25 | "devDependencies": { 26 | "ava": "^0.11.0", 27 | "proxyquire": "^1.7.4", 28 | "standard": "^5.4.1" 29 | }, 30 | "dependencies": { 31 | "commander": "^2.9.0", 32 | "guid": "0.0.12", 33 | "prompt-sync": "^4.2.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/fixtures/check_must_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "test", 3 | "web_accessible_resources": ["img/test.jpg"] 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/check_must_key_pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "description": "test", 4 | "version": "0.0.1", 5 | "applications": { 6 | "gecko": { 7 | "id": "sample@example.com" 8 | } 9 | }, 10 | "permissions": [ 11 | "" 12 | ], 13 | "manifest_version": 2, 14 | "default_locale": "en" 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "description": "test", 4 | "short_name": "Test", 5 | "author": "TestAuthor", 6 | "permissions": [ 7 | "", 8 | "activeTab", 9 | "notifications", 10 | "contextMenus", 11 | "tabs", 12 | "clipboardWrite", 13 | "clipboardRead", 14 | "storage" 15 | ], 16 | "background": { 17 | "scripts": ["main.js"], 18 | "persistent": false 19 | }, 20 | "browser_action": { 21 | "default_icon": { 22 | "19": "icons/19.png", 23 | "38": "icons/19@2x.png" 24 | } 25 | }, 26 | "web_accessible_resources": [ 27 | "static/image.jpg", 28 | "static/img.png" 29 | ], 30 | "icons": { 31 | "16": "icons/16.png" 32 | }, 33 | "options_ui": { 34 | "page": "option/options.html", 35 | "chrome_style": false 36 | }, 37 | "unsupported_key": "dummy", 38 | "manifest_version": 2, 39 | "default_locale": "en" 40 | } 41 | -------------------------------------------------------------------------------- /test/fixtures/unsupported_key.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "short_name": "Test", 4 | "author": "TestAuthor", 5 | "description": "test", 6 | "version": "0.0.1", 7 | "applications": { 8 | "gecko": { 9 | "id": "sample@example.com" 10 | } 11 | }, 12 | "permissions": [ 13 | "" 14 | ], 15 | "options_ui": { 16 | "page": "option/options.html" 17 | }, 18 | "unsupported_key": "dummy", 19 | "manifest_version": 2, 20 | "default_locale": "en" 21 | } 22 | -------------------------------------------------------------------------------- /test/libs/formatter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const test = require('ava') 3 | const proxyquire = require('proxyquire') 4 | const Formatter = require('../../libs/formatter') 5 | 6 | test('isValid', t => { 7 | const formatter = new Formatter('../fixtures/check_must_key.json') 8 | t.ok(formatter.isValid === false) 9 | }) 10 | 11 | test('checkUnsupportedKeyOrProps', t => { 12 | const formatter = new Formatter('../fixtures/manifest.json') 13 | t.ok(formatter.checkUnsupportedKeyOrProps() === false) 14 | }) 15 | 16 | test('checkRequiredKey', t => { 17 | const formatter = new Formatter('../fixtures/check_must_key.json') 18 | t.ok(formatter.checkRequiredKey() === false) 19 | t.ok(formatter.shouldContainKeys.indexOf('manifest_version') > -1 === true) 20 | t.ok(formatter.shouldContainKeys.indexOf('name') > -1 === true) 21 | t.ok(formatter.shouldContainKeys.indexOf('version') > -1 === true) 22 | 23 | const formatter2 = new Formatter('../fixtures/check_must_key_pass.json') 24 | t.ok(formatter2.checkRequiredKey() === true) 25 | t.is(formatter2.shouldContainKeys.length, 0) 26 | }) 27 | 28 | test('fillMustKey `version`', t => { 29 | const formatter = new Formatter('../fixtures/check_must_key.json') 30 | formatter.fillMustKey('version', '0.0.1') 31 | t.is(formatter.shouldContainKeys.indexOf('version') > -1, false) 32 | t.is(formatter.json.version, '0.0.1') 33 | 34 | const formatter2 = new Formatter('../fixtures/check_must_key.json') 35 | formatter2.fillMustKey({version: '0.0.1'}) 36 | t.is(formatter2.shouldContainKeys.indexOf('version') > -1, false) 37 | t.is(formatter2.json.version, '0.0.1') 38 | }) 39 | test('fillMustKey `manifest_version`', t => { 40 | const formatter = new Formatter('../fixtures/check_must_key.json') 41 | formatter.fillMustKey('manifest_version', '2') 42 | t.is(formatter.shouldContainKeys.indexOf('manifest_version') > -1, false) 43 | t.is(formatter.json.manifest_version, '2') 44 | 45 | const formatter2 = new Formatter('../fixtures/check_must_key.json') 46 | formatter2.fillMustKey({manifest_version: '2'}) 47 | t.is(formatter2.shouldContainKeys.indexOf('manifest_version') > -1, false) 48 | t.is(formatter2.json.manifest_version, '2') 49 | 50 | const formatter3 = new Formatter('../fixtures/check_must_key.json') 51 | formatter3.fillMustKey('manifest_version') 52 | t.is(formatter3.shouldContainKeys.indexOf('manifest_version') > -1, false) 53 | t.is(formatter3.json.manifest_version, '2') 54 | }) 55 | test('fillMustKey `name`', t => { 56 | const formatter = new Formatter('../fixtures/check_must_key.json') 57 | formatter.fillMustKey('name', 'test') 58 | t.is(formatter.shouldContainKeys.indexOf('name') > -1, false) 59 | t.is(formatter.json.name, 'test') 60 | 61 | const formatter2 = new Formatter('../fixtures/check_must_key.json') 62 | formatter2.fillMustKey({name: 'test'}) 63 | t.is(formatter2.shouldContainKeys.indexOf('name') > -1, false) 64 | t.is(formatter2.json.name, 'test') 65 | }) 66 | 67 | test('fillMustKeys', t => { 68 | const formatter = new Formatter('../fixtures/check_must_key.json') 69 | formatter.fillMustKey('name', 'test') 70 | formatter.fillMustKey('manifest_version') 71 | formatter.fillMustKey('version', '0.0.1') 72 | t.is(formatter.shouldContainKeys.length, 0) 73 | t.is(formatter.json.name, 'test') 74 | t.is(formatter.isValid, true) 75 | }) 76 | 77 | test('checkUnsupportedProps', t => { 78 | const formatter = new Formatter('../fixtures/check_must_key_pass.json') 79 | t.is(formatter.checkUnsupportedProps(), true) 80 | 81 | const formatter2 = new Formatter('../fixtures/manifest.json') 82 | t.is(formatter2.checkUnsupportedProps(), false) 83 | }) 84 | 85 | test('deleteUnsupportedProps', t => { 86 | const formatter = new Formatter('../fixtures/manifest.json') 87 | t.is(formatter.checkUnsupportedProps(), false) 88 | formatter.deleteUnsupportedProps() 89 | t.ok(formatter.checkUnsupportedProps()) 90 | }) 91 | 92 | test('deleteUnsupportedKey', t => { 93 | const formatter = new Formatter('../fixtures/unsupported_key.json') 94 | formatter.deleteUnsupportedKey() 95 | t.ok(formatter.isValid) 96 | }) 97 | 98 | test('Merge applications column from package.json', t => { 99 | const fsStub = {readFileSync: (path) => { 100 | if (path.match(/package\.json/)) { 101 | return JSON.stringify({ 102 | webextension: { 103 | applications: { 104 | gecko: { 105 | id: 'hoge@example.com' 106 | } 107 | } 108 | } 109 | }) 110 | } else { 111 | return require('fs').readFileSync(path) 112 | } 113 | }} 114 | const Formatter = proxyquire('../../libs/formatter', {fs: fsStub}) 115 | const formatter = new Formatter('../fixtures/check_must_key.json') 116 | t.is(formatter.json.applications.gecko.id, 'hoge@example.com') 117 | t.is(formatter.checkApplicationsKeyFormat(), true) 118 | 119 | const Formatter2 = proxyquire('../../libs/formatter', {fs: fsStub}) 120 | const formatter2 = new Formatter('../fixtures/check_must_key_pass.json') 121 | t.is(formatter2.json.applications.gecko.id, 'sample@example.com') 122 | t.is(formatter2.checkApplicationsKeyFormat(), true) 123 | }) 124 | 125 | test('General Check', t => { 126 | const formatter = new Formatter('../fixtures/manifest.json') 127 | t.is(formatter.isValid, false) 128 | formatter.fillMustKey('applications', 'sample-extension@example.com') 129 | formatter.fillMustKey('name', 'test') 130 | formatter.fillMustKey('manifest_version') 131 | formatter.fillMustKey('version', '0.0.1') 132 | t.is(formatter.isValid, false) 133 | formatter.deleteUnsupportedProps() 134 | t.is(formatter.isValid, false) 135 | formatter.deleteUnsupportedKey() 136 | t.ok(formatter.isValid) 137 | }) 138 | 139 | test('checkValidHostPattern', t => { 140 | // Patterns from https://developer.chrome.com/extensions/match_patterns 141 | const formatter = new Formatter('../fixtures/manifest.json') 142 | t.is(formatter.checkValidHostPattern('http:/bar'), false) 143 | t.is(formatter.checkValidHostPattern('foo://*'), false) 144 | t.is(formatter.checkValidHostPattern('http://*/*'), true) 145 | t.is(formatter.checkValidHostPattern('http://*/foo*'), true) 146 | t.is(formatter.checkValidHostPattern('https://*.google.com/foo*bar'), true) 147 | t.is(formatter.checkValidHostPattern('http://example.org/foo/bar.html'), true) 148 | t.is(formatter.checkValidHostPattern('file:///foo*'), true) 149 | t.is(formatter.checkValidHostPattern('http://127.0.0.1/*'), true) 150 | t.is(formatter.checkValidHostPattern('*://mail.google.com/*'), true) 151 | t.is(formatter.checkValidHostPattern(''), true) 152 | t.is(formatter.checkValidHostPattern('http://www.google.com'), false) 153 | t.is(formatter.checkValidHostPattern('http://*foo/bar'), false) 154 | t.is(formatter.checkValidHostPattern('http://foo.*.bar/baz'), false) 155 | }) 156 | --------------------------------------------------------------------------------