diff --git a/automation/check-doc.js b/automation/check-doc.js new file mode 100644 index 00000000..276b55b0 --- /dev/null +++ b/automation/check-doc.js @@ -0,0 +1,93 @@ +/** + * @license + * Copyright 2020 Balena Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { stripIndent } = require('common-tags'); +const _ = require('lodash'); +const { fs } = require('mz'); +const path = require('path'); +const simplegit = require('simple-git/promise'); + +const ROOT = path.normalize(path.join(__dirname, '..')); + +/** + * Compare the timestamp of cli.markdown with the timestamp of staged files, + * issuing an error if cli.markdown is older. Besides the purpose of ensuring + * that cli.markdown is updated, it effectively also ensures that coffeelint + * is executed (via `npm run build` or `npm test`) on the developers laptop, + * so that there is at least a chance that the developer will spot any linter + * warnings (that could reveal bugs) sooner than later. (The CI does not + * currently fail in case of coffeelint warnings.) + * If cli.markdown does not require updating and the developer cannot run + * `npm run build` on their laptop, the error message suggests a workaround + * using `touch`. + */ +async function checkBuildTimestamps() { + const git = simplegit(ROOT); + const docFile = path.join(ROOT, 'doc', 'cli.markdown'); + const [docStat, gitStatus] = await Promise.all([ + fs.stat(docFile), + git.status(), + ]); + const stagedFiles = _.uniq([ + ...gitStatus.created, + ...gitStatus.staged, + ...gitStatus.renamed.map(o => o.to), + ]) + // select only staged files that start with lib/ or typings/ + .filter(f => f.match(/^(lib|typings)[/\\]/)) + .map(f => path.join(ROOT, f)); + + const fStats = await Promise.all(stagedFiles.map(f => fs.stat(f))); + fStats.forEach((fStat, index) => { + if (fStat.mtimeMs > docStat.mtimeMs) { + const fPath = stagedFiles[index]; + throw new Error(stripIndent` + -------------------------------------------------------------------------------- + ERROR: at least one staged file: "${fPath}" + has a more recent modification timestamp than the documentation file: + "${docFile}" + + This probably means that \`npm run build\` or \`npm test\` have not been executed, + and this error can be fixed by doing so. Running \`npm run build\` or \`npm test\` + before commiting is currently a requirement (documented in the CONTRIBUTING.md + file) for three reasons: + 1. To update the CLI markdown documentation (in case any command-line options + were updated, added or removed). + 2. To reveal coffeelint warnings that the CI currently ignores (in case any + Coffeescript files were modified). + 3. To catch Typescript type check errors sooner and reduce overall waiting time, + given that balena-cli CI builds/tests are currently rather lengthy. + + If you need/wish to bypass this check without running \`npm run build\`, run: + npx touch -am "${docFile}" + and then try again. + -------------------------------------------------------------------------------- + `); + } + }); +} + +async function run() { + try { + await checkBuildTimestamps(); + } catch (err) { + console.error(err.message); + process.exitCode = 1; + } +} + +run(); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 9a363bc2..765612f9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -7233,9 +7233,9 @@ "integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ=" }, "husky": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.1.tgz", - "integrity": "sha512-Qa0lRreeIf4Tl92sSs42ER6qc3hzoyQPPorzOrFWfPEVbdi6LuvJEqWKPk905fOWIR76iBpp7ECZNIwk+a8xuQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.3.tgz", + "integrity": "sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==", "dev": true, "requires": { "chalk": "^3.0.0", @@ -16536,6 +16536,15 @@ "simple-concat": "^1.0.0" } }, + "simple-git": { + "version": "1.131.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.131.0.tgz", + "integrity": "sha512-z/art7YYtmPnnLItT/j+nKwJt6ap6nHZ4D8sYo9PdCKK/ug56SN6m/evfxJk7uDV3e9JuCa8qIyDU2P3cxmiNQ==", + "dev": true, + "requires": { + "debug": "^4.0.1" + } + }, "single-line-log": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz", diff --git a/package.json b/package.json index 1beec46f..8233db2f 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ }, "husky": { "hooks": { - "pre-commit": "node automation/check-npm-version.js" + "pre-commit": "node automation/check-npm-version.js && node automation/check-doc.js" } }, "oclif": { @@ -139,7 +139,7 @@ "gulp-coffee": "^2.2.0", "gulp-inline-source": "^2.1.0", "gulp-shell": "^0.5.2", - "husky": "^4.2.1", + "husky": "^4.2.3", "intercept-stdout": "^0.1.2", "mocha": "^6.2.2", "nock": "^11.7.2", @@ -149,6 +149,7 @@ "publish-release": "^1.6.1", "resin-lint": "^3.2.4", "rewire": "^3.0.2", + "simple-git": "^1.131.0", "sinon": "^7.5.0", "ts-node": "^8.6.2", "typescript": "^3.7.5"