diff --git a/scripts/build.mjs b/scripts/build.mjs index ec716e8..6dfd63b 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -165,6 +165,13 @@ function versionLocalAssets(source, assetVersion) { .replace(/((?:\.\.\/|\.\/)[^"'#?]+\.css)(?=["'])/g, `$1?v=${assetVersion}`) } +function rootPublicToolAssets(source, toolId) { + return source.replace( + /((?:href|src)=(["']))\.\/([^"']+)\2/g, + `$1/${toolId}/$3$2` + ) +} + function escapeHtml(value) { return value .replaceAll('&', '&') @@ -228,7 +235,8 @@ async function renderTemplate( outputPath, staffTargets = {}, assetVersion, - navigation = {} + navigation = {}, + publicToolId = null ) { const source = await readFile(sourcePath, 'utf8') const toolsRendered = source.replace( @@ -260,7 +268,11 @@ async function renderTemplate( if (withNavigation.includes('{{tool.') || withNavigation.includes('{{staff.') || withNavigation.includes('{{nav.')) { throw new Error(`Unresolved template token in ${sourcePath}`) } - await writeFile(outputPath, versionLocalAssets(withNavigation, assetVersion)) + const withVersionedAssets = versionLocalAssets(withNavigation, assetVersion) + const withStableToolAssets = publicToolId + ? rootPublicToolAssets(withVersionedAssets, publicToolId) + : withVersionedAssets + await writeFile(outputPath, withStableToolAssets) } async function renderPublicTool(toolId, outputRoot, assetVersion) { @@ -277,7 +289,8 @@ async function renderPublicTool(toolId, outputRoot, assetVersion) { resolve(outputPath, 'index.html'), {}, assetVersion, - { assetPrefix: '..', current: 'tools' } + { assetPrefix: '..', current: 'tools' }, + toolId ) } diff --git a/test/site-contract.test.mjs b/test/site-contract.test.mjs index ac4286e..1d81d63 100644 --- a/test/site-contract.test.mjs +++ b/test/site-contract.test.mjs @@ -137,14 +137,26 @@ test('public API routes include query strings while the Worker enforces exact pa test('built routes include each public tool and protected DateCalc', async () => { const outputs = await build() - for (const toolId of ['fio', 'token', 'status', 'orders']) { + const toolAssets = { + balance: ['style.css', 'app.js'], + fio: ['fio.js'], + token: ['token.js'], + status: ['status.js'], + orders: ['orders.js'] + } + for (const [toolId, assets] of Object.entries(toolAssets)) { const html = await readFile(resolve(outputs.mainOutput, toolId, 'index.html'), 'utf8') - assert.match(html, new RegExp(`assets/tool\\.css\\?v=${outputs.assetVersion}`)) + if (toolId !== 'balance') { + assert.match(html, new RegExp(`assets/tool\\.css\\?v=${outputs.assetVersion}`)) + } + for (const asset of assets) { + assert.match(html, new RegExp(`(?:href|src)="/${toolId}/${asset.replace('.', '\\.')}\\?v=${outputs.assetVersion}"`)) + await stat(resolve(outputs.mainOutput, toolId, asset)) + } + assert.doesNotMatch(html, /(?:href|src)="\.\//) assert.doesNotMatch(html, /\{\{nav\./) } const balance = await readFile(resolve(outputs.mainOutput, 'balance', 'index.html'), 'utf8') - assert.match(balance, new RegExp(`style\\.css\\?v=${outputs.assetVersion}`)) - assert.match(balance, new RegExp(`app\\.js\\?v=${outputs.assetVersion}`)) assert.doesNotMatch(balance, /assets\/intercom\.js|widget\.intercom\.io/) assert.match( await readFile(resolve(outputs.mainOutput, 'balance', 'app.js'), 'utf8'),