Skip to content

Commit

Permalink
Add update post script to update user's app javascripts
Browse files Browse the repository at this point in the history
  • Loading branch information
lfdebrux committed Jul 27, 2022
1 parent 9a12e2f commit 0a17593
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 4 deletions.
68 changes: 68 additions & 0 deletions lib/_update_javascripts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const fs = require('fs').promises
const path = require('path')

const { projectDir } = require('./path-utils')
const { getProjectVersion, fetchOriginal } = require('./update-utils')

const updateDir = path.join(projectDir, 'update')

async function removeKitJsFromApplicationJs () {
const userVersion = await getProjectVersion()

// If the user already has version 13 or greater of the kit installed then
// their application.js file is all their code and we don't don't want to
// change it
if (userVersion >= '13.0.0') {
return
}

const assetPath = 'assets/javascripts/application.js'
const original = await fetchOriginal(userVersion, path.posix.join('app', assetPath))
const theirs = await fs.readFile(path.resolve(projectDir, 'app', assetPath), 'utf8')

// If the user hasn't changed their application.js file we can just replace it completely
if (original === theirs) {
return fs.copyFile(path.join(updateDir, 'app', assetPath), path.join(projectDir, 'app', assetPath))
}

// Otherwise, if the original code is contained as-is in their file, we can
// remove the shared lines, and add our hints
if (theirs.includes(original)) {
const ours = await fs.readFile(path.resolve(updateDir, 'app', assetPath), 'utf8')

let merged
merged = theirs.replace(original, '')
merged = ours + merged
return fs.writeFile(path.resolve(projectDir, 'app', assetPath), merged, 'utf8')
}

// If the original code is not recognisable, we should give up, but not
// without giving a warning to the user
console.warn(
`WARNING: update.sh was not able to automatically update your ${assetPath} file.\n` +
'If you have issues when running your prototype please contact the GOV.UK Prototype Kit team for support,\n' +
'using one of the methods listed at https://design-system.service.gov.uk/get-in-touch/'
)
}

async function removeKitJsFromAppJsPath () {
const appJsPath = path.join(projectDir, 'app', 'assets', 'javascripts')
await fs.unlink(path.join(appJsPath, 'auto-store-data.js')).catch(() => {})
await fs.unlink(path.join(appJsPath, 'jquery-1.11.3.js')).catch(() => {})
await fs.unlink(path.join(appJsPath, 'step-by-step-nav.js')).catch(() => {})
await fs.unlink(path.join(appJsPath, 'step-by-step-navigation.js')).catch(() => {})
}

async function main () {
await removeKitJsFromAppJsPath()
await removeKitJsFromApplicationJs()
}

module.exports = {
/* exported for tests only */
removeKitJsFromApplicationJs
}

if (require.main === module) {
main()
}
113 changes: 113 additions & 0 deletions lib/_update_javascripts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/* eslint-env jest */

const fs = require('fs').promises
const path = require('path')

jest.mock('./update-utils')
const updateUtils = require('./update-utils')
const updatejs = require('./_update_javascripts')

const oldApplicationJs = `/* global $ */
// Warn about using the kit in production
if (window.console && window.console.info) {
window.console.info('GOV.UK Prototype Kit - do not use for production')
}
$(document).ready(function () {
window.GOVUKFrontend.initAll()
})
`

const newApplicationJs = `// Add extra JavaScript here
ready(() => {
// Add JavaScript that needs to be run when the page is loaded
})
`

