puter-cli 1.8.3 → 1.8.5
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/README.md +5 -9
- package/bin/index.js +18 -0
- package/package.json +1 -1
- package/src/commands/deploy.js +52 -0
- package/src/commands/files.js +37 -32
- package/src/commands/init.js +1 -1
- package/src/commands/sites.js +12 -4
- package/src/commons.js +118 -105
- package/src/executor.js +10 -2
- package/src/modules/ProfileModule.js +3 -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.8.5](https://github.com/HeyPuter/puter-cli/compare/v1.8.4...v1.8.5)
|
|
8
|
+
|
|
9
|
+
- feat(site): add site:deploy command for one-step project deployment [`94210ed`](https://github.com/HeyPuter/puter-cli/commit/94210ed796e7564647f4cd177a1b0bef72e66ef2)
|
|
10
|
+
- Fix and add some details to the README file [`8acb6ea`](https://github.com/HeyPuter/puter-cli/commit/8acb6ea753b4f83c146f26473f61756e613a4953)
|
|
11
|
+
- fix(files): touch command to creation a new file [`d30a067`](https://github.com/HeyPuter/puter-cli/commit/d30a0673e9aa5fb5b5d6b037a6a134a196a27529)
|
|
12
|
+
|
|
13
|
+
#### [v1.8.4](https://github.com/HeyPuter/puter-cli/compare/v1.8.3...v1.8.4)
|
|
14
|
+
|
|
15
|
+
> 20 September 2025
|
|
16
|
+
|
|
17
|
+
- fix(app): improve `app:delete` argument validation and usage [`eda41cd`](https://github.com/HeyPuter/puter-cli/commit/eda41cd7c623b8a3f0c60f586de756d4369face3)
|
|
18
|
+
|
|
7
19
|
#### [v1.8.3](https://github.com/HeyPuter/puter-cli/compare/v1.8.2...v1.8.3)
|
|
8
20
|
|
|
21
|
+
> 20 September 2025
|
|
22
|
+
|
|
9
23
|
- fix(app): improve `app:create` validation and usage [`73fc030`](https://github.com/HeyPuter/puter-cli/commit/73fc0300e244a7e94f16d266bfad6128528144b6)
|
|
10
24
|
- chore(config): add ignore patterns for AI tools and agent files [`2b19d6c`](https://github.com/HeyPuter/puter-cli/commit/2b19d6c4c8233d560d4ca16820b3f601875b6c79)
|
|
11
25
|
|
package/README.md
CHANGED
|
@@ -141,14 +141,9 @@ P.S. The `--delete` flag removes files in the remote directory that don't exist
|
|
|
141
141
|
```
|
|
142
142
|
P.S. This command will download the remote file to your local machine, open it in your default editor, and then upload the changes back to the remote instance. It uses `vim` by default, but you can change it by setting the `EDITOR` environment variable.
|
|
143
143
|
|
|
144
|
-
#### User Information
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
The addition describes the `update` command which allows for bidirectional synchronization between local and remote directories, including the optional flags for deleting files and recursive synchronization.
|
|
148
|
-
---
|
|
149
|
-
|
|
150
144
|
|
|
151
145
|
#### User Information
|
|
146
|
+
|
|
152
147
|
- **Get User Info**: Display user information.
|
|
153
148
|
```bash
|
|
154
149
|
puter> whoami
|
|
@@ -314,10 +309,10 @@ cp .env.example .env
|
|
|
314
309
|
|
|
315
310
|
## Known issues:
|
|
316
311
|
|
|
317
|
-
Most features are working fine. If you have any issues with this project
|
|
312
|
+
Most features are working fine. If you have any issues with this project, please let us know:
|
|
318
313
|
|
|
319
314
|
## Interactive Shell prompt:
|
|
320
|
-
If you want to stay in the interactive shell you should provide "-f" (aka: force delete) argument, when want to delete any object:
|
|
315
|
+
If you want to stay in the interactive shell you should provide "-f" (aka: force delete) argument, when you want to delete any object:
|
|
321
316
|
```bash
|
|
322
317
|
puter@username/myapp> rm README.md
|
|
323
318
|
The following items will be moved to Trash:
|
|
@@ -334,13 +329,14 @@ Otherwise, the Interactive Shell mode will be terminated.
|
|
|
334
329
|
|
|
335
330
|
## Notes
|
|
336
331
|
|
|
337
|
-
This project is not equivalent [phoenix](https://github.com/HeyPuter/puter/blob/main/src/phoenix/README.md), neither an attempt to mimic some of its features, it's rather a CLI tool to do most the Puter's API from the command line.
|
|
332
|
+
This project is not equivalent [phoenix](https://github.com/HeyPuter/puter/blob/main/src/phoenix/README.md), neither an attempt to mimic some of its features, it's rather a CLI tool to do most the Puter's API actions from the command line.
|
|
338
333
|
|
|
339
334
|
---
|
|
340
335
|
|
|
341
336
|
## Configuration
|
|
342
337
|
|
|
343
338
|
The CLI uses a configuration file to store user credentials and settings. You can use the `puter logout` to clear the configuration settings.
|
|
339
|
+
You can create a `.env` file to use your own settings for `PUTER_API_BASE` and `PUTER_BASE_URL` in order to work on Puter locally for development purposes.
|
|
344
340
|
|
|
345
341
|
---
|
|
346
342
|
|
package/bin/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { init } from '../src/commands/init.js';
|
|
|
6
6
|
import { startShell } from '../src/commands/shell.js';
|
|
7
7
|
import { PROJECT_NAME, getLatestVersion } from '../src/commons.js';
|
|
8
8
|
import { createApp } from '../src/commands/apps.js';
|
|
9
|
+
import { deploy } from '../src/commands/deploy.js';
|
|
9
10
|
|
|
10
11
|
async function main() {
|
|
11
12
|
const version = await getLatestVersion(PROJECT_NAME);
|
|
@@ -62,6 +63,23 @@ async function main() {
|
|
|
62
63
|
process.exit(0);
|
|
63
64
|
});
|
|
64
65
|
|
|
66
|
+
/*/ Deploy command
|
|
67
|
+
program
|
|
68
|
+
.command('site:deploy')
|
|
69
|
+
.description('Deploy a local web project to Puter')
|
|
70
|
+
.argument('[name]', 'Name of the site')
|
|
71
|
+
.argument('[remoteDir]', 'Remote directory path')
|
|
72
|
+
.option('--subdomain <subdomain>', 'Subdomain for the site')
|
|
73
|
+
.action(async (name, remoteDir, options) => {
|
|
74
|
+
try {
|
|
75
|
+
await deploy([name, remoteDir, `--subdomain=${options.subdomain}`].filter(Boolean));
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(chalk.red(error.message));
|
|
78
|
+
}
|
|
79
|
+
process.exit(0);
|
|
80
|
+
});
|
|
81
|
+
*/
|
|
82
|
+
|
|
65
83
|
if (process.argv.length === 2) {
|
|
66
84
|
startShell();
|
|
67
85
|
} else {
|
package/package.json
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { generateAppName, isValidAppName } from '../commons.js';
|
|
3
|
+
import { syncDirectory } from './files.js';
|
|
4
|
+
import { createSite } from './sites.js';
|
|
5
|
+
import { getCurrentDirectory } from './auth.js';
|
|
6
|
+
import { getSubdomains } from './subdomains.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Deploy a local web project to Puter.
|
|
10
|
+
* @param {string[]} args - Command-line arguments (e.g., <valid_site_app> [<remote_dir>] [--subdomain=<subdomain>]).
|
|
11
|
+
*/
|
|
12
|
+
export async function deploy(args = []) {
|
|
13
|
+
if (args.length < 1 || !isValidAppName(args[0])) {
|
|
14
|
+
console.log(chalk.red('Usage: site:deploy <valid_site_app> [<remote_dir>] [--subdomain=<subdomain>]'));
|
|
15
|
+
console.log(chalk.yellow('Example: site:deploy'));
|
|
16
|
+
console.log(chalk.yellow('Example: site:deploy my-app'));
|
|
17
|
+
console.log(chalk.yellow('Example: site:deploy my-app ./my-app'));
|
|
18
|
+
console.log(chalk.yellow('Example: site:deploy my-app ./my-app --subdomain=my-app-new'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const appName = args[0]; // && !args[0].startsWith('--') ? args[0] : generateAppName();
|
|
22
|
+
const remoteDirArg = args.find(arg => !arg.startsWith('--') && arg !== appName);
|
|
23
|
+
const remoteDir = remoteDirArg || '.'; // `./${appName}`;
|
|
24
|
+
const subdomain = args.find(arg => arg.startsWith('--subdomain='))?.split('=')[1] || appName;
|
|
25
|
+
const sourceDir = '.'; // Deploy from the current directory
|
|
26
|
+
|
|
27
|
+
console.log(chalk.cyan(`Deploying '${appName}' from local '${sourceDir}' to remote '${remoteDir}' at '${subdomain}.puter.site'...`));
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// 1. Upload files
|
|
31
|
+
console.log(chalk.cyan(`Uploading files from '${sourceDir}' to '${remoteDir}'...`));
|
|
32
|
+
await syncDirectory([sourceDir, remoteDir, '--delete', '-r', '--overwrite']);
|
|
33
|
+
|
|
34
|
+
// 2. Create the site
|
|
35
|
+
console.log(chalk.cyan(`Creating site...`));
|
|
36
|
+
const site = await createSite([appName, remoteDir, `--subdomain=${subdomain}`]);
|
|
37
|
+
|
|
38
|
+
if (site) {
|
|
39
|
+
console.log(chalk.green('Deployment successful!'));
|
|
40
|
+
} else {
|
|
41
|
+
console.log(chalk.yellow('Deployment successfuly updated!'));
|
|
42
|
+
}
|
|
43
|
+
if (subdomain){
|
|
44
|
+
console.log(chalk.cyan('Your site is live at:'));
|
|
45
|
+
console.log(chalk.green(`https://${subdomain}.puter.site`));
|
|
46
|
+
} else {
|
|
47
|
+
console.log(chalk.red('Deployment failed. Subdomain cannot be reserved!'));
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(chalk.red(`Deployment failed: ${error.message}`));
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/commands/files.js
CHANGED
|
@@ -7,7 +7,7 @@ import { minimatch } from 'minimatch';
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import Conf from 'conf';
|
|
9
9
|
import fetch from 'node-fetch';
|
|
10
|
-
import { API_BASE, BASE_URL, PROJECT_NAME, getHeaders, showDiskSpaceUsage, resolvePath } from '../commons.js';
|
|
10
|
+
import { API_BASE, BASE_URL, PROJECT_NAME, getHeaders, showDiskSpaceUsage, resolvePath, resolveRemotePath } from '../commons.js';
|
|
11
11
|
import { formatDateTime, formatSize, getSystemEditor } from '../utils.js';
|
|
12
12
|
import inquirer from 'inquirer';
|
|
13
13
|
import { getAuthToken, getCurrentDirectory, getCurrentUserName } from './auth.js';
|
|
@@ -626,7 +626,9 @@ export async function createFile(args = []) {
|
|
|
626
626
|
})
|
|
627
627
|
});
|
|
628
628
|
|
|
629
|
-
if (statResponse.ok) {
|
|
629
|
+
if (!statResponse.ok) {
|
|
630
|
+
console.log(chalk.cyan('File does not exists. Il will be created.'));
|
|
631
|
+
} else {
|
|
630
632
|
const statData = await statResponse.json();
|
|
631
633
|
if (statData && statData.id) {
|
|
632
634
|
if (!overwrite) {
|
|
@@ -635,9 +637,6 @@ export async function createFile(args = []) {
|
|
|
635
637
|
}
|
|
636
638
|
console.log(chalk.yellow(`File "${filePath}" already exists. It will be overwritten.`));
|
|
637
639
|
}
|
|
638
|
-
} else if (statResponse.status !== 404) {
|
|
639
|
-
console.error(chalk.red('Failed to check if file exists.'));
|
|
640
|
-
return false;
|
|
641
640
|
}
|
|
642
641
|
|
|
643
642
|
// Step 2: Check disk space
|
|
@@ -1263,7 +1262,7 @@ async function ensureRemoteDirectoryExists(remotePath) {
|
|
|
1263
1262
|
* @param {string[]} args - Command-line arguments (e.g., [localDir, remoteDir, --delete, -r]).
|
|
1264
1263
|
*/
|
|
1265
1264
|
export async function syncDirectory(args = []) {
|
|
1266
|
-
const usageMessage = 'Usage: update <local_directory> <remote_directory> [--delete] [-r]';
|
|
1265
|
+
const usageMessage = 'Usage: update <local_directory> <remote_directory> [--delete] [-r] [--overwrite]';
|
|
1267
1266
|
if (args.length < 2) {
|
|
1268
1267
|
console.log(chalk.red(usageMessage));
|
|
1269
1268
|
return;
|
|
@@ -1273,11 +1272,13 @@ export async function syncDirectory(args = []) {
|
|
|
1273
1272
|
let remoteDir = '';
|
|
1274
1273
|
let deleteFlag = '';
|
|
1275
1274
|
let recursiveFlag = false;
|
|
1275
|
+
let overwriteFlag = false;
|
|
1276
1276
|
try {
|
|
1277
1277
|
localDir = await resolveLocalDirectory(args[0]);
|
|
1278
|
-
remoteDir =
|
|
1278
|
+
remoteDir = resolveRemotePath(getCurrentDirectory(), args[1]);
|
|
1279
1279
|
deleteFlag = args.includes('--delete'); // Whether to delete extra files
|
|
1280
1280
|
recursiveFlag = args.includes('-r'); // Whether to recursively process subdirectories
|
|
1281
|
+
overwriteFlag = args.includes('--overwrite');
|
|
1281
1282
|
} catch (error) {
|
|
1282
1283
|
console.error(chalk.red(error.message));
|
|
1283
1284
|
console.log(chalk.green(usageMessage));
|
|
@@ -1294,10 +1295,10 @@ export async function syncDirectory(args = []) {
|
|
|
1294
1295
|
}
|
|
1295
1296
|
|
|
1296
1297
|
// Step 2: Fetch remote directory contents
|
|
1297
|
-
|
|
1298
|
+
let remoteFiles = await listRemoteFiles(remoteDir);
|
|
1298
1299
|
if (!Array.isArray(remoteFiles)) {
|
|
1299
|
-
console.
|
|
1300
|
-
|
|
1300
|
+
console.log(chalk.yellow('Remote directory is empty or does not exist. Continuing...'));
|
|
1301
|
+
remoteFiles = [];
|
|
1301
1302
|
}
|
|
1302
1303
|
|
|
1303
1304
|
// Step 3: List local files
|
|
@@ -1311,30 +1312,34 @@ export async function syncDirectory(args = []) {
|
|
|
1311
1312
|
// Step 5: Handle conflicts (if any)
|
|
1312
1313
|
const conflicts = findConflicts(toUpload, toDownload);
|
|
1313
1314
|
if (conflicts.length > 0) {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
conflicts.forEach(file => console.log(chalk.dim(`- ${file}`)));
|
|
1317
|
-
|
|
1318
|
-
const { resolve } = await inquirer.prompt([
|
|
1319
|
-
{
|
|
1320
|
-
type: 'list',
|
|
1321
|
-
name: 'resolve',
|
|
1322
|
-
message: 'How would you like to resolve conflicts?',
|
|
1323
|
-
choices: [
|
|
1324
|
-
{ name: 'Keep local version', value: 'local' },
|
|
1325
|
-
{ name: 'Keep remote version', value: 'remote' },
|
|
1326
|
-
{ name: 'Skip conflicting files', value: 'skip' }
|
|
1327
|
-
]
|
|
1328
|
-
}
|
|
1329
|
-
]);
|
|
1330
|
-
|
|
1331
|
-
if (resolve === 'local') {
|
|
1315
|
+
if (overwriteFlag) {
|
|
1316
|
+
console.log(chalk.yellow('Overwriting existing files with local version.'));
|
|
1332
1317
|
filteredToDownload = filteredToDownload.filter(file => !conflicts.includes(file.relativePath));
|
|
1333
|
-
} else if (resolve === 'remote') {
|
|
1334
|
-
filteredToUpload = filteredToUpload.filter(file => !conflicts.includes(file.relativePath));
|
|
1335
1318
|
} else {
|
|
1336
|
-
|
|
1337
|
-
|
|
1319
|
+
console.log(chalk.yellow('The following files have conflicts:'));
|
|
1320
|
+
conflicts.forEach(file => console.log(chalk.dim(`- ${file}`)));
|
|
1321
|
+
|
|
1322
|
+
const { resolve } = await inquirer.prompt([
|
|
1323
|
+
{
|
|
1324
|
+
type: 'list',
|
|
1325
|
+
name: 'resolve',
|
|
1326
|
+
message: 'How would you like to resolve conflicts?',
|
|
1327
|
+
choices: [
|
|
1328
|
+
{ name: 'Keep local version', value: 'local' },
|
|
1329
|
+
{ name: 'Keep remote version', value: 'remote' },
|
|
1330
|
+
{ name: 'Skip conflicting files', value: 'skip' }
|
|
1331
|
+
]
|
|
1332
|
+
}
|
|
1333
|
+
]);
|
|
1334
|
+
|
|
1335
|
+
if (resolve === 'local') {
|
|
1336
|
+
filteredToDownload = filteredToDownload.filter(file => !conflicts.includes(file.relativePath));
|
|
1337
|
+
} else if (resolve === 'remote') {
|
|
1338
|
+
filteredToUpload = filteredToUpload.filter(file => !conflicts.includes(file.relativePath));
|
|
1339
|
+
} else {
|
|
1340
|
+
filteredToUpload = filteredToUpload.filter(file => !conflicts.includes(file.relativePath));
|
|
1341
|
+
filteredToDownload = filteredToDownload.filter(file => !conflicts.includes(file.relativePath));
|
|
1342
|
+
}
|
|
1338
1343
|
}
|
|
1339
1344
|
}
|
|
1340
1345
|
|
package/src/commands/init.js
CHANGED
package/src/commands/sites.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import fetch from 'node-fetch';
|
|
3
3
|
import Table from 'cli-table3';
|
|
4
4
|
import { getCurrentUserName, getCurrentDirectory } from './auth.js';
|
|
5
|
-
import { API_BASE, getHeaders, generateAppName,
|
|
5
|
+
import { API_BASE, getHeaders, generateAppName, resolveRemotePath, isValidAppName } from '../commons.js';
|
|
6
6
|
import { displayNonNullValues, formatDate, isValidAppUuid } from '../utils.js';
|
|
7
7
|
import { getSubdomains, createSubdomain, deleteSubdomain } from './subdomains.js';
|
|
8
8
|
import { ErrorAPI } from '../modules/ErrorModule.js';
|
|
@@ -98,7 +98,7 @@ export async function infoSite(args = []) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
/**
|
|
102
102
|
* Delete hosted web site
|
|
103
103
|
* @param {any[]} args Array of site uuid
|
|
104
104
|
*/
|
|
@@ -109,6 +109,10 @@ export async function infoSite(args = []) {
|
|
|
109
109
|
}
|
|
110
110
|
for (const uuid of args)
|
|
111
111
|
try {
|
|
112
|
+
if (!uuid){
|
|
113
|
+
console.log(chalk.yellow(`We could not find the site ID: ${uuid}`));
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
112
116
|
// The uuid must be prefixed with: 'subdomainObj-'
|
|
113
117
|
const response = await fetch(`${API_BASE}/delete-site`, {
|
|
114
118
|
headers: getHeaders(),
|
|
@@ -136,7 +140,7 @@ export async function infoSite(args = []) {
|
|
|
136
140
|
return true;
|
|
137
141
|
}
|
|
138
142
|
|
|
139
|
-
|
|
143
|
+
/**
|
|
140
144
|
* Create a static web app from the current directory to Puter cloud.
|
|
141
145
|
* @param {string[]} args - Command-line arguments (e.g., [name, --subdomain=<subdomain>]).
|
|
142
146
|
*/
|
|
@@ -151,8 +155,10 @@ export async function infoSite(args = []) {
|
|
|
151
155
|
|
|
152
156
|
const appName = args[0]; // Site name (required)
|
|
153
157
|
const subdomainOption = args.find(arg => arg.toLocaleLowerCase().startsWith('--subdomain='))?.split('=')[1]; // Optional subdomain
|
|
158
|
+
const remoteDirArg = (args[1] && !args[1].startsWith('--')) ? args[1] : '.';
|
|
159
|
+
|
|
154
160
|
// Use the current directory as the root directory if none specified
|
|
155
|
-
const remoteDir =
|
|
161
|
+
const remoteDir = resolveRemotePath(getCurrentDirectory(), remoteDirArg);
|
|
156
162
|
|
|
157
163
|
console.log(chalk.dim(`Creating site ${chalk.green(appName)} from: ${chalk.green(remoteDir)}...\n`));
|
|
158
164
|
try {
|
|
@@ -212,8 +218,10 @@ export async function infoSite(args = []) {
|
|
|
212
218
|
|
|
213
219
|
console.log(chalk.green(`App ${chalk.dim(appName)} created successfully and accessible at:`));
|
|
214
220
|
console.log(chalk.cyan(`https://${site.subdomain}.puter.site`));
|
|
221
|
+
return site;
|
|
215
222
|
} catch (error) {
|
|
216
223
|
console.error(chalk.red('Failed to create site.'));
|
|
217
224
|
console.error(chalk.red(`Error: ${error.message}`));
|
|
225
|
+
return null;
|
|
218
226
|
}
|
|
219
227
|
}
|
package/src/commons.js
CHANGED
|
@@ -155,6 +155,19 @@ export function resolvePath(currentPath, relativePath) {
|
|
|
155
155
|
return currentPath;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Resolve a remote path to an absolute path, handling both absolute and relative paths.
|
|
160
|
+
* @param {string} currentPath - The current working directory.
|
|
161
|
+
* @param {string} remotePath - The remote path to resolve.
|
|
162
|
+
* @returns {string} The resolved absolute path.
|
|
163
|
+
*/
|
|
164
|
+
export function resolveRemotePath(currentPath, remotePath) {
|
|
165
|
+
if (remotePath.startsWith('/')) {
|
|
166
|
+
return remotePath;
|
|
167
|
+
}
|
|
168
|
+
return resolvePath(currentPath, remotePath);
|
|
169
|
+
}
|
|
170
|
+
|
|
158
171
|
/**
|
|
159
172
|
* Checks if a given string is a valid app name.
|
|
160
173
|
* The name must:
|
|
@@ -207,114 +220,114 @@ export function isValidAppName(name) {
|
|
|
207
220
|
*/
|
|
208
221
|
export function getDefaultHomePage(appName, jsFiles = [], cssFiles= []) {
|
|
209
222
|
const defaultIndexContent = `<!DOCTYPE html>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
</div>
|
|
278
|
-
|
|
279
|
-
<h2>🌟 Getting Started</h2>
|
|
280
|
-
|
|
281
|
-
<p>Here's a simple example using Puter.js:</p>
|
|
282
|
-
|
|
283
|
-
<div class="code-block">
|
|
284
|
-
<script src="https://js.puter.com/v2/"></script>
|
|
285
|
-
<script>
|
|
286
|
-
// Create a new file in the cloud
|
|
287
|
-
puter.fs.write('hello.txt', 'Hello, Puter!')
|
|
288
|
-
.then(file => console.log(\`File created at: \${file.path}\`));
|
|
289
|
-
</script>
|
|
290
|
-
</div>
|
|
291
|
-
|
|
292
|
-
<h2>💡 Key Features</h2>
|
|
293
|
-
<ul>
|
|
294
|
-
<li>Cloud Storage</li>
|
|
295
|
-
<li>AI Services (GPT-4, DALL-E)</li>
|
|
296
|
-
<li>Static Website Hosting</li>
|
|
297
|
-
<li>Key-Value Store</li>
|
|
298
|
-
<li>Authentication</li>
|
|
299
|
-
</ul>
|
|
300
|
-
|
|
301
|
-
<div class="links">
|
|
302
|
-
<a href="https://docs.puter.com" target="_blank">📚 Documentation</a>
|
|
303
|
-
<a href="https://discord.gg/puter" target="_blank">💬 Discord Community</a>
|
|
304
|
-
<a href="https://github.com/HeyPuter" target="_blank">👩💻 GitHub</a>
|
|
305
|
-
</div>
|
|
223
|
+
<html lang="en">
|
|
224
|
+
<head>
|
|
225
|
+
<meta charset="UTF-8">
|
|
226
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
227
|
+
<title>${appName}</title>
|
|
228
|
+
${cssFiles.map(css => `<link href="${css}" rel="stylesheet">`).join('\n ')}
|
|
229
|
+
<style>
|
|
230
|
+
body {
|
|
231
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
232
|
+
line-height: 1.6;
|
|
233
|
+
max-width: 800px;
|
|
234
|
+
margin: 0 auto;
|
|
235
|
+
padding: 20px;
|
|
236
|
+
background: #f9fafb;
|
|
237
|
+
color: #1f2937;
|
|
238
|
+
}
|
|
239
|
+
.container {
|
|
240
|
+
background: white;
|
|
241
|
+
padding: 2rem;
|
|
242
|
+
border-radius: 8px;
|
|
243
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
244
|
+
}
|
|
245
|
+
h1 {
|
|
246
|
+
color: #2563eb;
|
|
247
|
+
margin-bottom: 1rem;
|
|
248
|
+
}
|
|
249
|
+
.code-block {
|
|
250
|
+
background: #f1f5f9;
|
|
251
|
+
padding: 1rem;
|
|
252
|
+
border-radius: 4px;
|
|
253
|
+
font-family: monospace;
|
|
254
|
+
overflow-x: auto;
|
|
255
|
+
}
|
|
256
|
+
.tip {
|
|
257
|
+
background: #dbeafe;
|
|
258
|
+
border-left: 4px solid #2563eb;
|
|
259
|
+
padding: 1rem;
|
|
260
|
+
margin: 1rem 0;
|
|
261
|
+
}
|
|
262
|
+
.links {
|
|
263
|
+
display: flex;
|
|
264
|
+
gap: 1rem;
|
|
265
|
+
margin-top: 2rem;
|
|
266
|
+
}
|
|
267
|
+
.links a {
|
|
268
|
+
color: #2563eb;
|
|
269
|
+
text-decoration: none;
|
|
270
|
+
}
|
|
271
|
+
.links a:hover {
|
|
272
|
+
text-decoration: underline;
|
|
273
|
+
}
|
|
274
|
+
.footer {
|
|
275
|
+
text-align: center;
|
|
276
|
+
margin-top: 50px;
|
|
277
|
+
color: var(--color-grey);
|
|
278
|
+
font-size: 0.9rem;
|
|
279
|
+
}
|
|
280
|
+
</style>
|
|
281
|
+
</head>
|
|
282
|
+
<body>
|
|
283
|
+
<div class="container">
|
|
284
|
+
<h1>🚀 Welcome to ${appName}!</h1>
|
|
285
|
+
|
|
286
|
+
<p>This is your new website powered by Puter. You can start customizing it right away!</p>
|
|
287
|
+
|
|
288
|
+
<div class="tip">
|
|
289
|
+
<strong>Quick Tip:</strong> Replace this content with your own by editing the <code>index.html</code> file.
|
|
306
290
|
</div>
|
|
307
291
|
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
292
|
+
<h2>🌟 Getting Started</h2>
|
|
293
|
+
|
|
294
|
+
<p>Here's a simple example using Puter.js:</p>
|
|
295
|
+
|
|
296
|
+
<div class="code-block">
|
|
297
|
+
<script src="https://js.puter.com/v2/"></script>
|
|
298
|
+
<script>
|
|
299
|
+
// Create a new file in the cloud
|
|
300
|
+
puter.fs.write('hello.txt', 'Hello, Puter!')
|
|
301
|
+
.then(file => console.log(\`File created at: \${file.path}\`));
|
|
302
|
+
</script>
|
|
303
|
+
</div>
|
|
311
304
|
|
|
312
|
-
<
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
305
|
+
<h2>💡 Key Features</h2>
|
|
306
|
+
<ul>
|
|
307
|
+
<li>Cloud Storage</li>
|
|
308
|
+
<li>AI Services (GPT-4, DALL-E)</li>
|
|
309
|
+
<li>Static Website Hosting</li>
|
|
310
|
+
<li>Key-Value Store</li>
|
|
311
|
+
<li>Authentication</li>
|
|
312
|
+
</ul>
|
|
313
|
+
|
|
314
|
+
<div class="links">
|
|
315
|
+
<a href="https://docs.puter.com" target="_blank">📚 Documentation</a>
|
|
316
|
+
<a href="https://discord.gg/puter" target="_blank">💬 Discord Community</a>
|
|
317
|
+
<a href="https://github.com/HeyPuter" target="_blank">👩💻 GitHub</a>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<footer class="footer">
|
|
322
|
+
© 2025 ${appName}. All rights reserved.
|
|
323
|
+
</footer>
|
|
324
|
+
|
|
325
|
+
<div id="${(jsFiles.length && jsFiles.some(f => f.includes('react'))) ? 'root' : 'app'}"></div>
|
|
326
|
+
${jsFiles.map(js =>
|
|
327
|
+
`<script ${js.endsWith('app.js') ? 'type="text/babel"' : ''} src="${js}"></script>`
|
|
328
|
+
).join('\n ')}
|
|
329
|
+
</body>
|
|
330
|
+
</html>`;
|
|
318
331
|
|
|
319
332
|
return defaultIndexContent;
|
|
320
333
|
}
|
package/src/executor.js
CHANGED
|
@@ -7,6 +7,7 @@ import { listFiles, makeDirectory, renameFileOrDirectory,
|
|
|
7
7
|
getInfo, getDiskUsage, createFile, readFile, uploadFile,
|
|
8
8
|
downloadFile, copyFile, syncDirectory, editFile } from './commands/files.js';
|
|
9
9
|
import { getUserInfo, getUsageInfo, login } from './commands/auth.js';
|
|
10
|
+
import { deploy } from './commands/deploy.js';
|
|
10
11
|
import { PROJECT_NAME, API_BASE, getHeaders } from './commons.js';
|
|
11
12
|
import inquirer from 'inquirer';
|
|
12
13
|
import { exec } from 'node:child_process';
|
|
@@ -100,8 +101,9 @@ const commands = {
|
|
|
100
101
|
'populate--': true
|
|
101
102
|
}
|
|
102
103
|
});
|
|
103
|
-
if (args.length < 1) {
|
|
104
|
-
console.log(chalk.red('
|
|
104
|
+
if (args._.length < 1) {
|
|
105
|
+
console.log(chalk.red('You must specify the app name:'));
|
|
106
|
+
console.log(chalk.yellow('Example: app:delete <name>'));
|
|
105
107
|
return;
|
|
106
108
|
}
|
|
107
109
|
const name = args._[0];
|
|
@@ -146,6 +148,7 @@ const commands = {
|
|
|
146
148
|
site: infoSite,
|
|
147
149
|
'site:delete': deleteSite,
|
|
148
150
|
'site:create': createSite,
|
|
151
|
+
'site:deploy': deploy,
|
|
149
152
|
};
|
|
150
153
|
|
|
151
154
|
/**
|
|
@@ -351,6 +354,11 @@ function showHelp(command) {
|
|
|
351
354
|
Create a static website from directory.
|
|
352
355
|
Example: site:create mywebsite /path/to/dir --subdomain=mywebsite
|
|
353
356
|
`,
|
|
357
|
+
'site:deploy': `
|
|
358
|
+
${chalk.cyan('site:deploy [<valid_name_app>] [<remote_dir>] [--subdomain=<subdomain>]')}
|
|
359
|
+
Deploy a local web project to Puter.
|
|
360
|
+
Example: site:deploy my-app ./my-app --subdomain my-app
|
|
361
|
+
`,
|
|
354
362
|
'!': `
|
|
355
363
|
${chalk.cyan('!<command>')}
|
|
356
364
|
Execute a command on the host machine.
|
|
@@ -116,7 +116,7 @@ class ProfileModule {
|
|
|
116
116
|
return this.addProfileWizard();
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
console.log('doing this branch');
|
|
119
|
+
// console.log('doing this branch');
|
|
120
120
|
const answer = await inquirer.prompt([
|
|
121
121
|
{
|
|
122
122
|
name: 'profile',
|
|
@@ -183,7 +183,7 @@ class ProfileModule {
|
|
|
183
183
|
|
|
184
184
|
|
|
185
185
|
const contentType = response.headers.get('content-type');
|
|
186
|
-
console.log('content type?', '|' + contentType + '|');
|
|
186
|
+
//console.log('content type?', '|' + contentType + '|');
|
|
187
187
|
|
|
188
188
|
// TODO: proper content type parsing
|
|
189
189
|
if ( ! contentType.trim().startsWith('application/json') ) {
|
|
@@ -241,7 +241,7 @@ class ProfileModule {
|
|
|
241
241
|
if (args.save) {
|
|
242
242
|
const localEnvFile = '.env';
|
|
243
243
|
try {
|
|
244
|
-
// Check if the file exists, if so then
|
|
244
|
+
// Check if the file exists, if so then append the api key to the EOF.
|
|
245
245
|
if (fs.existsSync(localEnvFile)) {
|
|
246
246
|
console.log(chalk.yellow(`File "${localEnvFile}" already exists... Adding token.`));
|
|
247
247
|
fs.appendFileSync(localEnvFile, `\nPUTER_API_KEY="${data.token}"`, 'utf8');
|