From 40febb5cf6160094bcb85d892948ebc21cf2b801 Mon Sep 17 00:00:00 2001 From: Viktor Varland Date: Fri, 7 Jun 2019 18:11:13 +0200 Subject: [PATCH] feat: add validations for dhis2 package.json files (#47) * feat: add validations for dhis2 package.json files * fix: clean up code a bit * fix: only do husky checks on root packages * feat: add fine-grained style setup * feat: check for publish access and autofix if possible * chore: run pkg check * refactor: minimize the amount of json parsing * refactor: format json when writing * chore: trigger apply * feat: add validate command * fix: do not add publish config to root dir * refactor: remove package subcommand * fix: use specific options for validation * refactor: add space where needed * refactor: add groups directory * refactor: use the groups directory for config * refactor: add all to the groups * refactor: use singular for consistency * refactor: use force to overwrite configs --- .commitlintrc.js | 3 + .dependabot/config.yml | 18 ++ config/eslint.config.js => .eslintrc.js | 0 config/github/dependabot.yml | 18 ++ config/github/stale.yml | 1 + config/{ => js}/browserslist.config.rc | 0 config/js/eslint.config.js | 39 +++++ config/{ => js}/prettier.config.js | 0 package.json | 8 +- src/cmds/js_cmds/apply.js | 3 + src/cmds/js_cmds/check.js | 3 + src/cmds/js_cmds/install.js | 6 +- src/cmds/setup.js | 24 +++ src/cmds/validate.js | 84 ++++++++++ src/config.js | 76 +++++---- src/files.js | 7 + src/groups.js | 73 ++++++++ src/tools/git/index.js | 1 + src/tools/js/eslint.js | 6 +- src/tools/js/index.js | 7 +- src/tools/js/prettier.js | 2 +- src/tools/package/index.js | 156 ++++++++++++++++++ src/tools/package/rules/husky-hooks.js | 81 +++++++++ .../rules/pin-dhis2-package-versions.js | 53 ++++++ .../package/rules/public-publish-access.js | 71 ++++++++ yarn.lock | 25 ++- 26 files changed, 719 insertions(+), 46 deletions(-) create mode 100644 .commitlintrc.js create mode 100644 .dependabot/config.yml rename config/eslint.config.js => .eslintrc.js (100%) create mode 100644 config/github/dependabot.yml create mode 100644 config/github/stale.yml rename config/{ => js}/browserslist.config.rc (100%) create mode 100644 config/js/eslint.config.js rename config/{ => js}/prettier.config.js (100%) create mode 100644 src/cmds/setup.js create mode 100644 src/cmds/validate.js create mode 100644 src/groups.js create mode 100644 src/tools/package/index.js create mode 100644 src/tools/package/rules/husky-hooks.js create mode 100644 src/tools/package/rules/pin-dhis2-package-versions.js create mode 100644 src/tools/package/rules/public-publish-access.js diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 00000000..3e16e7f1 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +} diff --git a/.dependabot/config.yml b/.dependabot/config.yml new file mode 100644 index 00000000..0746297b --- /dev/null +++ b/.dependabot/config.yml @@ -0,0 +1,18 @@ +version: 1 + +update_configs: + - package_manager: "javascript" + directory: "/" + update_schedule: "live" + default_assignees: + - @dhis2/front-end + version_requirement_updates: "increase_versions" + - package_manager: "java:maven" + directory: "/" + update_schedule: "monthly" + - package_manager: "docker" + directory: "/" + update_schedule: "weekly" + - package_manager: "submodules" + directory: "/" + update_schedule: "weekly" diff --git a/config/eslint.config.js b/.eslintrc.js similarity index 100% rename from config/eslint.config.js rename to .eslintrc.js diff --git a/config/github/dependabot.yml b/config/github/dependabot.yml new file mode 100644 index 00000000..0746297b --- /dev/null +++ b/config/github/dependabot.yml @@ -0,0 +1,18 @@ +version: 1 + +update_configs: + - package_manager: "javascript" + directory: "/" + update_schedule: "live" + default_assignees: + - @dhis2/front-end + version_requirement_updates: "increase_versions" + - package_manager: "java:maven" + directory: "/" + update_schedule: "monthly" + - package_manager: "docker" + directory: "/" + update_schedule: "weekly" + - package_manager: "submodules" + directory: "/" + update_schedule: "weekly" diff --git a/config/github/stale.yml b/config/github/stale.yml new file mode 100644 index 00000000..0d0b1c99 --- /dev/null +++ b/config/github/stale.yml @@ -0,0 +1 @@ +_extends: .github diff --git a/config/browserslist.config.rc b/config/js/browserslist.config.rc similarity index 100% rename from config/browserslist.config.rc rename to config/js/browserslist.config.rc diff --git a/config/js/eslint.config.js b/config/js/eslint.config.js new file mode 100644 index 00000000..74c8bab3 --- /dev/null +++ b/config/js/eslint.config.js @@ -0,0 +1,39 @@ +const SEVERITY = 2 + +module.exports = { + root: true, + + parser: 'babel-eslint', + + env: { + browser: true, + node: true, + jest: true, + }, + + parserOptions: { + // latest standard is ok, eq. to 9 + ecmaVersion: 2018, + ecmaFeatures: { + jsx: true, + modules: true, + }, + }, + + rules: { + 'max-params': [ + SEVERITY, + { + max: 3, + }, + ], + 'prefer-const': [ + SEVERITY, + { + destructuring: 'any', + ignoreReadBeforeAssign: false, + }, + ], + 'no-mixed-spaces-and-tabs': [SEVERITY], + }, +} diff --git a/config/prettier.config.js b/config/js/prettier.config.js similarity index 100% rename from config/prettier.config.js rename to config/js/prettier.config.js diff --git a/package.json b/package.json index 43efb8c5..0bcf51ea 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,12 @@ "@commitlint/read": "^7.6.0", "@dhis2/cli-helpers-engine": "1.2.1", "babel-eslint": "^10.0.1", + "chalk": "^2.4.2", "eslint": "^5.16.0", + "fs-extra": "^8.0.1", "perfy": "^1.1.5", "prettier": "^1.15.3", + "semver": "^6.0.0", "yargs": "^13.1.0" }, "devDependencies": { @@ -30,7 +33,10 @@ "husky": { "hooks": { "commit-msg": "./bin/d2-style commit check", - "pre-commit": "./bin/d2-style js apply" + "pre-commit": "./bin/d2-style validate js package --fix" } + }, + "publishConfig": { + "access": "public" } } diff --git a/src/cmds/js_cmds/apply.js b/src/cmds/js_cmds/apply.js index 3e5c3d5f..fcd39268 100644 --- a/src/cmds/js_cmds/apply.js +++ b/src/cmds/js_cmds/apply.js @@ -38,6 +38,9 @@ exports.handler = argv => { report.summarize() if (report.hasViolations) { + log.error( + `${report.violations.length} file(s) violate the code standard.` + ) process.exit(1) } diff --git a/src/cmds/js_cmds/check.js b/src/cmds/js_cmds/check.js index e9f27a0f..c70d3fcd 100644 --- a/src/cmds/js_cmds/check.js +++ b/src/cmds/js_cmds/check.js @@ -28,6 +28,9 @@ exports.handler = argv => { report.summarize() if (report.hasViolations) { + log.error( + `${report.violations.length} file(s) violate the code standard.` + ) process.exit(1) } } diff --git a/src/cmds/js_cmds/install.js b/src/cmds/js_cmds/install.js index 8d06e1bf..1c364770 100644 --- a/src/cmds/js_cmds/install.js +++ b/src/cmds/js_cmds/install.js @@ -1,8 +1,10 @@ -const configure = require('../../config') +const { configure, cleanup } = require('../../config') exports.command = 'install' exports.describe = 'Install javascript tool configurations for use by IDE plugins' exports.handler = () => { - configure(process.cwd()) + const root = process.cwd() + cleanup(root) + configure(root, 'js', true) } diff --git a/src/cmds/setup.js b/src/cmds/setup.js new file mode 100644 index 00000000..714ce037 --- /dev/null +++ b/src/cmds/setup.js @@ -0,0 +1,24 @@ +const log = require('@dhis2/cli-helpers-engine').reporter + +const { configure } = require('../config') + +exports.command = 'setup [group..]' + +exports.describe = 'Setup DHIS2 configurations for a/all group(s)' + +exports.builder = { + force: { + describe: 'Overwrites existing configuration', + type: 'boolean', + default: 'false', + }, +} + +exports.handler = argv => { + log.info('Setting up configuration files...') + const { force, group } = argv + + const root = process.cwd() + + configure(root, group, force) +} diff --git a/src/cmds/validate.js b/src/cmds/validate.js new file mode 100644 index 00000000..c4af797f --- /dev/null +++ b/src/cmds/validate.js @@ -0,0 +1,84 @@ +const fs = require('fs-extra') +const path = require('path') + +const log = require('@dhis2/cli-helpers-engine').reporter + +const { selectFiles } = require('../files.js') +const { stageFiles } = require('../git-files.js') +const { groups, isValidGroup } = require('../groups.js') + +exports.command = 'validate [group..]' + +exports.describe = 'Validate DHIS2 configurations for a/all group(s)' + +exports.builder = { + fix: { + describe: 'Fix problems that can be fixed automatically', + type: 'boolean', + default: 'false', + }, + stage: { + describe: + 'By default the changed files are staged automatically, use `--no-stage` to avoid staging files automatically.', + type: 'boolean', + default: 'true', + }, + all: { + describe: + 'Default behaviour is to only format files staged with Git, use this option to format all files.', + type: 'boolean', + default: 'false', + }, +} + +exports.handler = argv => { + const { fix, group, stage, all } = argv + const root = process.cwd() + + const files = selectFiles(null, all, root) + + const reports = runners(files, group, fix) + + let violations = 0 + const fixedFiles = [] + + for (const report of reports) { + report.summarize() + + if (report.hasViolations) { + violations += report.violations.length + } + + if (fix) { + const fixed = report.fix() + fixedFiles.push(...fixed) + } + } + + if (violations > 0) { + log.error(`${violations} file(s) violate the code standard.`) + process.exit(1) + } + + if (stage && fixedFiles.length > 0) { + stageFiles(fixedFiles, root) + } +} + +function runners(files, group = ['all'], fix = false) { + const validGroups = group.filter(isValidGroup) + + if (validGroups.length === 0) { + log.warn( + `No valid group selected, use one of: ${Object.keys(groups).join( + ', ' + )}` + ) + } else { + log.info(`Running validations for group(s): ${validGroups.join(', ')}`) + } + + return validGroups + .map(g => groups[g].tools.map(fn => fn(files, fix))) + .reduce((a, b) => a.concat(b), []) +} diff --git a/src/config.js b/src/config.js index 13ae0806..9e61b094 100644 --- a/src/config.js +++ b/src/config.js @@ -1,9 +1,10 @@ const path = require('path') -const fs = require('fs') +const fs = require('fs-extra') const log = require('@dhis2/cli-helpers-engine').reporter const { readFile, writeFile } = require('./files.js') +const { groups, isValidGroup } = require('./groups.js') function wipeConfigProperties(repo) { const pkgPath = path.join(repo, 'package.json') @@ -50,43 +51,50 @@ function wipeConfigFiles(repo) { }) } -function cleanup(repo) { - wipeConfigProperties(repo) - wipeConfigFiles(repo) -} - -function copy(from, to) { +function copy(from, to, overwrite = true) { try { - fs.copyFileSync(from, to) - log.debug('copied cfg successfully: ' + to) + fs.ensureDirSync(path.dirname(to)) + fs.copySync(from, to, { overwrite }) + if (fs.existsSync(to) && overwrite) { + log.info( + `Installing configuration file: ${path.relative( + process.cwd(), + to + )}` + ) + } else { + log.warn( + `Skip existing configuration file: ${path.relative( + process.cwd(), + to + )}` + ) + } } catch (err) { - log.error('failed to copy cfg to: ' + to, err) + log.error(`Failed to install configuration file: ${to}`, err) } } -function configure(repo) { - // first house keeping - cleanup(repo) +module.exports = { + configure: function configure(repo, group = ['all'], overwrite) { + const validGroups = group.filter(isValidGroup) - // then fun stuff - const cfgs = [ - [ - path.join(__dirname, '../config/prettier.config.js'), - path.join(repo, '.prettierrc.js'), - ], - [ - path.join(__dirname, '../config/browserslist.config.rc'), - path.join(repo, '.browserslistrc'), - ], - [ - path.join(__dirname, '../config/editorconfig.config.rc'), - path.join(repo, '.editorconfig'), - ], - [ - path.join(__dirname, '../config/eslint.config.js'), - path.join(repo, '.eslintrc.js'), - ], - ].map(cfg => copy(cfg[0], cfg[1])) -} + if (validGroups.length === 0) { + log.warn( + `No valid group selected, use one of: ${Object.keys( + groups + ).join(', ')}` + ) + } else { + log.info(`Running setup for group(s): ${validGroups.join(', ')}`) + } -module.exports = configure + return validGroups.map(g => + groups[g].configs.map(c => copy(c[0], c[1], overwrite)) + ) + }, + cleanup: function cleanup(repo) { + wipeConfigProperties(repo) + wipeConfigFiles(repo) + }, +} diff --git a/src/files.js b/src/files.js index 108481a8..c8ee3322 100644 --- a/src/files.js +++ b/src/files.js @@ -9,6 +9,7 @@ const blacklist = ['node_modules', 'build', 'dist', 'target', '.git', 'vendor'] const whitelists = { js: ['.js', '.jsx', '.ts'], + json: ['.json'], all: ['.js', '.json', '.css', '.scss', '.md', '.jsx', '.ts'], } @@ -23,6 +24,11 @@ function jsFiles(arr) { return arr.filter(whitelist) } +function jsonFiles(arr) { + const whitelist = whitelisted(whitelists.json) + return arr.filter(whitelist) +} + function collectJsFiles(target) { const whitelist = whitelisted(whitelists.js) return collectFiles(target).filter(whitelist) @@ -89,6 +95,7 @@ module.exports = { collectJsFiles, selectFiles, jsFiles, + jsonFiles, readFile, writeFile, whitelisted, diff --git a/src/groups.js b/src/groups.js new file mode 100644 index 00000000..a2b5305b --- /dev/null +++ b/src/groups.js @@ -0,0 +1,73 @@ +const path = require('path') +const fs = require('fs-extra') + +const tool = t => require(path.join(__dirname, 'tools', t)).runner +const tools = { + js: tool('js'), + //git: tool('git'), + package: tool('package'), +} + +const groups = { + //git: [tools.git], + + repo: { + tools: [], + configs: [ + [ + path.join(__dirname, '../config/editorconfig.config.rc'), + path.join('.editorconfig'), + ], + [ + path.join(__dirname, '../config/github/dependabot.yml'), + path.join('.dependabot', 'config.yml'), + ], + [ + path.join(__dirname, '../config/github/stale.yml'), + path.join('.github', 'stale.yml'), + ], + ], + }, + + js: { + tools: [tools.js], + configs: [ + [ + path.join(__dirname, '../config/js/eslint.config.js'), + path.join('.eslintrc.js'), + ], + [ + path.join(__dirname, '../config/js/prettier.config.js'), + path.join('.prettierrc.js'), + ], + [ + path.join(__dirname, '../config/js/browserslist.config.rc'), + path.join('.browserslistrc'), + ], + [ + path.join(__dirname, '../config/commitlint.config.js'), + path.join('.commitlintrc.js'), + ], + ], + }, + + package: { + tools: [tools.package], + configs: [], + }, +} + +groups.all = { + tools: Object.values(tools), + configs: Object.values(groups) + .map(t => t.configs) + .reduce((a, b) => a.concat(b), []), +} + +const isValidGroup = group => groups.hasOwnProperty(group) + +module.exports = { + tools, + groups, + isValidGroup, +} diff --git a/src/tools/git/index.js b/src/tools/git/index.js index ea5203df..ee728e81 100644 --- a/src/tools/git/index.js +++ b/src/tools/git/index.js @@ -8,6 +8,7 @@ exports.runner = async function(msg) { const { result, report } = await commitlint(msg) return { + name: 'git', summarize: () => log.print(result), hasViolations: report.valid, } diff --git a/src/tools/js/eslint.js b/src/tools/js/eslint.js index 74d6e14a..2cf251a1 100644 --- a/src/tools/js/eslint.js +++ b/src/tools/js/eslint.js @@ -6,8 +6,10 @@ const log = require('@dhis2/cli-helpers-engine').reporter const { readFile, writeFile } = require('../../files.js') -const eslintConfig = require('../../../config/eslint.config.js') - +const eslintConfig = require(path.join( + __dirname, + '../../../config/js/eslint.config.js' +)) log.debug('ESLint configuration file', eslintConfig) /** diff --git a/src/tools/js/index.js b/src/tools/js/index.js index 2b4619f3..a7a50bed 100644 --- a/src/tools/js/index.js +++ b/src/tools/js/index.js @@ -89,7 +89,7 @@ function fix(fixable) { * Pretty print a report object */ function print(report, violations) { - log.info(`${report.length} file(s) checked.`) + log.info(`${report.length} javascript file(s) checked.`) if (violations.length > 0) { violations.forEach(f => { @@ -97,10 +97,8 @@ function print(report, violations) { log.info('') log.print(`${p}`) f.messages.map(m => log.info(`${m.message}`)) + log.info('') }) - - log.info('') - log.error(`${violations.length} file(s) violate the code standard.`) } } @@ -132,6 +130,7 @@ exports.runner = (files, apply = false) => { log.debug(`Autofixes: ${autofixes.length}`) return { + name: 'js', files: js, summarize: () => print(report, violations), fix: () => fix(autofixes), diff --git a/src/tools/js/prettier.js b/src/tools/js/prettier.js index 8a16edcb..25ec5fbb 100644 --- a/src/tools/js/prettier.js +++ b/src/tools/js/prettier.js @@ -7,7 +7,7 @@ const { readFile, writeFile } = require('../../files.js') const prettierConfig = path.join( __dirname, - '../../../config/prettier.config.js' + '../../../config/js/prettier.config.js' ) log.debug('Prettier configuration file', prettierConfig) diff --git a/src/tools/package/index.js b/src/tools/package/index.js new file mode 100644 index 00000000..73617a25 --- /dev/null +++ b/src/tools/package/index.js @@ -0,0 +1,156 @@ +const path = require('path') + +// measure performance for potential hotspots when running tools +// sequentially, to see results run with `--verbose` +const perf = require('perfy') + +const log = require('@dhis2/cli-helpers-engine').reporter + +const { readFile, writeFile, jsonFiles } = require('../../files.js') + +const tools = [ + require('./rules/pin-dhis2-package-versions.js'), + require('./rules/husky-hooks.js'), + require('./rules/public-publish-access.js'), +] + +/** + * @param {string} file path to the file to run tools on + * @param {boolean} apply set to true if the tool should apply fixes + */ +function runTools(file, apply = false) { + const p = path.relative(process.cwd(), file) + const original = readFile(file) + + let messages = [] + let source + + try { + source = JSON.parse(original) + } catch (e) { + log.error( + `${file} is not valid JSON, cannot proceed with JSON validations. Aborting...\n`, + e + ) + process.exit(1) + } + + let fixed = false + + perf.start('exec-file') + for (const tool of tools) { + const result = tool(file, source, apply) + + if (result.fixed) { + source = result.output + fixed = result.fixed + } + + messages = messages.concat(result.messages) + } + log.debug(`${p}: ${perf.end('exec-file').summary}`) + + return { + file, + messages, + fixed, + output: source, + name: path.basename(file), + } +} + +/** + * The executor which gathers an array of report objects + */ +function exec(files, apply = false) { + perf.start('exec-all-files') + const report = files.map(f => runTools(f, apply)) + log.debug(`${files.length} file(s): ${perf.end('exec-all-files').summary}`) + + return report +} + +function formatJSON(string) { + const obj = JSON.parse(string) + return JSON.stringify(obj, null, 2) +} + +/** + * Apply fixes for code standard violations automatically. + */ +function fix(fixable) { + if (fixable.length === 0) { + return [] + } + + const fixed = fixable.map(f => { + const success = writeFile(f.file, formatJSON(f.output)) + log.debug(`${f.file} written successfully: ${success}`) + + if (!success) { + log.error(`Failed to write ${f.name} to disk`) + process.exit(1) + } + + return f.file + }) + + log.info(`Applied fixes for ${fixed.length} file(s).`) + return fixed +} + +/** + * Pretty print a report object + */ +function print(report, violations) { + log.info(`${report.length} package.json file(s) checked.`) + + if (violations.length > 0) { + violations.forEach(f => { + const p = path.relative(process.cwd(), f.file) + log.info('') + log.print(`${p}`) + f.messages.map(m => log.info(`${m.message}`)) + log.info('') + }) + } +} + +function getViolations(report) { + return report.filter(f => f.messages.length > 0) +} + +function getAutoFixable(report) { + return report.filter(f => f.fixed) +} + +/** + * @param {Array} files list of files to check + * @param {boolean} apply set to true should fixes be automatically applied + * + * @return {Object} a report object + */ +exports.runner = (files = [], apply = false) => { + const packages = jsonFiles(files).filter(f => f.includes('package.json')) + + log.debug(`Files to operate on:\n${packages.join('\n')}`) + + const report = exec(packages, apply) + const autofixes = getAutoFixable(report) + const violations = getViolations(report) + + const hasViolations = violations.length > 0 + + log.debug(`Violations: ${violations.length}`) + log.debug(`Autofixes: ${autofixes.length}`) + + return { + name: 'package', + files: packages, + summarize: () => print(report, violations), + fix: () => fix(autofixes), + violations, + hasViolations, + report, + } +} diff --git a/src/tools/package/rules/husky-hooks.js b/src/tools/package/rules/husky-hooks.js new file mode 100644 index 00000000..c974bba0 --- /dev/null +++ b/src/tools/package/rules/husky-hooks.js @@ -0,0 +1,81 @@ +const log = require('@dhis2/cli-helpers-engine').reporter +const chalk = require('chalk') +const path = require('path') +const fs = require('fs') + +function isEmpty(obj) { + for (const x in obj) { + return false + } + return true +} + +function message(msg) { + return { + checker: 'husky-hooks', + message: msg, + } +} + +function validate(hook, cmd) { + const rules = { + 'commit-msg': /.*style commit check.*/, + 'pre-commit': /.*style (validate|(\w+ apply)).*/, + } + + if (!rules[hook].test(cmd)) { + return message( + chalk`The hook {yellow ${hook}} needs to run the style rules ({green ${ + rules[hook] + }})` + ) + } else { + return null + } +} + +function isRootPackage(fp) { + const dir = path.dirname(fp) + try { + // TODO: this won't work if the repo is not cloned by git + fs.accessSync(path.join(dir, '.git')) + return true + } catch (e) { + return false + } +} + +module.exports = (file, pkg, apply = false) => { + const response = { + messages: [], + output: pkg, + fixed: false, + } + + if (!isRootPackage(file)) { + return response + } + + const { husky } = pkg + + if (husky) { + if (isEmpty(husky.hooks)) { + response.messages.push( + message(chalk`No Hooks found in {yellow husky.hooks}.`) + ) + } + + for (const hook in husky.hooks) { + const result = validate(hook, husky.hooks[hook]) + if (result) { + response.messages.push(result) + } + } + } else { + response.messages.push( + message(chalk`No {yellow husky} property found.`) + ) + } + + return response +} diff --git a/src/tools/package/rules/pin-dhis2-package-versions.js b/src/tools/package/rules/pin-dhis2-package-versions.js new file mode 100644 index 00000000..485e0c55 --- /dev/null +++ b/src/tools/package/rules/pin-dhis2-package-versions.js @@ -0,0 +1,53 @@ +const semver = require('semver') +const chalk = require('chalk') + +function verify(dependency, version, category) { + if (dependency.startsWith('@dhis2')) { + // semver.clean produces an exact version + // https://github.com/npm/node-semver/blob/master/test/clean.js#L18-L21 + const clean = semver.clean(version) + if (!clean) { + return { + checker: 'pin-dhis2-package-version', + message: chalk`${dependency}@{red ${version}} in {yellow ${category}} should be exact version.`, + } + } + } + return null +} + +module.exports = (file, pkg, apply = false) => { + const response = { + messages: [], + output: pkg, + fixed: false, + } + + const { dependencies, peerDependencies, devDependencies } = pkg + + for (const dep in dependencies) { + const ver = dependencies[dep] + const result = verify(dep, ver, 'dependencies') + if (result) { + response.messages.push(result) + } + } + + for (const dep in devDependencies) { + const ver = devDependencies[dep] + const result = verify(dep, ver, 'devDependencies') + if (result) { + response.messages.push(result) + } + } + + for (const dep in peerDependencies) { + const ver = peerDependencies[dep] + const result = verify(dep, ver, 'peerDependencies') + if (result) { + response.messages.push(result) + } + } + + return response +} diff --git a/src/tools/package/rules/public-publish-access.js b/src/tools/package/rules/public-publish-access.js new file mode 100644 index 00000000..fe2cfc32 --- /dev/null +++ b/src/tools/package/rules/public-publish-access.js @@ -0,0 +1,71 @@ +const path = require('path') +const fs = require('fs-extra') + +const semver = require('semver') +const chalk = require('chalk') + +function isRootPackage(fp) { + const dir = path.dirname(fp) + try { + // TODO: this won't work if the repo is not cloned by git + fs.accessSync(path.join(dir, '.git')) + return true + } catch (e) { + return false + } +} + +function verify(pkg) { + const { publishConfig } = pkg + + if (!publishConfig) { + return { + checker: 'public-publish-access', + message: chalk`publishConfig is missing.`, + } + } + + if (publishConfig.access !== 'public') { + return { + checker: 'public-publish-access', + message: chalk`publishConfig.access must be set to 'public'.`, + } + } + + return null +} + +function fix(pkg) { + return JSON.stringify({ + ...pkg, + publishConfig: { + access: 'public', + }, + }) +} + +module.exports = (file, pkg, apply = false) => { + const response = { + messages: [], + output: pkg, + fixed: false, + } + + if (isRootPackage(file)) { + return response + } + + const result = verify(pkg) + + if (result) { + if (apply) { + const fixed = fix(pkg) + response.output = fixed + response.fixed = true + } else { + response.messages.push(result) + } + } + + return response +} diff --git a/yarn.lock b/yarn.lock index f78df41d..74aeb9fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1024,6 +1024,15 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +fs-extra@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.0.1.tgz#90294081f978b1f182f347a440a209154344285b" + integrity sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -1122,7 +1131,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== @@ -1431,6 +1440,13 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -2194,7 +2210,7 @@ semver-diff@^2.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@6.0.0: +semver@6.0.0, semver@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65" integrity sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ== @@ -2520,6 +2536,11 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + update-notifier@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-3.0.0.tgz#e9bbf8f0f5b7a2ce6666ca46334fdb29492e8fab"