import fetch from 'node-fetch' import { URL } from 'url' import { fileCache } from './files.js' import { config } from './config.js' import { hash, viteManifestToDeps } from './util.js' import { logger } from './logger.js' export const loadViteManifests = (() => { let lastManifest let lastCacheTime 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 () { await config.load() // vite manifests contains a set of objects with the vite-manifests // from the corresponding registered services const viteManifests = await Promise.all(config.urls.map(async baseUrl => { // fetch the manifests 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})`) try { const manifest = await result.json() for (const file in manifest) { logger.debug(`retrieved ${file} from ${baseUrl}`) manifest[file].meta = manifest[file].meta || {} manifest[file].meta.baseUrl = baseUrl } return manifest } catch (err) { throw new Error(`Failed to load manifest for url ${result.url}: ${err}`) } })) // combine all manifests by keys. With duplicates, last wins return viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {}) } return function loadViteManifests ({ useCache = true, warmUp = true } = {}) { const CACHE_TTL = parseInt(process.env.CACHE_TTL) const timeElapsed = () => +((+new Date() - lastCacheTime) / 1000).toFixed(2) if (!lastManifest || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) { const promise = (async () => { const newHash = await getHash() if (lastManifest && lastHash === newHash) return const manifest = await reload() if (useCache) logger.info(`reloaded manifests after ${timeElapsed()} seconds`) // 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() } return lastManifest } })() export function getOxManifests () { loadViteManifests() return fileCache.oxManifests } export function getDependencies () { loadViteManifests() return fileCache.dependencies } export async function getCSSDependenciesFor (file) { const allDependencies = await getDependencies() const dependencies = allDependencies[file] || [] return dependencies.filter(dep => /\.css/i.test(dep)) } export function getVersion () { loadViteManifests() return fileCache.hash }