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 +14 -0
- package/package.json +1 -1
- package/src/commands/init.js +241 -20
- package/src/commons.js +33 -3
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
package/src/commands/init.js
CHANGED
|
@@ -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: '
|
|
19
|
-
message: '
|
|
20
|
-
choices: ['
|
|
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
|
-
|
|
34
|
-
|
|
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(
|
|
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':
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
© 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
|
-
|
|
309
|
-
return null;
|
|
339
|
+
return getVersionFromPackage();
|
|
310
340
|
}
|
|
311
341
|
}
|