UNPKG

@wp-forge/cli

Version:

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

387 lines (359 loc) 11.5 kB
#!/usr/bin/env node // src/create.ts import prompts from "prompts"; import chalk from "chalk"; import ora from "ora"; import { execa } from "execa"; import fs from "fs-extra"; import path from "path"; import validatePackageName from "validate-npm-package-name"; async function main() { console.log(chalk.bold.cyan("\n\u2692\uFE0F Create WP-Forge Theme\n")); const response = await prompts([ { type: "text", name: "name", message: "Theme name:", initial: "My Awesome Theme" }, { type: "text", name: "slug", message: "Theme slug (directory name):", initial: (prev) => prev.toLowerCase().replace(/\s+/g, "-"), validate: (value) => { const validation = validatePackageName(value); if (!validation.validForNewPackages) { return validation.errors?.[0] || "Invalid package name"; } return true; } }, { type: "text", name: "description", message: "Description:", initial: "A theme built with WP-Forge" }, { type: "text", name: "author", message: "Author name:" }, { type: "select", name: "cssFramework", message: "CSS Framework:", choices: [ { title: "Vanilla CSS (Custom Properties)", value: "vanilla" }, { title: "Tailwind CSS", value: "tailwind" }, { title: "UnoCSS (Recommended)", value: "unocss" }, { title: "Panda CSS (Type-safe)", value: "panda" } ], initial: 2 }, { type: "confirm", name: "typescript", message: "Use TypeScript?", initial: true }, { type: "confirm", name: "testing", message: "Include testing setup?", initial: true }, { type: "confirm", name: "ai", message: "Enable AI-powered features?", initial: false } ]); if (!response.slug) { console.log(chalk.red("\n\u2716 Theme creation cancelled\n")); process.exit(1); } const config = response; await createTheme(config); } async function createTheme(config) { const spinner = ora("Creating theme...").start(); try { const themePath = path.resolve(process.cwd(), config.slug); if (await fs.pathExists(themePath)) { spinner.fail(`Directory ${config.slug} already exists`); process.exit(1); } await fs.ensureDir(themePath); spinner.text = "Copying template files..."; await createBasicStructure(themePath, config); spinner.text = "Installing dependencies..."; await execa("pnpm", ["install"], { cwd: themePath }); spinner.succeed(chalk.green("Theme created successfully!")); console.log(chalk.cyan("\n\u{1F4E6} Next steps:\n")); console.log(` cd ${config.slug}`); console.log(" pnpm dev"); console.log(); } catch (error) { spinner.fail("Failed to create theme"); console.error(error); process.exit(1); } } async function createBasicStructure(themePath, config) { const packageJson = { name: config.slug, version: "1.0.0", description: config.description, author: config.author, license: "GPL-3.0-or-later", type: "module", scripts: { dev: "vite", build: "vite build", preview: "vite preview" }, dependencies: { "@wordpress/block-editor": "^12.19.0", "@wordpress/blocks": "^12.28.0", "@wordpress/components": "^27.0.0", "@wordpress/element": "^5.28.0", "@wordpress/i18n": "^4.51.0" }, devDependencies: { "@wp-forge/cli": "^0.2.0", "@wp-forge/vite-plugin": "^0.2.0", "@vitejs/plugin-react": "^4.2.1", vite: "^5.0.10", typescript: config.typescript ? "^5.3.3" : void 0 } }; await fs.writeJson(path.join(themePath, "package.json"), packageJson, { spaces: 2 }); const styleCSS = `/* Theme Name: ${config.name} Theme URI: https://example.com Author: ${config.author} Description: ${config.description} Version: 1.0.0 License: GPL-3.0-or-later Text Domain: ${config.slug} Built with \u2692\uFE0F WP-Forge */`; await fs.writeFile(path.join(themePath, "style.css"), styleCSS); const readme = `# ${config.name} ${config.description} ## Development \`\`\`bash pnpm dev \`\`\` ## Build \`\`\`bash pnpm build \`\`\` Built with [WP-Forge](https://github.com/JonImmsWordpressDev/WP-Forge) `; await fs.writeFile(path.join(themePath, "README.md"), readme); const viteConfig = `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { wpForge } from '@wp-forge/vite-plugin' export default defineConfig({ plugins: [ react(), wpForge({ blocks: { dir: 'src/blocks', autoRegister: true, namespace: '${config.slug}', }, ${config.cssFramework !== "vanilla" ? `designSystem: { enabled: true, framework: '${config.cssFramework === "unocss" ? "unocss" : config.cssFramework === "tailwind" ? "tailwind" : "none"}', wordpressPresets: true, },` : ""} performance: { criticalCSS: { enabled: true }, lazyLoading: { enabled: true }, preload: { enabled: true }, }, phpHmr: { enabled: true, watch: ['**/*.php', 'theme.json', 'templates/**/*'], }, manifest: { enabled: true, wordpress: true, }, }), ], build: { rollupOptions: { input: { main: './src/js/main.ts', editor: './src/js/editor.ts', }, }, }, })`; await fs.writeFile(path.join(themePath, "vite.config.ts"), viteConfig); const functionsPhp = `<?php /** * Theme functions */ if (!defined('ABSPATH')) { exit; } // Load Vite assets require_once get_template_directory() . '/inc/vite-assets.php'; `; await fs.writeFile(path.join(themePath, "functions.php"), functionsPhp); const themeJson = { $schema: "https://schemas.wp.org/trunk/theme.json", version: 2, settings: { appearanceTools: true, layout: { contentSize: "800px", wideSize: "1200px" } } }; await fs.writeJson(path.join(themePath, "theme.json"), themeJson, { spaces: 2 }); await fs.ensureDir(path.join(themePath, "src")); await fs.ensureDir(path.join(themePath, "src/css")); await fs.ensureDir(path.join(themePath, "src/js")); await fs.ensureDir(path.join(themePath, "src/blocks")); await fs.ensureDir(path.join(themePath, "inc")); await fs.ensureDir(path.join(themePath, "templates")); await fs.ensureDir(path.join(themePath, "parts")); await fs.writeFile( path.join(themePath, "src/js/main.ts"), `import '../css/main.css' console.log('${config.name} loaded') ` ); await fs.writeFile( path.join(themePath, "src/js/editor.ts"), `import '../css/editor.css' ` ); await fs.writeFile( path.join(themePath, "src/css/main.css"), `/* Main stylesheet for ${config.name} */ body { font-family: system-ui, sans-serif; } ` ); await fs.writeFile( path.join(themePath, "src/css/editor.css"), `/* Editor stylesheet */ ` ); const viteAssetsPhp = `<?php /** * Vite asset loading helper */ function load_vite_assets() { $manifest_path = get_template_directory() . '/dist/.vite/manifest.json'; if (!file_exists($manifest_path)) { return; } $manifest = json_decode(file_get_contents($manifest_path), true); if (isset($manifest['src/js/main.ts'])) { wp_enqueue_script( '${config.slug}-main', get_template_directory_uri() . '/dist/' . $manifest['src/js/main.ts']['file'], [], null, true ); if (isset($manifest['src/js/main.ts']['css'])) { foreach ($manifest['src/js/main.ts']['css'] as $css) { wp_enqueue_style( '${config.slug}-main-css', get_template_directory_uri() . '/dist/' . $css, [], null ); } } } } add_action('wp_enqueue_scripts', 'load_vite_assets'); `; await fs.writeFile(path.join(themePath, "inc/vite-assets.php"), viteAssetsPhp); const indexTemplate = `<!-- wp:template-part {"slug":"header","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-title {"isLink":true} /--> <!-- wp:post-date /--> <!-- 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 posts found.</p> <!-- /wp:paragraph --> <!-- /wp:query-no-results --> </div> <!-- /wp:query --> </main> <!-- /wp:group --> <!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->`; await fs.writeFile(path.join(themePath, "templates/index.html"), indexTemplate); const headerTemplate = `<!-- wp:group {"tagName":"header","style":{"spacing":{"padding":{"top":"2rem","bottom":"2rem"}}},"layout":{"type":"constrained"}} --> <header class="wp-block-group" style="padding-top:2rem;padding-bottom:2rem"> <!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"space-between"}} --> <div class="wp-block-group"> <!-- wp:site-title /--> <!-- wp:navigation /--> </div> <!-- /wp:group --> </header> <!-- /wp:group -->`; await fs.writeFile(path.join(themePath, "parts/header.html"), headerTemplate); const footerTemplate = `<!-- wp:group {"tagName":"footer","style":{"spacing":{"padding":{"top":"2rem","bottom":"2rem"}}},"layout":{"type":"constrained"}} --> <footer class="wp-block-group" style="padding-top:2rem;padding-bottom:2rem"> <!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"center"}} --> <div class="wp-block-group"> <!-- wp:paragraph --> <p>Built with WP-Forge</p> <!-- /wp:paragraph --> </div> <!-- /wp:group --> </footer> <!-- /wp:group -->`; await fs.writeFile(path.join(themePath, "parts/footer.html"), footerTemplate); const singleTemplate = `<!-- wp:template-part {"slug":"header","tagName":"header"} /--> <!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} --> <main class="wp-block-group"> <!-- wp:post-title /--> <!-- wp:post-featured-image /--> <!-- wp:post-content /--> </main> <!-- /wp:group --> <!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->`; await fs.writeFile(path.join(themePath, "templates/single.html"), singleTemplate); const pageTemplate = `<!-- wp:template-part {"slug":"header","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","tagName":"footer"} /-->`; await fs.writeFile(path.join(themePath, "templates/page.html"), pageTemplate); } main().catch((error) => { console.error(chalk.red("Error:"), error); process.exit(1); });