create-rmv-project
Version:
A CLI tool to set up a new web project how Ruby likes it.
265 lines (221 loc) • 10.4 kB
JavaScript
#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
import simpleGit from 'simple-git';
import { execa } from 'execa';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const program = new Command();
program
.version('1.1.1') // Updated version slightly
.description('Set up a Kirby CMS project with Tailwind CSS v4, Typography plugin, jQuery, and Fancybox, like Ruby likes it.')
.option('-n, --name <name>', 'Project name', 'my-rmv-project')
.parse(process.argv);
const options = program.opts();
async function npmInstall(packages, isDev = false) {
const args = ['install', ...packages];
if (isDev) args.push('--save-dev');
console.log(chalk.blue(`Running: npm ${args.join(' ')}`)); // Log command
// Run with stdio inherited to show npm output directly
return execa('npm', args, { stdio: 'inherit' });
}
async function runNpx(command, args) {
console.log(chalk.blue(`Running: npx ${command} ${args.join(' ')}`)); // Log command
return execa('npx', [command, ...args], { stdio: 'inherit' });
}
async function setupProject() {
const projectDir = path.join(process.cwd(), options.name);
const art = `
_____ __ ___ __ ____ __ _____ __
/ ___/______ ___ _/ /____ / _ \\/ |/ / | / / / __(_) /____
/ /__/ __/ -_) _ \`/ __/ -_) / , _/ /|_/ /| |/ / _\\ \\/ / __/ -_)
\\___/_/ \\__/\\_,_/\\__/\\__/ /_/|_/_/ /_/ |___/ /___/_/\\__/\\__/
`;
console.log(chalk.cyan(art));
console.log(chalk.cyan(`Setting up Kirby project: ${options.name} in ${projectDir}`));
// Clone Kirby Plainkit
console.log(chalk.blue('Cloning Kirby Plainkit...'));
await simpleGit().clone('https://github.com/getkirby/plainkit.git', projectDir);
console.log(chalk.green('Kirby Plainkit cloned.'));
// Remove .git directory
console.log(chalk.blue('Removing Plainkit .git directory...'));
await fs.remove(path.join(projectDir, '.git'));
console.log(chalk.green('.git directory removed.'));
// Initialize new git repository
console.log(chalk.blue('Initializing new git repository...'));
const git = simpleGit(projectDir);
await git.init();
console.log(chalk.green('New git repository initialized.'));
// Set up Tailwind CSS and dependencies
console.log(chalk.cyan('Setting up Tailwind CSS v4 and dependencies...'));
// Create package.json if it doesn't exist
const packageJsonPath = path.join(projectDir, 'package.json');
if (!await fs.pathExists(packageJsonPath)) {
console.log(chalk.blue('Creating package.json...'));
await fs.writeJson(packageJsonPath, {
name: options.name,
version: '1.0.0',
private: true,
// type: "module" // Keep default CJS for wider compatibility unless specifically needed
}, { spaces: 2 });
console.log(chalk.green('package.json created.'));
}
// Change directory before running npm/npx
const originalCwd = process.cwd();
process.chdir(projectDir);
console.log(chalk.blue(`Changed directory to: ${projectDir}`));
try {
// Install dependencies
console.log(chalk.blue('Installing npm dependencies...'));
await npmInstall([
'tailwindcss@latest', // Tailwind core
'@tailwindcss/cli@latest', // Tailwind CLI (v4 requirement)
'@tailwindcss/typography', // Typography plugin
'jquery', // jQuery
'@fancyapps/ui' // Fancybox
], true); // Install as dev dependencies
console.log(chalk.green('npm dependencies installed.'));
// --- REMOVED: Tailwind v4 CLI does not have 'init' ---
// console.log(chalk.blue('Initializing Tailwind CSS configuration...'));
// await runNpx('@tailwindcss/cli', ['init']); // This command fails in v4
// console.log(chalk.green('Tailwind CSS initialized (tailwind.config.js created).'));
// ---
// --- ADDED: Manually create tailwind.config.js ---
const tailwindConfigPath = path.join(projectDir, 'tailwind.config.js');
const tailwindConfigContent = `
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./site/**/*.php',
'./assets/**/*.js', // Include JS files in assets
'./content/**/*.txt' // Kirby content files
],
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/typography'),
],
}
`;
console.log(chalk.blue(`Creating Tailwind config file: ${tailwindConfigPath}`));
await fs.writeFile(tailwindConfigPath, tailwindConfigContent.trim());
console.log(chalk.green('Tailwind config file (tailwind.config.js) created.'));
// ---
// Create Tailwind CSS input file
const tailwindCssContent = `
@import "tailwindcss";
/* Fancybox styles */
@import "@fancyapps/ui/dist/fancybox/fancybox.css";
`;
// Ensure the assets/css directory exists
const cssDirPath = path.join(projectDir, 'assets', 'css');
console.log(chalk.blue(`Ensuring directory exists: ${cssDirPath}`));
await fs.ensureDir(cssDirPath);
// Write the Tailwind CSS input file
const inputCssPath = path.join(cssDirPath, 'processing.css');
console.log(chalk.blue(`Creating Tailwind input CSS file: ${inputCssPath}`));
await fs.writeFile(inputCssPath, tailwindCssContent.trim());
console.log(chalk.green('Tailwind input CSS file created.'));
// --- REMOVED: We now create the file directly, no need to update ---
// console.log(chalk.blue(`Updating Tailwind config: ${tailwindConfigPath}`));
// if (await fs.pathExists(tailwindConfigPath)) {
// // ... (previous update logic removed) ...
// console.log(chalk.green('Tailwind config updated with content paths and typography plugin.'));
// } else {
// console.log(chalk.yellow('Warning: tailwind.config.js not found after init. Skipping update.'));
// }
// ---
// Add build/watch scripts to package.json
console.log(chalk.blue('Updating package.json scripts...'));
const packageJson = await fs.readJson(packageJsonPath);
packageJson.scripts = {
...packageJson.scripts,
// Use @tailwindcss/cli for build and watch
"build": "npx @tailwindcss/cli -i ./assets/css/processing.css -o ./assets/css/tailwind.css --minify",
"watch": "npx @tailwindcss/cli -i ./assets/css/processing.css -o ./assets/css/tailwind.css --watch"
};
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
console.log(chalk.green('package.json scripts updated.'));
// Create a JavaScript file to initialize jQuery and Fancybox
const jsContent = `
import jQuery from 'jquery';
import { Fancybox } from "@fancyapps/ui";
// Make jQuery global for potential other scripts or inline needs (optional)
window.$ = window.jQuery = jQuery;
// Initialize Fancybox on elements with the data-fancybox attribute
Fancybox.bind("[data-fancybox]", {
// Your custom options for Fancybox can go here
// Example: Thumbs: false,
});
console.log('jQuery and Fancybox initialized.');
// Your custom JavaScript code can go below this line
document.addEventListener('DOMContentLoaded', () => {
// Example: Add a class to the body when JS is loaded
// document.body.classList.add('js-loaded');
});
`;
const jsDirPath = path.join(projectDir, 'assets', 'js');
console.log(chalk.blue(`Ensuring directory exists: ${jsDirPath}`));
await fs.ensureDir(jsDirPath);
const mainJsPath = path.join(jsDirPath, 'main.js');
console.log(chalk.blue(`Creating main JS file: ${mainJsPath}`));
await fs.writeFile(mainJsPath, jsContent.trim());
console.log(chalk.green('Main JS file created.'));
// Initial build
console.log(chalk.blue('Running initial Tailwind build...'));
// Use npm run build directly as it's defined in package.json
await execa('npm', ['run', 'build'], { stdio: 'inherit' });
console.log(chalk.green('Initial Tailwind build complete.'));
// Commit changes
console.log(chalk.blue('Committing changes to git...'));
await git.add('.');
await git.commit('Initial setup with Kirby, Tailwind CSS v4, jQuery, and Fancybox');
await git.branch(['-M', 'main']);
console.log(chalk.green('Changes committed to main branch.'));
console.log(chalk.green.bold(`
---------------------------------------------------------
Project setup complete! Ruby is very proud of you.
---------------------------------------------------------
Your Kirby project '${options.name}' with Tailwind CSS v4,
Typography plugin, jQuery, and Fancybox is ready in:
${projectDir}
Available npm scripts:
- npm run build: Compile and minify CSS for production.
- npm run watch: Watch for changes and recompile CSS automatically.
Remember to include the generated CSS and your JS file
in your Kirby templates (e.g., in a snippet like site/snippets/header.php):
PHP Snippet:
<?= css('assets/css/tailwind.css') ?>
<?php /* For production, consider using a JS bundler (Vite, esbuild, Rollup)
to bundle 'main.js' and its dependencies into a single file.
For development, 'type="module"' works well. */ ?>
<?= js('assets/js/main.js', ['defer' => true, 'type' => 'module']) ?>
To use the Typography plugin, add the "prose" class to your content container.
To use Fancybox, add the "data-fancybox" attribute to your lightbox elements (e.g., <a> tags linking to images).
Next steps:
1. cd ${options.name}
2. npm run watch (to start developing with live CSS updates)
3. Configure Kirby (e.g., config/config.php, create user accounts via the panel)
4. Start building your awesome site!
`));
} catch (error) {
console.error(chalk.red('An error occurred during setup:'), error);
// Attempt to clean up the partially created directory
console.log(chalk.yellow(`Attempting to clean up ${projectDir}...`));
process.chdir(originalCwd); // Change back to original dir before removing
await fs.remove(projectDir);
console.log(chalk.yellow(`Directory ${projectDir} removed.`));
process.exit(1);
} finally {
// Ensure we change back to the original directory even if there's an error
if (process.cwd() !== originalCwd) {
process.chdir(originalCwd);
console.log(chalk.blue(`Changed directory back to: ${originalCwd}`));
}
}
}
setupProject();