puter-cli 1.3.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,22 @@ 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
+
12
+ #### [v1.4.0](https://github.com/bitsnaps/puter-cli/compare/v1.3.0...v1.4.0)
13
+
14
+ > 26 January 2025
15
+
16
+ - offline mode to get version [`d3c1dc2`](https://github.com/bitsnaps/puter-cli/commit/d3c1dc275bf8a1f38f083c8cdb066fb884a08138)
17
+ - changelog: update [`f0cc1e3`](https://github.com/bitsnaps/puter-cli/commit/f0cc1e36b2b246d65a88f32c626942c0e15ac245)
18
+
7
19
  #### [v1.3.0](https://github.com/bitsnaps/puter-cli/compare/v1.2.1...v1.3.0)
8
20
 
21
+ > 22 January 2025
22
+
9
23
  - ci: simplify workflow [`757b41c`](https://github.com/bitsnaps/puter-cli/commit/757b41caa62dc71946e7f1ffb34a32f2871248e0)
10
24
  - test: setup coverage [`2dd6500`](https://github.com/bitsnaps/puter-cli/commit/2dd650088ad9ecb6f7f9cd60b3dab80d48ac2611)
11
25
  - ci: update packages [`b346932`](https://github.com/bitsnaps/puter-cli/commit/b346932c4b6af2d8e43279ad3f35c45e451fd9f0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "puter-cli",
3
- "version": "1.3.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
@@ -1,5 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { getAuthToken } from './commands/auth.js';
3
+ import { readFile } from 'fs/promises';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
3
6
 
4
7
  export const PROJECT_NAME = 'puter-cli';
5
8
  export const API_BASE = 'https://api.puter.com';
@@ -187,13 +190,19 @@ export function isValidAppName(name) {
187
190
  return true;
188
191
  }
189
192
 
190
- 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= []) {
191
199
  const defaultIndexContent = `<!DOCTYPE html>
192
200
  <html lang="en">
193
201
  <head>
194
202
  <meta charset="UTF-8">
195
203
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
196
204
  <title>${appName}</title>
205
+ ${cssFiles.map(css => `<link href="${css}" rel="stylesheet">`).join('\n ')}
197
206
  <style>
198
207
  body {
199
208
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -289,6 +298,11 @@ export function getDefaultHomePage(appName) {
289
298
  <footer class="footer">
290
299
  &copy; 2025 ${appName}. All rights reserved.
291
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 ')}
292
306
  </body>
293
307
  </html>`;
294
308
 
@@ -296,6 +310,23 @@ export function getDefaultHomePage(appName) {
296
310
  }
297
311
 
298
312
 
313
+ /**
314
+ * Read latest package from package file
315
+ */
316
+ export async function getVersionFromPackage() {
317
+ try {
318
+ const __filename = fileURLToPath(import.meta.url);
319
+ const __dirname = dirname(__filename);
320
+ const packageJson = JSON.parse(
321
+ await readFile(join(__dirname, 'package.json'), 'utf8')
322
+ );
323
+ return packageJson.version;
324
+ } catch (error) {
325
+ console.error(`Error fetching latest version:`, error.message);
326
+ return null;
327
+ }
328
+ }
329
+
299
330
  /**
300
331
  * Get latest package info from npm registery
301
332
  */
@@ -305,7 +336,6 @@ export async function getLatestVersion(packageName) {
305
336
  let data = await response.json();
306
337
  return data;
307
338
  } catch (error) {
308
- console.error(`Error fetching latest version for ${packageName}:`, error.message);
309
- return null;
339
+ return getVersionFromPackage();
310
340
  }
311
341
  }