Skip to content

Search for <ThemeInit /> in the project and warn if it's not found #1605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/dull-ducks-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"flowbite-react": patch
---

Search for `<ThemeInit />` in the project and warn if it's not found instead of warning all the time

### Changes

- during commands `build` and `dev` check files content for custom configuration and display a warning if `<ThemeInit />` is not found
- switch tests in `src/cli` and `src/helpers` from `vitest` -> `bun:test`
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@
"prepack": "clean-package",
"prepare": "bun run generate-metadata",
"prepublishOnly": "bun run build",
"test": "bun test scripts && vitest",
"test": "bun test scripts src/cli src/helpers && vitest",
"test:coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/scripts/generate-metadata.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it } from "bun:test";
import { extractClassList, extractDependencyList } from "./generate-metadata";

describe("extractClassList", () => {
Expand Down
17 changes: 17 additions & 0 deletions packages/ui/src/cli/commands/build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from "fs/promises";
import { allowedExtensions, automaticClassGenerationMessage, classListFilePath, excludeDirs } from "../consts";
import { buildClassList } from "../utils/build-class-list";
import { createInitLogger } from "../utils/create-init-logger";
import { extractComponentImports } from "../utils/extract-component-imports";
import { findFiles } from "../utils/find-files";
import { getConfig } from "../utils/get-config";
Expand All @@ -13,11 +14,24 @@ export async function build() {
try {
const config = await getConfig();
await setupInit(config);
const initLogger = createInitLogger(config);

const importedComponents: string[] = [];

if (config.components.length) {
console.warn(automaticClassGenerationMessage);

if (initLogger.isCustomConfig) {
const files = await findFiles({
patterns: allowedExtensions.map((ext) => `**/*${ext}`),
excludeDirs,
});

for (const file of files) {
const content = await fs.readFile(file, "utf-8");
initLogger.check(file, content);
}
}
} else {
const files = await findFiles({
patterns: allowedExtensions.map((ext) => `**/*${ext}`),
Expand All @@ -27,13 +41,16 @@ export async function build() {
for (const file of files) {
const content = await fs.readFile(file, "utf-8");
const components = extractComponentImports(content);
initLogger.check(file, content);

if (components.length) {
importedComponents.push(...components);
}
}
}

initLogger.log();

const classList = buildClassList({
components: config.components.length ? config.components : [...new Set(importedComponents)],
dark: config.dark,
Expand Down
26 changes: 18 additions & 8 deletions packages/ui/src/cli/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
initJsxFilePath,
} from "../consts";
import { buildClassList } from "../utils/build-class-list";
import { createInitLogger } from "../utils/create-init-logger";
import { extractComponentImports } from "../utils/extract-component-imports";
import { findFiles } from "../utils/find-files";
import { getClassList } from "../utils/get-class-list";
Expand All @@ -25,6 +26,7 @@ export async function dev() {
await setupOutputDirectory();
let config = await getConfig();
await setupInit(config);
const initLogger = createInitLogger(config);

if (config.components.length) {
console.warn(automaticClassGenerationMessage);
Expand All @@ -42,12 +44,15 @@ export async function dev() {
for (const file of files) {
const content = await fs.readFile(file, "utf-8");
const componentImports = extractComponentImports(content);
initLogger.check(file, content);

if (componentImports.length) {
importedComponentsMap[file] = componentImports;
}
}

initLogger.log();

const newImportedComponents = [...new Set(Object.values(importedComponentsMap).flat())];
const newClassList = buildClassList({
components: config.components.length ? config.components : newImportedComponents,
Expand All @@ -63,9 +68,19 @@ export async function dev() {

// watch for changes
async function handleChange(path: string, eventName: "change" | "unlink") {
if ([configFilePath, initFilePath, initJsxFilePath].includes(path)) {
config = await getConfig();
await setupInit(config);
initLogger.config = config;
}
if (path === gitIgnoreFilePath) {
await setupGitIgnore();
}

if (eventName === "change") {
const content = await fs.readFile(path, "utf-8");
const componentImports = extractComponentImports(content);
initLogger.check(path, content);

if (componentImports.length) {
importedComponentsMap[path] = componentImports;
Expand All @@ -75,17 +90,12 @@ export async function dev() {
}
if (eventName === "unlink") {
delete importedComponentsMap[path];
initLogger.checkedMap.delete(path);
}

const newImportedComponents = [...new Set(Object.values(importedComponentsMap).flat())];
initLogger.log();

if ([configFilePath, initFilePath, initJsxFilePath].includes(path)) {
config = await getConfig();
await setupInit(config);
}
if (path === gitIgnoreFilePath) {
await setupGitIgnore();
}
const newImportedComponents = [...new Set(Object.values(importedComponentsMap).flat())];

const newClassList = buildClassList({
components: config.components.length ? config.components : newImportedComponents,
Expand Down
37 changes: 3 additions & 34 deletions packages/ui/src/cli/commands/setup-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,18 @@ import { klona } from "klona/json";
import { isEqual } from "../../helpers/is-equal";
import { COMPONENT_TO_CLASS_LIST_MAP } from "../../metadata/class-list";
import { configFilePath } from "../consts";
import { createConfig, type Config } from "../utils/create-config";
import { getTailwindVersion } from "../utils/get-tailwind-version";

export interface Config {
$schema: string;
components: string[];
dark: boolean;
path: string;
prefix: string;
rsc: boolean;
tsx: boolean;
version: 3 | 4;
}

/**
* Sets up the `.flowbite-react/config.json` file in the project.
*
* This function creates or updates the configuration file with default values and validates existing configurations.
*/
export async function setupConfig(): Promise<Config> {
const defaultConfig: Config = {
$schema: "https://unpkg.com/flowbite-react/schema.json",
components: [],
dark: true,
path: "src/components",
// TODO: infer from project
prefix: "",
rsc: true,
tsx: true,
const defaultConfig = createConfig({
version: await getTailwindVersion(),
};
});
const writeTimeout = 10;

try {
Expand Down Expand Up @@ -102,19 +84,6 @@ export async function setupConfig(): Promise<Config> {
setTimeout(() => fs.writeFile(configFilePath, JSON.stringify(newConfig, null, 2)), writeTimeout);
}

if (
newConfig.dark !== defaultConfig.dark ||
newConfig.prefix !== defaultConfig.prefix ||
newConfig.version !== defaultConfig.version
) {
// TODO: search for <ThemeInit /> in the project and warn if it's not found
console.info(
`\n[!] Custom values detected in ${configFilePath}, render <ThemeInit /> at root level of your app to sync runtime with node config values.`,
`\n[!] Otherwise, your app will use the default values instead of your custom configuration.`,
`\n[!] Example: In case of custom 'prefix' or 'version', the app will not display the correct class names.`,
);
}

return newConfig;
} catch (error) {
if (error instanceof Error && error.message.includes("ENOENT")) {
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/cli/commands/setup-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { namedTypes } from "ast-types";
import { parse } from "recast";
import { initFilePath, initJsxFilePath } from "../consts";
import { compareNodes } from "../utils/compare-nodes";
import type { Config } from "./setup-config";
import type { Config } from "../utils/create-config";

/**
* Sets up the `.flowbite-react/init.tsx` file in the project.
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/cli/utils/add-import.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it } from "bun:test";
import { addImport } from "./add-import";

describe("addImport", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/cli/utils/add-plugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it } from "bun:test";
import { addPlugin } from "./add-plugin";

describe("addPlugin", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/cli/utils/add-to-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "bun:test";
import * as recast from "recast";
import { describe, expect, it } from "vitest";
import { addToConfig } from "./add-to-config";

describe("addToConfig", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/cli/utils/compare-nodes.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "bun:test";
import { parse } from "recast";
import { describe, expect, it } from "vitest";
import { compareNodes } from "./compare-nodes";

describe("compareNodes", () => {
Expand Down
24 changes: 24 additions & 0 deletions packages/ui/src/cli/utils/create-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export interface Config {
$schema: string;
components: string[];
dark: boolean;
path: string;
prefix: string;
rsc: boolean;
tsx: boolean;
version: 3 | 4;
}

export function createConfig(input: Partial<Config> = {}): Config {
return {
$schema: input.$schema ?? "https://unpkg.com/flowbite-react/schema.json",
components: input.components ?? [],
dark: input.dark ?? true,
path: input.path ?? "src/components",
// TODO: infer from project
prefix: input.prefix ?? "",
rsc: input.rsc ?? true,
tsx: input.tsx ?? true,
version: input.version ?? 4,
};
}
Loading
Loading