UNPKG

puter-cli

Version:

Command line interface for Puter cloud platform

318 lines (287 loc) 10.3 kB
import inquirer from 'inquirer'; import chalk from 'chalk'; import ora from 'ora'; import { promises as fs } from 'fs'; import path from 'path'; import { generateAppName, getDefaultHomePage } from '../commons.js'; const JS_BUNDLERS = ['Vite', 'Webpack', 'Parcel', 'esbuild', 'Farm']; const FULLSTACK_FRAMEWORKS = ['Next', 'Nuxt', 'SvelteKit', 'Astro']; const JS_LIBRARIES = ['React', 'Vue', 'Angular', 'Svelte', 'jQuery']; const CSS_LIBRARIES = ['Bootstrap', 'Bulma', 'shadcn', 'Tailwind', 'Material-UI', 'Semantic UI', 'AntDesign', 'Element-Plus', 'PostCSS', 'AutoPrefixer']; export async function init() { const answers = await inquirer.prompt([ { type: 'input', name: 'name', message: 'What is your app name?', default: `${generateAppName()}` }, { type: 'list', name: 'useBundler', message: 'Do you want to use a JavaScript bundler?', choices: ['Yes', 'No (Use CDN)'] } ]); let jsFiles = []; let jsDevFiles = []; let cssFiles = []; let jsExtraLibraries = []; let extraFiles = []; let bundlerAnswers = null; let frameworkAnswers = null; if (answers.useBundler === 'Yes') { bundlerAnswers = await inquirer.prompt([ { type: 'list', name: 'bundler', message: 'Select a JavaScript bundler:', choices: JS_BUNDLERS }, { type: 'list', name: 'frameworkType', message: 'Do you want to use a full-stack framework or custom libraries?', choices: ['Full-stack framework', 'Custom libraries'] } ]); if (bundlerAnswers.frameworkType === 'Full-stack framework') { frameworkAnswers = await inquirer.prompt([ { type: 'list', name: 'framework', message: 'Select a full-stack framework:', choices: FULLSTACK_FRAMEWORKS } ]); switch (frameworkAnswers.framework) { case FULLSTACK_FRAMEWORKS[0]: jsFiles.push('next@latest'); extraFiles.push({ path: 'src/index.tsx', content: `export default function Home() { return (<h1>${answers.name}</h1>) }` }); break; case FULLSTACK_FRAMEWORKS[1]: jsFiles.push('nuxt@latest'); extraFiles.push({ path: 'src/app.vue', content: `<template><h1>${answers.name}</h1></template>` }); break; case FULLSTACK_FRAMEWORKS[2]: jsFiles.push('svelte@latest', 'sveltekit@latest'); extraFiles.push({ path: 'src/app.vue', content: `<template><h1>${answers.name}</h1></template>` }); break; case FULLSTACK_FRAMEWORKS[3]: jsFiles.push('astro@latest', 'astro@latest'); extraFiles.push({ path: 'src/pages/index.astro', content: `---\n\n<Layout title="Welcome to ${answers.name}."><h1>${answers.name}</h1></Layout>` }); break; } } else { const libraryAnswers = await inquirer.prompt([ { type: 'list', name: 'library', message: 'Select a JavaScript library/framework:', choices: JS_LIBRARIES } ]); switch (libraryAnswers.library) { case JS_LIBRARIES[0]: jsFiles.push('react@latest', 'react-dom@latest'); const reactLibs = await inquirer.prompt([ { type: 'checkbox', name: 'reactLibraries', message: 'Select React libraries:', choices: CSS_LIBRARIES.concat(['react-router-dom', 'react-redux', 'react-bootstrap', '@chakra-ui/react', 'semantic-ui-react']) } ]); jsFiles.push(...reactLibs.reactLibraries); extraFiles.push({ path: 'src/App.jsx', content: `export default function Home() { return (<h1>${answers.name}</h1>) }` }); break; case JS_LIBRARIES[1]: jsFiles.push('vue@latest'); jsDevFiles.push('@vitejs/plugin-vue'); const vueLibs = await inquirer.prompt([ { type: 'checkbox', name: 'vueLibraries', message: 'Select Vue libraries:', choices: CSS_LIBRARIES.concat(['shadcn-vue', 'UnoCSS', 'NaiveUI', 'bootstrap-vue-next', 'buefy', 'vue-router', 'pinia']) } ]); jsFiles.push(...vueLibs.vueLibraries); extraFiles.push( { path: 'src/App.vue', content: `<template><h1>${answers.name}</h1></template>` }, { path: 'vite.config.js', content: `import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()] }) `}, { path: 'main.js', content: `import { createApp } from 'vue' import './style.css'; import App from './App.vue'; const app = createApp(App); app.mount('#app'); `}, ); break; case JS_LIBRARIES[2]: jsFiles.push('@angular/core@latest'); extraFiles.push({ path: 'src/index.controller.js', content: `(function () { angular.module('app', [])})` }); break; case JS_LIBRARIES[3]: jsFiles.push('svelte@latest'); break; case JS_LIBRARIES[4]: jsFiles.push('jquery@latest'); extraFiles.push({ path: 'src/main.js', content: `$(function(){})` }); break; } } } else { const cdnAnswers = await inquirer.prompt([ { type: 'list', name: 'jsFramework', message: 'Select a JavaScript framework/library (CDN):', choices: JS_LIBRARIES }, { type: 'list', name: 'cssFramework', message: 'Select a CSS framework/library (CDN):', choices: CSS_LIBRARIES //'Tailwind', 'Bootstrap', 'Bulma'... } ]); switch (cdnAnswers.jsFramework) { case JS_LIBRARIES[0]: jsFiles.push('https://unpkg.com/react@latest/umd/react.production.min.js'); jsFiles.push('https://unpkg.com/react-dom@latest/umd/react-dom.production.min.js'); break; case JS_LIBRARIES[1]: jsFiles.push('https://unpkg.com/vue@latest/dist/vue.global.js'); break; case JS_LIBRARIES[2]: jsFiles.push('https://unpkg.com/@angular/core@latest/bundles/core.umd.js'); break; case JS_LIBRARIES[3]: jsFiles.push('https://unpkg.com/svelte@latest/compiled/svelte.js'); break; case JS_LIBRARIES[4]: jsFiles.push('https://code.jquery.com/jquery-latest.min.js'); break; } switch (cdnAnswers.cssFramework) { case CSS_LIBRARIES[0]: cssFiles.push('https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css'); break; case CSS_LIBRARIES[1]: cssFiles.push('https://cdn.jsdelivr.net/npm/bulma@latest/css/bulma.min.css'); break; case CSS_LIBRARIES[2]: cssFiles.push('https://cdn.tailwindcss.com'); break; } } const spinner = ora('Creating Puter app...').start(); try { const useBundler = answers.useBundler === 'Yes'; // Create basic app structure await createAppStructure(answers.name, useBundler, bundlerAnswers, frameworkAnswers, jsFiles, jsDevFiles, cssFiles, extraFiles); spinner.succeed(chalk.green('Successfully created Puter app!')); console.log('\nNext steps:'); console.log(chalk.cyan('1. cd'), answers.name); if (useBundler) { console.log(chalk.cyan('2. npm install')); console.log(chalk.cyan('3. npm start')); } else { console.log(chalk.cyan('2. Open index.html in your browser')); } } catch (error) { spinner.fail(chalk.red('Failed to create app')); console.error(error); } } async function createAppStructure(name, useBundler, bundlerAnswers, frameworkAnswers, jsFiles, jsDevFiles, cssFiles, extraFiles) { // Create project directory await fs.mkdir(name, { recursive: true }); // Generate default home page const homePage = useBundler?getDefaultHomePage(name): getDefaultHomePage(name, jsFiles, cssFiles); // Create basic files const files = { '.env': `APP_NAME=${name}\nPUTER_API_KEY=`, 'index.html': homePage, 'styles.css': `body { font-family: 'Segoe UI', Roboto, sans-serif; margin: 0 auto; padding: 10px; }`, 'app.js': `// Initialize Puter app console.log('Puter app initialized!');`, 'README.md': `# ${name}\n\nA Puter app created with puter-cli` }; for (const [filename, content] of Object.entries(files)) { await fs.writeFile(path.join(name, filename), content); } // If using a bundler, create a package.json // if (jsFiles.some(file => !file.startsWith('http'))) { if (useBundler) { const useFullStackFramework = bundlerAnswers.frameworkType === 'Full-stack framework'; const bundler = bundlerAnswers.bundler.toString().toLowerCase(); const framework = useFullStackFramework?frameworkAnswers.framework.toLowerCase():null; const scripts = { start: `${useFullStackFramework?`${framework} dev`:bundler} dev`, build: `${useFullStackFramework?`${framework} build`:bundler} build`, }; const packageJson = { name: name, version: '1.0.0', type: 'module', scripts, dependencies: {}, devDependencies: {} }; jsFiles.forEach(lib => { if (!lib.startsWith('http')) { packageJson.dependencies[lib.split('@')[0].toString().toLowerCase()] = lib.split('@')[1] || 'latest'; } }); jsDevFiles.forEach(lib => { packageJson.devDependencies[lib] = 'latest'; }); packageJson.devDependencies[bundler] = 'latest'; await fs.writeFile(path.join(name, 'package.json'), JSON.stringify(packageJson, null, 2)); extraFiles.forEach(async (extraFile) => { const fullPath = path.join(name, extraFile.path); // Create directories recursively if they don't exist await fs.mkdir(path.dirname(fullPath), { recursive: true }); await fs.writeFile(fullPath, extraFile.content); }); } }