puter-cli 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,8 +4,15 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [v1.4.1](https://github.com/bitsnaps/puter-cli/compare/v1.4.0...v1.4.1)
8
+
9
+ - feat: init command will scaffold new project [`c61d3f8`](https://github.com/bitsnaps/puter-cli/commit/c61d3f8669d66eb1a869db38b877d226d00b8aca)
10
+ - changelog: update [`c09aa17`](https://github.com/bitsnaps/puter-cli/commit/c09aa179bd0ad2f437bdf779916050c2496424cd)
11
+
7
12
  #### [v1.4.0](https://github.com/bitsnaps/puter-cli/compare/v1.3.0...v1.4.0)
8
13
 
14
+ > 26 January 2025
15
+
9
16
  - offline mode to get version [`d3c1dc2`](https://github.com/bitsnaps/puter-cli/commit/d3c1dc275bf8a1f38f083c8cdb066fb884a08138)
10
17
  - changelog: update [`f0cc1e3`](https://github.com/bitsnaps/puter-cli/commit/f0cc1e36b2b246d65a88f32c626942c0e15ac245)
11
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "puter-cli",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Command line interface for Puter cloud platform",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -3,7 +3,12 @@ import chalk from 'chalk';
3
3
  import ora from 'ora';
4
4
  import { promises as fs } from 'fs';
5
5
  import 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'];
7
12
 
8
13
  export async function init() {
9
14
  const answers = await inquirer.prompt([
@@ -15,47 +20,223 @@ export async function init() {
15
20
  },
16
21
  {
17
22
  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)']
21
26
  }
22
27
  ]);
23
28
 
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
+
24
202
  const spinner = ora('Creating Puter app...').start();
25
203
 
26
204
  try {
205
+ const useBundler = answers.useBundler === 'Yes';
27
206
  // Create basic app structure
28
- await createAppStructure(answers);
207
+ await createAppStructure(answers.name, useBundler, bundlerAnswers, frameworkAnswers, jsFiles, cssFiles, extraFiles);
29
208
  spinner.succeed(chalk.green('Successfully created Puter app!'));
30
-
209
+
31
210
  console.log('\nNext steps:');
32
211
  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
+ }
35
218
  } catch (error) {
36
219
  spinner.fail(chalk.red('Failed to create app'));
37
220
  console.error(error);
38
221
  }
39
222
  }
40
223
 
41
- async function createAppStructure({ name, template }) {
224
+ async function createAppStructure(name, useBundler, bundlerAnswers, frameworkAnswers, jsFiles, cssFiles, extraFiles) {
42
225
  // Create project directory
43
226
  await fs.mkdir(name, { recursive: true });
44
227
 
228
+ // Generate default home page
229
+ const homePage = useBundler?getDefaultHomePage(name): getDefaultHomePage(name, jsFiles, cssFiles);
230
+
45
231
  // Create basic files
46
232
  const files = {
47
233
  '.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
+ }`,
59
240
  'app.js': `// Initialize Puter app
60
241
  console.log('Puter app initialized!');`,
61
242
  'README.md': `# ${name}\n\nA Puter app created with puter-cli`
@@ -64,4 +245,44 @@ console.log('Puter app initialized!');`,
64
245
  for (const [filename, content] of Object.entries(files)) {
65
246
  await fs.writeFile(path.join(name, filename), content);
66
247
  }
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
+ }
67
288
  }
package/src/commons.js CHANGED
@@ -190,13 +190,19 @@ export function isValidAppName(name) {
190
190
  return true;
191
191
  }
192
192
 
193
- export function getDefaultHomePage(appName) {
193
+ /**
194
+ * Generate the default home page for a new web application
195
+ * @param {string} appName The name of the web application
196
+ * @returns HTML template of the app
197
+ */
198
+ export function getDefaultHomePage(appName, jsFiles = [], cssFiles= []) {
194
199
  const defaultIndexContent = `<!DOCTYPE html>
195
200
  <html lang="en">
196
201
  <head>
197
202
  <meta charset="UTF-8">
198
203
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
199
204
  <title>${appName}</title>
205
+ ${cssFiles.map(css => `<link href="${css}" rel="stylesheet">`).join('\n ')}
200
206
  <style>
201
207
  body {
202
208
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -292,6 +298,11 @@ export function getDefaultHomePage(appName) {
292
298
  <footer class="footer">
293
299
  &copy; 2025 ${appName}. All rights reserved.
294
300
  </footer>
301
+
302
+ <div id="${(jsFiles.length && jsFiles.some(f => f.includes('react'))) ? 'root' : 'app'}"></div>
303
+ ${jsFiles.map(js =>
304
+ `<script ${js.endsWith('app.js') ? 'type="text/babel"' : ''} src="${js}"></script>`
305
+ ).join('\n ')}
295
306
  </body>
296
307
  </html>`;
297
308