UNPKG

psrworld

Version:

A TypeScript-powered project scaffolding tool with dual CommonJS/ESM build support

1,247 lines (1,236 loc) 54.9 kB
#!/usr/bin/env node 'use strict'; var fs = require('node:fs'); var path = require('node:path'); var node_url = require('node:url'); var mri = require('mri'); var prompts = require('@clack/prompts'); var colors = require('picocolors'); var crossSpawn = require('cross-spawn'); var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var prompts__namespace = /*#__PURE__*/_interopNamespaceDefault(prompts); var colors__namespace = /*#__PURE__*/_interopNamespaceDefault(colors); const { blue: blue$1, blueBright: blueBright$1, cyan: cyan$3, green: green$2, greenBright: greenBright$1, magenta: magenta$1, red: red$2, redBright: redBright$1, reset, yellow: yellow$2, gray: gray$1, } = colors__namespace; /** * Framework configurations for project scaffolding * Each framework contains variants with different setups and features */ const FRAMEWORKS = [ { name: 'vanilla', display: 'Vanilla', color: yellow$2, desc: 'Vanilla JavaScript with TypeScript, Vite, and modern tooling setup', variants: [ { name: 'vanilla-ts', display: 'TypeScript', color: blue$1, templateDir: 'template-vanilla-ts', desc: 'Vanilla JavaScript with TypeScript, Vite, and modern tooling setup', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-vanilla-ts', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-vanilla-ts', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'vanilla', display: 'JavaScript', color: yellow$2, templateDir: 'template-vanilla', desc: 'Pure vanilla JavaScript with Vite for fast development and building', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-vanilla', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-vanilla', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, ], }, { name: 'vue', display: 'Vue', color: green$2, variants: [ { name: 'vue-ts', display: 'TypeScript', color: blue$1, templateDir: 'template-vue-ts', desc: 'Vue 3 with TypeScript, Composition API, and single file components', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-vue-ts', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-vue-ts', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'vue', display: 'JavaScript', color: yellow$2, templateDir: 'template-vue', desc: 'Vue 3 with JavaScript, Composition API, and reactive components', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-vue', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-vue', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'custom-create-vue', display: 'Official Vue Starter ↗', color: green$2, customCommand: 'npm create vue@latest TARGET_DIR', desc: 'Official Vue project generator with customizable features and tooling', }, { name: 'custom-nuxt', display: 'Nuxt ↗', color: greenBright$1, customCommand: 'npm exec nuxi init TARGET_DIR', desc: 'Full-stack Vue framework with SSR, file-based routing, and auto-imports', }, ], }, { name: 'react', display: 'React', color: cyan$3, variants: [ { name: 'react-ts', display: 'TypeScript', color: blue$1, templateDir: 'template-react-ts', desc: 'React 18 with TypeScript, JSX, hooks, and modern development setup', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-react-ts', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-react-ts', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'react-swc-ts', display: 'TypeScript + SWC', color: blue$1, templateDir: 'template-react-swc-ts', desc: 'React with TypeScript and SWC compiler for faster builds and HMR', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-react-swc-ts', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-react-swc-ts', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'react', display: 'JavaScript', color: yellow$2, templateDir: 'template-react', desc: 'React 18 with JavaScript, JSX, and component-based architecture', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-react', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-react', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'react-swc', display: 'JavaScript + SWC', color: yellow$2, templateDir: 'template-react-swc', desc: 'React with JavaScript and SWC compiler for enhanced performance', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-react-swc', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-react-swc', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'custom-react-router', display: 'React Router v7 ↗', color: cyan$3, customCommand: 'npm create react-router@latest TARGET_DIR', desc: 'React with declarative routing, nested routes, and navigation management', }, { name: 'custom-tanstack-router', display: 'TanStack Router ↗', color: cyan$3, customCommand: 'npm create -- tsrouter-app@latest TARGET_DIR --framework React --interactive', desc: 'Type-safe router with search params, loaders, and advanced routing features', }, { name: 'redwoodsdk-standard', display: 'RedwoodSDK ↗', color: red$2, customCommand: 'npm exec degit redwoodjs/sdk/starters/standard TARGET_DIR', desc: 'Full-stack React framework with GraphQL, Prisma, and JAMstack architecture', }, { name: 'rsc', display: 'RSC ↗', color: magenta$1, customCommand: 'npm exec degit vitejs/vite-plugin-react/packages/plugin-rsc/examples/starter TARGET_DIR', desc: 'React Server Components with server-side rendering and streaming', }, ], }, { name: 'preact', display: 'Preact', color: magenta$1, variants: [ { name: 'preact-ts', display: 'TypeScript', color: blue$1, templateDir: 'template-preact-ts', desc: 'Lightweight React alternative with TypeScript and 3KB runtime', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-preact-ts', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-preact-ts', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'preact', display: 'JavaScript', color: yellow$2, templateDir: 'template-preact', desc: 'Fast 3KB React alternative with same modern API and ecosystem', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-preact', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-preact', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'custom-create-preact', display: 'Official Preact Starter ↗', color: magenta$1, customCommand: 'npm create preact@latest TARGET_DIR', desc: 'Official Preact generator with PWA features and optimized builds', }, ], }, { name: 'lit', display: 'Lit', color: redBright$1, variants: [ { name: 'lit-ts', display: 'TypeScript', color: blue$1, templateDir: 'template-lit-ts', desc: 'Web Components with Lit library, TypeScript, and reactive properties', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-lit-ts', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-lit-ts', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'lit', display: 'JavaScript', color: yellow$2, templateDir: 'template-lit', desc: 'Simple and fast web components using Lit library and templates', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-lit', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-lit', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, ], }, { name: 'svelte', display: 'Svelte', color: red$2, variants: [ { name: 'svelte-ts', display: 'TypeScript', color: blue$1, templateDir: 'template-svelte-ts', desc: 'Compile-time optimized framework with TypeScript and reactive state', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-svelte-ts', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-svelte-ts', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'svelte', display: 'JavaScript', color: yellow$2, templateDir: 'template-svelte', desc: 'No virtual DOM framework with compiled components and reactive updates', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-svelte', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-svelte', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'custom-svelte-kit', display: 'SvelteKit ↗', color: red$2, customCommand: 'npm exec sv create TARGET_DIR', desc: 'Full-stack Svelte framework with SSR, routing, and adapter system', }, ], }, { name: 'solid', display: 'Solid', color: blue$1, variants: [ { name: 'solid-ts', display: 'TypeScript', color: blue$1, templateDir: 'template-solid-ts', desc: 'Fine-grained reactive framework with TypeScript and JSX syntax', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-solid-ts', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-solid-ts', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'solid', display: 'JavaScript', color: yellow$2, templateDir: 'template-solid', desc: 'Performant reactive framework with signals and compiled templates', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-solid', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-solid', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'custom-tanstack-router', display: 'TanStack Router ↗', color: cyan$3, customCommand: 'npm create -- tsrouter-app@latest TARGET_DIR --framework Solid --interactive', desc: 'Solid with type-safe routing, search params, and data loading', }, ], }, { name: 'qwik', display: 'Qwik', color: blueBright$1, variants: [ { name: 'qwik-ts', display: 'TypeScript', color: blueBright$1, templateDir: 'template-qwik-ts', desc: 'Resumable framework with TypeScript and instant-on applications', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-qwik-ts', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-qwik-ts', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'qwik', display: 'JavaScript', color: yellow$2, templateDir: 'template-qwik', desc: 'Zero hydration framework with resumable execution and lazy loading', tailwindCssVariants: [ { name: 'tailwind-v4', display: 'Tailwind V4', color: green$2, templateDir: 'v4-template-qwik', desc: 'Tailwind CSS v4 with new CSS-first configuration and Lightning CSS engine', }, { name: 'tailwind-v3', display: 'Tailwind V3', color: blue$1, templateDir: 'v3-template-qwik', desc: 'Tailwind CSS v3 with traditional configuration and build process', }, ], }, { name: 'custom-qwik-city', display: 'QwikCity ↗', color: blueBright$1, customCommand: 'npm create qwik@latest basic TARGET_DIR', desc: 'Meta-framework for Qwik with routing, layouts, and SSR capabilities', }, ], }, { name: 'angular', display: 'Angular', color: red$2, desc: 'Full-featured TypeScript framework with dependency injection and comprehensive tooling', variants: [ { name: 'custom-angular', display: 'Angular ↗', color: red$2, customCommand: 'npm exec @angular/cli@latest new TARGET_DIR', desc: 'Full-featured framework with TypeScript, dependency injection, and CLI tools', }, { name: 'custom-analog', display: 'Analog ↗', color: yellow$2, customCommand: 'npm create analog@latest TARGET_DIR', desc: 'Angular meta-framework with file-based routing and server-side rendering', }, ], }, { name: 'marko', display: 'Marko', color: magenta$1, desc: "eBay's UI framework with streaming SSR and optimized performance", variants: [ { name: 'marko-run', display: 'Marko Run ↗', color: magenta$1, customCommand: 'npm create -- marko@latest --name TARGET_DIR', desc: "eBay's UI framework with streaming SSR and component compilation", }, ], }, { name: 'nextjs', display: 'Next.js', color: cyan$3, desc: 'Production-ready React framework with SSR and full-stack capabilities', variants: [ { name: 'custom-nextjs', display: 'Next.js ↗', color: cyan$3, customCommand: 'npm create next-app@latest TARGET_DIR', desc: 'React framework with SSR, API routes, file-based routing, and deployment optimization', }, ], }, { name: 'astro', display: 'Astro', color: magenta$1, desc: 'Content-focused framework with islands architecture and multi-framework support', variants: [ { name: 'custom-astro', display: 'Astro ↗', color: magenta$1, customCommand: 'npm create astro@latest TARGET_DIR', desc: 'Multi-framework static site generator with partial hydration and islands architecture', }, ], }, { name: 'remix', display: 'Remix', color: blue$1, desc: 'Full-stack React framework focused on web standards and user experience', variants: [ { name: 'custom-remix', display: 'Remix ↗', color: blue$1, customCommand: 'npx create-remix@latest TARGET_DIR', desc: 'Full-stack React framework focusing on web standards and progressive enhancement', }, ], }, { name: 'meteor', display: 'Meteor.js', color: gray$1, desc: 'Full-stack JavaScript platform with real-time capabilities and integrated toolchain', variants: [ { name: 'custom-meteor', display: 'Meteor.js ↗', color: gray$1, customCommand: 'meteor create TARGET_DIR', desc: 'Full-stack JavaScript platform with real-time data, MongoDB integration, and deployment tools', }, ], }, { name: 'gatsby', display: 'Gatsby', color: magenta$1, desc: 'React-based static site generator with GraphQL and performance optimization', variants: [ { name: 'custom-gatsby', display: 'Gatsby ↗', color: magenta$1, customCommand: 'npm init gatsby@latest TARGET_DIR', desc: 'React-based static site generator with GraphQL data layer and plugin ecosystem', }, ], }, { name: 'symfony', display: 'Symfony', color: blue$1, desc: 'Professional PHP framework with enterprise-grade components and architecture', variants: [ { name: 'custom-symfony', display: 'Symfony ↗', color: blue$1, customCommand: 'symfony new TARGET_DIR', desc: 'PHP framework with reusable components, dependency injection, and rapid development tools', }, ], }, { name: 'rails', display: 'Rails', color: redBright$1, desc: 'Ruby web framework with convention over configuration and rapid development', variants: [ { name: 'custom-rails', display: 'Rails ↗', color: redBright$1, customCommand: 'rails new TARGET_DIR', desc: 'Ruby web framework with convention over configuration and MVC architecture', }, ], }, { name: 'phoenix', display: 'Phoenix', color: cyan$3, desc: 'Elixir web framework with real-time features and fault-tolerant architecture', variants: [ { name: 'custom-phoenix', display: 'Phoenix ↗', color: cyan$3, customCommand: 'mix phx.new TARGET_DIR', desc: 'Elixir web framework with real-time features, fault tolerance, and high concurrency', }, ], }, { name: 'django', display: 'Django', color: green$2, desc: 'Python web framework with batteries included and rapid development focus', variants: [ { name: 'custom-django', display: 'Django ↗', color: green$2, customCommand: 'django-admin startproject TARGET_DIR', desc: 'Python web framework with batteries included, admin interface, and ORM', }, ], }, { name: 'flask', display: 'Flask', color: yellow$2, desc: 'Lightweight Python web framework with flexible architecture and simplicity', variants: [ { name: 'custom-flask', display: 'Flask ↗', color: yellow$2, customCommand: 'flask new TARGET_DIR', desc: 'Lightweight Python web framework with flexible architecture and extensibility', }, ], }, { name: 'blazor', display: 'Blazor', color: blueBright$1, desc: 'C# web framework running in browser with WebAssembly and .NET ecosystem', variants: [ { name: 'custom-blazor', display: 'Blazor ↗', color: blueBright$1, customCommand: 'dotnet new blazorwasm -o TARGET_DIR', desc: 'C# web framework running in browser via WebAssembly with .NET ecosystem', }, ], }, { name: 'hugo', display: 'Hugo', color: gray$1, desc: 'Fast static site generator written in Go with themes and content management', variants: [ { name: 'custom-hugo', display: 'Hugo ↗', color: gray$1, customCommand: 'hugo new site TARGET_DIR', desc: 'Fast static site generator written in Go with themes and content management', }, ], }, { name: 'others', display: 'Others', color: reset, desc: 'Additional starters and specialized project templates', variants: [ { name: 'create-vite-extra', display: 'Extra Vite Starters ↗', color: reset, customCommand: 'npm create vite-extra@latest TARGET_DIR', desc: 'Additional Vite templates and starters for various frameworks and setups', }, { name: 'create-electron-vite', display: 'Electron ↗', color: reset, customCommand: 'npm create electron-vite@latest TARGET_DIR', desc: 'Desktop applications with Electron, Vite, and modern web technologies', }, ], }, ]; /** * Find variant by name across all frameworks * @param frameworks - Array of framework configurations * @param variantName - Name of the variant to find * @returns Object with variant and framework, or null if not found */ function getVariantByName(frameworks, variantName) { for (const framework of frameworks) { if (framework.variants) { const variant = framework.variants.find(v => v.name === variantName); if (variant) { return { variant, framework }; } } } return null; } /** * Get Tailwind CSS variants for a specific framework variant * @param variant - Framework variant * @returns Array of Tailwind CSS variants or empty array if none */ function getTailwindVariants(variant) { return variant.tailwindCssVariants || []; } /** * Find Tailwind variant by name within a framework variant * @param variant - Framework variant * @param tailwindVariantName - Name of the Tailwind variant * @returns Tailwind variant or null if not found */ function getTailwindVariantByName(variant, tailwindVariantName) { if (!variant.tailwindCssVariants) return null; return (variant.tailwindCssVariants.find(tv => tv.name === tailwindVariantName) || null); } function formatTargetDir(targetDir) { return targetDir?.trim().replace(/\/+$/g, '') || ''; } function toValidPackageName(projectName) { return projectName .trim() .toLowerCase() .replace(/\s+/g, '-') .replace(/^[._]/, '') .replace(/[^a-z0-9-~]+/g, '-') .replace(/^-+|-+$/g, '') // Remove leading and trailing hyphens .replace(/-+/g, '-'); // Replace multiple consecutive hyphens with single hyphen } function writeTemplate(src, dest, projectName) { const stats = fs.statSync(src); if (stats.isDirectory()) { fs.mkdirSync(dest, { recursive: true }); for (const file of fs.readdirSync(src)) { const srcFile = path.resolve(src, file); const destFile = path.resolve(dest, file); writeTemplate(srcFile, destFile, projectName); } } else { let content = fs.readFileSync(src, 'utf-8'); // Generate a valid package name from the project name const packageName = toValidPackageName(projectName); // Replace template variables content = content.replace(/{{projectName}}/g, projectName); content = content.replace(/{{package}}/g, packageName); fs.writeFileSync(dest, content); } } function isEmpty(path) { const files = fs.readdirSync(path); return files.length === 0 || (files.length === 1 && files[0] === '.git'); } function emptyDir(dir) { if (!fs.existsSync(dir)) { return; } for (const file of fs.readdirSync(dir)) { if (file === '.git') { continue; } fs.rmSync(path.resolve(dir, file), { recursive: true, force: true }); } } const { blue, blueBright, cyan: cyan$2, green: green$1, greenBright, magenta, red: red$1, redBright, yellow: yellow$1, gray, } = colors__namespace; const helpMessage = `\ Usage: create-mycustomlib [OPTION]... [DIRECTORY] Create a new project from a template. With no arguments, start the CLI in interactive mode. Options: -h, --help show this help message -t, --template NAME use a specific template -y, --yes use default options (vanilla-ts with Tailwind v4) Available templates: ${yellow$1('vanilla-ts vanilla')} ${green$1('vue-ts vue')} ${cyan$2('react-ts react')} ${cyan$2('react-swc-ts react-swc')} ${magenta('preact-ts preact')} ${redBright('lit-ts lit')} ${red$1('svelte-ts svelte')} ${blue('solid-ts solid')} ${blueBright('qwik-ts qwik')} ${cyan$2('custom-nextjs')} ${greenBright('nuxt nuxt')} ${magenta('astro astro')} ${blue('remix remix')} ${gray('meteor meteor')} ${magenta('gatsby gatsby')} ${blue('symfony symfony')} ${redBright('rails rails')} ${cyan$2('phoenix phoenix')} ${green$1('django django')} ${yellow$1('flask flask')} ${blueBright('blazor blazor')} ${gray('hugo hugo')}`; function printHelp() { console.log(helpMessage); } function pkgFromUserAgent(userAgent) { if (!userAgent) return undefined; const pkgSpec = userAgent.split(' ')[0]; const pkgSpecArr = pkgSpec.split('/'); return { name: pkgSpecArr[0], version: pkgSpecArr[1], }; } const { cyan: cyan$1 } = colors__namespace; function executeCustomCommand(command, targetDir, selectedOptions = null) { return new Promise((resolve, reject) => { // Replace TARGET_DIR placeholder with actual target directory const finalCommand = command.replace('TARGET_DIR', targetDir); // Parse command and arguments const [cmd, ...args] = finalCommand.split(' '); console.log(`\nExecuting: ${cyan$1(finalCommand)}\n`); const child = crossSpawn.spawn(cmd, args, { stdio: 'inherit', shell: process.platform === 'win32', }); child.on('close', code => { if (code !== 0) { reject(new Error(`Command failed with exit code ${code}`)); } else { resolve(); } }); child.on('error', error => { reject(error); }); }); } const { cyan, green, red, yellow } = colors; const __dirname$1 = path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('create-psrworld.cjs', document.baseURI).href)))); // Get template directory path - adjust for the new structure const templateDir = path.resolve(__dirname$1, '../templates'); function cancel() { prompts__namespace.cancel('Operation cancelled'); process.exit(0); } async function handleTemplateCreation(selectedVariant, framework, projectName, templateName, installDeps = true, addTailwind = false, tailwindVariant = null) { if (selectedVariant?.customCommand) { // Handle custom command execution path.join(process.cwd(), projectName); try { await executeCustomCommand(selectedVariant.customCommand, projectName, null); prompts__namespace.outro(green('✅ Project created successfully using custom command!')); } catch (error) { console.error(red('✖ Custom command failed:'), error.message); process.exit(1); } return; } // Handle regular template-based scaffolding const root = path.join(process.cwd(), projectName); if (!fs.existsSync(root)) { fs.mkdirSync(root, { recursive: true }); } console.log(`\nScaffolding project in ${root}...`); // Determine the template path let templatePath; if (addTailwind && tailwindVariant && selectedVariant) { // Get the Tailwind variant configuration const tailwindConfig = getTailwindVariantByName(selectedVariant, tailwindVariant); if (tailwindConfig?.templateDir) { // Use Tailwind-integrated template (e.g., v4-template-react-ts) templatePath = path.resolve(templateDir, tailwindConfig.templateDir); // Fallback to regular template if Tailwind template doesn't exist if (!fs.existsSync(templatePath)) { console.log(yellow(`Tailwind template not found at ${tailwindConfig.templateDir}, using regular template...`)); templatePath = path.resolve(templateDir, selectedVariant.templateDir || templateName); } } else { // Use regular template templatePath = path.resolve(templateDir, selectedVariant.templateDir || templateName); } } else if (selectedVariant?.templateDir) { // Use specified template directory templatePath = path.resolve(templateDir, selectedVariant.templateDir); } else { // Use default template name templatePath = path.resolve(templateDir, templateName); } if (!fs.existsSync(templatePath)) { throw new Error(`Template not found at ${templatePath}!`); } // Copy template files and replace variables writeTemplate(templatePath, root, projectName); const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent); const pkgManager = pkgInfo ? pkgInfo.name : 'npm'; // Install dependencies if requested if (installDeps) { console.log(cyan(`\nInstalling dependencies via ${pkgManager}...`)); try { // Change to project directory process.chdir(root); const installCommand = pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`; await executeCustomCommand(installCommand, '.'); console.log(green('✅ Dependencies installed successfully!')); } catch (error) { console.error(yellow('⚠️ Dependency installation failed, but you can run it manually:')); console.error(` cd ${path.relative(process.cwd(), root)}`); console.error(` ${pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`}`); } } // Install Tailwind CSS if requested (only for non-integrated templates) if (addTailwind && tailwindVariant && selectedVariant) { const tailwindConfig = getTailwindVariantByName(selectedVariant, tailwindVariant); if (tailwindConfig) { // Check if it's a template-based or command-based Tailwind setup if (tailwindConfig.templateDir) { const tailwindTemplatePath = path.resolve(templateDir, tailwindConfig.templateDir); // Only show message if we're not using an integrated template if (!fs.existsSync(tailwindTemplatePath)) { console.log(yellow(`Tailwind template not found at ${tailwindConfig.templateDir}, Tailwind was not integrated.`)); } else { console.log(green('✅ Using Tailwind-integrated template!')); } } else if (tailwindConfig.customCommand) { // Run Tailwind installation command console.log(cyan('\nSetting up Tailwind CSS...')); try { // Ensure we're in the project directory process.chdir(root); // Replace PKG_MANAGER placeholder with actual package manager const tailwindCommand = tailwindConfig.customCommand.replace(/PKG_MANAGER/g, pkgManager); await executeCustomCommand(tailwindCommand, '.'); console.log(green('✅ Tailwind CSS setup completed!')); } catch (error) { console.error(yellow('⚠️ Tailwind CSS setup failed, but you can run it manually:')); console.error(` cd ${projectName}`); const tailwindCommand = tailwindConfig.customCommand.replace(/PKG_MANAGER/g, pkgManager); console.error(` ${tailwindCommand}`); } } } } console.log('\nDone. Now run:\n'); // Always show cd command for the project directory console.log(` cd ${projectName}`); // Show install command only if dependencies weren't installed if (!installDeps) { switch (pkgManager) { case 'yarn': console.log(' yarn'); break; default: console.log(` ${pkgManager} install`); break; } } // Show dev command switch (pkgManager) { case 'yarn': console.log(' yarn dev'); break; default: console.log(` ${pkgManager} run dev`); break; } console.log(); prompts__namespace.outro(green('✅ Project created successfully!')); } // Get default selections for --yes flag function getDefaultSelections() { return { framework: 'vanilla', // Default to Vanilla variant: 'vanilla-ts', // Default to TypeScript variant installDeps: true, addTailwind: true, tailwindVariant: 'tailwind-v4', // Default to Tailwind V4 }; } async function init() { const argv = mri(process.argv.slice(2), { string: ['_'], boolean: ['yes', 'y'], alias: { h: 'help', t: 'template', y: 'yes', }, }); const argTargetDir = formatTargetDir(argv._[0]); const cliTemplate = argv.template; const useDefaults = argv.yes || argv.y; // Check for help flag if (argv.help || argv.h) { printHelp(); return; } const defaultTargetDir = 'my-project'; let result = {}; prompts__namespace.intro(cyan('🚀 Create Custom Project')); // 1. Get project name and target dir let targetDir = argTargetDir; if (!targetDir) { if (useDefaults) { targetDir = defaultTargetDir; console.log(`Project name: ${cyan(targetDir)} ${yellow('(default)')}`); } else { const projectName = await prompts__namespace.text({ message: 'Project name:', defaultValue: defaultTargetDir, placeholder: defaultTargetDir, validate: (value) => { return value.length === 0 || formatTargetDir(value).length > 0 ? undefined : 'Invalid project name'; }, }); if (prompts__namespace.isCancel(projectName)) return cancel(); targetDir = formatTargetDir(projectName); } } // If template is provided via CLI, use it directly if (cliTemplate) { const result = getVariantByName(FRAMEWORKS, cliTemplate); if (!result) { console.error(red(`✖ Template "${cliTemplate}" not found!`)); console.log('\nAvailable templates:'); FRAMEWORKS.forEach(framework => { if (framework.variants) { framework.variants.forEach(variant => { console.log(` ${variant.name}`); }); } }); process.exit(1); } const { variant: selectedVariant, framework } = result; const projectName = targetDir || defaultTargetDir; // Handle directory overwrite check for CLI template const root = path.join(process.cwd(), projectName); if (fs.existsSync(root) && !isEmpty(root)) { if (useDefaults) { console.log(`Directory ${yellow(projectName)} is not empty. Removing existing files... ${yellow('(--yes)')}`); emptyDir(root); } else { const shouldOverwrite = await prompts__namespace.confirm({ message: `Directory ${yellow(projectName)} is not empty. Remove existing files and continue?`, }); if (prompts__namespace.isCancel(shouldOverwrite) || !shouldOverwrite) { return cancel(); } emptyDir(root); } } // Use defaults for template-based creation when --yes is used const defaults = useDefaults ? getDefaultSelections() : { installDeps: true, addTailwind: false, tailwindVariant: null }; await handleTemplateCreation(selectedVariant, framework, projectName, cliTemplate, defaults.installDeps, defaults.addTailwind, defaults.tailwindVariant); return; } // Handle --yes flag with default selections if (useDefaults) { const defaults = getDefaultSelections(); const projectName = targetDir || defaultTargetDir; // Handle directory overwrite check const root = path.join(process.cwd(), projectName); if (fs.existsSync(root) && !isEmpty(root)) { console.log(`Directory ${yellow(projectName)} is not empty. Removing existing files... ${yellow('(--yes)')}`); emptyDir(root); } // Display selected defaults console.log(`Selected framework: ${cyan('Vanilla')} ${yellow('(default)')}`); console.log(`Selected variant: ${cyan('TypeScript')} ${yellow('(default)')}`); console.log(`Add Tailwind CSS: ${cyan('Yes')} ${yellow('(default)')}`); console.log(`Tailwind version: ${cyan('Tailwind V4')} ${yellow('(default)')}`); console.log(`Install dependencies: ${cyan('Yes')} ${yellow('(default)')}`); // Get the selected variant and framework const selectedResult = getVariantByName(FRAMEWORKS, defaults.variant); const selectedVariant = selectedResult?.variant || null; const selectedFramework = selectedResult?.framework || null; await handleTemplateCreation(selectedVariant, selectedFramework, projectName, defaults.variant, defaults.installDeps, defaults.addTailwind, defaults.tailwindVariant); return; } try { result = await prompts__namespace.group({ shouldOverwrite: () => { const dir = path.resolve(targetDir || defaultTargetDir); if (!fs.existsSync(dir) || isEmpty(dir)) { return; } return prompts__namespace.confirm({ message: `Directory ${yellow(targetDir |