@heyputer/shell 2.1.0 → 3.0.0

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.
@@ -1,356 +0,0 @@
1
- import path from 'path';
2
- import chalk from 'chalk';
3
- import fetch from 'node-fetch';
4
- import Table from 'cli-table3';
5
- import { displayNonNullValues, formatDate } from '../utils.js';
6
- import { API_BASE, getHeaders, getDefaultHomePage, isValidAppName, resolvePath } from '../commons.js';
7
- import { createSubdomain, getSubdomains } from './subdomains.js';
8
- import { deleteSite } from './sites.js';
9
- import { copyFile, createFile, listRemoteFiles, pathExists, removeFileOrDirectory } from './files.js';
10
- import { getCurrentDirectory } from './auth.js';
11
- import crypto from '../crypto.js';
12
- import { getPuter } from '../modules/PuterModule.js';
13
-
14
- /**
15
- * List all apps
16
- *
17
- * @param {object} options
18
- * ```json
19
- * {
20
- * statsPeriod: [all (default), today, yesterday, 7d, 30d, this_month, last_month, this_year, last_year, month_to_date, year_to_date, last_12_months],
21
- * iconSize: [16, 32, 64, 128, 256, 512]
22
- * }
23
- * ```
24
- */
25
- export async function listApps({ statsPeriod = 'all', iconSize = 64 } = {}) {
26
- console.log(chalk.green(`Listing of apps during period "${chalk.cyan(statsPeriod)}" (try also: today, yesterday, 7d, 30d, this_month, last_month):\n`));
27
- const puter = getPuter();
28
- try {
29
- const result = await puter.apps.list({
30
- icon_size: iconSize,
31
- stats_period: statsPeriod
32
- });
33
- if (result) {
34
- // Create a new table instance
35
- const table = new Table({
36
- head: [
37
- chalk.cyan('#'),
38
- chalk.cyan('Title'),
39
- chalk.cyan('Name'),
40
- chalk.cyan('Created'),
41
- chalk.cyan('Subdomain'),
42
- // chalk.cyan('Description'),
43
- chalk.cyan('#Open'),
44
- chalk.cyan('#User')
45
- ],
46
- colWidths: [5, 20, 30, 25, 35, 8, 8],
47
- wordWrap: false
48
- });
49
-
50
- // Populate the table with app data
51
- let i = 0;
52
- for (const app of result) {
53
- table.push([
54
- i++,
55
- app['title'],
56
- app['name'],
57
- formatDate(app['created_at']),
58
- app['index_url']?app['index_url'].split('.')[0].split('//')[1]:'<NO_URL>',
59
- // app['description'].slice(0, 10) || 'N/A',
60
- app['stats']['open_count'],
61
- app['stats']['user_count']
62
- ]);
63
- }
64
-
65
- // Display the table
66
- console.log(table.toString());
67
- console.log(chalk.green(`You have in total: ${chalk.cyan(result.length)} application(s).`));
68
- } else {
69
- console.error(chalk.red('Unable to list your apps. Please check your credentials.'));
70
- }
71
- } catch (error) {
72
- console.error(chalk.red(`Failed to list apps. Error: ${error.message}`));
73
- }
74
- }
75
-
76
- /**
77
- * Get app informations
78
- *
79
- * @param {Array} List of options (only "name" is supported at the moment)
80
- * @example:
81
- * ```json
82
- * const data = await appInfo("app name");
83
- * ```
84
- */
85
- export async function appInfo(args = []) {
86
- if (!args || args.length == 0){
87
- console.log(chalk.red('Usage: app <name>'));
88
- return;
89
- }
90
- const appName = args[0].trim()
91
- console.log(chalk.green(`Looking for "${chalk.dim(appName)}" app informations:\n`));
92
- const puter = getPuter();
93
- try {
94
- const result = await puter.apps.get(appName);
95
- if (result) {
96
- // Display the informations
97
- displayNonNullValues(result);
98
- } else {
99
- console.error(chalk.red('Could not find this app.'));
100
- }
101
- } catch (error) {
102
- console.error(chalk.red(`Failed to get app info. Error: ${error.message}`));
103
- }
104
- }
105
-
106
- /**
107
- * Create a new web application
108
- * @param {string} name The name of the App
109
- * @param {string} directory Optional directory path
110
- * @param {string} description A description of the App
111
- * @param {string} url A default coming-soon URL
112
- * @returns {Promise<Object>} Output JSON data
113
- */
114
- export async function createApp(args) {
115
- const name = args.name; // App name (required)
116
- if (!name || !isValidAppName(name)) {
117
- console.log(chalk.red('Usage: app:create <name> <directory>'));
118
- console.log(chalk.yellow('Example: app:create myApp .'));
119
- console.log(chalk.yellow('Example: app:create myApp ./myApp'));
120
- return;
121
- }
122
- // Use the default home page if the root directory if none specified
123
- const localDir = args.directory ? resolvePath(getCurrentDirectory(), args.directory) : '';
124
- // Optional description
125
- const description = args.description || '';
126
- const url = args.url || '';
127
-
128
- console.log(chalk.green(`Creating app "${name}"...`));
129
- console.log(chalk.dim(`Directory: ${localDir || '[default]'}`));
130
- console.log(chalk.dim(`Description: ${description}`));
131
- console.log(chalk.dim(`URL: ${url}`));
132
-
133
- const puter = getPuter();
134
- try {
135
- // Step 1: Create the app
136
- const createAppData = await puter.apps.create({
137
- name: name,
138
- indexURL: url,
139
- title: name,
140
- description: description,
141
- maximizeOnStart: false,
142
- dedupeName: true
143
- });
144
- if (!createAppData) {
145
- console.error(chalk.red(`Failed to create app "${name}"`));
146
- return;
147
- }
148
- const appUid = createAppData.uid;
149
- const appName = createAppData.name;
150
- const username = createAppData.owner.username;
151
- console.log(chalk.green(`App "${chalk.dim(name)}" created successfully!`));
152
- console.log(chalk.cyan(`AppName: ${chalk.dim(appName)}\nUID: ${chalk.dim(appUid)}\nUsername: ${chalk.dim(username)}`));
153
-
154
- // Step 2: Create a directory for the app
155
- const uid = crypto.randomUUID();
156
- const appDir = `/${username}/AppData/${appUid}`;
157
- console.log(chalk.green(`Creating directory...\nPath: ${chalk.dim(appDir)}\nApp: ${chalk.dim(name)}\nUID: ${chalk.dim(uid)}\n`));
158
- const createDirData = await puter.fs.mkdir(`${appDir}/app-${uid}`, {
159
- overwrite: true,
160
- dedupeName: false,
161
- createMissingParents: true,
162
- })
163
- if (!createDirData || !createDirData.uid) {
164
- console.error(chalk.red(`Failed to create directory for app "${name}"`));
165
- return;
166
- }
167
- const dirUid = createDirData.uid;
168
- console.log(chalk.green(`Directory created successfully!`));
169
- console.log(chalk.cyan(`Directory UID: ${chalk.dim(dirUid)}`));
170
-
171
- // Step 3: Create a subdomain for the app
172
- const subdomainName = `${name}-${uid.split('-')[0]}`;
173
- const remoteDir = `${appDir}/${createDirData.name}`;
174
- console.log(chalk.green(`Linking to subdomain...\nSubdomain: "${chalk.dim(subdomainName)}"\nPath: ${chalk.dim(remoteDir)}\n`));
175
- const subdomainResult = await createSubdomain(subdomainName, remoteDir);
176
- if (!subdomainResult) {
177
- console.error(chalk.red(`Failed to create subdomain: "${subdomainName}"`));
178
- return;
179
- }
180
- console.log(chalk.green(`Subdomain created successfully!`));
181
- console.log(chalk.cyan(`Subdomain: ${chalk.dim(subdomainName)}`));
182
-
183
- // Step 4: Create a home page
184
- if (localDir.length > 0){
185
- // List files in the current "localDir" then copy them to the "remoteDir"
186
- const files = await listRemoteFiles(localDir);
187
- if (Array.isArray(files) && files.length > 0) {
188
- console.log(chalk.cyan(`Copying ${chalk.dim(files.length)} files from: ${chalk.dim(localDir)}`));
189
- console.log(chalk.cyan(`To destination: ${chalk.dim(remoteDir)}`));
190
- for (const file of files) {
191
- const fileSource = path.join(localDir, file.name);
192
- await copyFile([fileSource, remoteDir]);
193
- }
194
- } else {
195
- console.log(chalk.yellow("We could not find any file in the specified directory!"));
196
- }
197
- } else {
198
- const homePageResult = await createFile([path.join(remoteDir, 'index.html'), getDefaultHomePage(appName)]);
199
- if (!homePageResult){
200
- console.log(chalk.yellow("We could not create the home page file!"));
201
- }
202
- }
203
-
204
- // Step 5: Update the app's index_url to point to the subdomain
205
- console.log(chalk.green(`Set "${chalk.dim(subdomainName)}" as a subdomain for app: "${chalk.dim(appName)}"...\n`));
206
- const updateAppData = await puter.apps.update(appName, {
207
- indexURL: `https://${subdomainName}.puter.site`,
208
- title: name
209
- })
210
- if (!updateAppData) {
211
- console.error(chalk.red(`Failed to update app "${name}" with new subdomain`));
212
- return;
213
- }
214
- console.log(chalk.green(`App deployed successfully at:`));
215
- console.log(chalk.cyanBright(`https://${subdomainName}.puter.site`));
216
- } catch (error) {
217
- console.error(chalk.red(`Failed to create app "${name}".\nError: ${error.message}`));
218
- }
219
- }
220
-
221
- /**
222
- * Update an application from the directory
223
- * @param {string} name The name of the App
224
- * @param {string} remote_dir The remote directory
225
- */
226
- export async function updateApp(args = []) {
227
- if (args.length < 1) {
228
- console.log(chalk.red('Usage: app:update <valid_name_app> [<remote_dir>]'));
229
- console.log(chalk.yellow('Example: app:create myapp'));
230
- console.log(chalk.yellow('Example: app:create myapp ./myapp'));
231
- return;
232
- }
233
- const name = args[0]; // App name (required)
234
- // Fix: Properly handle absolute paths by checking if the path starts with '/'
235
- let remoteDir;
236
- if (args[1] && args[1].startsWith('/')) {
237
- remoteDir = args[1]; // Use the absolute path as-is
238
- } else {
239
- remoteDir = resolvePath(getCurrentDirectory(), args[1] || '.');
240
- }
241
-
242
- const remoteDirExists = await pathExists(remoteDir);
243
-
244
- if (!remoteDirExists){
245
- console.log(chalk.red(`Cannot find directory: ${chalk.dim(remoteDir)}...\n`));
246
- return;
247
- }
248
-
249
- const puter = getPuter();
250
- console.log(chalk.green(`Updating app: "${chalk.dim(name)}" from directory: ${chalk.dim(remoteDir)}\n`));
251
- try {
252
- // Step 1: Get the app info
253
- const data = await puter.apps.get(name);
254
- if (!data) {
255
- console.error(chalk.red(`Failed to find app: "${name}"`));
256
- return;
257
- }
258
- const appUid = data.uid;
259
- const appName = data.name;
260
- const username = data.owner.username;
261
- const indexUrl = data.index_url;
262
- const appDir = `/${username}/AppData/${appUid}`;
263
- console.log(chalk.cyan(`AppName: ${chalk.dim(appName)}\nUID: ${chalk.dim(appUid)}\nUsername: ${chalk.dim(username)}`));
264
-
265
- // Step 2: Find the path from subdomain
266
- const subdomains = await getSubdomains();
267
- const appSubdomain = subdomains.find(sd => sd.root_dir?.dirname?.endsWith(appUid));
268
- if (!appSubdomain){
269
- console.error(chalk.red(`Sorry! We could not find the subdomain for ${chalk.cyan(name)} application.`));
270
- return;
271
- }
272
- const subdomainDir = appSubdomain['root_dir']['path'];
273
- if (!subdomainDir){
274
- console.error(chalk.red(`Sorry! We could not find the path for ${chalk.cyan(name)} application.`));
275
- return;
276
- }
277
-
278
- // Step 3: List files in the current "remoteDir" then copy them to the "subdomainDir"
279
- const files = await listRemoteFiles(remoteDir);
280
- if (Array.isArray(files) && files.length > 0) {
281
- console.log(chalk.cyan(`Copying ${chalk.dim(files.length)} files from: ${chalk.dim(remoteDir)}`));
282
- console.log(chalk.cyan(`To destination: ${chalk.dim(subdomainDir)}`));
283
- for (const file of files) {
284
- const fileSource = path.join(remoteDir, file.name);
285
- const fileDest = path.join(subdomainDir, file.name);
286
- if ((await pathExists(fileDest))){
287
- await removeFileOrDirectory([fileDest, '-f']);
288
- }
289
- await copyFile([fileSource, subdomainDir]);
290
- }
291
- } else {
292
- console.log(chalk.red("We could not find any file in the specified directory!"));
293
- }
294
-
295
- console.log(chalk.green(`App updated successfully at:`));
296
- console.log(chalk.dim(indexUrl));
297
- } catch (error) {
298
- console.error(chalk.red(`Failed to update app "${name}".\nError: ${error.message}`));
299
- console.error(error);
300
- }
301
- }
302
-
303
- /**
304
- * Delete an app by its name
305
- * @param {string} name The name of the app to delete
306
- * @returns a boolean success value
307
- */
308
- export async function deleteApp(name) {
309
- if (!name || name.length == 0){
310
- console.log(chalk.red('Usage: app:delete <name>'));
311
- return false;
312
- }
313
- const puter = getPuter();
314
- console.log(chalk.green(`Checking app "${name}"...\n`));
315
- try {
316
- // Step 1: Read app details
317
- const readData = await puter.apps.get(name);
318
-
319
- if (!readData) {
320
- console.log(chalk.red(`App "${chalk.bold(name)}" not found.`));
321
- return false;
322
- }
323
-
324
- // Show app details and confirm deletion
325
- console.log(chalk.cyan('\nApp Details:'));
326
- console.log(chalk.dim('----------------------------------------'));
327
- console.log(chalk.dim(`Name: ${chalk.cyan(readData.name)}`));
328
- console.log(chalk.dim(`Title: ${chalk.cyan(readData.title)}`));
329
- console.log(chalk.dim(`Created: ${chalk.cyan(formatDate(readData.created_at))}`));
330
- console.log(chalk.dim(`URL: ${readData.index_url}`));
331
- console.log(chalk.dim('----------------------------------------'));
332
-
333
- // Step 2: Delete the app
334
- console.log(chalk.green(`Deleting app "${chalk.red(name)}"...`));
335
-
336
- const deleteData = await puter.apps.delete(name);
337
- if (!deleteData) {
338
- console.error(chalk.red(`Failed to delete app "${name}".\nP.S. Make sure to provide the 'name' attribute not the 'title'.`));
339
- return false;
340
- }
341
-
342
- // Lookup subdomainUID then delete it
343
- const subdomains = await getSubdomains();
344
- const appSubdomain = subdomains.find(sd => sd.root_dir?.dirname?.endsWith(readData.uid));
345
- const subdomainDeleted = await deleteSite([appSubdomain.uid]);
346
- if (subdomainDeleted){
347
- console.log(chalk.green(`Subdomain: ${chalk.dim(appSubdomain.uid)} deleted.`));
348
- }
349
-
350
- console.log(chalk.green(`App "${chalk.dim(name)}" deleted successfully!`));
351
- } catch (error) {
352
- console.error(chalk.red(`Failed to delete app "${name}".\nError: ${error.message}`));
353
- return false;
354
- }
355
- return true;
356
- }
@@ -1,54 +0,0 @@
1
- import chalk from 'chalk';
2
- import { generateAppName } from '../commons.js';
3
- import { syncDirectory } from './files.js';
4
- import { createSite } from './sites.js';
5
- import { getPuter } from '../modules/PuterModule.js';
6
-
7
- /**
8
- * Deploy a local web project to Puter.
9
- * @param {string[]} args - Command-line arguments (e.g., <local_dir> [--subdomain=<subdomain>]).
10
- */
11
- export async function deploy(args = []) {
12
- if (args.length < 1) {
13
- console.log(chalk.red('Usage: site:deploy <local_dir> [--subdomain=<subdomain>]'));
14
- console.log(chalk.yellow('Example: site:deploy .'));
15
- console.log(chalk.yellow('Example: site:deploy ./dist'));
16
- console.log(chalk.yellow('Example: site:deploy ./dist --subdomain=my-app-new'));
17
- return;
18
- }
19
- const puter = getPuter();
20
-
21
- const sourceDirArg = args.find(arg => !arg.startsWith('--'));
22
- const sourceDir = sourceDirArg || '.';
23
-
24
- let subdomain = args.find(arg => arg.startsWith('--subdomain='))?.split('=')[1];
25
- if (!subdomain) {
26
- subdomain = generateAppName();
27
- }
28
-
29
- const remoteDir = `~/sites/${subdomain}/deployment`;
30
-
31
- // this will handle the increments
32
- const directory = await puter.fs.mkdir(remoteDir, {
33
- dedupeName: true,
34
- createMissingParents: true
35
- })
36
-
37
- console.log(chalk.cyan(`Deploying '${sourceDir}' to '${subdomain}.puter.site'...`));
38
-
39
- try {
40
- // 1. Upload files
41
- await syncDirectory([sourceDir, directory.path, '--delete', '-r', '--overwrite']);
42
-
43
- // 2. Create the site
44
- const site = await createSite([subdomain, directory.path, `--subdomain=${subdomain}`]);
45
-
46
- if (site) {
47
- console.log(chalk.green('Deployment successful!'));
48
- } else {
49
- console.log(chalk.yellow('Deployment successfuly updated!'));
50
- }
51
- } catch (error) {
52
- console.error(chalk.red(`Deployment failed: ${error.message}`));
53
- }
54
- }