diff --git a/constants.js b/constants.js index 5392926..1713dc5 100644 --- a/constants.js +++ b/constants.js @@ -1,6 +1,6 @@ module.exports = { - SOURCE: 'templates', - DIST: 'dist', + SOURCE_DIR: 'templates', + DIST_DIR: 'dist', WORKING_DIR: 'tmp', CONFIGURATION_FILE: 'conf.json' } diff --git a/gulpfile.js b/gulpfile.js index b4421e2..f28957a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,11 +1,11 @@ const gulp = require('gulp'); const plumber = require('gulp-plumber'); -const { SOURCE, DIST, WORKING_DIR, CONFIGURATION_FILE } = require('./constants'); +const { SOURCE_DIR, DIST_DIR, WORKING_DIR, CONFIGURATION_FILE } = require('./constants'); const options = { - source: SOURCE, - dist: DIST, + sourceDir: SOURCE_DIR, + distDir: DIST_DIR, workingDir: WORKING_DIR, configurationFile: CONFIGURATION_FILE, src: function plumbedSrc() { @@ -22,11 +22,14 @@ require('./tasks/less')(options); require('./tasks/lint')(options); require('./tasks/postcss')(options); require('./tasks/sass')(options); -require('./tasks/check-for-missing')(options); +require('./tasks/check-for-unused').checkForUnusedTask(options); require('./tasks/check-deps')(options); /* Runs the entire pipeline once. */ -gulp.task('run-pipeline', gulp.series('dupe', 'less', 'sass', 'postcss', 'lint', 'build', 'check-for-missing')); +gulp.task( + 'run-pipeline', + gulp.series('dupe', 'less', 'sass', 'postcss', 'lint', 'build', gulp.parallel('check-for-unused')) +); /* By default templates will be built into '/dist'. */ gulp.task( @@ -35,11 +38,11 @@ gulp.task( /* gulp will watch for changes in '/templates'. */ gulp.watch( [ - options.source + '/**/*.html', - options.source + '/**/*.css', - options.source + '/**/*.scss', - options.source + '/**/*.less', - options.source + '/**/conf.json' + options.sourceDir + '/**/*.html', + options.sourceDir + '/**/*.css', + options.sourceDir + '/**/*.scss', + options.sourceDir + '/**/*.less', + options.sourceDir + '/**/conf.json' ], { delay: 500 }, gulp.series('run-pipeline') diff --git a/package-lock.json b/package-lock.json index 1d2de05..eb64d89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1635,9 +1635,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", diff --git a/package.json b/package.json index 9bbb218..7521521 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,13 @@ "start": "node ./node_modules/.bin/gulp", "once": "node ./node_modules/.bin/gulp run-pipeline", "deploy": "npm run test && cp -r dist demo && git push origin `git subtree split --prefix demo develop`:gh-pages --force", - "test": "npm run once && node ./node_modules/.bin/ava", - "test:watch": "npm run once && node ./node_modules/.bin/ava --watch", - "format": "node ./node_modules/.bin/prettier {tasks,tests}/**/*.js gulpfile.js .eslintrc.js --write", - "lint": "node ./node_modules/.bin/eslint ./**/*.js gulpfile.js" + "e2e-test": "npm run once && node ./node_modules/.bin/ava", + "format": "prettier {tasks,tests}/**/*.js gulpfile.js .eslintrc.js --write", + "lint": "eslint ./**/*.js gulpfile.js" }, "dependencies": { - "autoprefixer": "^9.7.4", + "autoprefixer": "^9.6.1", + "chalk": "^2.4.2", "del": "^5.1.0", "graceful-fs": "^4.2.3", "gulp": "^4.0.2", diff --git a/tasks/build.js b/tasks/build.js index ea92b30..b0a072d 100644 --- a/tasks/build.js +++ b/tasks/build.js @@ -58,7 +58,7 @@ function buildTask(options) { return path; }) ) - .pipe(gulp.dest(options.dist)); + .pipe(gulp.dest(options.distDir)); }); } @@ -66,7 +66,7 @@ function buildTask(options) { * Clean up & then read from workingDir to generate templates. * For each found config, a template group will be generated through `makeTemplates`. */ - return del(options.dist) + return del(options.distDir) .then(() => { /** * Loop through dirs and load their conf files. diff --git a/tasks/check-for-missing.js b/tasks/check-for-missing.js index c4d6af3..890cdcf 100644 --- a/tasks/check-for-missing.js +++ b/tasks/check-for-missing.js @@ -1,28 +1,9 @@ const gulp = require('gulp'); -const { getConfigsForDir, getFilePathsForDir } = require('./util/util'); -function checkForMissingTask(options) { - gulp.task('check-for-missing', done => { - const configs = getConfigsForDir(options.workingDir, options.configurationFile); - - configs.map(({ dir, confItems }) => { - confItems.forEach(async confItem => { - const definedStrings = Object.keys(confItem).map(key => { - return { - src: `@echo ${key}`, - used: false - }; - }); - - const cwd = `${options.workingDir}/${dir}`; - const files = await getFilePathsForDir(cwd); - const htmlTemplates = files.filter(file => !!file.match(/.*\.html/) && !file.match(/.*\.inc*\.html/)); // Read only CSS files. - console.log(definedStrings, htmlTemplates); - }); - - done(); - }); +function checkForUnusedTask(options) { + gulp.task('check-for-unused', async done => { + done(); }); } -module.exports = checkForMissingTask; +module.exports = checkForUnusedTask; diff --git a/tasks/check-for-unused.js b/tasks/check-for-unused.js index e69de29..0308434 100644 --- a/tasks/check-for-unused.js +++ b/tasks/check-for-unused.js @@ -0,0 +1,81 @@ +const gulp = require('gulp'); +const fs = require('fs'); +const chalk = require('chalk'); +const { getConfigsForDir, getFilePathsForDir, log } = require('./util/util'); + +const OUTPUT_KEYWORD = '@echo'; + +// todo: needs a proper refactor. +function checkForUnusedTask(options) { + gulp.task('check-for-unused', async done => { + const configs = getConfigsForDir(options.workingDir, options.configurationFile); + const unusedItems = await checkForUnusedItemsInConfigs(options.workingDir, configs); + outputWarningsForUnusedItems(unusedItems, configs); + + done(); + }); +} + +// todo: find configs by id instead of using the index? +const outputWarningsForUnusedItems = (unusedItems, configs) => { + const find = OUTPUT_KEYWORD; + const regex = new RegExp(find, 'g'); + unusedItems.forEach((unusedInConfigs, index) => { + const { dir } = configs[index]; + + unusedInConfigs.forEach((unusedInConfItems, index) => { + log.warn( + `${unusedInConfItems.length} unused properties in ${dir}: ${unusedInConfItems + .reduce((acc, cur) => (acc ? `${acc}, ${chalk.white(cur)}` : chalk.white(cur)), '') + .replace(regex, '')}` + ); + }); + }); +}; + +const checkForUnusedItemsInConfigs = (rootDir, configs) => { + return Promise.all( + configs.map(async ({ dir, confItems }) => { + return Promise.all( + confItems.map(async confItem => { + const definedStrings = Object.keys(confItem).map(key => `${OUTPUT_KEYWORD} ${key}`); + const cwd = `${rootDir}/${dir}`; + const files = await getFilePathsForDir(cwd); + const htmlTemplates = await self.getHtmlTemplatesFromFilelist(files); + const concatenatedTemplates = htmlTemplates.join(''); + + return definedStrings.filter(str => concatenatedTemplates.includes(str)); + }) + ); + }) + ); +}; + +// todo: should be util, so should the one in build.js +const getHtmlTemplatesFromFilelist = filelist => { + return Promise.all( + filelist + .filter(file => !!file.match(/.*\.html/) && !file.match(/.*\.inc*\.html/)) + .map( + htmlTemplate => + new Promise((resolve, reject) => { + fs.readFile(htmlTemplate, 'utf8', (error, data) => { + if (error) { + reject(error); + } + + resolve(data); + }); + }) + ) + ); +}; + +const self = { + checkForUnusedTask, + outputWarningsForUnusedItems, + checkForUnusedItemsInConfigs, + getHtmlTemplatesFromFilelist +}; + +module.exports = self; diff --git a/tasks/dupe.js b/tasks/dupe.js index 46d9fe1..412709e 100644 --- a/tasks/dupe.js +++ b/tasks/dupe.js @@ -5,7 +5,7 @@ function dupeTask(options) { gulp.task('dupe', function() { del.sync([options.workingDir]); - return options.src([options.source + '/**/*']).pipe(gulp.dest('./' + options.workingDir)); + return options.src([options.sourceDir + '/**/*']).pipe(gulp.dest('./' + options.workingDir)); }); } diff --git a/tasks/util/util.js b/tasks/util/util.js index 57a3294..3f9a995 100644 --- a/tasks/util/util.js +++ b/tasks/util/util.js @@ -1,7 +1,9 @@ const fs = require('fs'); const path = require('path'); const klaw = require('klaw'); +const chalk = require('chalk'); +// todo test /** * Given a directory, scans all directories in it (not deep) and returns found config items. * @@ -16,7 +18,7 @@ const getConfigsForDir = (rootDir, configFileName) => { /** Exit with warn if no configuration file found. */ if (!fs.existsSync(path.resolve(rootDir, confPath))) { - console.warn(`Missing configuration in "${dir}". Did you remember to create "${dir}/${configFileName}"?`); + self.log.warn(`Missing configuration in "${dir}". Did you remember to create "${dir}/${configFileName}"?`); return false; } @@ -41,8 +43,11 @@ const getConfigsForDir = (rootDir, configFileName) => { .filter(config => config); }; +// todo test /** * Given a directory, gets all file paths in it. + * + * @param { string } dir Dir to get files paths for. */ const getFilePathsForDir = dir => { const files = []; @@ -63,7 +68,24 @@ const getFilePathsForDir = dir => { }); }; -module.exports = { +const log = { + warn: (...messages) => { + console.warn('🔵 ', chalk.yellow(messages)); + }, + + log: (...messages) => { + console.log('🔘 ', chalk.gray(messages)); + }, + + error: (...messages) => { + console.error('🔴 ', chalk.red(messages)); + } +}; + +const self = { + log, getConfigsForDir, getFilePathsForDir }; + +module.exports = self; diff --git a/tests/e2e/end-to-end.test.js b/tests/e2e/end-to-end.test.js new file mode 100644 index 0000000..5d5dcf2 --- /dev/null +++ b/tests/e2e/end-to-end.test.js @@ -0,0 +1,40 @@ +const test = require('ava'); +const fs = require('fs'); +const path = require('path'); + +const readFileSync = path => fs.readFileSync(('../', path), 'utf8'); + +test('dark signature output', async t => { + const expected = readFileSync('tests/sample/dark/signature-dark.html'); + const built = readFileSync('dist/dark/signature-dark.html'); + + t.deepEqual(expected, built); +}); + +test('dark signature reply output', async t => { + const expected = readFileSync('tests/sample/dark/signature-reply-dark.html'); + const built = readFileSync('dist/dark/signature-reply-dark.html'); + + t.deepEqual(expected, built); +}); + +test('light signature output', async t => { + const expected = readFileSync('tests/sample/light/signature-light.html'); + const built = readFileSync('dist/light/signature-light.html'); + + t.deepEqual(expected, built); +}); + +test('light signature reply output', async t => { + const expected = readFileSync('tests/sample/light/signature-reply-light.html'); + const built = readFileSync('dist/light/signature-reply-light.html'); + + t.deepEqual(expected, built); +}); + +test('light full mail output', async t => { + const expected = readFileSync('tests/sample/light/full-mail-light.html'); + const built = readFileSync('dist/light/full-mail-light.html'); + + t.deepEqual(expected, built); +}); diff --git a/tests/end-to-end.test.js b/tests/end-to-end.test.js deleted file mode 100644 index ccfed2a..0000000 --- a/tests/end-to-end.test.js +++ /dev/null @@ -1,38 +0,0 @@ -const test = require('ava'); -const fs = require('fs'); -const path = require('path'); - -test('dark signature output', async t => { - const expected = fs.readFileSync(path.resolve('tests/sample/dark/signature-dark.html'), 'utf8'); - const built = fs.readFileSync(path.resolve('dist/dark/signature-dark.html'), 'utf8'); - - t.deepEqual(expected, built); -}); - -test('dark signature reply output', async t => { - const expected = fs.readFileSync(path.resolve('tests/sample/dark/signature-reply-dark.html'), 'utf8'); - const built = fs.readFileSync(path.resolve('dist/dark/signature-reply-dark.html'), 'utf8'); - - t.deepEqual(expected, built); -}); - -test('light signature output', async t => { - const expected = fs.readFileSync(path.resolve('tests/sample/light/signature-light.html'), 'utf8'); - const built = fs.readFileSync(path.resolve('dist/light/signature-light.html'), 'utf8'); - - t.deepEqual(expected, built); -}); - -test('light signature reply output', async t => { - const expected = fs.readFileSync(path.resolve('tests/sample/light/signature-reply-light.html'), 'utf8'); - const built = fs.readFileSync(path.resolve('dist/light/signature-reply-light.html'), 'utf8'); - - t.deepEqual(expected, built); -}); - -test('light full mail output', async t => { - const expected = fs.readFileSync(path.resolve('tests/sample/light/full-mail-light.html'), 'utf8'); - const built = fs.readFileSync(path.resolve('dist/light/full-mail-light.html'), 'utf8'); - - t.deepEqual(expected, built); -}); diff --git a/tests/unit/check-for-unused.js b/tests/unit/check-for-unused.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/util.js b/tests/unit/util.js new file mode 100644 index 0000000..e69de29