flowbite-qwik-cli
Version:
This is a CLI tool to setup flowbite qwik into your project
356 lines (298 loc) • 9.47 kB
text/typescript
#!/usr/bin/env node
import { intro, outro, confirm, isCancel, spinner, log, select } from '@clack/prompts'
import * as fs from 'fs'
import { readFile } from 'fs/promises'
import { spawn } from 'child_process'
import * as prettier from 'prettier'
import * as path from 'path'
import { identifyMonorepoRoot } from 'identify-monorepo-root'
async function readJsonFile(path: string) {
const file = await readFile(path, 'utf8')
return JSON.parse(file)
}
function detectPackageManager(): string {
const rootFolder = identifyMonorepoRoot() || './'
if (fs.existsSync(path.resolve(rootFolder, 'yarn.lock'))) {
return 'yarn'
} else if (fs.existsSync(path.resolve(rootFolder, 'pnpm-lock.yaml'))) {
return 'pnpm'
} else if (fs.existsSync(path.resolve(rootFolder, 'package-lock.json'))) {
return 'npm'
} else {
return 'npm' // as default
}
}
async function tailwindInstalledInProject(): Promise<boolean> {
const packageJsonPath = path.resolve(process.cwd(), 'package.json')
if (!fs.existsSync(packageJsonPath)) {
console.error('package.json not found at:', packageJsonPath)
return false
}
const packageJson = await readJsonFile('./package.json')
const { dependencies, devDependencies } = packageJson
return dependencies?.['tailwindcss'] || devDependencies?.['tailwindcss']
}
function hasCssUtilities(): boolean {
const globalCssPath = path.resolve(process.cwd(), './src/global.css')
if (!fs.existsSync(globalCssPath)) {
console.error('global.css not found at:', globalCssPath)
return false
}
const content = fs.readFileSync(globalCssPath, 'utf8')
return content.includes('tailwindcss')
}
async function detectTailwindCSS(): Promise<boolean> {
return (await tailwindInstalledInProject()) && hasCssUtilities()
}
async function executeCommand(command: string, printMessages = true): Promise<string> {
return new Promise((resolve, reject) => {
const cli = spawn(command, [], { stdio: printMessages ? 'inherit' : 'ignore', shell: true })
cli.on('close', (code) => {
if (code !== 0) {
reject(`Command ${command} exited with code ${code}`)
} else {
resolve(`Command ${command} completed successfully`)
}
})
})
}
async function runCommand(cmd: string, packageManager: string): Promise<void> {
let command
if (packageManager === 'yarn') {
command = `yarn ${cmd}`
} else if (packageManager === 'pnpm') {
command = `pnpm ${cmd}`
} else {
command = `npm run ${cmd}`
}
await executeCommand(command)
}
async function installDependency(packageManager: string, packageName: string): Promise<void> {
let command
if (packageManager === 'yarn') {
command = `yarn add -D ${packageName}`
} else if (packageManager === 'pnpm') {
command = `pnpm add -D ${packageName}`
} else {
command = `npm install --save-dev ${packageName}`
}
await executeCommand(command, false)
}
async function addFlowbiteWrapper(theme: string, toastPosition: string, useDarkTheme: boolean): Promise<void> {
const rootPath = path.resolve(process.cwd(), './src/root.tsx')
if (!fs.existsSync(rootPath)) {
console.error('root.tsx not found at:', rootPath)
return
}
const rootContent = fs.readFileSync(rootPath, 'utf8')
if (rootContent.includes('FlowbiteProvider')) {
return
}
const hasRouterHead = rootContent.includes('<RouterHead />')
const hasRouterOutlet = rootContent.includes('<RouterOutlet />')
if (!hasRouterHead && useDarkTheme) {
log.error('We did not manage to include the FlowbiteProviderHeader component. Please follow the manual installation steps.')
}
if (!hasRouterOutlet) {
log.error('We did not manage to include the FlowbiteProvider component. Please follow the manual installation steps.')
return
}
const content = `
import { FlowbiteProvider ${useDarkTheme ? ', FlowbiteProviderHeader' : ''} } from 'flowbite-qwik';
${rootContent
.replace('<RouterHead />', useDarkTheme ? `<FlowbiteProviderHeader /><RouterHead />` : '<RouterHead />')
.replace('<RouterOutlet />', `<FlowbiteProvider theme="${theme}" toastPosition="${toastPosition}"><RouterOutlet /></FlowbiteProvider>`)}
`
const prettified = await prettier.format(content, {
singleQuote: true,
parser: 'babel',
})
fs.writeFileSync(rootPath, prettified)
}
async function addFlowbiteToGlobalCss() {
const globalCssPath = './src/global.css'
const globalCssContent = fs.readFileSync(globalCssPath, 'utf8')
if (!globalCssContent) {
log.error('global.css file not found')
return
}
if (globalCssContent.includes('flowbite/plugin')) return
const content = await prettier.format(
`
${globalCssContent}
@plugin 'flowbite/plugin';
@source "../node_modules/flowbite-qwik";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-bgContrast: #fff;
}
@layer theme {
.dark {
--color-bgContrast: #111827;
}
}
@theme {
--animate-from-left: slideFromLeft 0.2s 1;
--animate-from-right: slideFromRight 0.2s 1;
--min-width-screen-lg: 1024px;
--container-8xl: 90rem;
@keyframes slideFromLeft {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0);
}
}
@keyframes slideFromRight {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
--color-green-50: #ecfdf5;
--color-green-100: #d1fae5;
--color-green-200: #a7f3d0;
--color-green-300: #6ee7b7;
--color-green-400: #34d399;
--color-green-500: #10b981;
--color-green-600: #059669;
--color-green-700: #047857;
--color-green-800: #065f46;
--color-green-900: #064e3b;
--color-green-950: oklch(.266 .065 152.934);
--color-pink-50: #fdf2f8;
--color-pink-100: #fce7f3;
--color-pink-200: #fbcfe8;
--color-pink-300: #f9a8d4;
--color-pink-400: #f472b6;
--color-pink-500: #ec4899;
--color-pink-600: #db2777;
--color-pink-700: #be185d;
--color-pink-800: #9d174d;
--color-pink-900: #831843;
--color-purple-50: #f5f3ff;
--color-purple-100: #ede9fe;
--color-purple-200: #ddd6fe;
--color-purple-300: #c4b5fd;
--color-purple-400: #a78bfa;
--color-purple-500: #8b5cf6;
--color-purple-600: #7c3aed;
--color-purple-700: #6d28d9;
--color-purple-800: #5b21b6;
--color-purple-900: #4c1d95;
--color-purple-950: oklch(.291 .149 302.717);
--color-gray-50: #f9fafb;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-300: #d1d5db;
--color-gray-400: #9ca3af;
--color-gray-500: #6b7280;
--color-gray-600: #4b5563;
--color-gray-700: #374151;
--color-gray-800: #1f2937;
--color-gray-900: #111827;
--color-gray-950: oklch(.13 .028 261.692);
--color-blue-50: #eff6ff;
--color-blue-100: #dbeafe;
--color-blue-200: #bfdbfe;
--color-blue-300: #93c5fd;
--color-blue-400: #60a5fa;
--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
--color-blue-700: #1d4ed8;
--color-blue-800: #1e40af;
--color-blue-900: #1e3a8a;
--color-blue-950: oklch(.282 .091 267.935);
}
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
.dark {
color-scheme: dark;
background: #111827;
}
.light {
color-scheme: light;
background: #fff;
}`,
{
singleQuote: true,
parser: 'css',
},
)
fs.writeFileSync(globalCssPath, content)
}
async function installFlowbiteQwik(): Promise<void> {
const packageManager = detectPackageManager()
const loader = spinner()
loader.start('Installing flowbite-qwik...')
await installDependency(packageManager, 'flowbite-qwik flowbite flowbite-qwik-icons')
loader.stop('Flowbite Qwik installed! 🎉')
const hasTailwindInstalled = await detectTailwindCSS()
if (!hasTailwindInstalled) {
log.info("Tailwind CSS is not installed in your project. Let's install it!")
await runCommand('qwik add tailwind', packageManager)
}
const colorTheme = await select({
message: 'Choose your color theme',
initialValue: 'blue',
options: [
{ value: 'blue', label: 'Blue' },
{ value: 'green', label: 'Green' },
{ value: 'red', label: 'Red' },
{ value: 'yellow', label: 'Yellow' },
{ value: 'purple', label: 'Purple' },
{ value: 'pink', label: 'Pink' },
],
})
const toastPosition = await select({
message: 'Choose your toast position',
initialValue: 'top-right',
options: [
{ value: 'top-right', label: 'Top Right' },
{ value: 'top-left', label: 'Top Left' },
{ value: 'bottom-right', label: 'Bottom Right' },
{ value: 'bottom-left', label: 'Bottom Left' },
],
})
const useDarkTheme = await confirm({
message: 'Do you use dark theme?',
initialValue: true,
})
loader.start('Setup flowbite-qwik...')
try {
await addFlowbiteToGlobalCss()
await addFlowbiteWrapper(colorTheme as string, toastPosition as string, useDarkTheme as boolean)
} catch (error) {
console.log(error)
}
loader.stop('Flowbite Qwik configured! 🎉')
}
function bye() {
outro('Take care, see you soon! 👋')
process.exit(0)
}
async function init() {
intro(`Add flowbite-qwik to your project!`)
const proceed = await confirm({
message: 'Do you want to set flowbite integration?',
initialValue: true,
})
if (isCancel(proceed) || !proceed) {
bye()
}
await installFlowbiteQwik()
outro(`You're all set!`)
}
;(async () => {
await init()
})()