puter-cli 1.1.3 → 1.1.4
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/README.md +2 -2
- package/bin/index.js +0 -1
- package/commands/apps.js +6 -5
- package/commands/crypto.js +176 -0
- package/commands/executor.js +3 -3
- package/commands/files.js +1 -0
- package/commands/sites.js +4 -4
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ P.S. Please check the help command `help apps` for more details about any argume
|
|
|
148
148
|
|
|
149
149
|
- **Create Application**: Create a new application.
|
|
150
150
|
```bash
|
|
151
|
-
puter app:create <name> [<remote_dir>] [--
|
|
151
|
+
puter app:create <name> [<remote_dir>] [--url=<url>]
|
|
152
152
|
```
|
|
153
153
|
P.S. By default a new `index.html` with basic content will be created, but you can set a directory when you create a new application as follows: `app:create nameOfApp ./appDir`, so all files will be copied to the `AppData` directoy, you can then update your app using `app:update <name> <remote_dir>`. This command will attempt to create a subdomain with a random `uid` prefixed with the name of the app.
|
|
154
154
|
|
|
@@ -170,7 +170,7 @@ The static sites are served from the selected directory (or the current director
|
|
|
170
170
|
|
|
171
171
|
- **Deploy Site**: Deploy a static website from a directory.
|
|
172
172
|
```bash
|
|
173
|
-
puter site:create <dir> [--subdomain=<name>]
|
|
173
|
+
puter site:create <app_name> [<dir>] [--subdomain=<name>]
|
|
174
174
|
```
|
|
175
175
|
P.S. If the subdomain already exists, it will generate a new random one can set your own subdomain using `--subdomain` argument.
|
|
176
176
|
|
package/bin/index.js
CHANGED
package/commands/apps.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createSubdomain, getSubdomains } from './subdomains.js';
|
|
|
8
8
|
import { deleteSite } from './sites.js';
|
|
9
9
|
import { copyFile, createFile, listRemoteFiles, pathExists, removeFileOrDirectory } from './files.js';
|
|
10
10
|
import { getCurrentDirectory } from './auth.js';
|
|
11
|
+
import crypto from './crypto.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* List all apps
|
|
@@ -131,19 +132,19 @@ export async function appInfo(args = []) {
|
|
|
131
132
|
*/
|
|
132
133
|
export async function createApp(args = []) {
|
|
133
134
|
if (args.length < 1 || !isValidAppName(args[0])) {
|
|
134
|
-
console.log(chalk.red('Usage: app:create <valid_name_app> [<remote_dir>] [--
|
|
135
|
+
console.log(chalk.red('Usage: app:create <valid_name_app> [<remote_dir>] [--url=<url>]'));
|
|
135
136
|
console.log(chalk.yellow('Example: app:create myapp'));
|
|
136
137
|
console.log(chalk.yellow('Example: app:create myapp ./myapp'));
|
|
137
|
-
console.log(chalk.yellow('Example: app:create myapp --description=myapp'));
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
const name = args[0]; // App name (required)
|
|
141
141
|
// Use the default home page if the root directory if none specified
|
|
142
142
|
const localDir = (args[1] && !args[1].startsWith('--'))? resolvePath(getCurrentDirectory(), args[1]):'';
|
|
143
|
-
|
|
143
|
+
// Optional description (disabled at the moment)
|
|
144
|
+
const description = ''; // (args.find(arg => arg.toLocaleLowerCase().startsWith('--description='))?.split('=')[1]) || '';
|
|
144
145
|
const url = (args.find(arg => arg.toLocaleLowerCase().startsWith('--url='))?.split('=')[1]) || 'https://dev-center.puter.com/coming-soon.html'; // Optional url
|
|
145
146
|
|
|
146
|
-
console.log(chalk.
|
|
147
|
+
console.log(chalk.dim(`Creating app: ${chalk.green(name)}...\n`));
|
|
147
148
|
try {
|
|
148
149
|
// Step 1: Create the app
|
|
149
150
|
const createAppResponse = await fetch(`${API_BASE}/drivers/call`, {
|
|
@@ -379,7 +380,7 @@ export async function deleteApp(name) {
|
|
|
379
380
|
const readData = await readResponse.json();
|
|
380
381
|
|
|
381
382
|
if (!readData.success || !readData.result) {
|
|
382
|
-
console.log(chalk.
|
|
383
|
+
console.log(chalk.red(`App "${chalk.bold(name)}" not found.`));
|
|
383
384
|
return false;
|
|
384
385
|
}
|
|
385
386
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
import { TextEncoder } from 'util';
|
|
3
|
+
|
|
4
|
+
class Hash {
|
|
5
|
+
constructor(algorithm) {
|
|
6
|
+
this.algorithm = algorithm;
|
|
7
|
+
this.data = [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
update(data) {
|
|
11
|
+
if (typeof data === 'string') {
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
data = encoder.encode(data);
|
|
14
|
+
}
|
|
15
|
+
this.data.push(Buffer.from(data));
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async digest(encoding = 'hex') {
|
|
20
|
+
const concatenatedData = Buffer.concat(this.data);
|
|
21
|
+
const hashBuffer = await crypto.subtle.digest(
|
|
22
|
+
this.algorithm.toUpperCase(),
|
|
23
|
+
concatenatedData
|
|
24
|
+
);
|
|
25
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
26
|
+
|
|
27
|
+
if (encoding === 'buffer') {
|
|
28
|
+
return Buffer.from(hashArray);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const hashHex = hashArray
|
|
32
|
+
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
33
|
+
.join('');
|
|
34
|
+
|
|
35
|
+
if (encoding === 'hex') return hashHex;
|
|
36
|
+
if (encoding === 'base64') return Buffer.from(hashHex, 'hex').toString('base64');
|
|
37
|
+
|
|
38
|
+
throw new Error(`Unsupported encoding: ${encoding}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class Hmac {
|
|
43
|
+
constructor(algorithm, key) {
|
|
44
|
+
this.algorithm = algorithm;
|
|
45
|
+
this.key = typeof key === 'string' ? Buffer.from(key) : key;
|
|
46
|
+
this.data = [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
update(data) {
|
|
50
|
+
if (typeof data === 'string') {
|
|
51
|
+
const encoder = new TextEncoder();
|
|
52
|
+
data = encoder.encode(data);
|
|
53
|
+
}
|
|
54
|
+
this.data.push(Buffer.from(data));
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async digest(encoding = 'hex') {
|
|
59
|
+
const concatenatedData = Buffer.concat(this.data);
|
|
60
|
+
const key = await crypto.subtle.importKey(
|
|
61
|
+
'raw',
|
|
62
|
+
this.key,
|
|
63
|
+
{ name: 'HMAC', hash: { name: this.algorithm.toUpperCase() } },
|
|
64
|
+
false,
|
|
65
|
+
['sign']
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const signature = await crypto.subtle.sign(
|
|
69
|
+
'HMAC',
|
|
70
|
+
key,
|
|
71
|
+
concatenatedData
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const hashArray = Array.from(new Uint8Array(signature));
|
|
75
|
+
|
|
76
|
+
if (encoding === 'buffer') {
|
|
77
|
+
return Buffer.from(hashArray);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const hashHex = hashArray
|
|
81
|
+
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
82
|
+
.join('');
|
|
83
|
+
|
|
84
|
+
if (encoding === 'hex') return hashHex;
|
|
85
|
+
if (encoding === 'base64') return Buffer.from(hashHex, 'hex').toString('base64');
|
|
86
|
+
|
|
87
|
+
throw new Error(`Unsupported encoding: ${encoding}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const randomBytes = (size) => {
|
|
92
|
+
const array = new Uint8Array(size);
|
|
93
|
+
crypto.getRandomValues(array);
|
|
94
|
+
return Buffer.from(array);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const createHash = (algorithm) => {
|
|
98
|
+
return new Hash(algorithm);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const createHmac = (algorithm, key) => {
|
|
102
|
+
return new Hmac(algorithm, key);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const randomUUID = () => {
|
|
106
|
+
return uuidv4();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const scrypt = async (password, salt, keylen, options = {}) => {
|
|
110
|
+
const encoder = new TextEncoder();
|
|
111
|
+
const passwordBuffer = encoder.encode(password);
|
|
112
|
+
const saltBuffer = encoder.encode(salt);
|
|
113
|
+
|
|
114
|
+
const N = options.N || 16384;
|
|
115
|
+
const r = options.r || 8;
|
|
116
|
+
const p = options.p || 1;
|
|
117
|
+
|
|
118
|
+
const key = await crypto.subtle.importKey(
|
|
119
|
+
'raw',
|
|
120
|
+
passwordBuffer,
|
|
121
|
+
'PBKDF2',
|
|
122
|
+
false,
|
|
123
|
+
['deriveBits']
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const derivedKey = await crypto.subtle.deriveBits(
|
|
127
|
+
{
|
|
128
|
+
name: 'PBKDF2',
|
|
129
|
+
salt: saltBuffer,
|
|
130
|
+
iterations: N * r * p,
|
|
131
|
+
hash: 'SHA-256'
|
|
132
|
+
},
|
|
133
|
+
key,
|
|
134
|
+
keylen * 8
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return Buffer.from(derivedKey);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const pbkdf2 = async (password, salt, iterations, keylen, digest) => {
|
|
141
|
+
const encoder = new TextEncoder();
|
|
142
|
+
const passwordBuffer = encoder.encode(password);
|
|
143
|
+
const saltBuffer = encoder.encode(salt);
|
|
144
|
+
|
|
145
|
+
const key = await crypto.subtle.importKey(
|
|
146
|
+
'raw',
|
|
147
|
+
passwordBuffer,
|
|
148
|
+
'PBKDF2',
|
|
149
|
+
false,
|
|
150
|
+
['deriveBits']
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const derivedKey = await crypto.subtle.deriveBits(
|
|
154
|
+
{
|
|
155
|
+
name: 'PBKDF2',
|
|
156
|
+
salt: saltBuffer,
|
|
157
|
+
iterations,
|
|
158
|
+
hash: digest.toUpperCase()
|
|
159
|
+
},
|
|
160
|
+
key,
|
|
161
|
+
keylen * 8
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return Buffer.from(derivedKey);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export default {
|
|
168
|
+
createHash,
|
|
169
|
+
createHmac,
|
|
170
|
+
randomBytes,
|
|
171
|
+
randomUUID,
|
|
172
|
+
scrypt,
|
|
173
|
+
pbkdf2,
|
|
174
|
+
Hash,
|
|
175
|
+
Hmac
|
|
176
|
+
};
|
package/commands/executor.js
CHANGED
|
@@ -187,7 +187,7 @@ function showHelp(command) {
|
|
|
187
187
|
Example: app myapp
|
|
188
188
|
`,
|
|
189
189
|
'app:create': `
|
|
190
|
-
${chalk.cyan('app:create <name> [url]')}
|
|
190
|
+
${chalk.cyan('app:create <name> [<remote_dir>] [--url=<url>]')}
|
|
191
191
|
Create a new app.
|
|
192
192
|
Example: app:create myapp https://example.com
|
|
193
193
|
`,
|
|
@@ -279,9 +279,9 @@ function showHelp(command) {
|
|
|
279
279
|
Example: site:delete sd-123456
|
|
280
280
|
`,
|
|
281
281
|
'site:create': `
|
|
282
|
-
${chalk.cyan('site:create <dir> [--subdomain=<name>]')}
|
|
282
|
+
${chalk.cyan('site:create <app_name> [<dir>] [--subdomain=<name>]')}
|
|
283
283
|
Create a static website from directory.
|
|
284
|
-
Example: site:create /path/to/dir --subdomain=
|
|
284
|
+
Example: site:create mywebsite /path/to/dir --subdomain=mywebsite
|
|
285
285
|
`,
|
|
286
286
|
'!': `
|
|
287
287
|
${chalk.cyan('!<command>')}
|
package/commands/files.js
CHANGED
|
@@ -11,6 +11,7 @@ import { formatDate, formatDateTime, formatSize } from './utils.js';
|
|
|
11
11
|
import inquirer from 'inquirer';
|
|
12
12
|
import { getAuthToken, getCurrentDirectory, getCurrentUserName } from './auth.js';
|
|
13
13
|
import { updatePrompt } from './shell.js';
|
|
14
|
+
import crypto from './crypto.js';
|
|
14
15
|
|
|
15
16
|
const config = new Conf({ projectName: PROJECT_NAME });
|
|
16
17
|
|
package/commands/sites.js
CHANGED
|
@@ -153,7 +153,7 @@ export async function infoSite(args = []) {
|
|
|
153
153
|
// Use the current directory as the root directory if none specified
|
|
154
154
|
const remoteDir = resolvePath(getCurrentDirectory(), (args[1] && !args[1].startsWith('--'))?args[1]:'.');
|
|
155
155
|
|
|
156
|
-
console.log(chalk.
|
|
156
|
+
console.log(chalk.dim(`Creating site ${chalk.green(appName)} from: ${chalk.green(remoteDir)}...\n`));
|
|
157
157
|
try {
|
|
158
158
|
// Step 1: Determine the subdomain
|
|
159
159
|
let subdomain;
|
|
@@ -182,7 +182,7 @@ export async function infoSite(args = []) {
|
|
|
182
182
|
} else {
|
|
183
183
|
console.log(chalk.yellow(`However, It's linked to different directory at: ${subdomainObj.root_dir?.path}`));
|
|
184
184
|
console.log(chalk.cyan(`We'll try to unlink this subdomain from that directory...`));
|
|
185
|
-
const result = await deleteSubdomain(subdomainObj
|
|
185
|
+
const result = await deleteSubdomain(subdomainObj?.uid);
|
|
186
186
|
if (result) {
|
|
187
187
|
console.log(chalk.green('Looks like this subdomain is free again, please try again.'));
|
|
188
188
|
return;
|
|
@@ -205,8 +205,8 @@ export async function infoSite(args = []) {
|
|
|
205
205
|
return;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
console.log(chalk.green(`App
|
|
209
|
-
console.log(chalk.
|
|
208
|
+
console.log(chalk.green(`App ${chalk.dim(appName)} created successfully and accessible at:`));
|
|
209
|
+
console.log(chalk.cyan(`https://${site.subdomain}.puter.site`));
|
|
210
210
|
} catch (error) {
|
|
211
211
|
console.error(chalk.red('Failed to create site.'));
|
|
212
212
|
console.error(chalk.red(`Error: ${error.message}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "puter-cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Command line interface for Puter cloud platform",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"inquirer": "^9.2.12",
|
|
34
34
|
"minimatch": "^10.0.1",
|
|
35
35
|
"node-fetch": "^3.3.2",
|
|
36
|
-
"ora": "^8.0.1"
|
|
36
|
+
"ora": "^8.0.1",
|
|
37
|
+
"uuid": "^11.0.5"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"vitest": "^2.1.8"
|