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 +7 -0
- package/package.json +1 -1
- package/src/commands/init.js +241 -20
- package/src/commons.js +12 -1
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
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
|
@@ -190,13 +190,19 @@ export function isValidAppName(name) {
|
|
|
190
190
|
return true;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
|
|
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
|
© 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
|
|