@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.
- package/.claude/settings.local.json +9 -0
- package/.env.example +3 -0
- package/.github/workflows/npm-build.yml +34 -0
- package/.github/workflows/npm-publish.yml +50 -0
- package/.idx/dev.nix +53 -0
- package/CHANGELOG.md +292 -0
- package/LICENSE.md +21 -0
- package/README.md +18 -0
- package/bin/index.js +258 -0
- package/package.json +58 -0
- package/screenshot.png +0 -0
- package/src/commands/apps.js +356 -0
- package/src/commands/auth.js +195 -0
- package/src/commands/deploy.js +54 -0
- package/src/commands/files.js +1187 -0
- package/src/commands/init.js +322 -0
- package/src/commands/shell.js +61 -0
- package/src/commands/sites.js +169 -0
- package/src/commands/subdomains.js +95 -0
- package/src/commons.js +409 -0
- package/src/crypto.js +9 -0
- package/src/executor.js +386 -0
- package/src/modules/ErrorModule.js +20 -0
- package/src/modules/ProfileModule.js +334 -0
- package/src/modules/PuterModule.js +30 -0
- package/src/utils.js +135 -0
- package/tests/ErrorModule.test.js +42 -0
- package/tests/ProfileModule.test.js +274 -0
- package/tests/PuterModule.test.js +56 -0
- package/tests/apps.test.js +194 -0
- package/tests/commons.test.js +380 -0
- package/tests/deploy.test.js +84 -0
- package/tests/executor.test.js +52 -0
- package/tests/files.test.js +640 -0
- package/tests/login.test.js +206 -0
- package/tests/shell.test.js +184 -0
- package/tests/sites.test.js +67 -0
- package/tests/subdomains.test.js +90 -0
- package/tests/utils.test.js +193 -0
package/src/commons.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getAuthToken } from './commands/auth.js';
|
|
3
|
+
import { formatSize } from './utils.js';
|
|
4
|
+
import { readFile } from 'fs/promises';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import dotenv from 'dotenv';
|
|
8
|
+
|
|
9
|
+
dotenv.config();
|
|
10
|
+
|
|
11
|
+
export const PROJECT_NAME = 'puter-cli';
|
|
12
|
+
// If you haven't defined your own values in .env file, we'll assume you're running Puter on a local instance:
|
|
13
|
+
export let API_BASE = process.env.PUTER_API_BASE || 'https://api.puter.com';
|
|
14
|
+
export let BASE_URL = process.env.PUTER_BASE_URL || 'https://puter.com';
|
|
15
|
+
export const NULL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
16
|
+
|
|
17
|
+
export const reconfigureURLs = ({ api, base }) => {
|
|
18
|
+
API_BASE = api;
|
|
19
|
+
BASE_URL = base;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get headers with the correct Content-Type for multipart form data.
|
|
24
|
+
* @param {string} contentType - The "Content-Type" argument for the header ('application/json' is the default)
|
|
25
|
+
* Use the multipart form data for upload a file.
|
|
26
|
+
* @returns {Object} The headers object.
|
|
27
|
+
*/
|
|
28
|
+
export function getHeaders(contentType = 'application/json') {
|
|
29
|
+
return {
|
|
30
|
+
'Accept': '*/*',
|
|
31
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
32
|
+
'Authorization': `Bearer ${getAuthToken()}`,
|
|
33
|
+
'Connection': 'keep-alive',
|
|
34
|
+
// 'Host': 'api.puter.com',
|
|
35
|
+
'Content-Type': contentType,
|
|
36
|
+
'Origin': `${BASE_URL}`,
|
|
37
|
+
'Referer': `${BASE_URL}/`,
|
|
38
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate a random app name
|
|
44
|
+
* @returns a random app name or null if it fails
|
|
45
|
+
* @see: [randName](https://github.com/HeyPuter/puter/blob/06a67a3b223a6cbd7ec2e16853b6d2304f621a88/src/puter-js/src/index.js#L389)
|
|
46
|
+
*/
|
|
47
|
+
export function generateAppName(separateWith = '-'){
|
|
48
|
+
console.log(chalk.cyan('Generating random name...'));
|
|
49
|
+
try {
|
|
50
|
+
const first_adj = ['helpful','sensible', 'loyal', 'honest', 'clever', 'capable','calm', 'smart', 'genius', 'bright', 'charming', 'creative', 'diligent', 'elegant', 'fancy',
|
|
51
|
+
'colorful', 'avid', 'active', 'gentle', 'happy', 'intelligent', 'jolly', 'kind', 'lively', 'merry', 'nice', 'optimistic', 'polite',
|
|
52
|
+
'quiet', 'relaxed', 'silly', 'victorious', 'witty', 'young', 'zealous', 'strong', 'brave', 'agile', 'bold'];
|
|
53
|
+
|
|
54
|
+
const nouns = ['street', 'roof', 'floor', 'tv', 'idea', 'morning', 'game', 'wheel', 'shoe', 'bag', 'clock', 'pencil', 'pen',
|
|
55
|
+
'magnet', 'chair', 'table', 'house', 'dog', 'room', 'book', 'car', 'cat', 'tree',
|
|
56
|
+
'flower', 'bird', 'fish', 'sun', 'moon', 'star', 'cloud', 'rain', 'snow', 'wind', 'mountain',
|
|
57
|
+
'river', 'lake', 'sea', 'ocean', 'island', 'bridge', 'road', 'train', 'plane', 'ship', 'bicycle',
|
|
58
|
+
'horse', 'elephant', 'lion', 'tiger', 'bear', 'zebra', 'giraffe', 'monkey', 'snake', 'rabbit', 'duck',
|
|
59
|
+
'goose', 'penguin', 'frog', 'crab', 'shrimp', 'whale', 'octopus', 'spider', 'ant', 'bee', 'butterfly', 'dragonfly',
|
|
60
|
+
'ladybug', 'snail', 'camel', 'kangaroo', 'koala', 'panda', 'piglet', 'sheep', 'wolf', 'fox', 'deer', 'mouse', 'seal',
|
|
61
|
+
'chicken', 'cow', 'dinosaur', 'puppy', 'kitten', 'circle', 'square', 'garden', 'otter', 'bunny', 'meerkat', 'harp']
|
|
62
|
+
|
|
63
|
+
// return a random combination of first_adj + noun + number (between 0 and 9999)
|
|
64
|
+
// e.g. clever-idea-123
|
|
65
|
+
const appName = first_adj[Math.floor(Math.random() * first_adj.length)] + separateWith + nouns[Math.floor(Math.random() * nouns.length)] + separateWith + Math.floor(Math.random() * 10000);
|
|
66
|
+
console.log(chalk.green(`Name: "${appName}"`));
|
|
67
|
+
return appName;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(`Error: ${error.message}`);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Display data in a structured format
|
|
76
|
+
* @param {Array} data - The data to display
|
|
77
|
+
* @param {Object} options - Display options
|
|
78
|
+
* @param {Array} options.headers - Headers for the table
|
|
79
|
+
* @param {Array} options.columns - Columns to display
|
|
80
|
+
* @param {number} options.columnWidth - Width of each column
|
|
81
|
+
*/
|
|
82
|
+
export function displayTable(data, options = {}) {
|
|
83
|
+
const { headers = [], columns = [], columnWidth = 20 } = options;
|
|
84
|
+
|
|
85
|
+
// Create the header row
|
|
86
|
+
const headerRow = headers.map(header => chalk.cyan(header.padEnd(columnWidth))).join(' | ');
|
|
87
|
+
console.log(headerRow);
|
|
88
|
+
console.log(chalk.dim('-'.repeat(headerRow.length)));
|
|
89
|
+
|
|
90
|
+
// Create and display each row of data
|
|
91
|
+
data.forEach(item => {
|
|
92
|
+
const row = columns.map(col => {
|
|
93
|
+
const value = item[col] || 'N/A';
|
|
94
|
+
return value.toString().padEnd(columnWidth);
|
|
95
|
+
}).join(' | ');
|
|
96
|
+
console.log(row);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Display structured ouput of disk usage informations
|
|
102
|
+
*/
|
|
103
|
+
export function showDiskSpaceUsage(data) {
|
|
104
|
+
const freeSpace = parseInt(data.capacity) - parseInt(data.used);
|
|
105
|
+
const usagePercentage = (parseInt(data.used) / parseInt(data.capacity)) * 100;
|
|
106
|
+
console.log(chalk.cyan('Disk Usage Information:'));
|
|
107
|
+
console.log(chalk.dim('----------------------------------------'));
|
|
108
|
+
console.log(chalk.cyan(`Total Capacity: `) + chalk.white(formatSize(data.capacity)));
|
|
109
|
+
console.log(chalk.cyan(`Used Space: `) + chalk.white(formatSize(data.used)));
|
|
110
|
+
console.log(chalk.cyan(`Free Space: `) + chalk.white(formatSize(freeSpace)));
|
|
111
|
+
// format the usagePercentage with 2 decimal floating point value:
|
|
112
|
+
console.log(chalk.cyan(`Usage Percentage: `) + chalk.white(`${usagePercentage.toFixed(2)}%`));
|
|
113
|
+
console.log(chalk.dim('----------------------------------------'));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a relative path to an absolute path
|
|
118
|
+
* @param {string} currentPath - The current working directory
|
|
119
|
+
* @param {string} relativePath - The relative path to resolve
|
|
120
|
+
* @returns {string} The resolved absolute path
|
|
121
|
+
*/
|
|
122
|
+
export function resolvePath(currentPath, relativePath) {
|
|
123
|
+
// Normalize the current path (remove trailing slashes)
|
|
124
|
+
currentPath = currentPath.replace(/\/+$/, '');
|
|
125
|
+
|
|
126
|
+
// Split the relative path into parts
|
|
127
|
+
const parts = relativePath.split('/').filter(p => p); // Remove empty parts
|
|
128
|
+
|
|
129
|
+
// Handle each part of the relative path
|
|
130
|
+
for (const part of parts) {
|
|
131
|
+
if (part === '..') {
|
|
132
|
+
// Move one level up
|
|
133
|
+
const currentParts = currentPath.split('/').filter(p => p);
|
|
134
|
+
if (currentParts.length > 0) {
|
|
135
|
+
currentParts.pop(); // Remove the last part
|
|
136
|
+
}
|
|
137
|
+
currentPath = '/' + currentParts.join('/');
|
|
138
|
+
} else if (part === '.') {
|
|
139
|
+
// Stay in the current directory (no change)
|
|
140
|
+
continue;
|
|
141
|
+
} else {
|
|
142
|
+
// Move into a subdirectory
|
|
143
|
+
currentPath += `/${part}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Normalize the final path (remove duplicate slashes)
|
|
148
|
+
currentPath = currentPath.replace(/\/+/g, '/');
|
|
149
|
+
|
|
150
|
+
// Ensure the path ends with a slash if it's the root
|
|
151
|
+
if (currentPath === '') {
|
|
152
|
+
currentPath = '/';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return currentPath;
|
|
156
|
+
}
|
|
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
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Checks if a given string is a valid app name.
|
|
173
|
+
* The name must:
|
|
174
|
+
* - Not be '.' or '..'
|
|
175
|
+
* - Not contain path separators ('/' or '\\')
|
|
176
|
+
* - Not contain wildcard characters ('*')
|
|
177
|
+
* - (Optional) Contain only allowed characters (letters, numbers, spaces, underscores, hyphens)
|
|
178
|
+
*
|
|
179
|
+
* @param {string} name - The app name to validate.
|
|
180
|
+
* @returns {boolean} - Returns true if valid, false otherwise.
|
|
181
|
+
*/
|
|
182
|
+
export function isValidAppName(name) {
|
|
183
|
+
// Ensure the name is a non-empty string
|
|
184
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Trim whitespace from both ends
|
|
189
|
+
const trimmedName = name.trim();
|
|
190
|
+
|
|
191
|
+
// Reject reserved names
|
|
192
|
+
if (trimmedName === '.' || trimmedName === '..') {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Regex patterns for invalid characters
|
|
197
|
+
const invalidPattern = /[\/\\*]/; // Disallow /, \, and *
|
|
198
|
+
|
|
199
|
+
if (invalidPattern.test(trimmedName)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Optional: Define allowed characters pattern
|
|
204
|
+
// Uncomment the following lines if you want to enforce allowed characters
|
|
205
|
+
/*
|
|
206
|
+
const allowedPattern = /^[A-Za-z0-9 _-]+$/;
|
|
207
|
+
if (!allowedPattern.test(trimmedName)) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
// All checks passed
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Generate the default home page for a new web application
|
|
218
|
+
* @param {string} appName The name of the web application
|
|
219
|
+
* @returns HTML template of the app
|
|
220
|
+
*/
|
|
221
|
+
export function getDefaultHomePage(appName, jsFiles = [], cssFiles= []) {
|
|
222
|
+
const defaultIndexContent = `<!DOCTYPE html>
|
|
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.
|
|
290
|
+
</div>
|
|
291
|
+
|
|
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>
|
|
304
|
+
|
|
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>`;
|
|
331
|
+
|
|
332
|
+
return defaultIndexContent;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Read latest package from package file
|
|
338
|
+
*/
|
|
339
|
+
export async function getVersionFromPackage() {
|
|
340
|
+
try {
|
|
341
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
342
|
+
const __dirname = dirname(__filename);
|
|
343
|
+
|
|
344
|
+
// First try parent directory (dev mode)
|
|
345
|
+
try {
|
|
346
|
+
const devPackage = JSON.parse(
|
|
347
|
+
await readFile(join(__dirname, '..', 'package.json'), 'utf8')
|
|
348
|
+
);
|
|
349
|
+
return devPackage.version;
|
|
350
|
+
} catch (devError) {
|
|
351
|
+
// Fallback to current directory (production)
|
|
352
|
+
const prodPackage = JSON.parse(
|
|
353
|
+
await readFile(join(__dirname, 'package.json'), 'utf8')
|
|
354
|
+
);
|
|
355
|
+
return prodPackage.version;
|
|
356
|
+
}
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error(`Error fetching latest version:`, error.message);
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get latest package info from npm registery
|
|
365
|
+
*/
|
|
366
|
+
export async function getLatestVersion(packageName) {
|
|
367
|
+
let currentVersion = 'unknown';
|
|
368
|
+
let latestVersion = null;
|
|
369
|
+
let status = 'offline'; // Default status
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
// Attempt to get the current version first
|
|
373
|
+
currentVersion = await getVersionFromPackage();
|
|
374
|
+
if (!currentVersion) {
|
|
375
|
+
currentVersion = 'unknown'; // Fallback if local version fetch fails
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Attempt to fetch the latest version from npm
|
|
379
|
+
try {
|
|
380
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
381
|
+
if (response.ok) {
|
|
382
|
+
const data = await response.json();
|
|
383
|
+
latestVersion = data.version;
|
|
384
|
+
}
|
|
385
|
+
} catch (fetchError) {
|
|
386
|
+
// Ignore fetch errors
|
|
387
|
+
// console.warn(chalk.yellow(`Could not fetch latest version for ${packageName}: ${fetchError.message}`));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Determine the status based on fetched versions
|
|
391
|
+
if (latestVersion) {
|
|
392
|
+
if (currentVersion !== 'unknown' && latestVersion === currentVersion) {
|
|
393
|
+
status = 'up-to-date';
|
|
394
|
+
} else if (currentVersion !== 'unknown' && latestVersion !== currentVersion) {
|
|
395
|
+
status = `latest: ${latestVersion}`;
|
|
396
|
+
} else {
|
|
397
|
+
// If currentVersion is unknown but we got latest, show latest
|
|
398
|
+
status = `latest: ${latestVersion}`;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// status remains 'offline'...
|
|
402
|
+
|
|
403
|
+
} catch (error) {
|
|
404
|
+
// Catch errors from getVersionFromPackage or other unexpected issues
|
|
405
|
+
console.error(chalk.red(`Error determining version status: ${error.message}`));
|
|
406
|
+
status = 'error'; // Indicate an error occurred
|
|
407
|
+
}
|
|
408
|
+
return `v${currentVersion} (${status})`;
|
|
409
|
+
}
|