@@ -3,7 +3,12 @@ import chalk from 'chalk';
33import ora from 'ora' ;
44import { promises as fs } from 'fs' ;
55import path from 'path' ;
6- import { generateAppName } from '../commons.js' ;
6+ import { generateAppName , getDefaultHomePage } from '../commons.js' ;
7+
8+ const JS_BUNDLERS = [ 'Vite' , 'Webpack' , 'Parcel' , 'esbuild' , 'Farm' ] ;
9+ const FULLSTACK_FRAMEWORKS = [ 'Next' , 'Nuxt' , 'SvelteKit' , 'Astro' ] ;
10+ const JS_LIBRARIES = [ 'React' , 'Vue' , 'Angular' , 'Svelte' , 'jQuery' ] ;
11+ const CSS_LIBRARIES = [ 'Bootstrap' , 'Bulma' , 'shadcn' , 'Tailwind' , 'Material-UI' , 'Semantic UI' , 'AntDesign' , 'Element-Plus' , 'PostCSS' , 'AutoPrefixer' ] ;
712
813export async function init ( ) {
914 const answers = await inquirer . prompt ( [
@@ -15,47 +20,223 @@ export async function init() {
1520 } ,
1621 {
1722 type : 'list' ,
18- name : 'template ' ,
19- message : 'Select a template: ' ,
20- choices : [ 'basic ' , 'static-site' , 'full-stack ']
23+ name : 'useBundler ' ,
24+ message : 'Do you want to use a JavaScript bundler? ' ,
25+ choices : [ 'Yes ' , 'No (Use CDN) ' ]
2126 }
2227 ] ) ;
2328
29+ let jsFiles = [ ] ;
30+ let cssFiles = [ ] ;
31+ let extraFiles = [ ] ;
32+ let bundlerAnswers = null ;
33+ let frameworkAnswers = null ;
34+
35+ if ( answers . useBundler === 'Yes' ) {
36+ bundlerAnswers = await inquirer . prompt ( [
37+ {
38+ type : 'list' ,
39+ name : 'bundler' ,
40+ message : 'Select a JavaScript bundler:' ,
41+ choices : JS_BUNDLERS
42+ } ,
43+ {
44+ type : 'list' ,
45+ name : 'frameworkType' ,
46+ message : 'Do you want to use a full-stack framework or custom libraries?' ,
47+ choices : [ 'Full-stack framework' , 'Custom libraries' ]
48+ }
49+ ] ) ;
50+
51+ if ( bundlerAnswers . frameworkType === 'Full-stack framework' ) {
52+ frameworkAnswers = await inquirer . prompt ( [
53+ {
54+ type : 'list' ,
55+ name : 'framework' ,
56+ message : 'Select a full-stack framework:' ,
57+ choices : FULLSTACK_FRAMEWORKS
58+ }
59+ ] ) ;
60+
61+ switch ( frameworkAnswers . framework ) {
62+ case FULLSTACK_FRAMEWORKS [ 0 ] :
63+ jsFiles . push ( 'next@latest' ) ;
64+ extraFiles . push ( {
65+ path : 'src/index.tsx' ,
66+ content : `export default function Home() { return (<h1>${ answers . name } </h1>) }`
67+ } ) ;
68+ break ;
69+ case FULLSTACK_FRAMEWORKS [ 1 ] :
70+ jsFiles . push ( 'nuxt@latest' ) ;
71+ extraFiles . push ( {
72+ path : 'src/app.vue' ,
73+ content : `<template><h1>${ answers . name } </h1></template>`
74+ } ) ;
75+ break ;
76+ case FULLSTACK_FRAMEWORKS [ 2 ] :
77+ jsFiles . push ( 'svelte@latest' , 'sveltekit@latest' ) ;
78+ extraFiles . push ( {
79+ path : 'src/app.vue' ,
80+ content : `<template><h1>${ answers . name } </h1></template>`
81+ } ) ;
82+ break ;
83+ case FULLSTACK_FRAMEWORKS [ 3 ] :
84+ jsFiles . push ( 'astro@latest' , 'astro@latest' ) ;
85+ extraFiles . push ( {
86+ path : 'src/pages/index.astro' ,
87+ content : `---\n\n<Layout title="Welcome to ${ answers . name } ."><h1>${ answers . name } </h1></Layout>`
88+ } ) ;
89+ break ;
90+ }
91+ } else {
92+ const libraryAnswers = await inquirer . prompt ( [
93+ {
94+ type : 'list' ,
95+ name : 'library' ,
96+ message : 'Select a JavaScript library/framework:' ,
97+ choices : JS_LIBRARIES
98+ }
99+ ] ) ;
100+
101+ switch ( libraryAnswers . library ) {
102+ case JS_LIBRARIES [ 0 ] :
103+ jsFiles . push ( 'react@latest' , 'react-dom@latest' ) ;
104+ const reactLibs = await inquirer . prompt ( [
105+ {
106+ type : 'checkbox' ,
107+ name : 'reactLibraries' ,
108+ message : 'Select React libraries:' ,
109+ choices : CSS_LIBRARIES . concat ( [ 'react-router-dom' , 'react-redux' , 'react-bootstrap' , '@chakra-ui/react' , 'semantic-ui-react' ] )
110+ }
111+ ] ) ;
112+ jsFiles . push ( ...reactLibs . reactLibraries ) ;
113+ extraFiles . push ( {
114+ path : 'src/App.jsx' ,
115+ content : `export default function Home() { return (<h1>${ answers . name } </h1>) }`
116+ } ) ;
117+ break ;
118+ case JS_LIBRARIES [ 1 ] :
119+ jsFiles . push ( 'vue@latest' ) ;
120+ const vueLibs = await inquirer . prompt ( [
121+ {
122+ type : 'checkbox' ,
123+ name : 'vueLibraries' ,
124+ message : 'Select Vue libraries:' ,
125+ choices : CSS_LIBRARIES . concat ( [ 'shadcn-vue' , 'UnoCSS' , 'NaiveUI' , 'bootstrap-vue-next' , 'buefy' , 'vue-router' , 'pinia' ] )
126+ }
127+ ] ) ;
128+ jsFiles . push ( ...vueLibs . vueLibraries ) ;
129+ extraFiles . push ( {
130+ path : 'src/App.vue' ,
131+ content : `<template><h1>${ answers . name } </h1></template>`
132+ } ) ;
133+ break ;
134+ case JS_LIBRARIES [ 2 ] :
135+ jsFiles . push ( '@angular/core@latest' ) ;
136+ extraFiles . push ( {
137+ path : 'src/index.controller.js' ,
138+ content : `(function () { angular.module('app', [])})`
139+ } ) ;
140+ break ;
141+ case JS_LIBRARIES [ 3 ] :
142+ jsFiles . push ( 'svelte@latest' ) ;
143+ break ;
144+ case JS_LIBRARIES [ 4 ] :
145+ jsFiles . push ( 'jquery@latest' ) ;
146+ extraFiles . push ( {
147+ path : 'src/main.js' ,
148+ content : `$(function(){})`
149+ } ) ;
150+ break ;
151+ }
152+ }
153+ } else {
154+
155+ const cdnAnswers = await inquirer . prompt ( [
156+ {
157+ type : 'list' ,
158+ name : 'jsFramework' ,
159+ message : 'Select a JavaScript framework/library (CDN):' ,
160+ choices : JS_LIBRARIES
161+ } ,
162+ {
163+ type : 'list' ,
164+ name : 'cssFramework' ,
165+ message : 'Select a CSS framework/library (CDN):' ,
166+ choices : CSS_LIBRARIES //'Tailwind', 'Bootstrap', 'Bulma'...
167+ }
168+ ] ) ;
169+
170+ switch ( cdnAnswers . jsFramework ) {
171+ case JS_LIBRARIES [ 0 ] :
172+ jsFiles . push ( 'https://unpkg.com/react@latest/umd/react.production.min.js' ) ;
173+ jsFiles . push ( 'https://unpkg.com/react-dom@latest/umd/react-dom.production.min.js' ) ;
174+ break ;
175+ case JS_LIBRARIES [ 1 ] :
176+ jsFiles . push ( 'https://unpkg.com/vue@latest/dist/vue.global.js' ) ;
177+ break ;
178+ case JS_LIBRARIES [ 2 ] :
179+ jsFiles . push ( 'https://unpkg.com/@angular/core@latest/bundles/core.umd.js' ) ;
180+ break ;
181+ case JS_LIBRARIES [ 3 ] :
182+ jsFiles . push ( 'https://unpkg.com/svelte@latest/compiled/svelte.js' ) ;
183+ break ;
184+ case JS_LIBRARIES [ 4 ] :
185+ jsFiles . push ( 'https://code.jquery.com/jquery-latest.min.js' ) ;
186+ break ;
187+ }
188+
189+ switch ( cdnAnswers . cssFramework ) {
190+ case CSS_LIBRARIES [ 0 ] :
191+ cssFiles . push ( 'https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css' ) ;
192+ break ;
193+ case CSS_LIBRARIES [ 1 ] :
194+ cssFiles . push ( 'https://cdn.jsdelivr.net/npm/bulma@latest/css/bulma.min.css' ) ;
195+ break ;
196+ case CSS_LIBRARIES [ 2 ] :
197+ cssFiles . push ( 'https://cdn.tailwindcss.com' ) ;
198+ break ;
199+ }
200+ }
201+
24202 const spinner = ora ( 'Creating Puter app...' ) . start ( ) ;
25203
26204 try {
205+ const useBundler = answers . useBundler === 'Yes' ;
27206 // Create basic app structure
28- await createAppStructure ( answers ) ;
207+ await createAppStructure ( answers . name , useBundler , bundlerAnswers , frameworkAnswers , jsFiles , cssFiles , extraFiles ) ;
29208 spinner . succeed ( chalk . green ( 'Successfully created Puter app!' ) ) ;
30-
209+
31210 console . log ( '\nNext steps:' ) ;
32211 console . log ( chalk . cyan ( '1. cd' ) , answers . name ) ;
33- console . log ( chalk . cyan ( '2. npm install' ) ) ;
34- console . log ( chalk . cyan ( '3. npm start' ) ) ;
212+ if ( useBundler ) {
213+ console . log ( chalk . cyan ( '2. npm install' ) ) ;
214+ console . log ( chalk . cyan ( '3. npm start' ) ) ;
215+ } else {
216+ console . log ( chalk . cyan ( '2. Open index.html in your browser' ) ) ;
217+ }
35218 } catch ( error ) {
36219 spinner . fail ( chalk . red ( 'Failed to create app' ) ) ;
37220 console . error ( error ) ;
38221 }
39222}
40223
41- async function createAppStructure ( { name, template } ) {
224+ async function createAppStructure ( name , useBundler , bundlerAnswers , frameworkAnswers , jsFiles , cssFiles , extraFiles ) {
42225 // Create project directory
43226 await fs . mkdir ( name , { recursive : true } ) ;
44227
228+ // Generate default home page
229+ const homePage = useBundler ?getDefaultHomePage ( name ) : getDefaultHomePage ( name , jsFiles , cssFiles ) ;
230+
45231 // Create basic files
46232 const files = {
47233 '.env' : `APP_NAME=${ name } \nPUTER_API_KEY=` ,
48- 'index.html' : `<!DOCTYPE html>
49- <html>
50- <head>
51- <title>${ name } </title>
52- </head>
53- <body>
54- <h1>Welcome to ${ name } </h1>
55- <script src="https://js.puter.com/v2/"></script>
56- <script src="app.js"></script>
57- </body>
58- </html>` ,
234+ 'index.html' : homePage ,
235+ 'styles.css' : `body {
236+ font-family: 'Segoe UI', Roboto, sans-serif;
237+ margin: 0 auto;
238+ padding: 10px;
239+ }` ,
59240 'app.js' : `// Initialize Puter app
60241console.log('Puter app initialized!');` ,
61242 'README.md' : `# ${ name } \n\nA Puter app created with puter-cli`
@@ -64,4 +245,44 @@ console.log('Puter app initialized!');`,
64245 for ( const [ filename , content ] of Object . entries ( files ) ) {
65246 await fs . writeFile ( path . join ( name , filename ) , content ) ;
66247 }
248+
249+ // If using a bundler, create a package.json
250+ // if (jsFiles.some(file => !file.startsWith('http'))) {
251+ if ( useBundler ) {
252+
253+ const useFullStackFramework = bundlerAnswers . frameworkType === 'Full-stack framework' ;
254+ const bundler = bundlerAnswers . bundler . toString ( ) . toLowerCase ( ) ;
255+ const framework = useFullStackFramework ?frameworkAnswers . framework . toLowerCase ( ) :null ;
256+
257+ const scripts = {
258+ start : `${ useFullStackFramework ?`${ framework } dev` :bundler } dev` ,
259+ build : `${ useFullStackFramework ?`${ framework } build` :bundler } build` ,
260+ } ;
261+
262+ const packageJson = {
263+ name : name ,
264+ version : '1.0.0' ,
265+ scripts,
266+ dependencies : { } ,
267+ devDependencies : { }
268+ } ;
269+
270+ jsFiles . forEach ( lib => {
271+ if ( ! lib . startsWith ( 'http' ) ) {
272+ packageJson . dependencies [ lib . split ( '@' ) [ 0 ] . toString ( ) . toLowerCase ( ) ] = lib . split ( '@' ) [ 1 ] || 'latest' ;
273+ }
274+ } ) ;
275+
276+ packageJson . devDependencies [ bundler ] = 'latest' ;
277+
278+ await fs . writeFile ( path . join ( name , 'package.json' ) , JSON . stringify ( packageJson , null , 2 ) ) ;
279+
280+ extraFiles . forEach ( async ( extraFile ) => {
281+ const fullPath = path . join ( name , extraFile . path ) ;
282+ // Create directories recursively if they don't exist
283+ await fs . mkdir ( path . dirname ( fullPath ) , { recursive : true } ) ;
284+ await fs . writeFile ( fullPath , extraFile . content ) ;
285+ } ) ;
286+
287+ }
67288}
0 commit comments