diff --git a/__tests__/spec/update-script.js b/__tests__/spec/update-script.js
index 9a881d28e7..5f750a65e6 100644
--- a/__tests__/spec/update-script.js
+++ b/__tests__/spec/update-script.js
@@ -540,6 +540,13 @@ describe('update.sh', () => {
' D app/assets/javascripts/step-by-step-nav.js'
]))
})
+
+ it('updates app template includes', () => {
+ expect(gitStatus).toEqual(expect.arrayContaining([
+ ' M app/views/includes/head.html',
+ ' M app/views/includes/scripts.html'
+ ]))
+ })
})
it('can be run as a piped script', async () => {
diff --git a/lib/_update/post.js b/lib/_update/post.js
index 521e06803f..6de7f65330 100644
--- a/lib/_update/post.js
+++ b/lib/_update/post.js
@@ -1,5 +1,7 @@
const { updateJavascripts } = require('./update-javascripts')
const { updateScss } = require('./update-scss')
+const { updateIncludes } = require('./update-includes')
updateJavascripts()
updateScss()
+updateIncludes()
diff --git a/lib/_update/update-includes.js b/lib/_update/update-includes.js
new file mode 100644
index 0000000000..7d962581a4
--- /dev/null
+++ b/lib/_update/update-includes.js
@@ -0,0 +1,19 @@
+const { getProjectVersion, patchUserFile } = require('./util')
+
+async function updateIncludes () {
+ 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
+ }
+
+ await patchUserFile(userVersion, 'app/views/includes/head.html')
+ await patchUserFile(userVersion, 'app/views/includes/scripts.html')
+}
+
+module.exports = {
+ updateIncludes
+}
diff --git a/lib/_update/update-includes.test.js b/lib/_update/update-includes.test.js
new file mode 100644
index 0000000000..387197921b
--- /dev/null
+++ b/lib/_update/update-includes.test.js
@@ -0,0 +1,202 @@
+/* eslint-env jest */
+
+const fs = require('fs')
+const fsp = require('fs').promises
+const path = require('path')
+
+jest.mock('./util/fetch-original')
+jest.mock('./util', () => {
+ const originalModule = jest.requireActual('./util')
+
+ return {
+ ...originalModule,
+ getProjectVersion: jest.fn(async () => '')
+ }
+})
+const { fetchOriginal: mockFetchOriginal } = require('./util/fetch-original')
+const { getProjectVersion: mockGetProjectVersion } = require('./util')
+const { projectDir } = require('../path-utils')
+
+const { updateIncludes } = require('./update-includes')
+
+const originalIncludesHead = `
+
+
+{% for stylesheetUrl in extensionConfig.stylesheets %}
+
+{% endfor %}
+`
+
+const newIncludesHead = fs.readFileSync(
+ path.join(projectDir, 'app', 'views', 'includes', 'head.html'),
+ 'utf8'
+)
+
+const originalIncludesScripts = `
+
+
+{% for scriptUrl in extensionConfig.scripts %}
+
+{% endfor %}
+
+
+
+{% if useAutoStoreData %}
+
+{% endif %}
+`
+
+const newIncludesScripts = fs.readFileSync(
+ path.join(projectDir, 'app', 'views', 'includes', 'scripts.html'),
+ 'utf8'
+)
+
+describe('updateIncludes', () => {
+ let mockCopyFile, mockReadFile, mockWriteFile
+
+ beforeEach(() => {
+ mockGetProjectVersion.mockResolvedValue(
+ '12.1.1'
+ )
+
+ mockFetchOriginal.mockRejectedValue(
+ new Error('fetchOriginal called more times than expected')
+ )
+
+ mockCopyFile = jest.spyOn(fsp, 'copyFile').mockImplementation(async () => {})
+ mockReadFile = jest.spyOn(fsp, 'readFile').mockResolvedValue(async () => {})
+ mockWriteFile = jest.spyOn(fsp, 'writeFile').mockImplementation(async () => {})
+ })
+
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
+
+ it('replaces app/views/includes/{head.html, scripts.html} if the user has not updated them', async () => {
+ // original head.html
+ mockFetchOriginal.mockResolvedValueOnce(
+ originalIncludesHead
+ )
+ // their head.html
+ mockReadFile.mockResolvedValueOnce(
+ originalIncludesHead
+ )
+ // our head.html
+ mockReadFile.mockResolvedValueOnce(
+ newIncludesHead
+ )
+
+ // original scripts.html
+ mockFetchOriginal.mockResolvedValueOnce(
+ originalIncludesScripts
+ )
+ // their scripts.html
+ mockReadFile.mockResolvedValueOnce(
+ originalIncludesScripts
+ )
+ // our scripts.html
+ mockReadFile.mockResolvedValueOnce(
+ newIncludesScripts
+ )
+
+ await updateIncludes()
+
+ expect(mockCopyFile).toHaveBeenCalledWith(
+ path.join(projectDir, 'update', 'app', 'views', 'includes', 'head.html'),
+ path.join(projectDir, 'app', 'views', 'includes', 'head.html')
+ )
+
+ expect(mockCopyFile).toHaveBeenCalledWith(
+ path.join(projectDir, 'update', 'app', 'views', 'includes', 'scripts.html'),
+ path.join(projectDir, 'app', 'views', 'includes', 'scripts.html')
+ )
+ })
+
+ it('rewrites app/views/includes/{head.html, scripts.html} if the user has added lines to the bottom of the file', async () => {
+ // original head.html
+ mockFetchOriginal.mockResolvedValueOnce(
+ originalIncludesHead
+ )
+ // their head.html
+ mockReadFile.mockResolvedValueOnce(
+ originalIncludesHead + '\n\n'
+ )
+ // our head.html
+ mockReadFile.mockResolvedValueOnce(
+ newIncludesHead
+ )
+
+ // original scripts.html
+ mockFetchOriginal.mockResolvedValueOnce(
+ originalIncludesScripts
+ )
+ // their scripts.html
+ mockReadFile.mockResolvedValueOnce(
+ originalIncludesScripts + '\n\n'
+ )
+ // our scripts.html
+ mockReadFile.mockResolvedValueOnce(
+ newIncludesScripts
+ )
+
+ await updateIncludes()
+
+ expect(mockWriteFile).toHaveBeenCalledWith(
+ path.join(projectDir, 'app', 'views', 'includes', 'head.html'),
+ newIncludesHead + '\n\n',
+ 'utf8'
+ )
+
+ expect(mockWriteFile).toHaveBeenCalledWith(
+ path.join(projectDir, 'app', 'views', 'includes', 'scripts.html'),
+ newIncludesScripts + '\n\n',
+ 'utf8'
+ )
+ })
+
+ it('rewrites app/views/includes/scripts.html even if it cannot rewrite head.html', async () => {
+ // original head.html
+ mockFetchOriginal.mockResolvedValueOnce(
+ originalIncludesHead
+ )
+ // their head.html
+ mockReadFile.mockResolvedValueOnce(
+ '\n\n'
+ )
+ // our head.html
+ mockReadFile.mockResolvedValueOnce(
+ newIncludesHead
+ )
+
+ // original scripts.html
+ mockFetchOriginal.mockResolvedValueOnce(
+ originalIncludesScripts
+ )
+ // their scripts.html
+ mockReadFile.mockResolvedValueOnce(
+ originalIncludesScripts + '\n\n'
+ )
+ // our scripts.html
+ mockReadFile.mockResolvedValueOnce(
+ newIncludesScripts
+ )
+
+ const mockConsoleWarn = jest.spyOn(global.console, 'warn').mockImplementation(() => {})
+
+ await updateIncludes()
+
+ expect(mockWriteFile).not.toHaveBeenCalledWith(
+ path.join(projectDir, 'app', 'views', 'includes', 'head.html'),
+ newIncludesHead + '\n\n',
+ 'utf8'
+ )
+
+ expect(mockWriteFile).toHaveBeenCalledWith(
+ path.join(projectDir, 'app', 'views', 'includes', 'scripts.html'),
+ newIncludesScripts + '\n\n',
+ 'utf8'
+ )
+
+ expect(mockConsoleWarn).toHaveBeenCalledTimes(1)
+ })
+})