79cfe855c9
- Added `recursive` option to GlobOptions to control whether simple glob patterns match recursively by default (true). - Changed glob pattern behavior to always apply recursive matching for simple patterns instead of requiring explicit `**/` prefix. - Improved symlink handling in glob file type filters to resolve symlink targets and match based on their actual type (file/dir). - Enhanced skill discovery in coding-agent to use dual-pass glob approach with deduplication and improved extension module detection. - Implemented dynamic Tailwind CSS compilation in stats package by extracting actual class names from source files instead of building with empty candidates. - Refactored Tailwind stylesheet imports to use centralized `tailwindcss/index.css` import with custom stylesheet loader callback.
103 lines
2.9 KiB
TypeScript
103 lines
2.9 KiB
TypeScript
import * as fs from "node:fs/promises";
|
|
import * as path from "node:path";
|
|
import { compile } from "tailwindcss";
|
|
|
|
/**
|
|
* Extract Tailwind class names from source files by scanning for className attributes.
|
|
*/
|
|
async function extractTailwindClasses(dir: string): Promise<Set<string>> {
|
|
const classes = new Set<string>();
|
|
const classPattern = /className\s*=\s*["'`]([^"'`]+)["'`]/g;
|
|
|
|
async function scanDir(currentDir: string): Promise<void> {
|
|
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(currentDir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
await scanDir(fullPath);
|
|
} else if (entry.isFile() && /\.(tsx|ts|jsx|js)$/.test(entry.name)) {
|
|
const content = await Bun.file(fullPath).text();
|
|
const matches = content.matchAll(classPattern);
|
|
for (const match of matches) {
|
|
for (const cls of match[1].split(/\s+/)) {
|
|
if (cls) classes.add(cls);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
await scanDir(dir);
|
|
return classes;
|
|
}
|
|
|
|
// Clean dist
|
|
await fs.rm("./dist/client", { recursive: true, force: true });
|
|
|
|
// Build Tailwind CSS
|
|
console.log("Building Tailwind CSS...");
|
|
const sourceCss = await Bun.file("./src/client/styles.css").text();
|
|
const candidates = await extractTailwindClasses("./src/client");
|
|
const baseDir = path.resolve("./src/client");
|
|
|
|
// Provide loadStylesheet for @import "tailwindcss" to work
|
|
const compiler = await compile(sourceCss, {
|
|
base: baseDir,
|
|
loadStylesheet: async (id: string, base: string) => {
|
|
// Handle tailwindcss import
|
|
if (id === "tailwindcss/index.css" || id === "tailwindcss") {
|
|
const tailwindPath = require.resolve("tailwindcss/index.css", { paths: [base] });
|
|
return {
|
|
path: tailwindPath,
|
|
base: path.dirname(tailwindPath),
|
|
content: await Bun.file(tailwindPath).text(),
|
|
};
|
|
}
|
|
// Handle other imports relative to base
|
|
const resolved = path.resolve(base, id);
|
|
return {
|
|
path: resolved,
|
|
base: path.dirname(resolved),
|
|
content: await Bun.file(resolved).text(),
|
|
};
|
|
},
|
|
});
|
|
const tailwindOutput = compiler.build([...candidates]);
|
|
await Bun.write("./dist/client/styles.css", tailwindOutput);
|
|
|
|
// Build React app
|
|
console.log("Building React app...");
|
|
const result = await Bun.build({
|
|
entrypoints: ["./src/client/index.tsx"],
|
|
outdir: "./dist/client",
|
|
minify: true,
|
|
naming: "[dir]/[name].[ext]",
|
|
});
|
|
|
|
if (!result.success) {
|
|
console.error("Build failed");
|
|
for (const message of result.logs) {
|
|
console.error(message);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
// Create index.html
|
|
const indexHtml = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AI Usage Statistics</title>
|
|
<link rel="stylesheet" href="styles.css">
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
<script src="index.js" type="module"></script>
|
|
</body>
|
|
</html>`;
|
|
|
|
await Bun.write("./dist/client/index.html", indexHtml);
|
|
|
|
console.log("Build complete");
|