describe('removeKitJsFromApplicationJs', () => {
let mockCopyFile, mockWriteFile

afterEach(() => {
jest.restoreAllMocks()
})

beforeEach(() => {
jest.spyOn(updateUtils, 'getProjectVersion').mockImplementation(
async () => '12.1.1'
)

jest.spyOn(updateUtils, 'fetchOriginal').mockImplementation(
async () => oldApplicationJs
)

mockCopyFile = jest.spyOn(fs, 'copyFile').mockImplementation(
async () => {}
)

mockWriteFile = jest.spyOn(fs, 'writeFile').mockImplementation(
async () => {}
)
})

it('replaces application.js if the user has not updated it', async () => {
jest.spyOn(fs, 'readFile').mockImplementationOnce(
async () => oldApplicationJs
)

await updatejs.removeKitJsFromApplicationJs()

expect(mockCopyFile).toHaveBeenCalledWith(
expect.stringContaining(path.join('update', 'app', 'assets', 'javascripts', 'application.js')),
expect.stringContaining(path.join('app', 'assets', 'javascripts', 'application.js'))
)
})

it('rewrites application.js if the user has added lines to the bottom of the file', async () => {
// theirs
jest.spyOn(fs, 'readFile').mockImplementationOnce(
async () => oldApplicationJs + '\ncallMyCode()\n'
)
// ours
jest.spyOn(fs, 'readFile').mockImplementationOnce(
async () => newApplicationJs
)

await updatejs.removeKitJsFromApplicationJs()

expect(mockWriteFile).toHaveBeenCalledWith(
expect.stringContaining(path.join('app', 'assets', 'javascripts', 'application.js')),
newApplicationJs + '\ncallMyCode()\n',
'utf8'
)
})

it('does not touch application.js if the user has rewritten it a lot', async () => {
// theirs
jest.spyOn(fs, 'readFile').mockImplementationOnce(
async () => 'justMyCode()\n'
)

await updatejs.removeKitJsFromApplicationJs()

expect(mockWriteFile).not.toHaveBeenCalled()
expect(mockCopyFile).not.toHaveBeenCalled()
})

it('does not touch application.js if the user prototype is already on v13', async () => {
jest.spyOn(updateUtils, 'getProjectVersion').mockImplementation(
async () => '13.0.0'
)

const mockReadFile = jest.spyOn(fs, 'readFile').mockImplementation(
async () => {}
)

await updatejs.removeKitJsFromApplicationJs()

expect(mockReadFile).not.toHaveBeenCalled()
expect(mockWriteFile).not.toHaveBeenCalled()
expect(mockCopyFile).not.toHaveBeenCalled()
})
})
42 changes: 42 additions & 0 deletions lib/update-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const fs = require('fs').promises
const https = require('https')
const path = require('path')

const { projectDir } = require('./path-utils')

async function getProjectVersion () {
return (await fs.readFile(path.join(projectDir, 'VERSION.txt'), 'utf8')).trim()
}

async function fetchOriginal (version, filePath) {
const remoteUrl = `https://raw.githubusercontent.com/alphagov/govuk-prototype-kit/v${version}/${filePath}`

let data = ''
return new Promise((resolve, reject) => {
https.get(remoteUrl, (response) => {
let error

if (response.statusCode !== 200) {
error = new Error(`could not fetch ${remoteUrl}: status code ${response.statusCode}`)
Object.assign(error, response)
response.resume()
reject(error)
}

response.setEncoding('utf8')

response.on('data', (chunk) => {
data += chunk
})

response.on('end', () => {
resolve(data)
})
})
})
}

module.exports = {
fetchOriginal,
getProjectVersion
}
49 changes: 49 additions & 0 deletions lib/update-utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* eslint-env jest */

const fs = require('fs').promises
const https = require('https')
const path = require('path')
const stream = require('stream')

const { fetchOriginal, getProjectVersion } = require('./update-utils.js')

afterEach(() => {
jest.restoreAllMocks()
})

describe('fetchOriginal', () => {
it('gets the contents of a file as of version from GitHub', async () => {
const mockHttpsGet = jest.spyOn(https, 'get').mockImplementation((url, callback) => {
const response = new stream.PassThrough()
response.statusCode = 200

callback(response)

response.write('foo\n')
response.write('bar\n')
response.end()
})

await expect(fetchOriginal('99.99.99', 'app/views/foo.html')).resolves.toEqual(
'foo\nbar\n'
)
expect(mockHttpsGet).toHaveBeenCalledWith(
'https://raw.githubusercontent.com/alphagov/govuk-prototype-kit/v99.99.99/app/views/foo.html',
expect.anything()
)
})
})

describe('getProjectVersion', () => {
it('reads the VERSION.txt file to get the version number', async () => {
const mockReadFile = jest.spyOn(fs, 'readFile').mockImplementation(
async () => '99.99.99\n'
)

await expect(getProjectVersion()).resolves.toEqual('99.99.99')
expect(mockReadFile).toHaveBeenCalledWith(
expect.stringContaining(`${path.sep}VERSION.txt`),
'utf8'
)
})
})
10 changes: 6 additions & 4 deletions update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,12 @@ copy () {
}

post () {
# execute _update_scss if it exists in the update folder
if [ -d "update/lib/_update_scss" ]; then
node "update/lib/_update_scss"
fi
if [ -d "update/lib/_update_scss" ]; then
node "update/lib/_update_scss"
fi
if [ -f "update/lib/_update_javascripts.js" ]; then
node "update/lib/_update_javascripts"
fi
}

if [ "$0" == "${BASH_SOURCE:-$0}" ]
Expand Down

0 comments on commit 0a17593

Please sign in to comment.