Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Locate GOV.UK Frontend using require.resolve() #2306

Merged
merged 4 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Fixes

- [#2306: Locate GOV.UK Frontend using `require.resolve()`](https://github.com/alphagov/govuk-prototype-kit/pull/2306)

## 13.13.3

### Fixes
Expand Down
13 changes: 8 additions & 5 deletions lib/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const sass = require('sass')
// local dependencies
const plugins = require('./plugins/plugins')
const {
packageDir,
projectDir,
appSassDir,
libSassDir,
Expand All @@ -21,9 +22,10 @@ const {
backupNunjucksDir,
appViewsDir
} = require('./utils/paths')
const { recursiveDirectoryContentsSync, getInternalGovukFrontendDir } = require('./utils')
const { recursiveDirectoryContentsSync } = require('./utils')
const { startPerformanceTimer, endPerformanceTimer } = require('./utils/performance')
const { verboseLog } = require('./utils/verboseLogger')
const { govukFrontendPaths } = require('./govukFrontendPaths')
const { hasRestartedAfterError } = require('./sync-changes')

let nodemonInstance
Expand Down Expand Up @@ -90,11 +92,12 @@ function sassInclude (filePath) {
function sassKitFrontendDependency () {
const timer = startPerformanceTimer()

const internalGovUkFrontendDir = getInternalGovukFrontendDir()
const internalGovUkFrontendConfig = fse.readJsonSync(path.join(internalGovUkFrontendDir, 'govuk-prototype-kit.config.json'))
// Find GOV.UK Frontend (via internal package, project fallback)
const govukFrontendInternal = govukFrontendPaths([packageDir, projectDir])

const govukFrontendSass = internalGovUkFrontendConfig.sass
.map(sassPath => path.join(internalGovUkFrontendDir, sassPath))
// Get GOV.UK Frontend (internal) stylesheets
const govukFrontendSass = (govukFrontendInternal.config?.sass || [])
.map(sassPath => path.join(govukFrontendInternal.baseDir, sassPath))

const fileContents = sassVariables('/manage-prototype/dependencies') +
govukFrontendSass
Expand Down
21 changes: 15 additions & 6 deletions lib/errorServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ const fs = require('fs')
const { getNunjucksAppEnv } = require('./nunjucks/nunjucksConfiguration')
const { getErrorModel } = require('./utils/errorModel')
const { verboseLog } = require('./utils/verboseLogger')
const { packageDir, projectDir } = require('./utils/paths')
const { govukFrontendPaths } = require('./govukFrontendPaths')
const syncChanges = require('./sync-changes')
const { flagError } = require('./sync-changes')
const { packageDir } = require('./utils/paths')
const { getInternalGovukFrontendDir } = require('./utils')

function runErrorServer (error) {
flagError(error)
Expand Down Expand Up @@ -45,6 +45,9 @@ function runErrorServer (error) {
}
}

// Find GOV.UK Frontend (via internal package, project fallback)
const govukFrontendInternal = govukFrontendPaths([packageDir, projectDir])

/**
* @type {http.RequestListener}
*/
Expand Down Expand Up @@ -79,7 +82,7 @@ function runErrorServer (error) {

// Route GOV.UK Frontend to internal package
const modulesDir = filePath.startsWith('govuk-frontend/') && knownRoute.startsWith('/manage-prototype/')
? path.dirname(getInternalGovukFrontendDir())
? path.dirname(govukFrontendInternal.baseDir)
: path.join(process.cwd(), 'node_modules')

try {
Expand All @@ -99,15 +102,21 @@ function runErrorServer (error) {
res.setHeader('Content-Type', 'text/html')
res.writeHead(500)

// Get GOV.UK Frontend (internal) views
const govukFrontendNunjucksPaths = (govukFrontendInternal.config?.nunjucksPaths || [])
.map(nunjucksPath => path.join(govukFrontendInternal.baseDir, nunjucksPath))

const fileContentsParts = []

try {
const nunjucksAppEnv = getNunjucksAppEnv([
path.join(__dirname, 'nunjucks'),
path.join(packageDir, 'node_modules', 'govuk-frontend'),
path.join(process.cwd(), 'node_modules', 'govuk-frontend')
...govukFrontendNunjucksPaths
])
res.end(nunjucksAppEnv.render('views/error-handling/server-error', getErrorModel(error)))
res.end(nunjucksAppEnv.render('views/error-handling/server-error', {
govukFrontendInternal, // Add GOV.UK Frontend paths to Nunjucks context
...getErrorModel(error)
}))
} catch (ignoreThisError) {
console.log(JSON.stringify({ ignoreThisError }, null, 2))
fileContentsParts.push('<h1 class="govuk-heading-l">There is an error</h1>')
Expand Down
40 changes: 40 additions & 0 deletions lib/govukFrontendPaths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// core dependencies
const path = require('path')

// npm dependencies
const fse = require('fs-extra')

/**
* Find GOV.UK Frontend via search paths
*
* @param {string[]} searchPaths - Search paths for `require.resolve()`
* @returns {{ baseDir: string, includePath: string, assetPath: string, config: { [key: string]: unknown } }}
*/
function govukFrontendPaths (searchPaths = []) {
/**
* GOV.UK Frontend paths normalised
*
* v4.x - `/path/to/node_modules/govuk-frontend/govuk/all.js`
* v5.x - `/path/to/node_modules/govuk-frontend/dist/govuk/all.bundle.js`
*/
const entryPath = require.resolve('govuk-frontend', { paths: searchPaths })
const dependencyPath = path.join('node_modules/govuk-frontend')
const baseDir = path.join(entryPath.split(dependencyPath)[0], dependencyPath)
const includeDir = path.dirname(entryPath)

return {
baseDir,

includePath: `/${path.relative(baseDir, includeDir)}`,
assetPath: `/${path.relative(baseDir, path.join(includeDir, 'assets'))}`,

// GOV.UK Frontend plugin config
config: fse.readJsonSync(path.join(baseDir, 'govuk-prototype-kit.config.json'), {
throws: false
})
Comment on lines +31 to +34
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenSurgisonGDS has suggested a new getScriptsAndAssetsConfig() utility for this

Couldn't find a "sync" way using an existing helper so might be a better option

}
}

module.exports = {
govukFrontendPaths
}
4 changes: 2 additions & 2 deletions lib/manage-prototype-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,10 @@ async function getTemplatesHandler (req, res) {
const availableTemplates = getPluginTemplates()

const commonTemplatesPackageName = '@govuk-prototype-kit/common-templates'
const govUkFrontendPackageName = 'govuk-frontend'
const govukFrontendPackageName = 'govuk-frontend'
let commonTemplatesDetails
const installedPlugins = (await getInstalledPackages()).map((pkg) => pkg.packageName)
if (installedPlugins.includes(govUkFrontendPackageName) && !installedPlugins.includes(commonTemplatesPackageName)) {
if (installedPlugins.includes(govukFrontendPackageName) && !installedPlugins.includes(commonTemplatesPackageName)) {
commonTemplatesDetails = {
pluginDisplayName: plugins.preparePackageNameForDisplay(commonTemplatesPackageName),
installLink: `${contextPath}/plugins/install?package=${encodeURIComponent(commonTemplatesPackageName)}&returnTo=templates`
Expand Down
16 changes: 6 additions & 10 deletions lib/manage-prototype-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const {
pluginCacheMiddleware,
postPluginsHandler
} = require('./manage-prototype-handlers')
const path = require('path')
const { getInternalGovukFrontendDir } = require('./utils')
const { packageDir, projectDir } = require('./utils/paths')
const { govukFrontendPaths } = require('./govukFrontendPaths')

const router = require('../index').requests.setupRouter(contextPath)

Expand Down Expand Up @@ -79,14 +79,10 @@ router.post('/plugins/:mode', postPluginsModeMiddleware)

router.post('/plugins/:mode', csrfProtection, postPluginsModeHandler)

const partialGovukFrontendUrls = [
'govuk/assets',
'govuk/all.js',
'govuk-prototype-kit/init.js'
]
partialGovukFrontendUrls.forEach(url => {
router.use(`/dependencies/govuk-frontend/${url}`, express.static(path.join(getInternalGovukFrontendDir(), url)))
})
// Find GOV.UK Frontend (via internal package, project fallback)
router.use('/dependencies/govuk-frontend', express.static(
govukFrontendPaths([packageDir, projectDir]).baseDir)
)

setKitRestarted(true)

Expand Down
2 changes: 1 addition & 1 deletion lib/nunjucks/govuk-prototype-kit/layouts/govuk-branded.njk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{%- set assetPath = assetPath | default('/plugin-assets/govuk-frontend/govuk/assets') -%}
{%- set assetPath = assetPath | default('/plugin-assets/govuk-frontend' + govukFrontend.assetPath) -%}

{% extends "govuk/template.njk" %}

Expand Down
2 changes: 1 addition & 1 deletion lib/nunjucks/views/manage-prototype/layout.njk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{%- set assetPath = '/manage-prototype/dependencies/govuk-frontend/govuk/assets' -%}
{%- set assetPath = '/manage-prototype/dependencies/govuk-frontend' + govukFrontendInternal.assetPath -%}
{% extends "govuk-prototype-kit/layouts/govuk-branded.njk" %}

{% block pageTitle %}
Expand Down
15 changes: 1 addition & 14 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ const filters = require('../filters/api')
const functions = require('../functions/api')
const plugins = require('../plugins/plugins')
const routes = require('../routes/api')
const { appDir, projectDir, packageDir } = require('./paths')
const fse = require('fs-extra')
const { appDir, projectDir } = require('./paths')
const { asyncSeriesMap } = require('./asyncSeriesMap')

// Tweak the Markdown renderer
Expand Down Expand Up @@ -254,17 +253,6 @@ async function searchAndReplaceFiles (dir, searchText, replaceText, extensions)
return modifiedFiles.flat().filter(Boolean)
}

let internalGovukFrontendDir

function getInternalGovukFrontendDir () {
if (!internalGovukFrontendDir) {
const packageDirFrontend = path.join(packageDir, 'node_modules', 'govuk-frontend')
const projectDirFrontend = path.join(projectDir, 'node_modules', 'govuk-frontend')
internalGovukFrontendDir = fse.pathExistsSync(packageDirFrontend) ? packageDirFrontend : projectDirFrontend
}
return internalGovukFrontendDir
}

function sortByObjectKey (key) {
return function (a, b) {
if (a[key] > b[key]) {
Expand All @@ -291,6 +279,5 @@ module.exports = {
sessionFileStoreQuietLogFn,
searchAndReplaceFiles,
recursiveDirectoryContentsSync,
getInternalGovukFrontendDir,
sortByObjectKey
}
23 changes: 16 additions & 7 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const dotenv = require('dotenv')
const express = require('express')
const fse = require('fs-extra')
const { expressNunjucks, getNunjucksAppEnv, stopWatchingNunjucks } = require('./lib/nunjucks/nunjucksConfiguration')

// We want users to be able to keep api keys, config variables and other
Expand All @@ -20,11 +19,11 @@ dotenv.config()
const { projectDir, packageDir, finalBackupNunjucksDir } = require('./lib/utils/paths')
const config = require('./lib/config.js').getConfig()
const packageJson = require('./package.json')
const { govukFrontendPaths } = require('./lib/govukFrontendPaths')
const utils = require('./lib/utils')
const sessionUtils = require('./lib/session.js')
const plugins = require('./lib/plugins/plugins.js')
const routesApi = require('./lib/routes/api.js')
const { getInternalGovukFrontendDir } = require('./lib/utils')

const app = express()
routesApi.setApp(app)
Expand All @@ -40,6 +39,12 @@ if (isSecure) {
app.set('trust proxy', 1) // needed for secure cookies on heroku
}

// Find GOV.UK Frontend (via project, internal package fallback)
const govukFrontend = govukFrontendPaths([projectDir, packageDir])

// Find GOV.UK Frontend (via internal package, project fallback)
const govukFrontendInternal = govukFrontendPaths([packageDir, projectDir])

// Add variables that are available in all views
app.locals.asset_path = '/public/'
app.locals.useAutoStoreData = config.useAutoStoreData
Expand All @@ -56,6 +61,11 @@ if (plugins.legacyGovukFrontendFixesNeeded()) {
app.locals.pluginConfig = plugins.getAppConfig({
scripts: utils.prototypeAppScripts
})

// Add GOV.UK Frontend paths to Nunjucks locals
app.locals.govukFrontend = govukFrontend
app.locals.govukFrontendInternal = govukFrontendInternal

// keep extensionConfig around for backwards compatibility
// TODO: remove in v14
app.locals.extensionConfig = app.locals.pluginConfig
Expand All @@ -70,16 +80,15 @@ app.use(cookieParser())
// static assets to prevent unauthorised access
app.use(require('./lib/authentication.js')())

// Get internal govuk-frontend views
const internalGovUkFrontendDir = getInternalGovukFrontendDir()
const internalGovUkFrontendConfig = fse.readJsonSync(path.join(internalGovUkFrontendDir, 'govuk-prototype-kit.config.json'))
const internalGovUkFrontendViews = internalGovUkFrontendConfig.nunjucksPaths.map(viewPath => path.join(internalGovUkFrontendDir, viewPath))
// Get GOV.UK Frontend (internal) views
const govukFrontendNunjucksPaths = (govukFrontendInternal.config?.nunjucksPaths || [])
.map(nunjucksPath => path.join(govukFrontendInternal.baseDir, nunjucksPath))

// Set up App
const appViews = [
path.join(projectDir, '/app/views/')
].concat(plugins.getAppViews([
...internalGovUkFrontendViews,
...govukFrontendNunjucksPaths,
finalBackupNunjucksDir
]))

Expand Down