From e809de892d3f2897993f807d8f37b58dbd010bda Mon Sep 17 00:00:00 2001 From: 237899745 <237899745@users.noreply.git.workyai.cn> Date: Mon, 3 Aug 2026 22:47:15 +0800 Subject: [PATCH] fix: preserve workspace cascade in production build --- frontend/scripts/copy-runtime-assets.js | 33 ++++++++++++++++++++++++- frontend/scripts/verify-build.js | 17 +++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/frontend/scripts/copy-runtime-assets.js b/frontend/scripts/copy-runtime-assets.js index c21e9a1..ae97ff7 100644 --- a/frontend/scripts/copy-runtime-assets.js +++ b/frontend/scripts/copy-runtime-assets.js @@ -1,4 +1,12 @@ -import { copyFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { + copyFileSync, + existsSync, + mkdirSync, + readFileSync, + readdirSync, + writeFileSync +} from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -32,3 +40,26 @@ for (const entry of ['libs', 'favicon.ico']) { } } } + +// Vite hoists linked stylesheets into , ahead of legacy inline styles in +// app.html. Re-emit the workspace layer after those styles so production and +// development use the same cascade order. +const workspaceSourcePath = path.join(frontendRoot, 'workspace.css'); +const workspaceContents = readFileSync(workspaceSourcePath); +const workspaceHash = createHash('sha256').update(workspaceContents).digest('hex').slice(0, 12); +const workspaceFileName = `workspace-${workspaceHash}.css`; +copyFileSync(workspaceSourcePath, path.join(outputDirectory, workspaceFileName)); + +const builtAppPath = path.join(outputDirectory, 'app.html'); +const builtAppHtml = readFileSync(builtAppPath, 'utf8'); +const runtimeStylesheet = ` \n`; +const nextBuiltAppHtml = builtAppHtml.includes('data-workspace-runtime') + ? builtAppHtml.replace(/\s*]+data-workspace-runtime[^>]*>\s*/u, `\n${runtimeStylesheet}`) + : builtAppHtml.replace('', `${runtimeStylesheet}`); + +if (!nextBuiltAppHtml.includes('data-workspace-runtime')) { + throw new Error('Unable to inject the runtime workspace stylesheet into dist/app.html'); +} + +writeFileSync(builtAppPath, nextBuiltAppHtml, 'utf8'); +console.log(`Injected runtime workspace stylesheet: ${workspaceFileName}`); diff --git a/frontend/scripts/verify-build.js b/frontend/scripts/verify-build.js index 719e2e5..9a12176 100644 --- a/frontend/scripts/verify-build.js +++ b/frontend/scripts/verify-build.js @@ -32,4 +32,21 @@ for (const fileName of ['app.html', 'share.html']) { assert.strictEqual(/vue\.global(?:\.prod)?\.js|axios\.min\.js/.test(html), false, `${fileName} still loads global Vue/Axios`); } +const builtAppHtml = readFileSync(path.join(outputDirectory, 'app.html'), 'utf8'); +const runtimeWorkspaceMatch = builtAppHtml.match( + // +); +assert.ok(runtimeWorkspaceMatch, 'app.html is missing the hashed runtime workspace stylesheet'); + +const runtimeWorkspacePath = path.join(outputDirectory, runtimeWorkspaceMatch[1]); +assert.ok(existsSync(runtimeWorkspacePath), 'runtime workspace stylesheet is missing'); +assert.ok( + builtAppHtml.indexOf(runtimeWorkspaceMatch[0]) > builtAppHtml.lastIndexOf(''), + 'runtime workspace stylesheet must load after every legacy inline style' +); + +const runtimeWorkspaceCss = readFileSync(runtimeWorkspacePath, 'utf8'); +assert.ok(runtimeWorkspaceCss.includes('.mobile-file-create-trigger'), 'runtime workspace stylesheet is incomplete'); +assert.ok(runtimeWorkspaceCss.includes('@media (max-width: 900px)'), 'runtime workspace mobile breakpoint is missing'); + console.log(`Verified ${htmlFiles.length} HTML entries and their emitted assets.`);