diff --git a/.eslintignore b/.eslintignore index e757e5af8..471e068bf 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,3 +5,8 @@ static/ flow-typed/ website/ build/ +*.md +*.lock +*.yaml +Dockerfile +*.rpi diff --git a/.eslintrc b/.eslintrc index 24369a333..74398c153 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,23 +1,27 @@ { "plugins": [ - "react", "babel", + "react", "flowtype", - "jest" + "jest", + "verdaccio", + "jsx-a11y" ], "extends": [ "eslint:recommended", "google", - "plugin:react/recommended", "plugin:flowtype/recommended", "plugin:jest/recommended", - "plugin:prettier/recommended" + "plugin:prettier/recommended", + "plugin:react/recommended", + "plugin:verdaccio/recommended", + "plugin:jsx-a11y/recommended" ], "settings": { "react": { - "pragma": "React", // Pragma to use - "version": "16.4.2", // React version - "flowVersion": "0.81.0" // Flow version + "pragma": "React", + "version": "16.4.2", + "flowVersion": "0.81.0" } }, "parser": "babel-eslint", @@ -41,20 +45,84 @@ "rules": { "babel/no-invalid-this": 1, "prettier/prettier": ["error", null, "@prettier"], - "no-useless-escape": 2, - "no-invalid-this": 0, "react/no-deprecated": 1, "react/jsx-no-target-blank": 1, - "handle-callback-err": 2, - "no-fallthrough": 2, - "no-new-require": 2, - "max-len": [2, 160], + "react/destructuring-assignment": ["error", "always"], + "react/forbid-component-props": ["warn", { "forbid": ["style"] }], + "react/no-this-in-sfc": ["warn"], + "react/no-unsafe": ["warn"], + "react/sort-comp": ["warn", { + "order": [ + "static-methods", + "lifecycle", + "render", + "everything-else", + "/^on.+$/", + "/^render.+$/" + ] + }], + "react/void-dom-elements-no-children": ["warn"], + "react/no-did-mount-set-state": ["error", "disallow-in-func"], + "react/jsx-wrap-multilines": ["error",{ + "declaration": "parens", + "assignment": "parens", + "return": "parens", + "arrow": "parens", + "condition": "parens", + "logical": "parens", + "prop": "parens" + }], + "react/jsx-boolean-value": ["error", "always"], + "react/jsx-closing-tag-location": ["error"], + "react/jsx-curly-spacing": ["error", "never"], + "react/jsx-equals-spacing": ["error", "never"], + "react/jsx-first-prop-new-line": ["error", "multiline-multiprop"], + "react/jsx-handler-names": ["warn"], + "react/jsx-indent": ["error", 2], + "react/jsx-indent-props": ["error", 2], + "react/jsx-key": ["error"], + "react/jsx-max-depth": ["error", { "max": 2}], + "react/jsx-max-props-per-line": ["error", {"maximum": 3, "when": "multiline" }], + "react/jsx-no-bind": ["error"], + "react/jsx-no-comment-textnodes": ["error"], + "react/jsx-no-duplicate-props": ["error"], + "react/jsx-no-literals": ["error"], + "react/jsx-no-undef": ["error"], + "react/jsx-one-expression-per-line": ["error", {"allow": "single-child"}], + "react/jsx-curly-brace-presence": ["error", { "props": "always", "children": "ignore" }], + "react/jsx-pascal-case": ["error"], + "react/jsx-props-no-multi-spaces": ["error"], + "react/jsx-sort-default-props": ["error"], + "react/jsx-sort-props": ["error"], + "react/no-string-refs": ["error"], + "react/no-danger-with-children": ["error"], + "react/jsx-tag-spacing": ["error", { + "closingSlash": "never", + "beforeSelfClosing": "always", + "afterOpening": "allow-multiline", + "beforeClosing": "allow" + }], + "react/prefer-es6-class": [ + 2, + "always" + ], + "semi": ["error"], + "comma-dangle": ["error"], "camelcase": 0, + "no-useless-escape": ["error"], + "no-invalid-this": 0, + "handle-callback-err": ["error"], + "no-fallthrough": ["error"], + "no-new-require": ["error"], + "max-len": ["error", 160], "require-jsdoc": 0, "valid-jsdoc": 0, "prefer-spread": 1, "prefer-rest-params": 1, "linebreak-style": 0, - "quote-props":["error", "as-needed"] + "quote-props":["error", "as-needed"], + "verdaccio/jsx-no-style": ["warn"], + "verdaccio/jsx-spread": ["warn"], + "jest/expect-expect": 0 } } diff --git a/.prettierrc b/.prettierrc index 1a23632dc..f235af833 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,7 +5,7 @@ "singleQuote": true, "requirePragma": true, "bracketSpacing": true, - "jsxBracketSameLine": false, + "jsxBracketSameLine": true, "trailingComma": "es5", "semi": true, "parser": "flow" diff --git a/.stylelintrc b/.stylelintrc index 74aa86c6d..afc8dce1c 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,8 +1,41 @@ { - "extends": "stylelint-config-recommended-scss", - "rules": { - "selector-pseudo-class-no-unknown": [true, { - "ignorePseudoClasses": ["/global/"] - }] - } + "processors": ["stylelint-processor-styled-components"], + "extends": [ + "stylelint-config-recommended" + ], + "rules": { + "at-rule-no-unknown": true, + "block-no-empty": true, + "color-named": "always-where-possible", + "comment-no-empty": true, + "declaration-block-no-duplicate-properties": [ + true, + { + ignore: ["consecutive-duplicates-with-different-values"] + } + ], + "declaration-block-no-shorthand-property-overrides": true, + "font-family-no-duplicate-names": true, + "color-no-invalid-hex": true, + "font-family-no-missing-generic-family-keyword": true, + "function-calc-no-unspaced-operator": true, + "function-linear-gradient-no-nonstandard-direction": true, + "keyframe-declaration-no-important": true, + "property-no-vendor-prefix": true, + "media-feature-name-no-unknown": true, + "no-descending-specificity": [true, { "severity": "warning" }], + "no-duplicate-at-import-rules": true, + "no-duplicate-selectors": true, + "no-empty-source": true, + "no-extra-semicolons": true, + "no-invalid-double-slash-comments": true, + "property-no-unknown": true, + "selector-pseudo-class-no-unknown": true, + "selector-pseudo-element-no-unknown": true, + "selector-type-no-unknown": [true, { "severity": "warning" }], + "string-no-newline": true, + "unit-no-unknown": true + } + } + diff --git a/package.json b/package.json index 8920103f1..204c30917 100644 --- a/package.json +++ b/package.json @@ -94,16 +94,18 @@ "emotion": "9.2.12", "enzyme": "3.6.0", "enzyme-adapter-react-16": "1.5.0", - "eslint": "5.6.0", - "eslint-config-google": "0.10.0", - "eslint-config-prettier": "3.1.0", + "eslint": "5.10.0", + "eslint-config-google": "0.11.0", + "eslint-config-prettier": "3.3.0", "eslint-loader": "2.1.1", "eslint-plugin-babel": "5.3.0", - "eslint-plugin-flowtype": "2.50.1", + "eslint-plugin-flowtype": "3.2.0", "eslint-plugin-import": "2.14.0", - "eslint-plugin-jest": "21.22.1", - "eslint-plugin-prettier": "2.6.2", + "eslint-plugin-jest": "22.1.2", + "eslint-plugin-jsx-a11y": "6.1.2", + "eslint-plugin-prettier": "3.0.0", "eslint-plugin-react": "7.11.1", + "eslint-plugin-verdaccio": "0.0.5", "file-loader": "2.0.0", "flow-bin": "0.81.0", "flow-runtime": "0.17.0", @@ -140,8 +142,11 @@ "source-map-loader": "0.2.4", "standard-version": "4.4.0", "style-loader": "0.23.0", - "stylelint": "9.5.0", + "stylelint": "9.9.0", + "stylelint-config-recommended": "2.1.0", "stylelint-config-recommended-scss": "3.2.0", + "stylelint-config-styled-components": "0.1.1", + "stylelint-processor-styled-components": "1.5.1", "stylelint-scss": "3.3.1", "stylelint-webpack-plugin": "0.10.5", "supertest": "3.3.0", @@ -173,15 +178,16 @@ "pretest": "npm run code:build", "test": "npm run test:unit", "test:clean": "npx jest --clearCache", - "test:unit": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC FORCE_COLOR=1 jest --config ./jest.config.js --maxWorkers 2", - "test:functional": "cross-env NODE_ENV=test jest --config ./test/jest.config.functional.js --testPathPattern ./test/functional/index*", + "test:unit": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC FORCE_COLOR=1 jest --config ./jest.config.js --maxWorkers 2 --passWithNoTests" , + "test:functional": "cross-env NODE_ENV=test jest --config ./test/jest.config.functional.js --testPathPattern ./test/functional/index* --passWithNoTests", "test:e2e": "cross-env BABEL_ENV=test jest --config ./test/jest.config.e2e.js", "test:size": "bundlesize", "test:all": "npm run build:webui && npm run test && npm run test:functional && npm run test:e2e && npm run test:size", "pre:ci": "npm run lint && npm run build:webui", "coverage:publish": "codecov", - "lint": "npm run flow && eslint . && npm run lint:css", - "lint:css": "stylelint 'src/**/*.scss' --syntax scss", + "lint": "npm run flow && npm run lint:js && npm run lint:css", + "lint:js": "eslint .", + "lint:css": "stylelint 'src/webui/**/styles.js'", "dev:start": "cross-env BABEL_ENV=registry babel-node src/lib/cli", "code:build": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --ignore src/webui/ --copy-files", "code:docker-build": "cross-env BABEL_ENV=registry-docker babel src/ --out-dir build/ --ignore src/webui/ --copy-files", @@ -205,14 +211,19 @@ } }, "lint-staged": { - "*.yaml": [ - "prettier --parser yaml --no-config --single-quote --write", - "git add" - ], - "*.js": [ - "eslint .", - "prettier --write", - "git add" + "linters": { + "*.yaml": [ + "prettier --parser yaml --no-config --single-quote --write", + "git add" + ], + "*": [ + "eslint .", + "prettier --write", + "git add" + ] + }, + "ignore": [ + "*.json" ] }, "bundlesize": [ diff --git a/src/api/endpoint/api/dist-tags.js b/src/api/endpoint/api/dist-tags.js index 436776e8d..91e92253f 100644 --- a/src/api/endpoint/api/dist-tags.js +++ b/src/api/endpoint/api/dist-tags.js @@ -17,7 +17,7 @@ export default function(route: Router, auth: IAuth, storage: IStorageHandler) { return next('route'); } - let tags = {}; + const tags = {}; tags[req.params.tag] = req.body; storage.mergeTags(req.params.package, tags, function(err) { if (err) { diff --git a/src/api/endpoint/api/publish.js b/src/api/endpoint/api/publish.js index 1347f905e..55cbd5173 100644 --- a/src/api/endpoint/api/publish.js +++ b/src/api/endpoint/api/publish.js @@ -46,7 +46,7 @@ export function publishPackage(storage: IStorageHandler, config: Config) { * Write tarball of stream data from package clients. */ const createTarball = function(filename: string, data, cb: Callback) { - let stream = storage.addTarball(packageName, filename); + const stream = storage.addTarball(packageName, filename); stream.on('error', function(err) { cb(err); }); @@ -74,7 +74,7 @@ export function publishPackage(storage: IStorageHandler, config: Config) { }; const afterChange = function(error, okMessage, metadata) { - let metadataCopy = { ...metadata }; + const metadataCopy = { ...metadata }; const { _attachments, versions } = metadataCopy; // old npm behavior, if there is no attachments diff --git a/src/api/endpoint/api/search.js b/src/api/endpoint/api/search.js index 4130337b9..c5872e4a4 100644 --- a/src/api/endpoint/api/search.js +++ b/src/api/endpoint/api/search.js @@ -56,7 +56,7 @@ export default function(route, auth, storage) { res.write('{"_updated":' + 99999); } - let stream = storage.search(req.query.startkey || 0, { req: req }); + const stream = storage.search(req.query.startkey || 0, { req: req }); stream.on('data', function each(pkg) { processing_pkgs++; diff --git a/src/api/middleware.js b/src/api/middleware.js index 3e4a736c1..9a474ae4e 100644 --- a/src/api/middleware.js +++ b/src/api/middleware.js @@ -82,7 +82,7 @@ export function expectJson(req: $RequestExtend, res: $ResponseExtend, next: $Nex export function antiLoop(config: Config) { return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) { if (req.headers.via != null) { - let arr = req.headers.via.split(','); + const arr = req.headers.via.split(','); for (let i = 0; i < arr.length; i++) { const m = arr[i].match(/\s*(\S+)\s+(\S+)/); @@ -167,12 +167,12 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti // logger req.log = logger.child({ sub: 'in' }); - let _auth = req.headers.authorization; + const _auth = req.headers.authorization; if (_.isNil(_auth) === false) { req.headers.authorization = ''; } - let _cookie = req.headers.cookie; + const _cookie = req.headers.cookie; if (_.isNil(_cookie) === false) { req.headers.cookie = ''; } @@ -195,7 +195,7 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti }); let bytesout = 0; - let _write = res.write; + const _write = res.write; res.write = function(buf) { bytesout += buf.length; /* eslint prefer-rest-params: "off" */ @@ -203,9 +203,9 @@ export function log(req: $RequestExtend, res: $ResponseExtend, next: $NextFuncti }; const log = function() { - let forwardedFor = req.headers['x-forwarded-for']; - let remoteAddress = req.connection.remoteAddress; - let remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress; + const forwardedFor = req.headers['x-forwarded-for']; + const remoteAddress = req.connection.remoteAddress; + const remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress; let message = "@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}'"; if (res._verdaccio_error) { message += ', error: @{!error}'; diff --git a/src/api/web/endpoint/package.js b/src/api/web/endpoint/package.js index dd0ff7b5e..0d3c85fdb 100644 --- a/src/api/web/endpoint/package.js +++ b/src/api/web/endpoint/package.js @@ -37,7 +37,7 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth) async function processPermissionsPackages(packages) { const permissions = []; - for (let pkg of packages) { + for (const pkg of packages) { try { if (await checkAllow(pkg.name, req.remote_user)) { permissions.push(pkg); diff --git a/src/api/web/index.js b/src/api/web/index.js index c19697879..fac81799c 100644 --- a/src/api/web/index.js +++ b/src/api/web/index.js @@ -62,7 +62,7 @@ module.exports = function(config, auth, storage) { router.get('/', function(req, res) { const base = combineBaseUrl(getWebProtocol(req.get(HEADERS.FORWARDED_PROTO), req.protocol), req.get('host'), config.url_prefix); - let webPage = template + const webPage = template .replace(/ToReplaceByVerdaccio/g, base) .replace(/ToReplaceByTitle/g, _.get(config, 'web.title') ? config.web.title : WEB_TITLE) .replace(/ToReplaceByScope/g, _.get(config, 'web.scope') ? config.web.scope : ''); diff --git a/src/lib/auth.js b/src/lib/auth.js index d5f02f4f5..bd154f419 100644 --- a/src/lib/auth.js +++ b/src/lib/auth.js @@ -128,12 +128,12 @@ class Auth implements IAuth { } add_user(user: string, password: string, cb: Callback) { - let self = this; - let plugins = this.plugins.slice(0); + const self = this; + const plugins = this.plugins.slice(0); this.logger.trace({ user }, 'add user @{user}'); (function next() { - let plugin = plugins.shift(); + const plugin = plugins.shift(); let method = 'adduser'; if (_.isFunction(plugin[method]) === false) { method = 'add_user'; @@ -161,9 +161,9 @@ class Auth implements IAuth { * Allow user to access a package. */ allow_access(packageName: string, user: RemoteUser, callback: Callback) { - let plugins = this.plugins.slice(0); + const plugins = this.plugins.slice(0); // $FlowFixMe - let pkg = Object.assign({ name: packageName }, getMatchedPackagesSpec(packageName, this.config.packages)); + const pkg = Object.assign({ name: packageName }, getMatchedPackagesSpec(packageName, this.config.packages)); const self = this; this.logger.trace({ packageName }, 'allow access for @{packageName}'); @@ -194,10 +194,10 @@ class Auth implements IAuth { * Allow user to publish a package. */ allow_publish(packageName: string, user: string, callback: Callback) { - let plugins = this.plugins.slice(0); + const plugins = this.plugins.slice(0); const self = this; // $FlowFixMe - let pkg = Object.assign({ name: packageName }, getMatchedPackagesSpec(packageName, this.config.packages)); + const pkg = Object.assign({ name: packageName }, getMatchedPackagesSpec(packageName, this.config.packages)); this.logger.trace({ packageName }, 'allow publish for @{packageName}'); (function next() { diff --git a/src/lib/config-utils.js b/src/lib/config-utils.js index 15b1e8340..e9d13720b 100644 --- a/src/lib/config-utils.js +++ b/src/lib/config-utils.js @@ -49,7 +49,7 @@ export function uplinkSanityCheck(uplinks: UpLinksConfList, users: any = BLACKLI const newUplinks = _.clone(uplinks); let newUsers = _.clone(users); - for (let uplink in newUplinks) { + for (const uplink in newUplinks) { if (Object.prototype.hasOwnProperty.call(newUplinks, uplink)) { if (_.isNil(newUplinks[uplink].cache)) { newUplinks[uplink].cache = true; @@ -73,7 +73,7 @@ export function sanityCheckNames(item: string, users: any) { export function sanityCheckUplinksProps(configUpLinks: any) { const uplinks = _.clone(configUpLinks); - for (let uplink in uplinks) { + for (const uplink in uplinks) { if (Object.prototype.hasOwnProperty.call(uplinks, uplink)) { assert(uplinks[uplink].url, 'CONFIG: no url for uplink: ' + uplink); assert(_.isString(uplinks[uplink].url), 'CONFIG: wrong url format for uplink: ' + uplink); @@ -98,7 +98,7 @@ export function hasProxyTo(pkg: string, upLink: string, packages: PackageList): } export function getMatchedPackagesSpec(pkgName: string, packages: PackageList): MatchedPackage { - for (let i in packages) { + for (const i in packages) { // $FlowFixMe if (minimatch.makeRe(i).exec(pkgName)) { return packages[i]; @@ -114,7 +114,7 @@ export function normalisePackageAccess(packages: PackageList): PackageList { normalizedPkgs['**'] = { access: [], publish: [] }; } - for (let pkg in packages) { + for (const pkg in packages) { if (Object.prototype.hasOwnProperty.call(packages, pkg)) { assert(_.isObject(packages[pkg]) && _.isArray(packages[pkg]) === false, `CONFIG: bad "'${pkg}'" package description (object expected)`); normalizedPkgs[pkg].access = normalizeUserList(packages[pkg].allow_access, packages[pkg].access); diff --git a/src/lib/config.js b/src/lib/config.js index 43d872b98..f5edd9f74 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -44,7 +44,7 @@ class Config implements AppConfig { this.storage = config.storage; this.plugins = config.plugins; - for (let configProp in config) { + for (const configProp in config) { if (self[configProp] == null) { self[configProp] = config[configProp]; } diff --git a/src/lib/local-storage.js b/src/lib/local-storage.js index 59bcd4068..679a2d2f4 100644 --- a/src/lib/local-storage.js +++ b/src/lib/local-storage.js @@ -61,7 +61,7 @@ class LocalStorage implements IStorage { * @return {Function} */ removePackage(name: string, callback: Callback) { - let storage: any = this._getLocalStorage(name); + const storage: any = this._getLocalStorage(name); if (_.isNil(storage)) { return callback(ErrorCode.getNotFound()); @@ -114,7 +114,7 @@ class LocalStorage implements IStorage { if (packageInfo.readme !== packageLocalJson.readme) { change = true; } - for (let versionId in packageInfo.versions) { + for (const versionId in packageInfo.versions) { if (_.isNil(packageLocalJson.versions[versionId])) { let version = packageInfo.versions[versionId]; @@ -132,7 +132,7 @@ class LocalStorage implements IStorage { // we do NOT overwrite any existing records if (_.isNil(packageLocalJson._distfiles[filename])) { - let hash: DistFile = (packageLocalJson._distfiles[filename] = { + const hash: DistFile = (packageLocalJson._distfiles[filename] = { url: version.dist.tarball, sha: version.dist.shasum, }); @@ -148,14 +148,14 @@ class LocalStorage implements IStorage { } } - for (let tag in packageInfo[DIST_TAGS]) { + for (const tag in packageInfo[DIST_TAGS]) { if (!packageLocalJson[DIST_TAGS][tag] || packageLocalJson[DIST_TAGS][tag] !== packageInfo[DIST_TAGS][tag]) { change = true; packageLocalJson[DIST_TAGS][tag] = packageInfo[DIST_TAGS][tag]; } } - for (let up in packageInfo._uplinks) { + for (const up in packageInfo._uplinks) { if (Object.prototype.hasOwnProperty.call(packageInfo._uplinks, up)) { const need_change = !isObject(packageLocalJson._uplinks[up]) || @@ -210,7 +210,7 @@ class LocalStorage implements IStorage { // if uploaded tarball has a different shasum, it's very likely that we have some kind of error if (isObject(metadata.dist) && _.isString(metadata.dist.tarball)) { - let tarball = metadata.dist.tarball.replace(/.*\//, ''); + const tarball = metadata.dist.tarball.replace(/.*\//, ''); if (isObject(data._attachments[tarball])) { if (_.isNil(data._attachments[tarball].shasum) === false && _.isNil(metadata.dist.shasum) === false) { @@ -220,7 +220,7 @@ class LocalStorage implements IStorage { } } - let currentDate = new Date().toISOString(); + const currentDate = new Date().toISOString(); // some old storage do not have this field #740 if (_.isNil(data.time)) { @@ -264,7 +264,7 @@ class LocalStorage implements IStorage { pkgName, (data, cb) => { /* eslint guard-for-in: 0 */ - for (let tag: string in tags) { + for (const tag: string in tags) { // this handle dist-tag rm command if (_.isNull(tags[tag])) { delete data[DIST_TAGS][tag]; @@ -318,14 +318,14 @@ class LocalStorage implements IStorage { this._updatePackage( name, (localData, cb) => { - for (let version in localData.versions) { + for (const version in localData.versions) { if (_.isNil(incomingPkg.versions[version])) { this.logger.info({ name: name, version: version }, 'unpublishing @{name}@@{version}'); delete localData.versions[version]; delete localData.time[version]; - for (let file in localData._attachments) { + for (const file in localData._attachments) { if (localData._attachments[file].version === version) { delete localData._attachments[file].version; } diff --git a/src/lib/logger.js b/src/lib/logger.js index 616fff40e..b1b4f721a 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -52,7 +52,7 @@ class VerdaccioRotatingFileStream extends Logger.RotatingFileStream { * @param {*} logs list of log configuration */ function setup(logs) { - let streams = []; + const streams = []; if (logs == null) { logs = [{ type: 'stdout', format: 'pretty', level: 'http' }]; } @@ -165,7 +165,7 @@ const levels = { }; let max = 0; -for (let l in levels) { +for (const l in levels) { if (Object.prototype.hasOwnProperty.call(levels, l)) { max = Math.max(max, l.length); } @@ -192,9 +192,9 @@ function fillInMsgTemplate(msg, obj, colors) { is_error = true; } - let _ref = name.split('.'); + const _ref = name.split('.'); for (let _i = 0; _i < _ref.length; _i++) { - let id = _ref[_i]; + const id = _ref[_i]; if (isObject(str) || Array.isArray(str)) { str = str[id]; } else { diff --git a/src/lib/metadata-utils.js b/src/lib/metadata-utils.js index 823c85768..c86c2da05 100644 --- a/src/lib/metadata-utils.js +++ b/src/lib/metadata-utils.js @@ -20,13 +20,13 @@ import type { Package } from '@verdaccio/types'; export function mergeVersions(local: Package, up: Package) { // copy new versions to a cache // NOTE: if a certain version was updated, we can't refresh it reliably - for (let i in up.versions) { + for (const i in up.versions) { if (_.isNil(local.versions[i])) { local.versions[i] = up.versions[i]; } } - for (let i in up[DIST_TAGS]) { + for (const i in up[DIST_TAGS]) { if (local[DIST_TAGS][i] !== up[DIST_TAGS][i]) { if (!local[DIST_TAGS][i] || semver.lte(local[DIST_TAGS][i], up[DIST_TAGS][i])) { local[DIST_TAGS][i] = up[DIST_TAGS][i]; diff --git a/src/lib/search.js b/src/lib/search.js index 73305c9e4..4c4652875 100644 --- a/src/lib/search.js +++ b/src/lib/search.js @@ -72,7 +72,7 @@ class Search implements IWebSearch { * Force a re-index. */ reindex() { - let self = this; + const self = this; this.storage.getLocalDatabase(function(error, packages) { if (error) { // that function shouldn't produce any diff --git a/src/lib/storage-utils.js b/src/lib/storage-utils.js index 68b42a64b..5db683b1a 100644 --- a/src/lib/storage-utils.js +++ b/src/lib/storage-utils.js @@ -101,7 +101,7 @@ export function cleanUpLinksRef(keepUpLinkData: boolean, result: Package): Packa propertyToKeep.push('_uplinks'); } - for (let i in result) { + for (const i in result) { if (propertyToKeep.indexOf(i) === -1) { // Remove sections like '_uplinks' from response delete result[i]; diff --git a/src/lib/storage.js b/src/lib/storage.js index 96c41fa2a..778d10c53 100644 --- a/src/lib/storage.js +++ b/src/lib/storage.js @@ -125,10 +125,10 @@ class Storage implements IStorageHandler { Used storages: local || uplink (just one) */ getTarball(name: string, filename: string) { - let readStream = new ReadTarball(); + const readStream = new ReadTarball(); (readStream: any).abort = function() {}; - let self = this; + const self = this; // if someone requesting tarball, it means that we should already have some // information about it, so fetching package info is unnecessary @@ -143,7 +143,7 @@ class Storage implements IStorageHandler { } // local reported 404 - let err404 = err; + const err404 = err; localStream.abort(); localStream = null; // we force for garbage collector self.localStorage.getPackageMetadata(name, (err, info: Package) => { @@ -180,7 +180,7 @@ class Storage implements IStorageHandler { function serveFile(file: DistFile) { let uplink: any = null; - for (let uplinkId in self.uplinks) { + for (const uplinkId in self.uplinks) { if (self.uplinks[uplinkId].isUplinkValid(file.url)) { uplink = self.uplinks[uplinkId]; } @@ -205,7 +205,7 @@ class Storage implements IStorageHandler { let on_open = function() { // prevent it from being called twice on_open = function() {}; - let rstream2 = uplink.fetchTarball(file.url); + const rstream2 = uplink.fetchTarball(file.url); rstream2.on('error', function(err) { if (savestream) { savestream.abort(); @@ -302,9 +302,9 @@ class Storage implements IStorageHandler { * @return {Stream} */ search(startkey: string, options: any) { - let self = this; + const self = this; // stream to write a tarball - let stream: any = new Stream.PassThrough({ objectMode: true }); + const stream: any = new Stream.PassThrough({ objectMode: true }); async.eachSeries( Object.keys(this.uplinks), @@ -314,7 +314,7 @@ class Storage implements IStorageHandler { return cb(); } // search by keyword for each uplink - let lstream: IUploadTarball = self.uplinks[up_name].search(options); + const lstream: IUploadTarball = self.uplinks[up_name].search(options); // join streams lstream.pipe( stream, @@ -338,7 +338,7 @@ class Storage implements IStorageHandler { // executed after all series function() { // attach a local search results - let lstream: IReadTarball = self.localStorage.search(startkey, options); + const lstream: IReadTarball = self.localStorage.search(startkey, options); stream.abort = function() { lstream.abort(); }; @@ -361,13 +361,13 @@ class Storage implements IStorageHandler { * @param {*} callback */ getLocalDatabase(callback: Callback) { - let self = this; + const self = this; this.localStorage.localData.get((err, locals) => { if (err) { callback(err); } - let packages = []; + const packages = []; const getPackage = function(itemPkg) { self.localStorage.getPackageMetadata(locals[itemPkg], function(err, info) { if (_.isNil(err)) { @@ -416,7 +416,7 @@ class Storage implements IStorageHandler { packageInfo = generatePackageTemplate(name); } - for (let uplink in this.uplinks) { + for (const uplink in this.uplinks) { if (hasProxyTo(name, uplink, this.config.packages) && hasToLookIntoUplinks) { upLinks.push(this.uplinks[uplink]); } @@ -426,7 +426,7 @@ class Storage implements IStorageHandler { upLinks, (upLink, cb) => { const _options = Object.assign({}, options); - let upLinkMeta = packageInfo._uplinks[upLink.upname]; + const upLinkMeta = packageInfo._uplinks[upLink.upname]; if (isObject(upLinkMeta)) { const fetched = upLinkMeta.fetched; @@ -511,7 +511,7 @@ class Storage implements IStorageHandler { * @private */ _updateVersionsHiddenUpLink(versions: Versions, upLink: IProxy) { - for (let i in versions) { + for (const i in versions) { if (Object.prototype.hasOwnProperty.call(versions, i)) { const version = versions[i]; diff --git a/src/lib/up-storage.js b/src/lib/up-storage.js index 9f649528f..20b227a5e 100644 --- a/src/lib/up-storage.js +++ b/src/lib/up-storage.js @@ -102,7 +102,7 @@ class ProxyStorage implements IProxy { let json; if (this._statusCheck() === false) { - let streamRead = new Stream.Readable(); + const streamRead = new Stream.Readable(); process.nextTick(function() { if (cb) { @@ -117,8 +117,8 @@ class ProxyStorage implements IProxy { return streamRead; } - let self = this; - let headers = this._setHeaders(options); + const self = this; + const headers = this._setHeaders(options); this._addProxyHeaders(options.req, headers); this._overrideWithUplinkConfigHeaders(headers); @@ -140,7 +140,7 @@ class ProxyStorage implements IProxy { headers['Content-Type'] = headers['Content-Type'] || HEADERS.JSON; } - let requestCallback = cb + const requestCallback = cb ? function(err, res, body) { let error; const responseLength = err ? 0 : body.length; @@ -373,7 +373,7 @@ class ProxyStorage implements IProxy { // add/override headers specified in the config /* eslint guard-for-in: 0 */ - for (let key in this.config.headers) { + for (const key in this.config.headers) { headers[key] = this.config.headers[key]; } } @@ -499,7 +499,7 @@ class ProxyStorage implements IProxy { }, }); - let parsePackage = pkg => { + const parsePackage = pkg => { if (isObject(pkg)) { transformStream.emit('data', pkg); } @@ -614,7 +614,7 @@ class ProxyStorage implements IProxy { */ _setupProxy(hostname: string, config: UpLinkConf, mainconfig: Config, isHTTPS: boolean) { let noProxyList; - let proxy_key: string = isHTTPS ? 'https_proxy' : 'http_proxy'; + const proxy_key: string = isHTTPS ? 'https_proxy' : 'http_proxy'; // get http_proxy and no_proxy configs if (proxy_key in config) { diff --git a/src/lib/uplink-util.js b/src/lib/uplink-util.js index 9364b8512..6cb6049df 100644 --- a/src/lib/uplink-util.js +++ b/src/lib/uplink-util.js @@ -13,7 +13,7 @@ import type { IProxy, ProxyList } from '../../types'; export function setupUpLinks(config: Config): ProxyList { const uplinks: ProxyList = {}; - for (let uplinkName in config.uplinks) { + for (const uplinkName in config.uplinks) { if (Object.prototype.hasOwnProperty.call(config.uplinks, uplinkName)) { // instance for each up-link definition const proxy: IProxy = new ProxyStorage(config.uplinks[uplinkName], config); @@ -27,7 +27,7 @@ export function setupUpLinks(config: Config): ProxyList { } export function updateVersionsHiddenUpLink(versions: Versions, upLink: IProxy) { - for (let i in versions) { + for (const i in versions) { if (Object.prototype.hasOwnProperty.call(versions, i)) { const version = versions[i]; diff --git a/src/lib/utils.js b/src/lib/utils.js index d34d87394..d342010ca 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -137,7 +137,7 @@ export function extractTarballFromUrl(url: string) { * @return {String} a filtered package */ export function convertDistRemoteToLocalTarballUrls(pkg: Package, req: $Request, urlPrefix: string | void) { - for (let ver in pkg.versions) { + for (const ver in pkg.versions) { if (Object.prototype.hasOwnProperty.call(pkg.versions, ver)) { const distName = pkg.versions[ver].dist; @@ -194,7 +194,7 @@ export function getVersion(pkg: Package, version: any) { try { version = semver.parse(version, true); - for (let versionItem in pkg.versions) { + for (const versionItem in pkg.versions) { // $FlowFixMe if (version.compare(semver.parse(versionItem, true)) === 0) { return pkg.versions[versionItem]; @@ -277,7 +277,7 @@ export function normalizeDistTags(pkg: Package) { } } - for (let tag in pkg[DIST_TAGS]) { + for (const tag in pkg[DIST_TAGS]) { if (_.isArray(pkg[DIST_TAGS][tag])) { if (pkg[DIST_TAGS][tag].length) { // sort array @@ -324,7 +324,7 @@ export function parseInterval(interval: any): number { let last_suffix = Infinity; interval.split(/\s+/).forEach(function(x) { if (!x) return; - let m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/); + const m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/); if (!m || parseIntervalTable[m[4]] >= last_suffix || (m[4] === '' && last_suffix !== Infinity)) { throw Error('invalid interval: ' + interval); } diff --git a/src/webui/.eslintrc b/src/webui/.eslintrc index eb219c736..507caf85a 100644 --- a/src/webui/.eslintrc +++ b/src/webui/.eslintrc @@ -10,6 +10,7 @@ }, "rules": { "require-jsdoc": 0, + "camelcase": ["error"], "no-console": [ 1, { @@ -24,14 +25,6 @@ "vars": "all", "args": "all" } - ], - "comma-dangle": 0, - "semi": 1, - "react/no-danger-with-children": 1, - "react/no-string-refs": 1, - "react/prefer-es6-class": [ - 2, - "always" ] } } diff --git a/src/webui/app.js b/src/webui/app.js index 24f593df3..3d0edaed9 100644 --- a/src/webui/app.js +++ b/src/webui/app.js @@ -36,15 +36,16 @@ export default class App extends Component { // eslint-disable-next-line no-unused-vars componentDidUpdate(_, prevState) { - if (prevState.isUserLoggedIn !== this.state.isUserLoggedIn) { + const { isUserLoggedIn } = this.state; + if (prevState.isUserLoggedIn !== isUserLoggedIn) { this.loadPackages(); } } - loadLogo = async () => { + loadLogo = async () => { const logoUrl = await logo(); - this.setState({ - logoUrl + this.setState({ + logoUrl, }); } @@ -57,7 +58,7 @@ export default class App extends Component { } else { this.setState({ user: { username, token }, - isUserLoggedIn: true + isUserLoggedIn: true, }); } } @@ -66,13 +67,13 @@ export default class App extends Component { try { this.req = await API.request('packages', 'GET'); this.setState({ - packages: this.req, - isLoading: false + packages: this.req, + isLoading: false, }); } catch (error) { this.handleShowAlertDialog({ title: 'Warning', - message: `Unable to load package list: ${error.message}` + message: `Unable to load package list: ${error.message}`, }); this.setLoading(false); } @@ -80,7 +81,7 @@ export default class App extends Component { setLoading = isLoading => ( this.setState({ - isLoading + isLoading, }) ) @@ -88,10 +89,10 @@ export default class App extends Component { * Toggles the login modal * Required by:
*/ - toggleLoginModal = () => { + handleToggleLoginModal = () => { this.setState((prevState) => ({ showLoginModal: !prevState.showLoginModal, - error: {} + error: {}, })); } @@ -99,7 +100,7 @@ export default class App extends Component { * handles login * Required by:
*/ - doLogin = async (usernameValue, passwordValue) => { + handleDoLogin = async (usernameValue, passwordValue) => { const { username, token, error } = await makeLogin( usernameValue, passwordValue @@ -114,7 +115,7 @@ export default class App extends Component { if (error) { this.setState({ user: {}, - error + error, }); } } @@ -126,7 +127,7 @@ export default class App extends Component { token, }, isUserLoggedIn: true, // close login modal after successful login - showLoginModal: false // set isUserLoggedIn to true + showLoginModal: false, // set isUserLoggedIn to true }); } /** @@ -138,36 +139,10 @@ export default class App extends Component { storage.removeItem('token'); this.setState({ user: {}, - isUserLoggedIn: false + isUserLoggedIn: false, }); } - renderHeader = () => { - const { logoUrl, user, scope } = this.state; - return ( -
- ); - } - - renderLoginModal = () => { - const { error, showLoginModal } = this.state; - return ( - - ); - } - render() { const { isLoading, isUserLoggedIn, packages } = this.state; return ( @@ -187,4 +162,30 @@ export default class App extends Component { ); } + + renderLoginModal = () => { + const { error, showLoginModal } = this.state; + return ( + + ); + } + + renderHeader = () => { + const { logoUrl, user, scope } = this.state; + return ( +
+ ); + } } diff --git a/src/webui/components/AutoComplete/index.js b/src/webui/components/AutoComplete/index.js index 2203cd8bd..52ab90cdd 100644 --- a/src/webui/components/AutoComplete/index.js +++ b/src/webui/components/AutoComplete/index.js @@ -19,7 +19,6 @@ const renderInputComponent = (inputProps): Node => { const { ref, startAdornment, disableUnderline, onKeyDown, ...others } = inputProps; return ( { ref(node); @@ -28,6 +27,7 @@ const renderInputComponent = (inputProps): Node => { disableUnderline, onKeyDown, }} + fullWidth={true} {...others} /> ); @@ -39,15 +39,15 @@ const renderSuggestion = (suggestion, { query, isHighlighted }): Node => { const matches = match(suggestion.name, query); const parts = parse(suggestion.name, matches); return ( - +
{parts.map((part, index) => { return part.highlight ? ( - + {part.text} ) : ( - + {part.text} ); @@ -59,7 +59,7 @@ const renderSuggestion = (suggestion, { query, isHighlighted }): Node => { const renderMessage = (message): Node => { return ( - +
{message}
); @@ -96,30 +96,32 @@ const AutoComplete = ({ onSuggestionsFetchRequested: onSuggestionsFetch, onSuggestionsClearRequested: onCleanSuggestions, }; + const inputProps = { + value, + onChange, + placeholder, + startAdornment, + disableUnderline, + color, + onKeyDown, + onBlur, + }; + + // this format avoid arrow function eslint rule + function renderSuggestionsContainer({ containerProps, children, query }) { + return ( + + {suggestionsLoaded && children === null && query && renderMessage(SUGGESTIONS_RESPONSE.NO_RESULT)} + {suggestionsLoading && query && renderMessage(SUGGESTIONS_RESPONSE.LOADING)} + {suggestionsError && renderMessage(SUGGESTIONS_RESPONSE.FAILURE)} + {children} + + ); + } + return ( - ( - - {suggestionsLoaded && children === null && query && renderMessage(SUGGESTIONS_RESPONSE.NO_RESULT)} - {suggestionsLoading && query && renderMessage(SUGGESTIONS_RESPONSE.LOADING)} - {suggestionsError && renderMessage(SUGGESTIONS_RESPONSE.FAILURE)} - {children} - - )} - onSuggestionSelected={onClick} - /> + ); }; diff --git a/src/webui/components/CopyToClipBoard/index.js b/src/webui/components/CopyToClipBoard/index.js index 6ba1f742a..4600e8a3b 100644 --- a/src/webui/components/CopyToClipBoard/index.js +++ b/src/webui/components/CopyToClipBoard/index.js @@ -29,15 +29,20 @@ const copyToClipBoardUtility = (str: string) => (event: SyntheticEvent ( - - {text} - +const CopyToClipBoard = ({ text }: IProps): Node => { + const renderToolTipFileCopy = () => ( + - -); + ); + return ( + + {text} + {renderToolTipFileCopy()} + + ); +}; export default CopyToClipBoard; diff --git a/src/webui/components/Footer/index.js b/src/webui/components/Footer/index.js index 52448245f..331c835ca 100644 --- a/src/webui/components/Footer/index.js +++ b/src/webui/components/Footer/index.js @@ -8,32 +8,49 @@ import type { Element } from 'react'; import { version } from '../../../../package.json'; import { Wrapper, Left, Right, Earth, Flags, Love, Flag, Logo, Inner, ToolTip } from './styles'; +import { goToVerdaccioWebsite } from '../../utils/windows.js'; + +const renderTooltip = () => ( + + + + + + + + + + + + +); +const POWERED_LABEL = 'Powered by'; +const MADEWITH_LABEL = ' Made with'; +const ON_LABEL = 'on'; +const HEARTH_EMOJI = '♥'; + +const renderRight = () => ( + + {POWERED_LABEL} + + {`/ ${version}`} + +); + +const renderLeft = () => ( + + {MADEWITH_LABEL} + {HEARTH_EMOJI} + {ON_LABEL} + {renderTooltip()} + +); const Footer = (): Element => ( - - Made with - - on - - - - - - - - - - - - - - - Powered by - window.open('http://www.verdaccio.org/', '_blank')} /> - {`/ ${version}`} - + {renderLeft()} + {renderRight()} ); diff --git a/src/webui/components/Footer/styles.js b/src/webui/components/Footer/styles.js index 82938d428..846c2da8b 100644 --- a/src/webui/components/Footer/styles.js +++ b/src/webui/components/Footer/styles.js @@ -37,7 +37,6 @@ export const Inner = styled.div` export const Left = styled.div` && { - display: flex; align-items: center; display: none; ${mq.medium(css` @@ -61,7 +60,7 @@ export const ToolTip = styled.span` export const Earth = styled(Icon)` && { - padding 0 10px; + padding: 0 10px; } `; @@ -105,4 +104,4 @@ export const Flag = styled(Icon)` } `; -export const Logo = styled(Flag)``; +export const Logo = Flag; diff --git a/src/webui/components/Header/index.js b/src/webui/components/Header/index.js index 3bba1afaf..278e7ddd5 100644 --- a/src/webui/components/Header/index.js +++ b/src/webui/components/Header/index.js @@ -25,6 +25,7 @@ import Label from '../Label'; import Search from '../Search'; import { IProps, IState } from './types'; +import type { ToolTipType } from './types'; import { Greetings, NavBar, InnerNavBar, MobileNavBar, InnerMobileNavBar, LeftSide, RightSide, IconSearchButton, SearchWrapper } from './styles'; class Header extends Component { @@ -39,18 +40,11 @@ class Header extends Component { super(props); this.state = { openInfoDialog: false, - registryUrl: '', + registryUrl: getRegistryURL(), showMobileNavBar: false, }; } - componentDidMount() { - const registryUrl = getRegistryURL(); - this.setState({ - registryUrl, - }); - } - /** * opens popover menu for logged in user. */ @@ -91,17 +85,19 @@ class Header extends Component { * close/open popover menu for logged in users. */ handleToggleLogin = () => { + const { onToggleLoginModal } = this.props; this.setState( { anchorEl: null, }, - this.props.toggleLoginModal + onToggleLoginModal ); }; handleToggleMNav = () => { + const { showMobileNavBar } = this.state; this.setState({ - showMobileNavBar: !this.state.showMobileNavBar, + showMobileNavBar: !showMobileNavBar, }); }; @@ -115,7 +111,7 @@ class Header extends Component { const { withoutSearch = false } = this.props; return ( - + {!withoutSearch && ( @@ -127,71 +123,94 @@ class Header extends Component { ); }; - renderRightSide = (): Node => { - const { username = '', withoutSearch = false } = this.props; - const installationLink = 'https://verdaccio.org/docs/en/installation'; - return ( - - {!withoutSearch && ( - - - - - - )} - - + renderToolTipIcon = (title: string, type: ToolTipType) => { + let content; + switch (type) { + case 'help': + content = ( + - - - + ); + break; + case 'info': + content = ( + - + ); + break; + case 'search': + content = ( + + + + ); + break; + } + return ( + + {content} + + ); + }; + + renderRightSide = (): Node => { + const { username = '', withoutSearch = false } = this.props; + return ( + + {!withoutSearch && this.renderToolTipIcon('Search packages', 'search')} + {this.renderToolTipIcon('Documentation', 'help')} + {this.renderToolTipIcon('Registry Information', 'info')} {username ? ( this.renderMenu() ) : ( - )} ); }; + renderGreetings = () => { + const { username = '' } = this.props; + return ( + <> + {`Hi,`} +