UNPKG

@wp-forge/cli

Version:

CLI tool for WP-Forge - create and manage WordPress themes

984 lines (916 loc) 33.8 kB
#!/usr/bin/env node // src/index.ts import { Command } from "commander"; import chalk10 from "chalk"; // src/commands/dev.ts import chalk from "chalk"; import ora from "ora"; import { execa } from "execa"; async function devCommand(options) { console.log(chalk.bold.cyan("\u2692\uFE0F WP-Forge Development Server\n")); const spinner = ora("Starting Vite dev server...").start(); try { const viteProcess = execa("vite", ["--port", options.port, "--host", options.host], { stdio: "inherit" }); spinner.succeed("Development server started!"); console.log(); console.log(chalk.green(" \u279C") + " Local: " + chalk.cyan(`http://${options.host}:${options.port}`)); console.log(chalk.green(" \u279C") + " Hot reload enabled"); console.log(chalk.green(" \u279C") + " TypeScript checking in background"); console.log(); console.log(chalk.dim(" Press Ctrl+C to stop")); console.log(); await viteProcess; } catch (error) { spinner.fail("Failed to start development server"); console.error(error); process.exit(1); } } // src/commands/build.ts import chalk2 from "chalk"; import ora2 from "ora"; import { execa as execa2 } from "execa"; async function buildCommand(options) { console.log(chalk2.bold.cyan("\u2692\uFE0F Building for Production\n")); const spinner = ora2("Building theme...").start(); try { const args = ["build"]; if (options.analyze) { args.push("--mode", "analyze"); } await execa2("vite", args, { stdio: "inherit" }); spinner.succeed(chalk2.green("Build complete!")); console.log(); console.log(chalk2.cyan("\u{1F4E6} Output:") + " dist/"); console.log(chalk2.dim(" Ready for production deployment")); console.log(); } catch (error) { spinner.fail("Build failed"); console.error(error); process.exit(1); } } // src/commands/block.ts import chalk3 from "chalk"; import ora3 from "ora"; import fs from "fs-extra"; import path from "path"; async function blockCommand(name, options) { console.log(chalk3.bold.cyan("\u2692\uFE0F Creating Block\n")); console.log(chalk3.dim(`Name: ${name}`)); console.log(chalk3.dim(`Type: ${options.type}`)); console.log(chalk3.dim(`Category: ${options.category} `)); const spinner = ora3("Generating block...").start(); try { const slug = name.toLowerCase().replace(/\s+/g, "-"); const blockDir = path.join(process.cwd(), "src/blocks", slug); await fs.ensureDir(blockDir); await generateBlockConfig(blockDir, name, slug, options); await generateBlockEdit(blockDir, name, slug, options.styleFramework || "none"); await generateBlockRender(blockDir, name, slug, options.type, options.styleFramework || "none"); await generateBlockStyles(blockDir, slug, options.styleFramework || "none"); spinner.succeed(chalk3.green(`Block "${name}" created!`)); console.log(); console.log(chalk3.cyan("\u{1F4C1} Files created:")); console.log(chalk3.dim(` src/blocks/${slug}/block.json`)); console.log(chalk3.dim(` src/blocks/${slug}/edit.tsx`)); console.log(chalk3.dim(` src/blocks/${slug}/render.${options.type === "dynamic" ? "php" : "tsx"}`)); console.log(chalk3.dim(` src/blocks/${slug}/style.css`)); console.log(); } catch (error) { spinner.fail("Failed to create block"); console.error(error); process.exit(1); } } async function generateBlockConfig(blockDir, name, slug, options) { const config = { $schema: "https://schemas.wp.org/trunk/block.json", apiVersion: 3, name: `wp-forge/${slug}`, title: name, category: options.category, icon: "smiley", description: `${name} block`, supports: { html: false, align: true }, attributes: {}, editorScript: "file:./edit.tsx", ...options.type === "dynamic" && { render: "file:./render.php" }, style: "file:./style.css" }; await fs.writeJson(path.join(blockDir, "block.json"), config, { spaces: 2 }); } async function generateBlockEdit(blockDir, name, slug, framework) { const useTailwind = framework === "tailwind"; const useUno = framework === "unocss"; const classes = useTailwind || useUno ? 'className="p-4 bg-gray-100 rounded-lg"' : ""; const headingClasses = useTailwind || useUno ? 'className="text-2xl font-bold mb-2"' : ""; const content = `import { useBlockProps } from '@wordpress/block-editor' export default function Edit() { const blockProps = useBlockProps(${useTailwind || useUno ? "{ className: 'p-4 bg-gray-100 rounded-lg' }" : ""}) return ( <div {...blockProps}> <h3 ${headingClasses}>${name}</h3> <p>Edit your block here...</p> </div> ) } `; await fs.writeFile(path.join(blockDir, "edit.tsx"), content); } async function generateBlockRender(blockDir, name, slug, type, framework) { if (type === "dynamic") { const useTailwind = framework === "tailwind"; const useUno = framework === "unocss"; const classes = useTailwind || useUno ? ' class="p-4 bg-gray-100 rounded-lg"' : ""; const headingClasses = useTailwind || useUno ? ' class="text-2xl font-bold mb-2"' : ""; const content = `<?php /** * ${name} Block Render * * @param array $attributes Block attributes * @param string $content Block content * @return string Rendered block */ $block_wrapper_attributes = get_block_wrapper_attributes(); ?> <div <?php echo $block_wrapper_attributes; ?>${classes}> <h3${headingClasses}><?php echo esc_html__( '${name}', 'wp-forge' ); ?></h3> <!-- Add your dynamic content here --> </div> `; await fs.writeFile(path.join(blockDir, "render.php"), content); } } async function generateBlockStyles(blockDir, slug, framework) { let content; if (framework === "tailwind" || framework === "unocss") { content = `/* * ${framework === "tailwind" ? "Tailwind CSS" : "UnoCSS"} utilities are used inline. * Add custom styles here only if needed. */ .wp-block-wp-forge-${slug} { /* Custom styles */ } `; } else { content = `.wp-block-wp-forge-${slug} { padding: 1rem; background-color: #f3f4f6; border-radius: 0.5rem; } .wp-block-wp-forge-${slug} h3 { font-size: 1.5rem; font-weight: bold; margin-bottom: 0.5rem; } `; } await fs.writeFile(path.join(blockDir, "style.css"), content); } // src/commands/component.ts import path3 from "path"; import chalk5 from "chalk"; // src/utils/validation.ts import validateNpmPackageName from "validate-npm-package-name"; function slugify(text) { return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/^-+|-+$/g, ""); } function toPascalCase(text) { return text.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^(.)/, (char) => char.toUpperCase()); } function validateComponentName(name) { if (!name || name.trim().length === 0) { return { valid: false, error: "Component name cannot be empty" }; } const pascal = toPascalCase(name); if (!/^[A-Z][a-zA-Z0-9]*$/.test(pascal)) { return { valid: false, error: "Component name must be valid PascalCase" }; } if (pascal.length > 50) { return { valid: false, error: "Component name is too long (max 50 characters)" }; } return { valid: true }; } function validateTemplateName(name) { if (!name || name.trim().length === 0) { return { valid: false, error: "Template name cannot be empty" }; } const slug = slugify(name); if (slug.length === 0) { return { valid: false, error: "Template name must contain valid characters" }; } if (slug.length > 50) { return { valid: false, error: "Template name is too long (max 50 characters)" }; } return { valid: true }; } function validatePartName(name) { if (!name || name.trim().length === 0) { return { valid: false, error: "Part name cannot be empty" }; } const slug = slugify(name); if (slug.length === 0) { return { valid: false, error: "Part name must contain valid characters" }; } if (slug.length > 50) { return { valid: false, error: "Part name is too long (max 50 characters)" }; } return { valid: true }; } function validateNamespace(namespace) { if (!namespace || namespace.trim().length === 0) { return { valid: false, error: "Namespace cannot be empty" }; } if (!/^[A-Z][a-zA-Z0-9]*(\\[A-Z][a-zA-Z0-9]*)*$/.test(namespace)) { return { valid: false, error: "Invalid PHP namespace format (e.g., MyTheme\\Components)" }; } return { valid: true }; } // src/utils/templates.ts function generateTemplateHTML(type, slug) { const templates = { page: `<!-- wp:template-part {"slug":"header","theme":"${slug}","tagName":"header"} /--> <!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} --> <main class="wp-block-group"> <!-- wp:post-title /--> <!-- wp:post-content /--> </main> <!-- /wp:group --> <!-- wp:template-part {"slug":"footer","theme":"${slug}","tagName":"footer"} /-->`, single: `<!-- wp:template-part {"slug":"header","theme":"${slug}","tagName":"header"} /--> <!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} --> <main class="wp-block-group"> <!-- wp:post-title /--> <!-- wp:post-featured-image /--> <!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap"}} --> <div class="wp-block-group"> <!-- wp:post-date /--> <!-- wp:post-author /--> </div> <!-- /wp:group --> <!-- wp:post-content /--> <!-- wp:post-terms {"term":"category"} /--> <!-- wp:post-terms {"term":"post_tag"} /--> </main> <!-- /wp:group --> <!-- wp:template-part {"slug":"footer","theme":"${slug}","tagName":"footer"} /-->`, archive: `<!-- wp:template-part {"slug":"header","theme":"${slug}","tagName":"header"} /--> <!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} --> <main class="wp-block-group"> <!-- wp:query-title {"type":"archive"} /--> <!-- wp:term-description /--> <!-- wp:query {"queryId":0,"query":{"perPage":10,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true}} --> <div class="wp-block-query"> <!-- wp:post-template --> <!-- wp:post-title {"isLink":true} /--> <!-- wp:post-excerpt /--> <!-- wp:post-date /--> <!-- /wp:post-template --> <!-- wp:query-pagination --> <!-- wp:query-pagination-previous /--> <!-- wp:query-pagination-numbers /--> <!-- wp:query-pagination-next /--> <!-- /wp:query-pagination --> </div> <!-- /wp:query --> </main> <!-- /wp:group --> <!-- wp:template-part {"slug":"footer","theme":"${slug}","tagName":"footer"} /-->`, "404": `<!-- wp:template-part {"slug":"header","theme":"${slug}","tagName":"header"} /--> <!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} --> <main class="wp-block-group"> <!-- wp:heading {"level":1} --> <h1>404 - Page Not Found</h1> <!-- /wp:heading --> <!-- wp:paragraph --> <p>The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.</p> <!-- /wp:paragraph --> <!-- wp:search {"label":"Search","showLabel":false,"placeholder":"Search..."} /--> </main> <!-- /wp:group --> <!-- wp:template-part {"slug":"footer","theme":"${slug}","tagName":"footer"} /-->`, home: `<!-- wp:template-part {"slug":"header","theme":"${slug}","tagName":"header"} /--> <!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} --> <main class="wp-block-group"> <!-- wp:query {"queryId":0,"query":{"perPage":10,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true}} --> <div class="wp-block-query"> <!-- wp:post-template --> <!-- wp:post-featured-image {"isLink":true} /--> <!-- wp:post-title {"isLink":true} /--> <!-- wp:post-excerpt /--> <!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap"}} --> <div class="wp-block-group"> <!-- wp:post-date /--> <!-- wp:post-author /--> </div> <!-- /wp:group --> <!-- /wp:post-template --> <!-- wp:query-pagination --> <!-- wp:query-pagination-previous /--> <!-- wp:query-pagination-numbers /--> <!-- wp:query-pagination-next /--> <!-- /wp:query-pagination --> </div> <!-- /wp:query --> </main> <!-- /wp:group --> <!-- wp:template-part {"slug":"footer","theme":"${slug}","tagName":"footer"} /-->`, search: `<!-- wp:template-part {"slug":"header","theme":"${slug}","tagName":"header"} /--> <!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} --> <main class="wp-block-group"> <!-- wp:query-title {"type":"search"} /--> <!-- wp:search {"label":"Search","showLabel":false} /--> <!-- wp:query {"queryId":0,"query":{"perPage":10,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true}} --> <div class="wp-block-query"> <!-- wp:post-template --> <!-- wp:post-title {"isLink":true} /--> <!-- wp:post-excerpt /--> <!-- /wp:post-template --> <!-- wp:query-pagination --> <!-- wp:query-pagination-previous /--> <!-- wp:query-pagination-numbers /--> <!-- wp:query-pagination-next /--> <!-- /wp:query-pagination --> <!-- wp:query-no-results --> <!-- wp:paragraph --> <p>No results found. Try a different search.</p> <!-- /wp:paragraph --> <!-- /wp:query-no-results --> </div> <!-- /wp:query --> </main> <!-- /wp:group --> <!-- wp:template-part {"slug":"footer","theme":"${slug}","tagName":"footer"} /-->`, custom: `<!-- wp:template-part {"slug":"header","theme":"${slug}","tagName":"header"} /--> <!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} --> <main class="wp-block-group"> <!-- Add your custom content here --> </main> <!-- /wp:group --> <!-- wp:template-part {"slug":"footer","theme":"${slug}","tagName":"footer"} /-->` }; return templates[type] || templates.custom; } function generatePartHTML(type) { const parts = { header: `<!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"space-between"}} --> <div class="wp-block-group"> <!-- wp:site-logo /--> <!-- wp:site-title /--> <!-- wp:navigation /--> </div> <!-- /wp:group -->`, footer: `<!-- wp:group {"layout":{"type":"constrained"}} --> <div class="wp-block-group"> <!-- wp:paragraph {"align":"center"} --> <p class="has-text-align-center">\xA9 <!-- wp:post-date {"format":"Y"} /--> All rights reserved.</p> <!-- /wp:paragraph --> <!-- wp:navigation {"layout":{"type":"flex","justifyContent":"center"}} /--> </div> <!-- /wp:group -->`, sidebar: `<!-- wp:group {"layout":{"type":"default"}} --> <div class="wp-block-group"> <!-- wp:heading --> <h2>Sidebar</h2> <!-- /wp:heading --> <!-- wp:search {"label":"Search","showLabel":false} /--> <!-- wp:latest-posts /--> <!-- wp:categories /--> </div> <!-- /wp:group -->`, content: `<!-- wp:post-title /--> <!-- wp:post-content /-->`, custom: `<!-- wp:group {"layout":{"type":"default"}} --> <div class="wp-block-group"> <!-- Add your custom content here --> </div> <!-- /wp:group -->` }; return parts[type] || parts.custom; } function generatePartPHP(type, slug) { const parts = { header: `<?php /** * Header Template Part * * @package ${slug} */ ?> <header class="site-header"> <div class="site-branding"> <?php if ( has_custom_logo() ) { the_custom_logo(); } ?> <h1 class="site-title"> <a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"> <?php bloginfo( 'name' ); ?> </a> </h1> <?php $description = get_bloginfo( 'description', 'display' ); if ( $description || is_customize_preview() ) : ?> <p class="site-description"><?php echo esc_html( $description ); ?></p> <?php endif; ?> </div> <nav class="main-navigation"> <?php wp_nav_menu( array( 'theme_location' => 'primary', 'menu_class' => 'primary-menu', 'fallback_cb' => false, ) ); ?> </nav> </header>`, footer: `<?php /** * Footer Template Part * * @package ${slug} */ ?> <footer class="site-footer"> <div class="site-info"> <p>&copy; <?php echo esc_html( date( 'Y' ) ); ?> <?php bloginfo( 'name' ); ?>. All rights reserved.</p> </div> <?php if ( has_nav_menu( 'footer' ) ) { wp_nav_menu( array( 'theme_location' => 'footer', 'menu_class' => 'footer-menu', 'depth' => 1, ) ); } ?> </footer>`, sidebar: `<?php /** * Sidebar Template Part * * @package ${slug} */ if ( ! is_active_sidebar( 'sidebar-1' ) ) { return; } ?> <aside class="widget-area"> <?php dynamic_sidebar( 'sidebar-1' ); ?> </aside>`, content: `<?php /** * Content Template Part * * @package ${slug} */ ?> <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <header class="entry-header"> <?php the_title( '<h1 class="entry-title">', '</h1>' ); ?> </header> <div class="entry-content"> <?php the_content(); wp_link_pages( array( 'before' => '<div class="page-links">' . esc_html__( 'Pages:', '${slug}' ), 'after' => '</div>', ) ); ?> </div> <footer class="entry-footer"> <?php // Post meta, tags, etc. ?> </footer> </article>`, custom: `<?php /** * Custom Template Part * * @package ${slug} */ ?> <!-- Add your custom PHP code here -->` }; return parts[type] || parts.custom; } function generateComponentClass(name, namespace, type) { return `<?php /** * ${name} Component * * @package ${namespace} */ namespace ${namespace}\\Components; use WPForge\\ComponentInterface; /** * Class ${name} */ class ${name} implements ComponentInterface { /** * Get component slug * * @return string */ public function get_slug(): string { return '${name.toLowerCase()}'; } /** * Initialize component * * @return void */ public function initialize(): void { // Add your initialization code here add_action( 'init', array( $this, 'register' ) ); } /** * Register component functionality * * @return void */ public function register(): void { // Add your registration code here } } `; } // src/utils/filesystem.ts import fs2 from "fs-extra"; import path2 from "path"; import chalk4 from "chalk"; import ora4 from "ora"; async function ensureDir(dirPath) { await fs2.ensureDir(dirPath); } async function writeFile(filePath, content) { await fs2.writeFile(filePath, content, "utf8"); } async function readFile(filePath) { return fs2.readFile(filePath, "utf8"); } async function createFileWithSpinner(filePath, content, description) { const spinner = ora4(description || `Creating ${path2.basename(filePath)}`).start(); try { await ensureDir(path2.dirname(filePath)); await writeFile(filePath, content); spinner.succeed(chalk4.green(`Created ${path2.relative(process.cwd(), filePath)}`)); } catch (error) { spinner.fail(chalk4.red(`Failed to create ${path2.basename(filePath)}`)); throw error; } } // src/commands/component.ts async function componentCommand(name, options) { console.log(chalk5.cyan("\n\u2692\uFE0F Creating PHP Component\n")); const validation = validateComponentName(name); if (!validation.valid) { console.error(chalk5.red(`\u2716 ${validation.error}`)); process.exit(1); } const className = toPascalCase(name); const cwd = process.cwd(); const componentsDir = path3.join(cwd, "inc", "Components"); const componentPath = path3.join(componentsDir, `${className}.php`); const fs3 = await import("fs-extra"); if (await fs3.pathExists(componentPath)) { console.error(chalk5.red(`\u2716 Component "${className}" already exists`)); process.exit(1); } let namespace = options.namespace; if (!namespace) { try { const existingComponents = await fs3.readdir(componentsDir); if (existingComponents.length > 0) { const firstComponent = existingComponents.find((f) => f.endsWith(".php")); if (firstComponent) { const content = await fs3.readFile(path3.join(componentsDir, firstComponent), "utf8"); const namespaceMatch = content.match(/namespace\s+([^;\\]+)/); if (namespaceMatch) { namespace = namespaceMatch[1]; } } } } catch { } if (!namespace) { const themeName = toPascalCase(path3.basename(cwd)); namespace = themeName; } } const namespaceValidation = validateNamespace(namespace); if (!namespaceValidation.valid) { console.error(chalk5.red(`\u2716 ${namespaceValidation.error}`)); process.exit(1); } await ensureDir(componentsDir); const componentContent = generateComponentClass(className, namespace, options.type); await createFileWithSpinner( componentPath, componentContent, `Creating ${className}.php component` ); console.log(chalk5.green("\n\u2713 Component created successfully!\n")); console.log(chalk5.dim(` Component: inc/Components/${className}.php`)); console.log(chalk5.dim(` Namespace: ${namespace}\\Components`)); console.log(chalk5.dim(` Type: ${options.type}`)); console.log(chalk5.cyan("\n Next steps:")); console.log(chalk5.dim(" 1. Add your component logic to the class")); console.log(chalk5.dim(" 2. Register in functions.php:")); console.log(chalk5.dim(` new ${namespace}\\Components\\${className}(), `)); } // src/commands/test.ts import chalk6 from "chalk"; async function testCommand(options) { console.log(chalk6.cyan("\u{1F9EA} Running tests...")); console.log(chalk6.yellow("\u26A0\uFE0F Coming soon!")); } // src/commands/template.ts import path4 from "path"; import chalk7 from "chalk"; async function templateCommand(name, options) { console.log(chalk7.cyan("\n\u2692\uFE0F Creating WordPress Template\n")); const validation = validateTemplateName(name); if (!validation.valid) { console.error(chalk7.red(`\u2716 ${validation.error}`)); process.exit(1); } const slug = slugify(name); const cwd = process.cwd(); const templatesDir = path4.join(cwd, "templates"); const templatePath = path4.join(templatesDir, `${slug}.html`); const fs3 = await import("fs-extra"); if (await fs3.pathExists(templatePath)) { console.error(chalk7.red(`\u2716 Template "${slug}" already exists`)); process.exit(1); } await ensureDir(templatesDir); let themeSlug = path4.basename(cwd); try { const packageJson = await fs3.readJson(path4.join(cwd, "package.json")); if (packageJson.name) { themeSlug = packageJson.name; } } catch { } const templateContent = generateTemplateHTML(options.type, themeSlug); await createFileWithSpinner( templatePath, templateContent, `Creating ${slug}.html template` ); console.log(chalk7.green("\n\u2713 Template created successfully!\n")); console.log(chalk7.dim(` Template: templates/${slug}.html`)); console.log(chalk7.dim(` Type: ${options.type}`)); if (options.description) { console.log(chalk7.dim(` Description: ${options.description}`)); } console.log(chalk7.cyan("\n Next steps:")); console.log(chalk7.dim(" 1. Edit the template in templates/" + slug + ".html")); console.log(chalk7.dim(" 2. Add your custom blocks and content")); console.log(chalk7.dim(" 3. The template will be available in WordPress theme editor\n")); } // src/commands/part.ts import path5 from "path"; import chalk8 from "chalk"; async function partCommand(name, options) { console.log(chalk8.cyan("\n\u2692\uFE0F Creating Template Part\n")); const validation = validatePartName(name); if (!validation.valid) { console.error(chalk8.red(`\u2716 ${validation.error}`)); process.exit(1); } const slug = slugify(name); const cwd = process.cwd(); const partsDir = path5.join(cwd, "parts"); const ext = options.markup === "html" ? "html" : "php"; const partPath = path5.join(partsDir, `${slug}.${ext}`); const fs3 = await import("fs-extra"); if (await fs3.pathExists(partPath)) { console.error(chalk8.red(`\u2716 Template part "${slug}.${ext}" already exists`)); process.exit(1); } await ensureDir(partsDir); let themeSlug = path5.basename(cwd); try { const packageJson = await fs3.readJson(path5.join(cwd, "package.json")); if (packageJson.name) { themeSlug = packageJson.name; } } catch { } const partContent = options.markup === "html" ? generatePartHTML(options.type) : generatePartPHP(options.type, themeSlug); await createFileWithSpinner( partPath, partContent, `Creating ${slug}.${ext} template part` ); console.log(chalk8.green("\n\u2713 Template part created successfully!\n")); console.log(chalk8.dim(` Part: parts/${slug}.${ext}`)); console.log(chalk8.dim(` Type: ${options.type}`)); console.log(chalk8.dim(` Markup: ${options.markup}`)); console.log(chalk8.cyan("\n Next steps:")); console.log(chalk8.dim(` 1. Edit the part in parts/${slug}.${ext}`)); console.log(chalk8.dim(' 2. Use in templates with <!-- wp:template-part {"slug":"' + slug + '"} /-->')); console.log(chalk8.dim(" 3. Or in PHP with get_template_part('parts/" + slug + "')\n")); } // src/commands/design-system.ts import path6 from "path"; import chalk9 from "chalk"; import ora5 from "ora"; import { execa as execa3 } from "execa"; async function designSystemCommand(framework) { console.log(chalk9.cyan("\n\u2692\uFE0F Setting up Design System\n")); if (framework !== "tailwind" && framework !== "unocss") { console.error(chalk9.red("\u2716 Invalid framework. Choose: tailwind or unocss")); process.exit(1); } const cwd = process.cwd(); const packageManager = await detectPackageManager(); await installDependencies(framework, packageManager); await createConfigFile(framework, cwd); await updateViteConfig(framework, cwd); await createCSSEntry(framework, cwd); console.log(chalk9.green("\n\u2713 Design system setup complete!\n")); console.log(chalk9.cyan(" Next steps:")); console.log(chalk9.dim(` 1. Import the CSS in your main JS file:`)); console.log(chalk9.dim(` import '../css/design-system.css'`)); console.log(chalk9.dim(` 2. Start using utility classes in your blocks`)); console.log(chalk9.dim(` 3. Customize ${framework === "tailwind" ? "tailwind" : "uno"}.config.${framework === "tailwind" ? "js" : "ts"} as needed `)); } async function detectPackageManager() { const fs3 = await import("fs-extra"); const cwd = process.cwd(); if (await fs3.pathExists(path6.join(cwd, "pnpm-lock.yaml"))) { return "pnpm"; } if (await fs3.pathExists(path6.join(cwd, "yarn.lock"))) { return "yarn"; } return "npm"; } async function installDependencies(framework, pm) { const spinner = ora5("Installing dependencies...").start(); try { const packages = framework === "tailwind" ? ["tailwindcss", "postcss", "autoprefixer"] : ["@unocss/vite", "unocss"]; const installCmd = pm === "yarn" ? "add" : "install"; const devFlag = pm === "yarn" ? "--dev" : "--save-dev"; await execa3(pm, [installCmd, devFlag, ...packages], { cwd: process.cwd(), stdio: "pipe" }); spinner.succeed(chalk9.green("Dependencies installed")); } catch (error) { spinner.fail("Failed to install dependencies"); console.error(error); process.exit(1); } } async function createConfigFile(framework, cwd) { if (framework === "tailwind") { const configContent = `import { wpForgeTailwindPreset } from '@wp-forge/vite-plugin/integrations/tailwind-preset' export default { presets: [wpForgeTailwindPreset], theme: { extend: { // Add your custom theme extensions here }, }, } `; await createFileWithSpinner( path6.join(cwd, "tailwind.config.js"), configContent, "Creating tailwind.config.js" ); } else { const configContent = `import { defineConfig, presetUno } from 'unocss' import { wpForgeUnoPreset } from '@wp-forge/vite-plugin/integrations/unocss-preset' export default defineConfig({ presets: [ presetUno(), wpForgeUnoPreset, ], theme: { // Add your custom theme here }, }) `; await createFileWithSpinner( path6.join(cwd, "uno.config.ts"), configContent, "Creating uno.config.ts" ); } } async function updateViteConfig(framework, cwd) { const spinner = ora5("Updating vite.config.ts...").start(); const viteConfigPath = path6.join(cwd, "vite.config.ts"); try { let config = await readFile(viteConfigPath); if (framework === "unocss" && !config.includes("unocss/vite")) { config = `import UnoCSS from '@unocss/vite' ${config}`; } if (config.includes("wpForge({")) { const designSystemConfig = ` designSystem: { enabled: true, framework: '${framework}', wordpressPresets: true, },`; config = config.replace(/(\s+}[\s\n]*\)[\s\n]*,?[\s\n]*plugins:)/, `${designSystemConfig}$1`); if (framework === "unocss" && !config.includes("UnoCSS()")) { config = config.replace(/(plugins:\s*\[)/, "$1\n UnoCSS(),"); } } await writeFile(viteConfigPath, config); spinner.succeed(chalk9.green("Updated vite.config.ts")); } catch (error) { spinner.fail("Failed to update vite.config.ts"); console.warn(chalk9.yellow(" Please manually update your vite.config.ts")); } } async function createCSSEntry(framework, cwd) { const cssDir = path6.join(cwd, "src", "css"); const cssPath = path6.join(cssDir, "design-system.css"); let content; if (framework === "tailwind") { content = `/** * Tailwind CSS Entry */ @tailwind base; @tailwind components; @tailwind utilities; /* Custom component styles */ @layer components { .btn { @apply px-4 py-2 rounded bg-wp-primary text-white hover:bg-wp-secondary transition; } } `; } else { content = `/** * UnoCSS Entry * * UnoCSS generates styles on-demand, so this file mainly contains * custom CSS that isn't covered by utility classes. */ /* Custom component styles */ .btn { padding: 1rem 1.5rem; border-radius: 0.25rem; background-color: var(--wp--preset--color--primary); color: white; transition: background-color 0.2s; } .btn:hover { background-color: var(--wp--preset--color--secondary); } `; } await createFileWithSpinner(cssPath, content, "Creating design-system.css"); } // src/index.ts var program = new Command(); program.name("wp-forge").description("\u2692\uFE0F A modern WordPress theme framework").version("0.2.2"); program.command("dev").description("Start development server with hot reload").option("-p, --port <port>", "Port number", "3000").option("-h, --host <host>", "Host address", "localhost").action(devCommand); program.command("build").description("Build theme for production").option("--analyze", "Analyze bundle size").action(buildCommand); program.command("block:new <name>").description("Create a new Gutenberg block").option("-t, --type <type>", "Block type (static|dynamic)", "dynamic").option("--category <category>", "Block category", "common").option("--styleFramework <framework>", "Style framework (none|tailwind|unocss)", "none").action(blockCommand); program.command("component:new <name>").description("Create a new theme component").option("-t, --type <type>", "Component type (service|feature|integration|custom)", "custom").option("--namespace <namespace>", "PHP namespace").action(componentCommand); program.command("template:new <name>").description("Create a new WordPress template").option("-t, --type <type>", "Template type (page|single|archive|404|home|search|custom)", "page").option("--description <text>", "Template description").action(templateCommand); program.command("part:new <name>").description("Create a new template part").option("-t, --type <type>", "Part type (header|footer|sidebar|content|custom)", "custom").option("--markup <markup>", "Markup style (html|php)", "php").action(partCommand); program.command("design-system:setup <framework>").description("Set up a design system (tailwind|unocss)").action(designSystemCommand); program.command("test").description("Run tests").option("--unit", "Run unit tests only").option("--e2e", "Run E2E tests only").option("--watch", "Watch mode").action(testCommand); program.command("storybook").description("Launch component explorer").action(() => { console.log(chalk10.cyan("\u{1F3AD} Launching component explorer...")); console.log(chalk10.dim("Coming soon!")); }); program.command("ai:block <description>").description("Generate block from description using AI").action((description) => { console.log(chalk10.magenta("\u{1F916} AI Block Generator")); console.log(chalk10.dim(`Description: ${description}`)); console.log(chalk10.yellow("\u26A0\uFE0F Coming soon!")); }); program.command("ai:optimize").description("AI-powered performance optimization").action(() => { console.log(chalk10.magenta("\u{1F916} AI Optimizer")); console.log(chalk10.yellow("\u26A0\uFE0F Coming soon!")); }); program.parse();