puter-cli 1.8.4 → 1.8.6
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 +17 -0
- package/README.md +13 -11
- package/bin/index.js +17 -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 +8 -2
- package/src/modules/ProfileModule.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,25 @@ 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.6](https://github.com/HeyPuter/puter-cli/compare/v1.8.5...v1.8.6)
|
|
8
|
+
|
|
9
|
+
- Use random name if not specified for `site:deploy` [`#24`](https://github.com/HeyPuter/puter-cli/pull/24)
|
|
10
|
+
- Use random name if not specified [`48ae3f0`](https://github.com/HeyPuter/puter-cli/commit/48ae3f04325f27b654e00050c7ec514bee72535e)
|
|
11
|
+
- docs(readme): update site command [`aa4ba6e`](https://github.com/HeyPuter/puter-cli/commit/aa4ba6e1ee52e1c6e2c0cb5fb74c120fb1b5c0c3)
|
|
12
|
+
- refactor(site): remove name argument from site:deploy [`1ffaacb`](https://github.com/HeyPuter/puter-cli/commit/1ffaacbd3e00dff4f75af66bd1d4b9aee1daf9c6)
|
|
13
|
+
|
|
14
|
+
#### [v1.8.5](https://github.com/HeyPuter/puter-cli/compare/v1.8.4...v1.8.5)
|
|
15
|
+
|
|
16
|
+
> 5 October 2025
|
|
17
|
+
|
|
18
|
+
- feat(site): add site:deploy command for one-step project deployment [`94210ed`](https://github.com/HeyPuter/puter-cli/commit/94210ed796e7564647f4cd177a1b0bef72e66ef2)
|
|
19
|
+
- Fix and add some details to the README file [`8acb6ea`](https://github.com/HeyPuter/puter-cli/commit/8acb6ea753b4f83c146f26473f61756e613a4953)
|
|
20
|
+
- fix(files): touch command to creation a new file [`d30a067`](https://github.com/HeyPuter/puter-cli/commit/d30a0673e9aa5fb5b5d6b037a6a134a196a27529)
|
|
21
|
+
|
|
7
22
|
#### [v1.8.4](https://github.com/HeyPuter/puter-cli/compare/v1.8.3...v1.8.4)
|
|
8
23
|
|
|
24
|
+
> 20 September 2025
|
|
25
|
+
|
|
9
26
|
- fix(app): improve `app:delete` argument validation and usage [`eda41cd`](https://github.com/HeyPuter/puter-cli/commit/eda41cd7c623b8a3f0c60f586de756d4369face3)
|
|
10
27
|
|
|
11
28
|
#### [v1.8.3](https://github.com/HeyPuter/puter-cli/compare/v1.8.2...v1.8.3)
|
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
|
|
@@ -202,12 +197,18 @@ P.S. This command will look for the allocated `subdomain` and attempt to delete
|
|
|
202
197
|
|
|
203
198
|
The static sites are served from the selected directory (or the current directory if none is specified).
|
|
204
199
|
|
|
205
|
-
- **
|
|
200
|
+
- **Create Site**: Create a static website from a directory.
|
|
206
201
|
```bash
|
|
207
|
-
|
|
202
|
+
puter> site:create <app_name> [<dir>] [--subdomain=<name>]
|
|
208
203
|
```
|
|
209
204
|
P.S. If the subdomain already exists, it will generate a new random one. You can set your own subdomain using `--subdomain` argument.
|
|
210
205
|
|
|
206
|
+
- **Deploy Site**: Deploy a static website from the current local directory to a remote directory. If no remote directory is specified, it deploys to the current directory on the remote instance.
|
|
207
|
+
```bash
|
|
208
|
+
puter> site:deploy [<remote_dir>] [--subdomain=<name>]
|
|
209
|
+
```
|
|
210
|
+
P.S. If the subdomain is not provided, it will be generated from the app name. The `--subdomain` argument allows you to specify a custom subdomain. All files in the remote directory will overwritten.
|
|
211
|
+
|
|
211
212
|
- **List Sites**: List all hosted sites.
|
|
212
213
|
```bash
|
|
213
214
|
puter> sites
|
|
@@ -314,10 +315,10 @@ cp .env.example .env
|
|
|
314
315
|
|
|
315
316
|
## Known issues:
|
|
316
317
|
|
|
317
|
-
Most features are working fine. If you have any issues with this project
|
|
318
|
+
Most features are working fine. If you have any issues with this project, please let us know:
|
|
318
319
|
|
|
319
320
|
## 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:
|
|
321
|
+
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
322
|
```bash
|
|
322
323
|
puter@username/myapp> rm README.md
|
|
323
324
|
The following items will be moved to Trash:
|
|
@@ -334,13 +335,14 @@ Otherwise, the Interactive Shell mode will be terminated.
|
|
|
334
335
|
|
|
335
336
|
## Notes
|
|
336
337
|
|
|
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.
|
|
338
|
+
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
339
|
|
|
339
340
|
---
|
|
340
341
|
|
|
341
342
|
## Configuration
|
|
342
343
|
|
|
343
344
|
The CLI uses a configuration file to store user credentials and settings. You can use the `puter logout` to clear the configuration settings.
|
|
345
|
+
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
346
|
|
|
345
347
|
---
|
|
346
348
|
|
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,22 @@ 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('[remoteDir]', 'Remote directory path')
|
|
71
|
+
.option('--subdomain <subdomain>', 'Subdomain for the site')
|
|
72
|
+
.action(async (name, remoteDir, options) => {
|
|
73
|
+
try {
|
|
74
|
+
await deploy([name, remoteDir, `--subdomain=${options.subdomain}`].filter(Boolean));
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(chalk.red(error.message));
|
|
77
|
+
}
|
|
78
|
+
process.exit(0);
|
|
79
|
+
});
|
|
80
|
+
*/
|
|
81
|
+
|
|
65
82
|
if (process.argv.length === 2) {
|
|
66
83
|
startShell();
|
|
67
84
|
} 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., [<remote_dir>] [--subdomain=<subdomain>]).
|
|
11
|
+
*/
|
|
12
|
+
export async function deploy(args = []) {
|
|
13
|
+
if (args.length > 2 || args.some(arg => arg.startsWith('--') && !arg.startsWith('--subdomain='))) {
|
|
14
|
+
console.log(chalk.red('Usage: site:deploy [<remote_dir>] [--subdomain=<subdomain>]'));
|
|
15
|
+
console.log(chalk.yellow('Example: site:deploy'));
|
|
16
|
+
console.log(chalk.yellow('Example: site:deploy'));
|
|
17
|
+
console.log(chalk.yellow('Example: site:deploy ./my-app'));
|
|
18
|
+
console.log(chalk.yellow('Example: site:deploy ./my-app --subdomain=my-app-new'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const appName = generateAppName();
|
|
22
|
+
const remoteDirArg = args.find(arg => !arg.startsWith('--'));
|
|
23
|
+
const remoteDir = remoteDirArg || '.';
|
|
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';
|
|
@@ -147,6 +148,7 @@ const commands = {
|
|
|
147
148
|
site: infoSite,
|
|
148
149
|
'site:delete': deleteSite,
|
|
149
150
|
'site:create': createSite,
|
|
151
|
+
'site:deploy': deploy,
|
|
150
152
|
};
|
|
151
153
|
|
|
152
154
|
/**
|
|
@@ -154,8 +156,7 @@ const commands = {
|
|
|
154
156
|
* @param {string} input The command line input
|
|
155
157
|
*/
|
|
156
158
|
export async function execCommand(context, input) {
|
|
157
|
-
const [cmd, ...args] = input.split(' ');
|
|
158
|
-
|
|
159
|
+
const [cmd, ...args] = input?input.split(' '):[];
|
|
159
160
|
|
|
160
161
|
// Add the command to history (skip the "history" command itself)
|
|
161
162
|
if (cmd !== 'history') {
|
|
@@ -352,6 +353,11 @@ function showHelp(command) {
|
|
|
352
353
|
Create a static website from directory.
|
|
353
354
|
Example: site:create mywebsite /path/to/dir --subdomain=mywebsite
|
|
354
355
|
`,
|
|
356
|
+
'site:deploy': `
|
|
357
|
+
${chalk.cyan('site:deploy [<remote_dir>] [--subdomain=<subdomain>]')}
|
|
358
|
+
Deploy a local web project to Puter.
|
|
359
|
+
Example: site:deploy ./my-app --subdomain my-app
|
|
360
|
+
`,
|
|
355
361
|
'!': `
|
|
356
362
|
${chalk.cyan('!<command>')}
|
|
357
363
|
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');
|