diff --git a/gulpfile.js b/gulpfile.js index 82f9d26cbc..bbe6f13e36 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,7 +1,7 @@ const gulp = require('gulp') const taskListing = require('gulp-task-listing') const configPaths = require('./config/paths.js') -const taskArguments = require('./tasks/task-arguments') +const { destination } = require('./tasks/task-arguments.js') // Gulp sub-tasks require('./tasks/gulp/compile-assets.js') @@ -12,7 +12,7 @@ require('./tasks/gulp/watch.js') const { buildSassdocs } = require('./tasks/sassdoc.js') const { runNodemon } = require('./tasks/nodemon.js') const { updateDistAssetsVersion } = require('./tasks/asset-version.js') -const { cleanDist, cleanPackage, cleanPublic } = require('./tasks/clean.js') +const { clean } = require('./tasks/clean.js') const { npmScriptTask } = require('./tasks/run.js') /** @@ -38,8 +38,8 @@ gulp.task('styles', gulp.series( * Copies assets to taskArguments.destination (public) */ gulp.task('copy:assets', () => { - return gulp.src(configPaths.src + 'assets/**/*') - .pipe(gulp.dest(taskArguments.destination + '/assets/')) + return gulp.src(`${configPaths.src}assets/**/*`) + .pipe(gulp.dest(`${destination}/assets/`)) }) /** @@ -67,7 +67,7 @@ gulp.task('serve', gulp.parallel( * Runs a sequence of tasks on start */ gulp.task('dev', gulp.series( - cleanPublic, + clean, 'compile', 'serve' )) @@ -77,7 +77,7 @@ gulp.task('dev', gulp.series( * Prepare package folder for publishing */ gulp.task('build:package', gulp.series( - cleanPackage, + clean, 'copy:files', 'js:compile' )) @@ -87,7 +87,7 @@ gulp.task('build:package', gulp.series( * Prepare dist folder for release */ gulp.task('build:dist', gulp.series( - cleanDist, + clean, 'compile', 'copy:assets', updateDistAssetsVersion diff --git a/package-lock.json b/package-lock.json index 25bf9be930..4a93fc487e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "sass-color-helpers": "^2.1.1", "sassdoc": "^2.7.4", "shuffle-seed": "^1.1.6", + "slash": "^3.0.0", "standard": "^17.0.0", "stylelint": "^14.13.0", "stylelint-config-gds": "^0.2.0", diff --git a/package.json b/package.json index 60132ef947..084c7a56b7 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "sass-color-helpers": "^2.1.1", "sassdoc": "^2.7.4", "shuffle-seed": "^1.1.6", + "slash": "^3.0.0", "standard": "^17.0.0", "stylelint": "^14.13.0", "stylelint-config-gds": "^0.2.0", diff --git a/tasks/asset-version.js b/tasks/asset-version.js index 7b5966ce26..93640c4165 100644 --- a/tasks/asset-version.js +++ b/tasks/asset-version.js @@ -1,19 +1,23 @@ -const configPaths = require('../config/paths.js') const { rename, writeFile } = require('fs/promises') const { basename, resolve } = require('path') +const configPaths = require('../config/paths.js') +const { destination, isDist } = require('./task-arguments.js') + // Update assets' version numbers // Uses the version number from `package/package.json` and writes it to various places async function updateDistAssetsVersion () { - const distFolder = 'dist' + const pkg = require(`../${configPaths.package}package.json`) - const pkg = require('../' + configPaths.package + 'package.json') + if (!isDist) { + throw new Error('Asset versions can only be applied to ./dist') + } // Files to process const assetFiles = [ - resolve(`${distFolder}/govuk-frontend.min.css`), - resolve(`${distFolder}/govuk-frontend-ie8.min.css`), - resolve(`${distFolder}/govuk-frontend.min.js`) + resolve(`${destination}/govuk-frontend.min.css`), + resolve(`${destination}/govuk-frontend-ie8.min.css`), + resolve(`${destination}/govuk-frontend.min.js`) ] // Loop through files @@ -26,7 +30,7 @@ async function updateDistAssetsVersion () { }) // Write VERSION.txt file - assetTasks.push(writeFile(resolve(`${distFolder}/VERSION.txt`), pkg.version + '\r\n')) + assetTasks.push(writeFile(resolve(`${destination}/VERSION.txt`), pkg.version + '\r\n')) // Resolve on completion return Promise.all(assetTasks) diff --git a/tasks/clean.js b/tasks/clean.js index c09390dc22..6fde036655 100644 --- a/tasks/clean.js +++ b/tasks/clean.js @@ -1,34 +1,30 @@ const del = require('del') -const configPaths = require('../config/paths.js') -function cleanDist () { - return del([ - `${configPaths.dist}**/*` - ]) -} +const { destination } = require('./task-arguments.js') + +function paths () { + const param = [`${destination}/**/*`] + + // Preserve package files + if (destination === 'package') { + param.push( + `!${destination}/`, + `!${destination}/package.json`, + `!${destination}/govuk-prototype-kit.config.json`, + `!${destination}/README.md` + ) + } -function cleanPackage () { - return del([ - `${configPaths.package}**`, - `!${configPaths.package}`, - `!${configPaths.package}package.json`, - `!${configPaths.package}govuk-prototype-kit.config.json`, - `!${configPaths.package}README.md` - ]) + return param } -function cleanPublic () { - return del([ - `${configPaths.public}**/*` - ]) +function clean () { + return del(paths()) } -cleanDist.displayName = 'clean:dist' -cleanPackage.displayName = 'clean:package' -cleanPublic.displayName = 'clean:public' +clean.displayName = `clean:${destination}` module.exports = { - cleanDist, - cleanPackage, - cleanPublic + paths, + clean } diff --git a/tasks/clean.unit.test.js b/tasks/clean.unit.test.js new file mode 100644 index 0000000000..d47ca9fd2d --- /dev/null +++ b/tasks/clean.unit.test.js @@ -0,0 +1,36 @@ +describe('Clean task', () => { + beforeEach(() => { + jest.resetModules() + }) + + it.each( + [ + { + destination: 'public', + paths: ['public/**/*'] + }, + { + destination: 'package', + paths: [ + 'package/**/*', + '!package/', + '!package/package.json', + '!package/govuk-prototype-kit.config.json', + '!package/README.md' + ] + }, + { + destination: 'dist', + paths: ['dist/**/*'] + }, + { + destination: 'custom/location/here', + paths: ['custom/location/here/**/*'] + } + ] + )('cleans destination "$destination"', async ({ destination, paths }) => { + jest.mock('./task-arguments.js', () => ({ destination })) + const clean = await import('./clean.js') + expect(clean.paths()).toEqual(paths) + }) +}) diff --git a/tasks/gulp/compile-assets.js b/tasks/gulp/compile-assets.js index fc2da92690..3957068d43 100644 --- a/tasks/gulp/compile-assets.js +++ b/tasks/gulp/compile-assets.js @@ -3,13 +3,11 @@ const { componentNameToJavaScriptModuleName } = require('../../lib/helper-functi const path = require('path') const gulp = require('gulp') -const configPaths = require('../../config/paths.js') const sass = require('gulp-sass')(require('node-sass')) const plumber = require('gulp-plumber') const postcss = require('gulp-postcss') const autoprefixer = require('autoprefixer') const rollup = require('gulp-better-rollup') -const taskArguments = require('../task-arguments') const gulpif = require('gulp-if') const uglify = require('gulp-uglify') const eol = require('gulp-eol') @@ -23,22 +21,19 @@ const postcsspseudoclasses = require('postcss-pseudo-classes')({ blacklist: [':not(', ':disabled)', ':first-child)', ':last-child)', ':focus)', ':active)', ':hover)'] }) +const configPaths = require('../../config/paths.js') +const { destination, isDist, isPublic } = require('../task-arguments.js') + // Compile CSS and JS task -------------- // -------------------------------------- -// check if destination flag is public (this is the default) -const isPublic = taskArguments.destination === 'public' || false - -// check if destination flag is dist -const isDist = taskArguments.destination === 'dist' || false - // Set the destination const destinationPath = function () { - // Public & Dist directories do no need namespaced with `govuk` - if (taskArguments.destination === 'dist' || taskArguments.destination === 'public') { - return taskArguments.destination + // Public & Dist directories not namespaced with `govuk` + if (isDist || isPublic) { + return destination } else { - return `${taskArguments.destination}/govuk/` + return `${destination}/govuk/` } } @@ -74,7 +69,7 @@ function compileStyles () { extname: '.min.css' }) )) - .pipe(gulp.dest(taskArguments.destination + '/')) + .pipe(gulp.dest(`${destination}/`)) } function compileOldIE () { @@ -111,7 +106,7 @@ function compileOldIE () { extname: '.min.css' }) )) - .pipe(gulp.dest(taskArguments.destination + '/')) + .pipe(gulp.dest(`${destination}/`)) } function compileLegacy () { @@ -126,7 +121,7 @@ function compileLegacy () { // :hover class you can use to simulate the hover state in the review app postcsspseudoclasses ])) - .pipe(gulp.dest(taskArguments.destination + '/')) + .pipe(gulp.dest(`${destination}/`)) } function compileLegacyIE () { @@ -145,7 +140,7 @@ function compileLegacyIE () { pseudo: { disable: true } }) ])) - .pipe(gulp.dest(taskArguments.destination + '/')) + .pipe(gulp.dest(`${destination}/`)) } function compileFullPageStyles () { @@ -158,7 +153,7 @@ function compileFullPageStyles () { location.basename = location.dirname location.dirname = '' })) - .pipe(gulp.dest(taskArguments.destination + '/full-page-examples/')) + .pipe(gulp.dest(`${destination}/full-page-examples/`)) } gulp.task('scss:compile', function (done) { diff --git a/tasks/gulp/copy-to-destination.js b/tasks/gulp/copy-to-destination.js index d971280a09..ac2302fe99 100644 --- a/tasks/gulp/copy-to-destination.js +++ b/tasks/gulp/copy-to-destination.js @@ -11,7 +11,7 @@ const merge = require('merge-stream') const rename = require('gulp-rename') const configPaths = require('../../config/paths.js') -const taskArguments = require('../task-arguments') +const { destination } = require('../task-arguments.js') gulp.task('copy:files', () => { return merge( @@ -22,7 +22,7 @@ gulp.task('copy:files', () => { gulp.src([ `${configPaths.src}**/*.mjs`, `!${configPaths.src}**/*.test.*` - ]).pipe(gulp.dest(`${taskArguments.destination}/govuk-esm/`)), + ]).pipe(gulp.dest(`${destination}/govuk-esm/`)), /** * Copy files to destination with './govuk' suffix @@ -77,7 +77,7 @@ gulp.task('copy:files', () => { basename: 'macro-options', extname: '.json' })) - ).pipe(gulp.dest(`${taskArguments.destination}/govuk/`)) + ).pipe(gulp.dest(`${destination}/govuk/`)) ) }) diff --git a/tasks/task-arguments.js b/tasks/task-arguments.js index 11be0b7efe..9332b223c0 100644 --- a/tasks/task-arguments.js +++ b/tasks/task-arguments.js @@ -1,4 +1,38 @@ -const argv = require('yargs').argv -const destination = argv.destination ? argv.destination : 'public' +const { dirname, relative, resolve } = require('path') -exports.destination = destination +const slash = require('slash') +const { argv } = require('yargs') + +// Defaults for known tasks +const destinations = [ + { + task: 'build:package', + destination: 'package' + }, + { + task: 'build:dist', + destination: 'dist' + } +] + +// Non-flag arguments +const { _: tasks } = argv + +// Prefer `--destination`, default for known task, or 'public' +const destination = argv.destination || (destinations + .filter(({ task }) => tasks.includes(task))[0]?.destination ?? 'public') + +const rootPath = dirname(__dirname) +const destPath = resolve(rootPath, destination) + +module.exports = { + argv, + + // Normalise slashes (Windows) for gulp.src + destination: slash(relative(rootPath, destPath)), + + // Check destination flags + isPackage: destination === 'package', + isPublic: destination === 'public', + isDist: destination === 'dist' +} diff --git a/tasks/task-arguments.unit.test.js b/tasks/task-arguments.unit.test.js new file mode 100644 index 0000000000..a3f57a73c1 --- /dev/null +++ b/tasks/task-arguments.unit.test.js @@ -0,0 +1,106 @@ +describe('Task arguments', () => { + let args + + beforeEach(() => { + jest.resetModules() + + args = { + posix: ['/usr/bin/node', '/path/to/project/node_modules/.bin/gulp'], + windows: ['node.exe', 'C:\\path\\to\\project\\node_modules\\.bin\\gulp.cmd'] + } + }) + + describe.each( + [ + { key: 'posix', description: 'Linux, macOS etc' }, + { key: 'windows', description: 'Windows only' } + ] + )('Platform: $description', ({ key }) => { + describe('Build destination', () => { + let argv + + beforeEach(() => { + argv = args[key] + }) + + describe('Defaults', () => { + it('defaults to ./public', async () => { + process.argv = [...argv] + + const { destination } = await import('./task-arguments.js') + expect(destination).toEqual('public') + }) + + it('defaults to ./public for "gulp build:compile"', async () => { + process.argv = [...argv, 'build:compile'] + + const { destination } = await import('./task-arguments.js') + expect(destination).toEqual('public') + }) + + it('defaults to ./package for "gulp build:package"', async () => { + process.argv = [...argv, 'build:package'] + + const { destination } = await import('./task-arguments.js') + expect(destination).toEqual('package') + }) + + it('defaults to ./dist for "gulp build:dist"', async () => { + process.argv = [...argv, 'build:dist'] + + const { destination } = await import('./task-arguments.js') + expect(destination).toEqual('dist') + }) + }) + + describe.each( + [ + // Override to public + { flag: 'public', destination: 'public' }, + { flag: './public', destination: 'public' }, + + // Override to package + { flag: 'package', destination: 'package' }, + { flag: './package', destination: 'package' }, + + // Override to dist + { flag: 'dist', destination: 'dist' }, + { flag: './dist', destination: 'dist' }, + + // Override to custom + { flag: 'custom/location/here', destination: 'custom/location/here' }, + { flag: 'custom\\location\\here', destination: 'custom/location/here' }, + { flag: './custom/location/here', destination: 'custom/location/here' } + ] + )('Override --destination "$flag"', ({ flag, destination: expected }) => { + it('uses flag by default', async () => { + process.argv = [...argv, '--destination', flag] + + const { destination } = await import('./task-arguments.js') + expect(destination).toEqual(expected) + }) + + it('uses flag for "gulp build:compile"', async () => { + process.argv = [...argv, 'build:compile', '--destination', flag] + + const { destination } = await import('./task-arguments.js') + expect(destination).toEqual(expected) + }) + + it('uses flag for "gulp build:package"', async () => { + process.argv = [...argv, 'build:package', '--destination', flag] + + const { destination } = await import('./task-arguments.js') + expect(destination).toEqual(expected) + }) + + it('uses flag for "gulp build:dist"', async () => { + process.argv = [...argv, 'build:dist', '--destination', flag] + + const { destination } = await import('./task-arguments.js') + expect(destination).toEqual(expected) + }) + }) + }) + }) +})