diff --git a/scripts/buildWebAfter.ts b/scripts/buildWebAfter.ts new file mode 100644 index 00000000..923e9690 --- /dev/null +++ b/scripts/buildWebAfter.ts @@ -0,0 +1,39 @@ +import { readFileSync, writeFileSync } from "fs"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const extractCssVars = () => { + const filePath = join(__dirname, "../out/search-chat/index.css"); + + const cssContent = readFileSync(filePath, "utf-8"); + + const vars: Record = {}; + + const propertyBlockRegex = /@property\s+(--[\w-]+)\s*\{([\s\S]*?)\}/g; + + let match: RegExpExecArray | null; + + while ((match = propertyBlockRegex.exec(cssContent))) { + const [, varName, body] = match; + + const initialValueMatch = /initial-value\s*:\s*([^;]+);/.exec(body); + + if (initialValueMatch) { + vars[varName] = initialValueMatch[1].trim(); + } + } + + const cssVarsBlock = + `.coco-container {\n` + + Object.entries(vars) + .map(([k, v]) => ` ${k}: ${v};`) + .join("\n") + + `\n}\n`; + + writeFileSync(filePath, `${cssContent}\n${cssVarsBlock}`, "utf-8"); +}; + +extractCssVars(); diff --git a/src/main.css b/src/main.css index b7f96b77..3232f4d3 100644 --- a/src/main.css +++ b/src/main.css @@ -73,8 +73,6 @@ --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); --sidebar-ring: oklch(0.708 0 0); - - --tw-border-style: solid; } @theme { @@ -105,7 +103,7 @@ --color-chart-5: var(--chart-5); } -#searchChat-container{ +#searchChat-container { /* Map tokens directly; they are oklch(...) or other full values */ --color-background: var(--background); --color-foreground: var(--foreground); diff --git a/tsup.config.ts b/tsup.config.ts index eab588a8..28c4823c 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,12 +1,18 @@ -import { defineConfig } from 'tsup'; -import { writeFileSync, readFileSync, readdirSync, statSync, existsSync } from 'fs'; -import { join, resolve } from 'path'; -import postcss from 'postcss'; -import tailwindcssPostcss from '@tailwindcss/postcss'; -import autoprefixer from 'autoprefixer'; +import { defineConfig } from "tsup"; +import { + writeFileSync, + readFileSync, + readdirSync, + statSync, + existsSync, +} from "fs"; +import { join, resolve } from "path"; +import postcss from "postcss"; +import tailwindcssPostcss from "@tailwindcss/postcss"; +import autoprefixer from "autoprefixer"; const projectPackageJson = JSON.parse( - readFileSync(join(__dirname, 'package.json'), 'utf-8') + readFileSync(join(__dirname, "package.json"), "utf-8") ); function walk(dir: string): string[] { @@ -29,8 +35,8 @@ function hasTauriRefs(content: string): boolean { } export default defineConfig({ - entry: ['src/pages/web/index.tsx'], - format: ['esm'], + entry: ["src/pages/web/index.tsx"], + format: ["esm"], dts: true, splitting: true, sourcemap: false, @@ -38,74 +44,73 @@ export default defineConfig({ treeshake: true, minify: true, env: { - BUILD_TARGET: 'web', - NODE_ENV: 'production', + BUILD_TARGET: "web", + NODE_ENV: "production", }, - external: [ - 'react', - 'react-dom', - ], + external: ["react", "react-dom"], esbuildOptions(options) { options.bundle = true; - options.platform = 'browser'; + options.platform = "browser"; // Enable Tailwind v4 CSS import resolution using the "style" condition // so that `@import "tailwindcss";` in CSS can be resolved by esbuild. // See: https://tailwindcss.com/docs/installation#bundlers (options as any).conditions = ["style", "browser", "module", "default"]; - options.loader = { - '.css': 'css', - '.scss': 'css', - '.svg': 'dataurl', - '.png': 'dataurl', - '.jpg': 'dataurl', - }, - options.alias = { - '@': resolve(__dirname, './src') - } + (options.loader = { + ".css": "css", + ".scss": "css", + ".svg": "dataurl", + ".png": "dataurl", + ".jpg": "dataurl", + }), + (options.alias = { + "@": resolve(__dirname, "./src"), + }); options.external = [ - '@tauri-apps/api', - '@tauri-apps/plugin-*', - 'tauri-plugin-*', + "@tauri-apps/api", + "@tauri-apps/plugin-*", + "tauri-plugin-*", ]; options.treeShaking = true; options.define = { - 'process.env.BUILD_TARGET': '"web"', - 'process.env.NODE_ENV': '"production"', - 'process.env.DEBUG': 'false', - 'process.env.IS_DEV': 'false', - 'process.env.VERSION': `"${projectPackageJson.version}"`, + "process.env.BUILD_TARGET": '"web"', + "process.env.NODE_ENV": '"production"', + "process.env.DEBUG": "false", + "process.env.IS_DEV": "false", + "process.env.VERSION": `"${projectPackageJson.version}"`, }; - options.pure = ['console.log']; - options.target = 'es2020'; - options.legalComments = 'none'; + options.pure = ["console.log"]; + options.target = "es2020"; + options.legalComments = "none"; options.ignoreAnnotations = false; }, esbuildPlugins: [ { - name: 'jsx-import-source', + name: "jsx-import-source", setup(build) { - build.initialOptions.jsx = 'automatic'; - build.initialOptions.jsxImportSource = 'react'; + build.initialOptions.jsx = "automatic"; + build.initialOptions.jsxImportSource = "react"; }, }, ], - outDir: 'out/search-chat', + outDir: "out/search-chat", async onSuccess() { - const outDir = join(__dirname, 'out/search-chat'); - const files = walk(outDir).filter(f => /\.(m?js|cjs)$/i.test(f)); - const tauriFiles = files.filter(f => { - const content = readFileSync(f, 'utf-8'); + const outDir = join(__dirname, "out/search-chat"); + const files = walk(outDir).filter((f) => /\.(m?js|cjs)$/i.test(f)); + const tauriFiles = files.filter((f) => { + const content = readFileSync(f, "utf-8"); return hasTauriRefs(content); }); if (tauriFiles.length) { throw new Error( - `Build output contains Tauri references:\n${tauriFiles.map(f => ` - ${f}`).join('\n')}` + `Build output contains Tauri references:\n${tauriFiles + .map((f) => ` - ${f}`) + .join("\n")}` ); } const projectPackageJson = JSON.parse( - readFileSync(join(__dirname, 'package.json'), 'utf-8') + readFileSync(join(__dirname, "package.json"), "utf-8") ); const packageJson = { @@ -117,57 +122,51 @@ export default defineConfig({ types: "index.d.ts", dependencies: projectPackageJson.dependencies as Record, peerDependencies: { - "react": "^18.0.0", - "react-dom": "^18.0.0" + react: "^18.0.0", + "react-dom": "^18.0.0", + }, + sideEffects: ["*.css", "*.scss"], + publishConfig: { + access: "public", + registry: "https://registry.npmjs.org/", }, - "sideEffects": [ - "*.css", - "*.scss" - ], - "publishConfig": { - "access": "public", - "registry": "https://registry.npmjs.org/" - } }; - - const noNeedDeps = [ - "dotenv", - "uuid", - "wavesurfer.js", - ] - const tauriDeps = Object.keys(packageJson.dependencies).filter(dep => - dep.includes('@tauri-apps') || - dep.includes('tauri-plugin') || - noNeedDeps.includes(dep) + const noNeedDeps = ["dotenv", "uuid", "wavesurfer.js"]; + + const tauriDeps = Object.keys(packageJson.dependencies).filter( + (dep) => + dep.includes("@tauri-apps") || + dep.includes("tauri-plugin") || + noNeedDeps.includes(dep) ); - tauriDeps.forEach(dep => { + tauriDeps.forEach((dep) => { delete packageJson.dependencies[dep]; }); writeFileSync( - join(__dirname, 'out/search-chat/package.json'), + join(__dirname, "out/search-chat/package.json"), JSON.stringify(packageJson, null, 2) ); try { - const readmePath = join(__dirname, 'src/pages/web/README.md'); - const readmeContent = readFileSync(readmePath, 'utf-8'); + const readmePath = join(__dirname, "src/pages/web/README.md"); + const readmeContent = readFileSync(readmePath, "utf-8"); writeFileSync( - join(__dirname, 'out/search-chat/README.md'), + join(__dirname, "out/search-chat/README.md"), readmeContent ); } catch (error) { - console.error('Failed to copy README.md:', error); + console.error("Failed to copy README.md:", error); } // Ensure Tailwind v4 directives (@import, @source, @apply, @theme) are compiled // into a final CSS for the library consumer. Esbuild doesn't process Tailwind, // so we run PostCSS with Tailwind + Autoprefixer here to produce index.css. try { - const cssInPath = join(__dirname, 'src/main.css'); - const cssOutPath = join(__dirname, 'out/search-chat/index.css'); - const cssIn = readFileSync(cssInPath, 'utf-8'); + const cssInPath = join(__dirname, "src/main.css"); + const cssOutPath = join(__dirname, "out/search-chat/index.css"); + const cssIn = readFileSync(cssInPath, "utf-8"); const result = await postcss([ // Use the Tailwind v4 PostCSS plugin from @tailwindcss/postcss @@ -183,20 +182,21 @@ export default defineConfig({ // This fixes consumer bundlers failing to resolve "/assets/*.png" from node_modules const assetRegex = /url\((['"])\/assets\/([^'"\)]+)\1\)/g; const rewrittenCss = result.css.replace(assetRegex, (_m, quote, file) => { - const srcAssetPath = join(__dirname, 'src/assets', file); + const srcAssetPath = join(__dirname, "src/assets", file); if (!existsSync(srcAssetPath)) { console.warn(`[build:web] Asset not found: ${srcAssetPath}`); return `url(${quote}/assets/${file}${quote})`; } try { const buffer = readFileSync(srcAssetPath); - const ext = file.split('.').pop()?.toLowerCase() ?? 'png'; - const mime = ext === 'svg' - ? 'image/svg+xml' - : ext === 'jpg' || ext === 'jpeg' - ? 'image/jpeg' - : 'image/png'; - const base64 = buffer.toString('base64'); + const ext = file.split(".").pop()?.toLowerCase() ?? "png"; + const mime = + ext === "svg" + ? "image/svg+xml" + : ext === "jpg" || ext === "jpeg" + ? "image/jpeg" + : "image/png"; + const base64 = buffer.toString("base64"); const dataUrl = `data:${mime};base64,${base64}`; return `url(${quote}${dataUrl}${quote})`; } catch (err) { @@ -207,7 +207,18 @@ export default defineConfig({ writeFileSync(cssOutPath, rewrittenCss); } catch (error) { - console.error('Failed to compile Tailwind CSS with PostCSS:', error); + console.error("Failed to compile Tailwind CSS with PostCSS:", error); } - } + + try { + console.log("[build:web] Executing buildWebAfter.ts script..."); + await import("./scripts/buildWebAfter.ts"); + console.log("[build:web] buildWebAfter.ts script executed successfully"); + } catch (error) { + console.error( + "[build:web] Failed to execute buildWebAfter.ts script:", + error + ); + } + }, });