@heyputer/shell 2.1.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.
@@ -0,0 +1,322 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { promises as fs } from 'fs';
5
+ import path from 'path';
6
+ import { generateAppName, getDefaultHomePage } from '../commons.js';
7
+ import { getProfileModule } from '../modules/ProfileModule.js';
8
+
9
+ const JS_BUNDLERS = ['Vite', 'Webpack', 'Parcel', 'esbuild', 'Farm'];
10
+ const FULLSTACK_FRAMEWORKS = ['Next', 'Nuxt', 'SvelteKit', 'Astro'];
11
+ const JS_LIBRARIES = ['React', 'Vue', 'Angular', 'Svelte', 'jQuery'];
12
+ const CSS_LIBRARIES = ['Bootstrap', 'Bulma', 'shadcn', 'Tailwind', 'Material-UI', 'Semantic UI', 'AntDesign', 'Element-Plus', 'PostCSS', 'AutoPrefixer'];
13
+
14
+ export async function init() {
15
+ const profileModule = getProfileModule();
16
+ await profileModule.checkLogin();
17
+
18
+ const answers = await inquirer.prompt([
19
+ {
20
+ type: 'input',
21
+ name: 'name',
22
+ message: 'What is your app name?',
23
+ default: `${generateAppName()}`
24
+ },
25
+ {
26
+ type: 'list',
27
+ name: 'useBundler',
28
+ message: 'Do you want to use a JavaScript bundler?',
29
+ choices: ['Yes', 'No (Use CDN)']
30
+ }
31
+ ]);
32
+
33
+ let jsFiles = [];
34
+ let jsDevFiles = [];
35
+ let cssFiles = [];
36
+ let jsExtraLibraries = [];
37
+ let extraFiles = [];
38
+ let bundlerAnswers = null;
39
+ let frameworkAnswers = null;
40
+
41
+ if (answers.useBundler === 'Yes') {
42
+ bundlerAnswers = await inquirer.prompt([
43
+ {
44
+ type: 'list',
45
+ name: 'bundler',
46
+ message: 'Select a JavaScript bundler:',
47
+ choices: JS_BUNDLERS
48
+ },
49
+ {
50
+ type: 'list',
51
+ name: 'frameworkType',
52
+ message: 'Do you want to use a full-stack framework or custom libraries?',
53
+ choices: ['Full-stack framework', 'Custom libraries']
54
+ }
55
+ ]);
56
+
57
+ if (bundlerAnswers.frameworkType === 'Full-stack framework') {
58
+ frameworkAnswers = await inquirer.prompt([
59
+ {
60
+ type: 'list',
61
+ name: 'framework',
62
+ message: 'Select a full-stack framework:',
63
+ choices: FULLSTACK_FRAMEWORKS
64
+ }
65
+ ]);
66
+
67
+ switch (frameworkAnswers.framework) {
68
+ case FULLSTACK_FRAMEWORKS[0]:
69
+ jsFiles.push('next@latest');
70
+ extraFiles.push({
71
+ path: 'src/index.tsx',
72
+ content: `export default function Home() { return (<h1>${answers.name}</h1>) }`
73
+ });
74
+ break;
75
+ case FULLSTACK_FRAMEWORKS[1]:
76
+ jsFiles.push('nuxt@latest');
77
+ extraFiles.push({
78
+ path: 'src/app.vue',
79
+ content: `<template><h1>${answers.name}</h1></template>`
80
+ });
81
+ break;
82
+ case FULLSTACK_FRAMEWORKS[2]:
83
+ jsFiles.push('svelte@latest', 'sveltekit@latest');
84
+ extraFiles.push({
85
+ path: 'src/app.vue',
86
+ content: `<template><h1>${answers.name}</h1></template>`
87
+ });
88
+ break;
89
+ case FULLSTACK_FRAMEWORKS[3]:
90
+ jsFiles.push('astro@latest', 'astro@latest');
91
+ extraFiles.push({
92
+ path: 'src/pages/index.astro',
93
+ content: `---\n\n<Layout title="Welcome to ${answers.name}."><h1>${answers.name}</h1></Layout>`
94
+ });
95
+ break;
96
+ }
97
+ } else {
98
+ const libraryAnswers = await inquirer.prompt([
99
+ {
100
+ type: 'list',
101
+ name: 'library',
102
+ message: 'Select a JavaScript library/framework:',
103
+ choices: JS_LIBRARIES
104
+ }
105
+ ]);
106
+
107
+ switch (libraryAnswers.library) {
108
+ case JS_LIBRARIES[0]:
109
+ jsFiles.push('react@latest', 'react-dom@latest');
110
+ const reactLibs = await inquirer.prompt([
111
+ {
112
+ type: 'checkbox',
113
+ name: 'reactLibraries',
114
+ message: 'Select React libraries:',
115
+ choices: CSS_LIBRARIES.concat(['react-router-dom', 'react-redux', 'react-bootstrap', '@chakra-ui/react', 'semantic-ui-react'])
116
+ }
117
+ ]);
118
+ jsFiles.push(...reactLibs.reactLibraries);
119
+ extraFiles.push({
120
+ path: 'src/App.jsx',
121
+ content: `export default function Home() { return (<h1>${answers.name}</h1>) }`
122
+ });
123
+ break;
124
+ case JS_LIBRARIES[1]:
125
+ jsFiles.push('vue@latest');
126
+ jsDevFiles.push('@vitejs/plugin-vue');
127
+ const vueLibs = await inquirer.prompt([
128
+ {
129
+ type: 'checkbox',
130
+ name: 'vueLibraries',
131
+ message: 'Select Vue libraries:',
132
+ choices: CSS_LIBRARIES.concat(['shadcn-vue', 'UnoCSS', 'NaiveUI', 'bootstrap-vue-next', 'buefy', 'vue-router', 'pinia'])
133
+ }
134
+ ]);
135
+ jsFiles.push(...vueLibs.vueLibraries);
136
+ extraFiles.push(
137
+ {
138
+ path: 'src/App.vue',
139
+ content: `<template><h1>${answers.name}</h1></template>`
140
+ },
141
+ {
142
+ path: 'vite.config.js',
143
+ content: `import { defineConfig } from 'vite';
144
+ import vue from '@vitejs/plugin-vue';
145
+
146
+ export default defineConfig({
147
+ plugins: [vue()]
148
+ })
149
+ `},
150
+ {
151
+ path: 'main.js',
152
+ content: `import { createApp } from 'vue'
153
+ import './style.css';
154
+ import App from './App.vue';
155
+
156
+ const app = createApp(App);
157
+ app.mount('#app');
158
+ `},
159
+ );
160
+ break;
161
+ case JS_LIBRARIES[2]:
162
+ jsFiles.push('@angular/core@latest');
163
+ extraFiles.push({
164
+ path: 'src/index.controller.js',
165
+ content: `(function () { angular.module('app', [])})`
166
+ });
167
+ break;
168
+ case JS_LIBRARIES[3]:
169
+ jsFiles.push('svelte@latest');
170
+ break;
171
+ case JS_LIBRARIES[4]:
172
+ jsFiles.push('jquery@latest');
173
+ extraFiles.push({
174
+ path: 'src/main.js',
175
+ content: `$(function(){})`
176
+ });
177
+ break;
178
+ }
179
+ }
180
+ } else {
181
+
182
+ const cdnAnswers = await inquirer.prompt([
183
+ {
184
+ type: 'list',
185
+ name: 'jsFramework',
186
+ message: 'Select a JavaScript framework/library (CDN):',
187
+ choices: JS_LIBRARIES
188
+ },
189
+ {
190
+ type: 'list',
191
+ name: 'cssFramework',
192
+ message: 'Select a CSS framework/library (CDN):',
193
+ choices: CSS_LIBRARIES //'Tailwind', 'Bootstrap', 'Bulma'...
194
+ }
195
+ ]);
196
+
197
+ switch (cdnAnswers.jsFramework) {
198
+ case JS_LIBRARIES[0]:
199
+ jsFiles.push('https://unpkg.com/react@latest/umd/react.production.min.js');
200
+ jsFiles.push('https://unpkg.com/react-dom@latest/umd/react-dom.production.min.js');
201
+ break;
202
+ case JS_LIBRARIES[1]:
203
+ jsFiles.push('https://unpkg.com/vue@latest/dist/vue.global.js');
204
+ break;
205
+ case JS_LIBRARIES[2]:
206
+ jsFiles.push('https://unpkg.com/@angular/core@latest/bundles/core.umd.js');
207
+ break;
208
+ case JS_LIBRARIES[3]:
209
+ jsFiles.push('https://unpkg.com/svelte@latest/compiled/svelte.js');
210
+ break;
211
+ case JS_LIBRARIES[4]:
212
+ jsFiles.push('https://code.jquery.com/jquery-latest.min.js');
213
+ break;
214
+ }
215
+
216
+ switch (cdnAnswers.cssFramework) {
217
+ case CSS_LIBRARIES[0]:
218
+ cssFiles.push('https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css');
219
+ break;
220
+ case CSS_LIBRARIES[1]:
221
+ cssFiles.push('https://cdn.jsdelivr.net/npm/bulma@latest/css/bulma.min.css');
222
+ break;
223
+ case CSS_LIBRARIES[2]:
224
+ cssFiles.push('https://cdn.tailwindcss.com');
225
+ break;
226
+ }
227
+ }
228
+
229
+
230
+ const spinner = ora('Creating Puter app...').start();
231
+
232
+ try {
233
+ const useBundler = answers.useBundler === 'Yes';
234
+ // Create basic app structure
235
+ await createAppStructure(answers.name, useBundler, bundlerAnswers, frameworkAnswers, jsFiles, jsDevFiles, cssFiles, extraFiles);
236
+ spinner.succeed(chalk.green('Successfully created Puter app!'));
237
+
238
+ console.log('\nNext steps:');
239
+ console.log(chalk.cyan('1. cd'), answers.name);
240
+ if (useBundler) {
241
+ console.log(chalk.cyan('2. npm install'));
242
+ console.log(chalk.cyan('3. npm start'));
243
+ } else {
244
+ console.log(chalk.cyan('2. Open index.html in your browser'));
245
+ }
246
+ } catch (error) {
247
+ spinner.fail(chalk.red('Failed to create app'));
248
+ console.error(error);
249
+ }
250
+ }
251
+
252
+ async function createAppStructure(name, useBundler, bundlerAnswers, frameworkAnswers, jsFiles, jsDevFiles, cssFiles, extraFiles) {
253
+ // Create project directory
254
+ await fs.mkdir(name, { recursive: true });
255
+
256
+ // Generate default home page
257
+ const homePage = useBundler?getDefaultHomePage(name): getDefaultHomePage(name, jsFiles, cssFiles);
258
+
259
+ // Create basic files
260
+ const files = {
261
+ '.env': `APP_NAME=${name}\nPUTER_API_KEY=`,
262
+ 'index.html': homePage,
263
+ 'styles.css': `body {
264
+ font-family: 'Segoe UI', Roboto, sans-serif;
265
+ margin: 0 auto;
266
+ padding: 10px;
267
+ }`,
268
+ 'app.js': `// Initialize Puter app
269
+ console.log('Puter app initialized!');`,
270
+ 'README.md': `# ${name}\n\nA Puter app created with puter-cli`
271
+ };
272
+
273
+ for (const [filename, content] of Object.entries(files)) {
274
+ await fs.writeFile(path.join(name, filename), content);
275
+ }
276
+
277
+ // If using a bundler, create a package.json
278
+ // if (jsFiles.some(file => !file.startsWith('http'))) {
279
+ if (useBundler) {
280
+
281
+ const useFullStackFramework = bundlerAnswers.frameworkType === 'Full-stack framework';
282
+ const bundler = bundlerAnswers.bundler.toString().toLowerCase();
283
+ const framework = useFullStackFramework?frameworkAnswers.framework.toLowerCase():null;
284
+
285
+ const scripts = {
286
+ start: `${useFullStackFramework?`${framework} dev`:bundler} dev`,
287
+ build: `${useFullStackFramework?`${framework} build`:bundler} build`,
288
+ };
289
+
290
+ const packageJson = {
291
+ name: name,
292
+ version: '1.0.0',
293
+ type: 'module',
294
+ scripts,
295
+ dependencies: {},
296
+ devDependencies: {}
297
+ };
298
+
299
+
300
+ jsFiles.forEach(lib => {
301
+ if (!lib.startsWith('http')) {
302
+ packageJson.dependencies[lib.split('@')[0].toString().toLowerCase()] = lib.split('@')[1] || 'latest';
303
+ }
304
+ });
305
+
306
+ jsDevFiles.forEach(lib => {
307
+ packageJson.devDependencies[lib] = 'latest';
308
+ });
309
+
310
+ packageJson.devDependencies[bundler] = 'latest';
311
+
312
+ await fs.writeFile(path.join(name, 'package.json'), JSON.stringify(packageJson, null, 2));
313
+
314
+ extraFiles.forEach(async (extraFile) => {
315
+ const fullPath = path.join(name, extraFile.path);
316
+ // Create directories recursively if they don't exist
317
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
318
+ await fs.writeFile(fullPath, extraFile.content);
319
+ });
320
+
321
+ }
322
+ }
@@ -0,0 +1,61 @@
1
+ import readline from 'node:readline';
2
+ import chalk from 'chalk';
3
+ import Conf from 'conf';
4
+ import { execCommand, getPrompt } from '../executor.js';
5
+ import { PROJECT_NAME } from '../commons.js';
6
+ import { getProfileModule } from '../modules/ProfileModule.js';
7
+
8
+ const config = new Conf({ projectName: PROJECT_NAME });
9
+
10
+ export let rl;
11
+
12
+ /**
13
+ * Update the current shell prompt
14
+ */
15
+ export function updatePrompt(currentPath) {
16
+ config.set('cwd', currentPath);
17
+ rl.setPrompt(getPrompt());
18
+ }
19
+
20
+ /**
21
+ * Start the interactive shell
22
+ */
23
+ export async function startShell(command) {
24
+ const profileModule = getProfileModule();
25
+ await profileModule.checkLogin();
26
+
27
+ // This argument enables the `puter <subcommand>` commands
28
+ if (command) {
29
+ await execCommand(command);
30
+ process.exit(0);
31
+ }
32
+
33
+ rl = readline.createInterface({
34
+ input: process.stdin,
35
+ output: process.stdout,
36
+ prompt: null
37
+ })
38
+
39
+ try {
40
+ console.log(chalk.green('Welcome to Puter-CLI! Type "help" for available commands.'));
41
+ rl.setPrompt(getPrompt());
42
+ rl.prompt();
43
+
44
+ rl.on('line', async (line) => {
45
+ const trimmedLine = line.trim();
46
+ if (trimmedLine) {
47
+ try {
48
+ await execCommand(trimmedLine);
49
+ } catch (error) {
50
+ console.error(chalk.red(error.message));
51
+ }
52
+ }
53
+ rl.prompt();
54
+ }).on('close', () => {
55
+ console.log(chalk.yellow('\nGoodbye!'));
56
+ process.exit(0);
57
+ });
58
+ } catch (error) {
59
+ console.error(chalk.red('Error starting shell:', error));
60
+ }
61
+ }
@@ -0,0 +1,169 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import { getCurrentUserName, getCurrentDirectory } from './auth.js';
4
+ import { resolveRemotePath, isValidAppName } from '../commons.js';
5
+ import { displayNonNullValues, formatDate, isValidAppUuid } from '../utils.js';
6
+ import { getSubdomains, createSubdomain, deleteSubdomain, updateSubdomain } from './subdomains.js';
7
+ import { getPuter } from '../modules/PuterModule.js';
8
+ import { report } from '../modules/ErrorModule.js';
9
+
10
+ /**
11
+ * Listing subdomains
12
+ */
13
+ export async function listSites(args = {}) {
14
+ try {
15
+ const result = await getSubdomains(args);
16
+
17
+ // Create table instance
18
+ const table = new Table({
19
+ head: [
20
+ chalk.cyan('#'),
21
+ chalk.cyan('UID'),
22
+ chalk.cyan('Subdomain'),
23
+ chalk.cyan('Created'),
24
+ chalk.cyan('Protected'),
25
+ // chalk.cyan('Owner'),
26
+ chalk.cyan('Directory')
27
+ ],
28
+ wordWrap: false
29
+ });
30
+
31
+ // Format and add data to table
32
+ let i = 0;
33
+ result.forEach(domain => {
34
+ let appDir = domain?.root_dir?.path.split('/').pop().split('-');
35
+ table.push([
36
+ i++,
37
+ domain.uid,
38
+ chalk.green(`${chalk.dim(domain.subdomain)}.puter.site`),
39
+ formatDate(domain.created_at).split(',')[0],
40
+ domain.protected ? chalk.red('Yes') : chalk.green('No'),
41
+ // domain.owner['username'],
42
+ appDir && (isValidAppUuid(appDir.join('-'))?`${appDir[0]}-...-${appDir.slice(-1)}`:appDir.join('-'))
43
+ ]);
44
+ });
45
+
46
+ // Print table
47
+ if (result.length === 0) {
48
+ console.log(chalk.yellow('No subdomains found'));
49
+ } else {
50
+ console.log(chalk.bold('\nYour Sites:'));
51
+ console.log(table.toString());
52
+ console.log(chalk.dim(`Total Sites: ${result.length}`));
53
+ }
54
+
55
+ } catch (error) {
56
+ report(error);
57
+ console.error(chalk.red('Error listing sites:'), error.message);
58
+ throw error;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Get Site info
64
+ * @param {any[]} args Array of site uuid
65
+ */
66
+ export async function infoSite(args = []) {
67
+ if (args.length < 1){
68
+ console.log(chalk.red('Usage: site <siteUID>'));
69
+ return;
70
+ }
71
+ const puter = getPuter();
72
+ for (const subdomain of args)
73
+ try {
74
+ const result = await puter.hosting.get(subdomain);
75
+ displayNonNullValues(result);
76
+ } catch (error) {
77
+ console.error(chalk.red('Error getting site info:'), error.message);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Delete hosted web site
83
+ * @param {any[]} args Array of subdomain
84
+ */
85
+ export async function deleteSite(args = []) {
86
+ if (args.length < 1){
87
+ console.log(chalk.red('Usage: site:delete <subdomain>'));
88
+ return false;
89
+ }
90
+ await deleteSubdomain(args);
91
+ return true;
92
+ }
93
+
94
+ /**
95
+ * Create a static web app from the current directory to Puter cloud.
96
+ * @param {string[]} args - Command-line arguments (e.g., [name, --subdomain=<subdomain>]).
97
+ */
98
+ export async function createSite(args = []) {
99
+ if (args.length < 1 || !isValidAppName(args[0])) {
100
+ console.log(chalk.red('Usage: site:create <valid_name_app> [<remote_dir>] [--subdomain=<subdomain>]'));
101
+ console.log(chalk.yellow('Example: site:create mysite'));
102
+ console.log(chalk.yellow('Example: site:create mysite ./mysite'));
103
+ console.log(chalk.yellow('Example: site:create mysite --subdomain=mysite'));
104
+ return;
105
+ }
106
+
107
+ const appName = args[0]; // Site name (required)
108
+ const subdomainOption = args.find(arg => arg.toLocaleLowerCase().startsWith('--subdomain='))?.split('=')[1]; // Optional subdomain
109
+ const remoteDirArg = (args[1] && !args[1].startsWith('--')) ? args[1] : '.';
110
+
111
+ // Use the current directory as the root directory if none specified
112
+ const remoteDir = resolveRemotePath(getCurrentDirectory(), remoteDirArg);
113
+
114
+ console.log(chalk.dim(`Creating site ${chalk.green(appName)} from: ${chalk.green(remoteDir)}...\n`));
115
+ try {
116
+ // Step 1: Determine the subdomain
117
+ let subdomain;
118
+ if (subdomainOption) {
119
+ subdomain = subdomainOption; // Use the provided subdomain
120
+ } else {
121
+ subdomain = appName; // Default to the app name as the subdomain
122
+ }
123
+
124
+ // Step 2: Check if the subdomain already exists
125
+ const subdomains = await getSubdomains();;
126
+ const subdomainObj = subdomains.find(sd => sd.subdomain === subdomain);
127
+ if (subdomainObj) {
128
+ console.error(chalk.cyan(`The subdomain "${subdomain}" is already in use and owned by: "${subdomainObj.owner['username']}"`));
129
+ if (subdomainObj.owner['username'] === getCurrentUserName()){
130
+ console.log(chalk.green(`It's yours, and linked to: ${subdomainObj.root_dir?.path}`));
131
+ if (subdomainObj.root_dir?.path === remoteDir){
132
+ console.log(chalk.cyan(`Which is already the selected directory, and created at:`));
133
+ console.log(chalk.green(`https://${subdomain}.puter.site`));
134
+ return;
135
+ } else {
136
+ console.log(chalk.yellow(`However, It's linked to different directory at: ${subdomainObj.root_dir?.path}`));
137
+ console.log(chalk.cyan(`Updating this subdomain directory...`));
138
+ const result = await updateSubdomain(subdomain, remoteDir);
139
+ if (result) {
140
+ console.log(chalk.green('Updating subdomain directory successful.'));
141
+ return;
142
+ } else {
143
+ console.log(chalk.red('Could not update this subdomain directory.'));
144
+ return;
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ // Use the chosen "subdomain"
151
+ console.log(chalk.cyan(`New generated subdomain: "${subdomain}" will be used if its not already in use.`));
152
+
153
+ // Step 3: Host the current directory under the subdomain
154
+ console.log(chalk.cyan(`Hosting site "${appName}" under subdomain "${subdomain}"...`));
155
+ const site = await createSubdomain(subdomain, remoteDir);
156
+ if (!site){
157
+ console.error(chalk.red(`Failed to create subdomain: "${chalk.red(subdomain)}"`));
158
+ return;
159
+ }
160
+
161
+ console.log(chalk.green(`Site ${chalk.dim(appName)} created successfully and accessible at:`));
162
+ console.log(chalk.cyan(`https://${site.subdomain}.puter.site`));
163
+ return site;
164
+ } catch (error) {
165
+ console.error(chalk.red('Failed to create site.'));
166
+ console.error(chalk.red(`Error: ${error.message}`));
167
+ return null;
168
+ }
169
+ }
@@ -0,0 +1,95 @@
1
+ import chalk from 'chalk';
2
+ import fetch from 'node-fetch';
3
+ import { API_BASE, getHeaders } from '../commons.js';
4
+ import { getPuter } from '../modules/PuterModule.js';
5
+
6
+ /**
7
+ * Get list of subdomains.
8
+ * @param {Object} args - Options for the query.
9
+ * @returns {Array} - Array of subdomains.
10
+ */
11
+ export async function getSubdomains(args = {}) {
12
+ const puter = getPuter();
13
+ let result;
14
+
15
+ try {
16
+ result = await puter.hosting.list();
17
+ } catch (error) {
18
+ console.log(chalk.red(`Error when getting subdomains.\nError: ${error?.message}`));
19
+ }
20
+
21
+ return result;
22
+ }
23
+
24
+ /**
25
+ * Delete a subdomain by id
26
+ * @param {Array} subdomain IDs
27
+ * @return {boolean} Result of the operation
28
+ */
29
+ export async function deleteSubdomain(args = []) {
30
+ if (args.length < 1){
31
+ console.log(chalk.red('Usage: domain:delete <subdomain_id>'));
32
+ return false;
33
+ }
34
+ const puter = getPuter();
35
+ const subdomains = args;
36
+ for (const subdomain of subdomains)
37
+ try {
38
+ const success = await puter.hosting.delete(subdomain);
39
+
40
+ if (!success) {
41
+ console.log(chalk.red(`Failed to delete subdomain: ${data.error?.message}`));
42
+ return false;
43
+ }
44
+ console.log(chalk.green('Subdomain deleted successfully'));
45
+ } catch (error) {
46
+ if (error.error?.code === 'entity_not_found') {
47
+ console.log(chalk.red(`Subdomain: "${subdomain}" not found`));
48
+ return false;
49
+ }
50
+ console.error(chalk.red('Error deleting subdomain:'), error.message);
51
+ }
52
+ return true;
53
+ }
54
+
55
+ /**
56
+ * Create a new subdomain into remote directory
57
+ * @param {string} subdomain - Subdomain name.
58
+ * @param {string} remoteDir - Remote directory path.
59
+ * @returns {Object} - Hosting details (e.g., subdomain).
60
+ */
61
+ export async function createSubdomain(subdomain, remoteDir) {
62
+ const puter = getPuter();
63
+ let result;
64
+
65
+ try {
66
+ result = await puter.hosting.create(subdomain, remoteDir);
67
+ } catch (error) {
68
+ if (error?.error?.code === 'already_in_use') {
69
+ console.log(chalk.yellow(`Subdomain already taken!\nMessage: ${error?.error?.message}`));
70
+ return false;
71
+ }
72
+ console.log(chalk.red(`Error when creating "${subdomain}".\nError: ${error?.error?.message}\nCode: ${error?.error?.code}`));
73
+ }
74
+ return result;
75
+ }
76
+
77
+ /**
78
+ * Update a subdomain into remote directory
79
+ * @param {string} subdomain - Subdomain name.
80
+ * @param {string} remoteDir - Remote directory path.
81
+ * @returns {Object} - Hosting details (e.g., subdomain).
82
+ */
83
+ export async function updateSubdomain(subdomain, remoteDir) {
84
+ const puter = getPuter();
85
+ let result;
86
+
87
+ try {
88
+ result = await puter.hosting.update(subdomain, remoteDir);
89
+ } catch (error) {
90
+ console.log(chalk.red(`Error when updating "${subdomain}".\nError: ${error?.message}`));
91
+ return null;
92
+ }
93
+
94
+ return result;
95
+ }