Skip to content
Snippets Groups Projects
Commit 32b50eb5 authored by richard.petersen's avatar richard.petersen :sailboat:
Browse files

Improve the version detection

parent c6794d7d
No related branches found
No related tags found
No related merge requests found
...@@ -37,7 +37,7 @@ describe('Responses contain custom headers', function () { ...@@ -37,7 +37,7 @@ describe('Responses contain custom headers', function () {
it('index.html has version', async function () { it('index.html has version', async function () {
const response = await request(app).get('/index.html') const response = await request(app).get('/index.html')
expect(response.statusCode).to.equal(200) expect(response.statusCode).to.equal(200)
expect(response.headers.version).to.equal('3038606729') expect(response.headers.version).to.equal('3215668592')
}) })
it('javascript file contains dependencies', async function () { it('javascript file contains dependencies', async function () {
...@@ -45,4 +45,75 @@ describe('Responses contain custom headers', function () { ...@@ -45,4 +45,75 @@ describe('Responses contain custom headers', function () {
expect(response.statusCode).to.equal(200) expect(response.statusCode).to.equal(200)
expect(response.headers.dependencies).to.equal('main.css') expect(response.headers.dependencies).to.equal('main.css')
}) })
describe('with different files', function () {
beforeEach(async function () {
td.reset()
mockConfig({ urls: ['http://ui-server/'] })
mockFetch({
'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({
'index.html': {}
}),
'/index.html': () => new Response('<html><head></head><body>it\'s me</body></html>', { headers: { 'content-type': 'text/html' } })
}
})
app = await mockApp()
})
it('index.html has version', async function () {
const response = await request(app).get('/index.html')
expect(response.statusCode).to.equal(200)
// important here is, that it is different than in the test without meta.json
expect(response.headers.version).to.equal('3961519424')
})
})
describe('with meta.json', function () {
beforeEach(async function () {
td.reset()
mockConfig({ urls: ['http://ui-server/'] })
mockFetch({
'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({
'index.html': {}
}),
'/index.html': () => new Response('<html><head></head><body>it\'s me</body></html>', { headers: { 'content-type': 'text/html' } }),
'/meta.json': { commitSha: '1234567890' }
}
})
app = await mockApp()
})
it('index.html has version', async function () {
const response = await request(app).get('/index.html')
expect(response.statusCode).to.equal(200)
// important here is, that it is different than in the test without meta.json
expect(response.headers.version).to.equal('1487554813')
})
})
describe('with different meta.json', function () {
beforeEach(async function () {
td.reset()
mockConfig({ urls: ['http://ui-server/'] })
mockFetch({
'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({
'index.html': {}
}),
'/index.html': () => new Response('<html><head></head><body>it\'s me</body></html>', { headers: { 'content-type': 'text/html' } }),
'/meta.json': { commitSha: '0987654321' }
}
})
app = await mockApp()
})
it('index.html has version', async function () {
const response = await request(app).get('/index.html')
expect(response.statusCode).to.equal(200)
// important here is, that it is different than in the test without meta.json
expect(response.headers.version).to.equal('319344871')
})
})
}) })
...@@ -42,7 +42,7 @@ export function createApp () { ...@@ -42,7 +42,7 @@ export function createApp () {
const startupCheck = new health.StartupCheck('warmup cache', async function () { const startupCheck = new health.StartupCheck('warmup cache', async function () {
const stopTimer = startUpTimeGauge.startTimer() const stopTimer = startUpTimeGauge.startTimer()
try { try {
const viteManifests = await loadViteManifests() const viteManifests = await loadViteManifests({ warmUp: false })
// also need to load ox manifests here, to make sure the cache is warm // also need to load ox manifests here, to make sure the cache is warm
await getOxManifests() await getOxManifests()
const deps = viteManifestToDeps(viteManifests) const deps = viteManifestToDeps(viteManifests)
......
...@@ -6,17 +6,42 @@ import { hash, viteManifestToDeps } from './util.js' ...@@ -6,17 +6,42 @@ import { hash, viteManifestToDeps } from './util.js'
import { logger } from './logger.js' import { logger } from './logger.js'
export const loadViteManifests = (() => { export const loadViteManifests = (() => {
let cachePromise let lastManifest
let lastCacheTime let lastCacheTime
let lastHash let lastHash
async function getHash () {
await config.load()
const infos = await Promise.all(config.urls.map(async baseUrl => {
try {
const response = await fetch(new URL('meta.json', baseUrl))
if (!response.ok) throw new Error()
const meta = await response.json()
const version = meta.commitSha || meta.buildDate || meta.version
if (!version) throw new Error()
return version
} catch (err) {
logger.debug(`UI container at ${baseUrl} does not have meta.json. Fall back to version hash based on manifest.`)
}
try {
const response = await fetch(new URL('manifest.json', baseUrl))
if (!response.ok) throw new Error()
const manifest = await response.json()
return hash(manifest)
} catch (err) {
logger.error(`Cannot fetch manifest from ${baseUrl}. Version info will not be correct.`)
}
}))
return hash(infos)
}
async function reload () { async function reload () {
await config.load() await config.load()
// vite manifests contains a set of objects with the vite-manifests // vite manifests contains a set of objects with the vite-manifests
// from the corresponding registered services // from the corresponding registered services
const viteManifests = await Promise.all(config.urls.map(async baseUrl => { const viteManifests = await Promise.all(config.urls.map(async baseUrl => {
// fetch the manifests // fetch the manifests
const result = await fetch(new URL('/manifest.json', baseUrl)) const result = await fetch(new URL('manifest.json', baseUrl))
if (!result.ok) throw new Error(`Failed to load manifest for url ${result.url} (Status: ${result.status}: ${result.statusText})`) if (!result.ok) throw new Error(`Failed to load manifest for url ${result.url} (Status: ${result.status}: ${result.statusText})`)
try { try {
const manifest = await result.json() const manifest = await result.json()
...@@ -32,55 +57,52 @@ export const loadViteManifests = (() => { ...@@ -32,55 +57,52 @@ export const loadViteManifests = (() => {
})) }))
// combine all manifests by keys. With duplicates, last wins // combine all manifests by keys. With duplicates, last wins
const viteManifest = viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {}) return viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {})
Object.defineProperty(viteManifest, '__hash__', {
enumerable: false,
writable: true
})
try {
viteManifest.__hash__ = hash(viteManifests)
} catch (err) {
logger.error(`Failed to calculate hash: ${err.message}`)
}
return viteManifest
} }
return function loadViteManifests ({ useCache = true } = {}) { return function loadViteManifests ({ useCache = true, warmUp = true } = {}) {
const CACHE_TTL = parseInt(process.env.CACHE_TTL) const CACHE_TTL = parseInt(process.env.CACHE_TTL)
const timeElapsed = () => +((+new Date() - lastCacheTime) / 1000).toFixed(2) const timeElapsed = () => +((+new Date() - lastCacheTime) / 1000).toFixed(2)
if (!cachePromise || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) { if (!lastManifest || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) {
cachePromise = reload() const promise = (async () => {
cachePromise.then(manifests => { const newHash = await getHash()
if (lastManifest && lastHash === newHash) return
const manifest = await reload()
if (useCache) logger.info(`reloaded manifests after ${timeElapsed()} seconds`) if (useCache) logger.info(`reloaded manifests after ${timeElapsed()} seconds`)
// update cache promise
const newHash = manifests.__hash__
if (newHash !== lastHash) {
if (lastHash) {
const deps = viteManifestToDeps(manifests)
// asynchronously rewarm the cache
fileCache.warmUp(manifests, deps)
}
lastHash = newHash // cache data
lastHash = newHash
lastManifest = manifest
if (warmUp) {
const deps = viteManifestToDeps(manifest)
// asynchronously rewarm the cache
fileCache.warmUp(manifest, deps)
} }
})
Object.defineProperty(manifest, '__hash__', {
enumerable: false,
writable: true,
value: newHash
})
return manifest
})().catch(err => logger.error(`Could not reload manifests: ${err}`))
lastManifest = lastManifest || promise
lastCacheTime = +new Date() lastCacheTime = +new Date()
} }
return cachePromise return lastManifest
} }
})() })()
export async function getOxManifests () { export function getOxManifests () {
await loadViteManifests().catch(() => {}) loadViteManifests()
return fileCache.oxManifests return fileCache.oxManifests
} }
export async function getDependencies () { export function getDependencies () {
// simply catch the error here. This might happen, when one of the UI containers is temporarily not available loadViteManifests()
await loadViteManifests().catch(() => {})
return fileCache.dependencies return fileCache.dependencies
} }
...@@ -90,7 +112,7 @@ export async function getCSSDependenciesFor (file) { ...@@ -90,7 +112,7 @@ export async function getCSSDependenciesFor (file) {
return dependencies.filter(dep => /\.css/i.test(dep)) return dependencies.filter(dep => /\.css/i.test(dep))
} }
export async function getVersion () { export function getVersion () {
await loadViteManifests().catch(() => {}) loadViteManifests()
return fileCache.hash return fileCache.hash
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment