2015-12-20 00:00:01 +00:00
|
|
|
// GULPFILE
|
|
|
|
|
// - - - - - - - - - - - - - - -
|
|
|
|
|
// This file processes all of the assets in the "src" folder
|
|
|
|
|
// and outputs the finished files in the "dist" folder.
|
|
|
|
|
|
|
|
|
|
// 1. LIBRARIES
|
|
|
|
|
// - - - - - - - - - - - - - - -
|
2019-04-24 13:27:26 +01:00
|
|
|
const { src, pipe, dest, series, parallel, watch } = require('gulp');
|
2019-10-11 10:26:25 +01:00
|
|
|
const rollupPluginCommonjs = require('rollup-plugin-commonjs');
|
|
|
|
|
const rollupPluginNodeResolve = require('rollup-plugin-node-resolve');
|
2019-11-20 13:43:07 +00:00
|
|
|
const streamqueue = require('streamqueue');
|
2019-04-01 09:58:13 +01:00
|
|
|
const stylish = require('jshint-stylish');
|
2023-04-27 13:32:23 -04:00
|
|
|
const uswds = require("@uswds/compile");
|
2016-07-19 15:13:23 +01:00
|
|
|
|
2019-04-05 13:31:13 +01:00
|
|
|
const plugins = {};
|
|
|
|
|
plugins.addSrc = require('gulp-add-src');
|
|
|
|
|
plugins.babel = require('gulp-babel');
|
2019-11-26 14:18:57 +00:00
|
|
|
plugins.cleanCSS = require('gulp-clean-css');
|
2019-04-05 13:31:13 +01:00
|
|
|
plugins.concat = require('gulp-concat');
|
|
|
|
|
plugins.cssUrlAdjuster = require('gulp-css-url-adjuster');
|
|
|
|
|
plugins.jshint = require('gulp-jshint');
|
|
|
|
|
plugins.prettyerror = require('gulp-prettyerror');
|
2019-11-26 14:18:57 +00:00
|
|
|
plugins.rollup = require('gulp-better-rollup')
|
2019-04-05 13:31:13 +01:00
|
|
|
plugins.uglify = require('gulp-uglify');
|
2015-12-20 00:00:01 +00:00
|
|
|
|
|
|
|
|
// 2. CONFIGURATION
|
|
|
|
|
// - - - - - - - - - - - - - - -
|
2019-04-01 09:58:13 +01:00
|
|
|
const paths = {
|
|
|
|
|
src: 'app/assets/',
|
|
|
|
|
dist: 'app/static/',
|
|
|
|
|
templates: 'app/templates/',
|
|
|
|
|
npm: 'node_modules/',
|
2019-10-09 15:03:35 +01:00
|
|
|
toolkit: 'node_modules/govuk_frontend_toolkit/',
|
|
|
|
|
govuk_frontend: 'node_modules/govuk-frontend/'
|
2019-04-01 09:58:13 +01:00
|
|
|
};
|
2019-11-26 14:18:57 +00:00
|
|
|
// Rewrite /static prefix for URLs in CSS files
|
|
|
|
|
let staticPathMatcher = new RegExp('^\/static\/');
|
|
|
|
|
if (process.env.NOTIFY_ENVIRONMENT == 'development') { // pass through if on development
|
|
|
|
|
staticPathMatcher = url => url;
|
|
|
|
|
}
|
2015-12-20 00:00:01 +00:00
|
|
|
|
|
|
|
|
// 3. TASKS
|
|
|
|
|
// - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
// Move GOV.UK template resources
|
|
|
|
|
|
2019-04-01 09:58:13 +01:00
|
|
|
const copy = {
|
2019-10-24 11:33:04 +01:00
|
|
|
error_pages: () => {
|
|
|
|
|
return src(paths.src + 'error_pages/**/*')
|
|
|
|
|
.pipe(dest(paths.dist + 'error_pages/'))
|
2019-10-09 15:03:35 +01:00
|
|
|
},
|
2022-12-14 13:30:46 -05:00
|
|
|
fonts: () => {
|
|
|
|
|
return src(paths.src + 'fonts/**/*')
|
|
|
|
|
.pipe(dest(paths.dist + 'fonts/'));
|
2023-06-12 13:52:38 -04:00
|
|
|
},
|
|
|
|
|
gtm: () => {
|
|
|
|
|
return src(paths.src + 'js/gtm_head.js')
|
|
|
|
|
.pipe(dest(paths.dist + 'js/'));
|
2022-12-14 13:30:46 -05:00
|
|
|
}
|
2019-04-01 09:58:13 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2019-10-11 10:26:25 +01:00
|
|
|
|
|
|
|
|
|
2019-04-01 09:58:13 +01:00
|
|
|
const javascripts = () => {
|
2019-11-20 13:43:07 +00:00
|
|
|
// JS from third-party sources
|
|
|
|
|
// We assume none of it will need to pass through Babel
|
|
|
|
|
const vendored = src(paths.src + 'javascripts/modules/all.mjs')
|
|
|
|
|
// Use Rollup to combine all JS in JS module format into a Immediately Invoked Function
|
|
|
|
|
// Expression (IIFE) to:
|
|
|
|
|
// - deliver it in one bundle
|
|
|
|
|
// - allow it to run in browsers without support for JS Modules
|
|
|
|
|
.pipe(plugins.rollup(
|
|
|
|
|
{
|
|
|
|
|
plugins: [
|
|
|
|
|
// determine module entry points from either 'module' or 'main' fields in package.json
|
|
|
|
|
rollupPluginNodeResolve({
|
|
|
|
|
mainFields: ['module', 'main']
|
|
|
|
|
}),
|
|
|
|
|
// gulp rollup runs on nodeJS so reads modules in commonJS format
|
|
|
|
|
// this adds node_modules to the require path so it can find the GOVUK Frontend modules
|
|
|
|
|
rollupPluginCommonjs({
|
|
|
|
|
include: 'node_modules/**'
|
|
|
|
|
})
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
format: 'iife',
|
|
|
|
|
name: 'GOVUK'
|
|
|
|
|
}
|
|
|
|
|
))
|
|
|
|
|
// return a stream which pipes these files before the JS modules bundle
|
2019-04-01 09:58:13 +01:00
|
|
|
.pipe(plugins.addSrc.prepend([
|
|
|
|
|
paths.npm + 'hogan.js/dist/hogan-3.0.2.js',
|
|
|
|
|
paths.npm + 'jquery/dist/jquery.min.js',
|
|
|
|
|
paths.npm + 'query-command-supported/dist/queryCommandSupported.min.js',
|
|
|
|
|
paths.npm + 'timeago/jquery.timeago.js',
|
Support registering a new authenticator
This adds Yubico's FIDO2 library and two APIs for working with the
"navigator.credentials.create()" function in JavaScript. The GET
API uses the library to generate options for the "create()" function,
and the POST API decodes and verifies the resulting credential. While
the options and response are dict-like, CBOR is necessary to encode
some of the byte-level values, which can't be represented in JSON.
Much of the code here is based on the Yubico library example [1][2].
Implementation notes:
- There are definitely better ways to alert the user about failure, but
window.alert() will do for the time being. Using location.reload() is
also a bit jarring if the page scrolls, but not a major issue.
- Ideally we would use window.fetch() to do AJAX calls, but we don't
have a polyfill for this, and we use $.ajax() elsewhere [3]. We need
to do a few weird tricks [6] to stop jQuery trashing the data.
- The FIDO2 server doesn't serve web requests; it's just a "server" in
the sense of WebAuthn terminology. It lives in its own module, since it
needs to be initialised with the app / config.
- $.ajax returns a promise-like object. Although we've used ".fail()"
elsewhere [3], I couldn't find a stub object that supports it, so I've
gone for ".catch()", and used a Promise stub object in tests.
- WebAuthn only works over HTTPS, but there's an exception for "localhost"
[4]. However, the library is a bit too strict [5], so we have to disable
origin verification to avoid needing HTTPS for dev work.
[1]: https://github.com/Yubico/python-fido2/blob/c42d9628a4f33d20c4401096fa8d3fc466d5b77f/examples/server/server.py
[2]: https://github.com/Yubico/python-fido2/blob/c42d9628a4f33d20c4401096fa8d3fc466d5b77f/examples/server/static/register.html
[3]: https://github.com/alphagov/notifications-admin/blob/91453d36395b7a0cf2998dfb8a5f52cc9e96640f/app/assets/javascripts/updateContent.js#L33
[4]: https://stackoverflow.com/questions/55971593/navigator-credentials-is-null-on-local-server
[5]: https://github.com/Yubico/python-fido2/blob/c42d9628a4f33d20c4401096fa8d3fc466d5b77f/fido2/rpid.py#L69
[6]: https://stackoverflow.com/questions/12394622/does-jquery-ajax-or-load-allow-for-responsetype-arraybuffer
2021-05-07 18:10:07 +01:00
|
|
|
paths.npm + 'textarea-caret/index.js',
|
|
|
|
|
paths.npm + 'cbor-js/cbor.js'
|
2019-11-20 13:43:07 +00:00
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
// JS local to this application
|
|
|
|
|
const local = src([
|
|
|
|
|
paths.toolkit + 'javascripts/govuk/modules.js',
|
|
|
|
|
paths.toolkit + 'javascripts/govuk/show-hide-content.js',
|
2021-08-27 17:32:06 +01:00
|
|
|
paths.src + 'javascripts/copyToClipboard.js',
|
2019-11-20 13:43:07 +00:00
|
|
|
paths.src + 'javascripts/autofocus.js',
|
|
|
|
|
paths.src + 'javascripts/enhancedTextbox.js',
|
|
|
|
|
paths.src + 'javascripts/fileUpload.js',
|
|
|
|
|
paths.src + 'javascripts/radioSelect.js',
|
|
|
|
|
paths.src + 'javascripts/updateContent.js',
|
|
|
|
|
paths.src + 'javascripts/listEntry.js',
|
|
|
|
|
paths.src + 'javascripts/liveSearch.js',
|
|
|
|
|
paths.src + 'javascripts/errorTracking.js',
|
|
|
|
|
paths.src + 'javascripts/preventDuplicateFormSubmissions.js',
|
|
|
|
|
paths.src + 'javascripts/fullscreenTable.js',
|
|
|
|
|
paths.src + 'javascripts/colourPreview.js',
|
|
|
|
|
paths.src + 'javascripts/templateFolderForm.js',
|
|
|
|
|
paths.src + 'javascripts/collapsibleCheckboxes.js',
|
2019-12-02 18:15:00 +00:00
|
|
|
paths.src + 'javascripts/radioSlider.js',
|
2021-01-06 17:32:46 +00:00
|
|
|
paths.src + 'javascripts/updateStatus.js',
|
2021-07-20 12:14:10 +01:00
|
|
|
paths.src + 'javascripts/errorBanner.js',
|
2021-02-11 16:44:22 +00:00
|
|
|
paths.src + 'javascripts/homepage.js',
|
2023-09-07 08:38:41 -06:00
|
|
|
paths.src + 'javascripts/timeoutPopup.js',
|
2023-11-16 12:02:59 -05:00
|
|
|
paths.src + 'javascripts/date.js',
|
2019-12-15 19:38:49 +00:00
|
|
|
paths.src + 'javascripts/main.js',
|
2019-11-20 13:43:07 +00:00
|
|
|
])
|
2022-07-21 18:25:23 -07:00
|
|
|
.pipe(plugins.prettyerror())
|
|
|
|
|
.pipe(plugins.babel({
|
|
|
|
|
presets: ['@babel/preset-env']
|
|
|
|
|
}));
|
2019-11-20 13:43:07 +00:00
|
|
|
|
|
|
|
|
// return single stream of all vinyl objects piped from the end of the vendored stream, then
|
|
|
|
|
// those from the end of the local stream
|
|
|
|
|
return streamqueue({ objectMode: true }, vendored, local)
|
2019-04-01 09:58:13 +01:00
|
|
|
.pipe(plugins.uglify())
|
|
|
|
|
.pipe(plugins.concat('all.js'))
|
|
|
|
|
.pipe(dest(paths.dist + 'javascripts/'))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2015-12-20 00:00:01 +00:00
|
|
|
// Copy images
|
|
|
|
|
|
2019-04-01 09:58:13 +01:00
|
|
|
const images = () => {
|
|
|
|
|
return src([
|
2022-08-03 17:16:35 -07:00
|
|
|
paths.toolkit + 'images/**/*',
|
|
|
|
|
paths.govuk_frontend + 'assets/images/**/*',
|
2022-07-21 18:25:23 -07:00
|
|
|
paths.src + 'images/**/*',
|
2023-04-27 13:32:23 -04:00
|
|
|
paths.src + 'img/**/*',
|
2022-07-21 18:25:23 -07:00
|
|
|
paths.template + 'assets/images/**/*'
|
2022-08-03 17:16:35 -07:00
|
|
|
|
2022-07-21 18:25:23 -07:00
|
|
|
])
|
2019-04-01 09:58:13 +01:00
|
|
|
.pipe(dest(paths.dist + 'images/'))
|
|
|
|
|
};
|
2016-09-12 16:11:34 +01:00
|
|
|
|
2015-12-20 00:00:01 +00:00
|
|
|
|
2019-04-24 13:27:26 +01:00
|
|
|
const watchFiles = {
|
|
|
|
|
javascripts: (cb) => {
|
|
|
|
|
watch([paths.src + 'javascripts/**/*'], javascripts);
|
|
|
|
|
cb();
|
|
|
|
|
},
|
|
|
|
|
images: (cb) => {
|
|
|
|
|
watch([paths.src + 'images/**/*'], images);
|
|
|
|
|
cb();
|
|
|
|
|
},
|
2023-04-27 13:32:23 -04:00
|
|
|
uswds: (cb) => {
|
|
|
|
|
watch([paths.src + 'sass/**/*'], uswds.watch);
|
|
|
|
|
cb();
|
|
|
|
|
},
|
2019-04-24 13:27:26 +01:00
|
|
|
self: (cb) => {
|
|
|
|
|
watch(['gulpfile.js'], defaultTask);
|
|
|
|
|
cb();
|
|
|
|
|
}
|
2019-04-01 09:58:13 +01:00
|
|
|
};
|
|
|
|
|
|
2019-04-24 13:27:26 +01:00
|
|
|
|
2019-04-01 09:58:13 +01:00
|
|
|
const lint = {
|
|
|
|
|
'js': (cb) => {
|
2019-10-11 10:26:25 +01:00
|
|
|
return src(
|
2022-07-21 18:25:23 -07:00
|
|
|
paths.src + 'javascripts/**/*.js'
|
|
|
|
|
)
|
2019-04-01 09:58:13 +01:00
|
|
|
.pipe(plugins.jshint())
|
|
|
|
|
.pipe(plugins.jshint.reporter(stylish))
|
|
|
|
|
.pipe(plugins.jshint.reporter('fail'))
|
|
|
|
|
}
|
|
|
|
|
};
|
2016-02-08 11:05:07 +00:00
|
|
|
|
2019-04-24 13:27:26 +01:00
|
|
|
|
2019-04-01 09:58:13 +01:00
|
|
|
// Default: compile everything
|
|
|
|
|
const defaultTask = parallel(
|
2019-11-25 13:04:23 +00:00
|
|
|
parallel(
|
2022-12-14 13:30:46 -05:00
|
|
|
copy.fonts,
|
2022-07-21 18:25:23 -07:00
|
|
|
images
|
2019-04-01 09:58:13 +01:00
|
|
|
),
|
|
|
|
|
series(
|
2019-10-24 11:33:04 +01:00
|
|
|
copy.error_pages,
|
2019-10-11 10:26:25 +01:00
|
|
|
series(
|
2019-11-20 13:43:07 +00:00
|
|
|
javascripts
|
2019-10-11 10:26:25 +01:00
|
|
|
),
|
2023-06-06 15:28:24 -04:00
|
|
|
uswds.compile,
|
2023-06-12 13:52:38 -04:00
|
|
|
uswds.copyAssets,
|
|
|
|
|
copy.gtm
|
2019-04-01 09:58:13 +01:00
|
|
|
)
|
2016-02-08 11:05:07 +00:00
|
|
|
);
|
|
|
|
|
|
2019-04-24 13:27:26 +01:00
|
|
|
|
|
|
|
|
// Watch for changes and re-run tasks
|
|
|
|
|
const watchForChanges = parallel(
|
|
|
|
|
watchFiles.javascripts,
|
|
|
|
|
watchFiles.images,
|
|
|
|
|
watchFiles.self
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
2019-04-01 09:58:13 +01:00
|
|
|
exports.default = defaultTask;
|
2016-02-08 11:05:07 +00:00
|
|
|
|
2023-08-28 16:01:47 -04:00
|
|
|
exports.lint = series(lint.js);
|
2015-12-20 00:00:01 +00:00
|
|
|
|
|
|
|
|
// Optional: recompile on changes
|
2019-04-01 09:58:13 +01:00
|
|
|
exports.watch = series(defaultTask, watchForChanges);
|
2023-04-24 14:57:35 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Compile USWDS
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* USWDS version
|
|
|
|
|
* Set the major version of USWDS you're using
|
|
|
|
|
* (Current options are the numbers 2 or 3)
|
|
|
|
|
*/
|
|
|
|
|
uswds.settings.version = 3;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Path settings
|
|
|
|
|
* Set as many as you need
|
|
|
|
|
*/
|
|
|
|
|
uswds.paths.dist.css = './app/static/css';
|
2023-06-06 15:28:24 -04:00
|
|
|
uswds.paths.dist.js = './app/static/js';
|
|
|
|
|
uswds.paths.dist.img = './app/static/img';
|
|
|
|
|
uswds.paths.dist.fonts = './app/static/fonts';
|
2023-04-24 14:57:35 -04:00
|
|
|
uswds.paths.dist.theme = './app/assets/sass/uswds';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Exports
|
|
|
|
|
* Add as many as you need
|
|
|
|
|
*/
|
|
|
|
|
exports.init = uswds.init;
|
|
|
|
|
exports.compile = uswds.compile;
|
2023-06-08 13:12:00 -04:00
|
|
|
exports.copyAll = uswds.copyAll;
|
2023-06-06 15:28:24 -04:00
|
|
|
exports.watch = uswds.watch;
|
2023-08-14 16:59:38 -04:00
|
|
|
exports.copyAssets = uswds.copyAssets;
|