8bitsapps-gcp-utils 1.0.4 → 1.0.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/GCPUtilities/Storage.js +47 -0
- package/README.md +3 -0
- package/commands/storageNavigator.js +104 -7
- package/gcpUtils.js +54 -12
- package/package.json +1 -1
- package/utils/prompts/listWithEscape.js +18 -0
- package/utils/updateChecker.js +66 -0
package/GCPUtilities/Storage.js
CHANGED
|
@@ -180,6 +180,53 @@ class Storage {
|
|
|
180
180
|
resumable: false
|
|
181
181
|
});
|
|
182
182
|
}
|
|
183
|
+
//
|
|
184
|
+
/**
|
|
185
|
+
* Creates a folder (empty placeholder object) in the bucket.
|
|
186
|
+
* @param {string} bucketName - Name of the bucket.
|
|
187
|
+
* @param {string} folderPath - Path of the folder (must end with /).
|
|
188
|
+
* @returns {Promise<void>}
|
|
189
|
+
*/
|
|
190
|
+
async createFolder(bucketName, folderPath) {
|
|
191
|
+
await this.loadConfiguration();
|
|
192
|
+
//
|
|
193
|
+
const bucket = this.storage.bucket(bucketName);
|
|
194
|
+
const normalizedPath = folderPath.endsWith("/") ? folderPath : folderPath + "/";
|
|
195
|
+
const file = bucket.file(normalizedPath);
|
|
196
|
+
await file.save("", { contentType: "application/x-directory" });
|
|
197
|
+
}
|
|
198
|
+
//
|
|
199
|
+
/**
|
|
200
|
+
* Deletes a file from the bucket.
|
|
201
|
+
* @param {string} bucketName - Name of the bucket.
|
|
202
|
+
* @param {string} filePath - Path of the file to delete.
|
|
203
|
+
* @returns {Promise<void>}
|
|
204
|
+
*/
|
|
205
|
+
async deleteFile(bucketName, filePath) {
|
|
206
|
+
await this.loadConfiguration();
|
|
207
|
+
//
|
|
208
|
+
const bucket = this.storage.bucket(bucketName);
|
|
209
|
+
const file = bucket.file(filePath);
|
|
210
|
+
await file.delete();
|
|
211
|
+
}
|
|
212
|
+
//
|
|
213
|
+
/**
|
|
214
|
+
* Deletes a folder and all its contents from the bucket.
|
|
215
|
+
* @param {string} bucketName - Name of the bucket.
|
|
216
|
+
* @param {string} folderPath - Path of the folder to delete.
|
|
217
|
+
* @returns {Promise<number>} Number of deleted files.
|
|
218
|
+
*/
|
|
219
|
+
async deleteFolder(bucketName, folderPath) {
|
|
220
|
+
await this.loadConfiguration();
|
|
221
|
+
//
|
|
222
|
+
const bucket = this.storage.bucket(bucketName);
|
|
223
|
+
const [files] = await bucket.getFiles({ prefix: folderPath });
|
|
224
|
+
//
|
|
225
|
+
for (const file of files) {
|
|
226
|
+
await file.delete();
|
|
227
|
+
}
|
|
228
|
+
return files.length;
|
|
229
|
+
}
|
|
183
230
|
}
|
|
184
231
|
|
|
185
232
|
module.exports = Storage;
|
package/README.md
CHANGED
|
@@ -20,6 +20,9 @@ Interactive navigator for Google Cloud Storage:
|
|
|
20
20
|
- View files with size information.
|
|
21
21
|
- Download files to current directory.
|
|
22
22
|
- Upload local files to GCS.
|
|
23
|
+
- Create folders in GCS.
|
|
24
|
+
- Delete files from GCS.
|
|
25
|
+
- Delete folders from GCS (recursive).
|
|
23
26
|
- Support for multiple buckets per configuration.
|
|
24
27
|
|
|
25
28
|
### Initialize configuration
|
|
@@ -108,20 +108,25 @@ async function showNavigationMenu(bucketName, currentPath, contents, options) {
|
|
|
108
108
|
choices.push(new inquirer.Separator(chalk.yellow(`(showing first ${maxItems} items)`)));
|
|
109
109
|
}
|
|
110
110
|
choices.push({
|
|
111
|
-
name: chalk.green("Upload file here"),
|
|
111
|
+
name: chalk.green("↑ Upload file here"),
|
|
112
112
|
value: { action: "upload", value: currentPath }
|
|
113
113
|
});
|
|
114
|
+
choices.push({
|
|
115
|
+
name: chalk.green("+ Create folder here"),
|
|
116
|
+
value: { action: "createFolder", value: currentPath }
|
|
117
|
+
});
|
|
114
118
|
//
|
|
115
119
|
const message = backEnabled
|
|
116
|
-
? `${bucketName}:${displayPath} (← back, ESC exit)`
|
|
117
|
-
: `${bucketName}:${displayPath} (ESC exit)`;
|
|
120
|
+
? `${bucketName}:${displayPath} (← back, DEL delete, ESC exit)`
|
|
121
|
+
: `${bucketName}:${displayPath} (DEL delete, ESC exit)`;
|
|
118
122
|
//
|
|
119
123
|
const { selected } = await inquirer.prompt([{
|
|
120
124
|
type: "listWithEscape",
|
|
121
125
|
name: "selected",
|
|
122
126
|
message,
|
|
123
127
|
choices,
|
|
124
|
-
enableBack: backEnabled
|
|
128
|
+
enableBack: backEnabled,
|
|
129
|
+
deleteFilter: (value) => value?.action === "file" || value?.action === "folder"
|
|
125
130
|
}]);
|
|
126
131
|
//
|
|
127
132
|
return selected;
|
|
@@ -141,6 +146,41 @@ async function promptUploadPath() {
|
|
|
141
146
|
return filePath || null;
|
|
142
147
|
}
|
|
143
148
|
//
|
|
149
|
+
/**
|
|
150
|
+
* Prompts for folder name to create.
|
|
151
|
+
* @returns {Promise<string|null>} Folder name or null if cancelled.
|
|
152
|
+
*/
|
|
153
|
+
async function promptFolderName() {
|
|
154
|
+
const { folderName } = await inquirer.prompt([{
|
|
155
|
+
type: "input",
|
|
156
|
+
name: "folderName",
|
|
157
|
+
message: "Enter folder name (or leave empty to cancel):",
|
|
158
|
+
validate: (input) => {
|
|
159
|
+
if (!input.trim()) return true;
|
|
160
|
+
if (input.includes("/")) return "Folder name cannot contain /";
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}]);
|
|
164
|
+
//
|
|
165
|
+
return folderName.trim() || null;
|
|
166
|
+
}
|
|
167
|
+
//
|
|
168
|
+
/**
|
|
169
|
+
* Prompts for delete confirmation.
|
|
170
|
+
* @param {string} itemName - Name of the item to delete.
|
|
171
|
+
* @param {string} itemType - Type of item ("file" or "folder").
|
|
172
|
+
* @returns {Promise<boolean>} True if confirmed.
|
|
173
|
+
*/
|
|
174
|
+
async function confirmDelete(itemName, itemType) {
|
|
175
|
+
const { confirmed } = await inquirer.prompt([{
|
|
176
|
+
type: "confirm",
|
|
177
|
+
name: "confirmed",
|
|
178
|
+
message: `Delete ${itemType} "${itemName}"?`,
|
|
179
|
+
default: false
|
|
180
|
+
}]);
|
|
181
|
+
return confirmed;
|
|
182
|
+
}
|
|
183
|
+
//
|
|
144
184
|
/**
|
|
145
185
|
* Shows the operation log section.
|
|
146
186
|
* @param {Array<{type: string, message: string}>} logs - Array of log entries.
|
|
@@ -183,6 +223,9 @@ const command = {
|
|
|
183
223
|
if (bucketName === null || bucketName?.action === "back") {
|
|
184
224
|
return; // ESC or back arrow pressed.
|
|
185
225
|
}
|
|
226
|
+
// Position cursor at end of bucket selection line.
|
|
227
|
+
process.stdout.write("\x1b[1A\x1b[2K\x1b[G");
|
|
228
|
+
process.stdout.write(`? Select bucket (ESC exit): ${bucketName}`);
|
|
186
229
|
//
|
|
187
230
|
// Navigation loop.
|
|
188
231
|
const pathHistory = [""];
|
|
@@ -213,7 +256,7 @@ const command = {
|
|
|
213
256
|
if (result === null) {
|
|
214
257
|
// ESC pressed - exit storage navigator completely.
|
|
215
258
|
const exitDisplayPath = currentPath || "/";
|
|
216
|
-
const exitHint = backEnabled ? "(← back, ESC exit)" : "(ESC exit)";
|
|
259
|
+
const exitHint = backEnabled ? "(← back, DEL delete, ESC exit)" : "(DEL delete, ESC exit)";
|
|
217
260
|
process.stdout.write(`\x1b[1A\x1b[2K\x1b[G? ${bucketName}:${exitDisplayPath} ${exitHint} ${chalk.red("<- exit")}`);
|
|
218
261
|
console.log();
|
|
219
262
|
return;
|
|
@@ -222,18 +265,51 @@ const command = {
|
|
|
222
265
|
if (result.action === "back") {
|
|
223
266
|
// Left arrow pressed - go back one level (only possible when backEnabled is true).
|
|
224
267
|
const backDisplayPath = currentPath || "/";
|
|
225
|
-
process.stdout.write(`\x1b[1A\x1b[2K\x1b[G? ${bucketName}:${backDisplayPath} (← back, ESC exit) ${chalk.cyan("<- back")}`);
|
|
268
|
+
process.stdout.write(`\x1b[1A\x1b[2K\x1b[G? ${bucketName}:${backDisplayPath} (← back, DEL delete, ESC exit) ${chalk.cyan("<- back")}`);
|
|
226
269
|
pathHistory.pop();
|
|
227
270
|
continue;
|
|
228
271
|
}
|
|
229
272
|
//
|
|
273
|
+
if (result.action === "delete") {
|
|
274
|
+
const selectedValue = result.value;
|
|
275
|
+
// Skip if not a file or folder (e.g., upload/createFolder option).
|
|
276
|
+
if (!selectedValue?.action || (selectedValue.action !== "file" && selectedValue.action !== "folder")) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
//
|
|
280
|
+
const isFolder = selectedValue.action === "folder";
|
|
281
|
+
const itemPath = selectedValue.value;
|
|
282
|
+
const itemName = getDisplayName(itemPath, currentPath);
|
|
283
|
+
//
|
|
284
|
+
const confirmed = await confirmDelete(itemName, isFolder ? "folder" : "file");
|
|
285
|
+
if (confirmed) {
|
|
286
|
+
const itemType = isFolder ? "folder" : "file";
|
|
287
|
+
process.stdout.write(chalk.yellow(`\n⏳ Deleting ${itemType} ${itemName}...`));
|
|
288
|
+
try {
|
|
289
|
+
if (isFolder) {
|
|
290
|
+
const count = await storage.deleteFolder(bucketName, itemPath);
|
|
291
|
+
process.stdout.write("\x1b[2K\x1b[G");
|
|
292
|
+
operationLogs.push({ type: "success", message: `Deleted folder "${itemName}" (${count} files)` });
|
|
293
|
+
} else {
|
|
294
|
+
await storage.deleteFile(bucketName, itemPath);
|
|
295
|
+
process.stdout.write("\x1b[2K\x1b[G");
|
|
296
|
+
operationLogs.push({ type: "success", message: `Deleted file "${itemName}"` });
|
|
297
|
+
}
|
|
298
|
+
} catch (err) {
|
|
299
|
+
process.stdout.write("\x1b[2K\x1b[G");
|
|
300
|
+
operationLogs.push({ type: "error", message: `Delete failed: ${itemName} - ${err.message}` });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
//
|
|
230
306
|
switch (result.action) {
|
|
231
307
|
case "folder": {
|
|
232
308
|
// Force newline, then go back up and overwrite inquirer's colored line.
|
|
233
309
|
const folderDisplayName = getDisplayName(result.value, currentPath);
|
|
234
310
|
const folderDisplayPath = currentPath || "/";
|
|
235
311
|
process.stdout.write("\x1b[1A\x1b[2K\x1b[G");
|
|
236
|
-
process.stdout.write(`? ${bucketName}:${folderDisplayPath} (← back, ESC exit) ${chalk.cyan(`[D] ${folderDisplayName}`)}`);
|
|
312
|
+
process.stdout.write(`? ${bucketName}:${folderDisplayPath} (← back, DEL delete, ESC exit) ${chalk.cyan(`[D] ${folderDisplayName}`)}`);
|
|
237
313
|
// Enter folder.
|
|
238
314
|
pathHistory.push(result.value);
|
|
239
315
|
break;
|
|
@@ -279,6 +355,27 @@ const command = {
|
|
|
279
355
|
}
|
|
280
356
|
break;
|
|
281
357
|
}
|
|
358
|
+
//
|
|
359
|
+
case "createFolder": {
|
|
360
|
+
// Create folder.
|
|
361
|
+
const folderName = await promptFolderName();
|
|
362
|
+
if (folderName) {
|
|
363
|
+
const remotePath = currentPath + folderName + "/";
|
|
364
|
+
//
|
|
365
|
+
process.stdout.write(chalk.yellow(`\n⏳ Creating folder ${folderName}...`));
|
|
366
|
+
try {
|
|
367
|
+
await storage.createFolder(bucketName, remotePath);
|
|
368
|
+
// Clear the "Creating..." line.
|
|
369
|
+
process.stdout.write("\x1b[2K\x1b[G");
|
|
370
|
+
operationLogs.push({ type: "success", message: `Folder created: ${folderName} at ${bucketName}:${currentPath || "/"}` });
|
|
371
|
+
} catch (err) {
|
|
372
|
+
// Clear the "Creating..." line.
|
|
373
|
+
process.stdout.write("\x1b[2K\x1b[G");
|
|
374
|
+
operationLogs.push({ type: "error", message: `Create folder failed: ${folderName} - ${err.message}` });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
282
379
|
}
|
|
283
380
|
}
|
|
284
381
|
}
|
package/gcpUtils.js
CHANGED
|
@@ -6,8 +6,10 @@ const { isLocalMode, getConfigurationsDir } = require("./utils/paths.js");
|
|
|
6
6
|
const { listConfigurations } = require("./utils/configLoader.js");
|
|
7
7
|
const commands = require("./commands/index.js");
|
|
8
8
|
const ListWithEscapePrompt = require("./utils/prompts/listWithEscape.js");
|
|
9
|
+
const { checkForUpdates } = require("./utils/updateChecker.js");
|
|
9
10
|
//
|
|
10
11
|
const packageJson = require("./package.json");
|
|
12
|
+
const boxWidth = 55;
|
|
11
13
|
//
|
|
12
14
|
// Register custom prompt with ESC support.
|
|
13
15
|
inquirer.registerPrompt("listWithEscape", ListWithEscapePrompt);
|
|
@@ -109,18 +111,36 @@ async function waitForKeypress() {
|
|
|
109
111
|
function showBanner() {
|
|
110
112
|
const mode = isLocalMode() ? "local" : "global";
|
|
111
113
|
console.log(
|
|
112
|
-
|
|
113
|
-
${chalk.hex("#FFFFFF")("
|
|
114
|
-
${chalk.hex("#ffff00")("
|
|
115
|
-
${chalk.hex("#fffF00")("
|
|
116
|
-
${chalk.hex("#FFCE00")("
|
|
117
|
-
${chalk.hex("#FFCE00")("█████████████ ")}
|
|
118
|
-
${chalk.hex("#F77B00")("█ ███████ █ ")}
|
|
119
|
-
${chalk.hex("#F77B00")("█ ███████ █ ")}
|
|
120
|
-
${chalk.hex("#E73100")("
|
|
121
|
-
${chalk.hex("#E73100")("
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
`${chalk.hex("#F77B00")("=".repeat(boxWidth))}
|
|
115
|
+
${chalk.hex("#FFFFFF")(" █ █ ")} |
|
|
116
|
+
${chalk.hex("#ffff00")(" █ █ ")} |
|
|
117
|
+
${chalk.hex("#fffF00")(" ███████ ")} | ${chalk.hex("#F77B00").bold("GCP Utils")} ${chalk.gray(`v${packageJson.version}`)}
|
|
118
|
+
${chalk.hex("#FFCE00")(" ███ █ ███ ")} | by ${chalk.whiteBright.bold("8BitsApps")}
|
|
119
|
+
${chalk.hex("#FFCE00")(" █████████████ ")} |
|
|
120
|
+
${chalk.hex("#F77B00")(" █ ███████ █ ")} | ${chalk.bgHex("#FFCE00").hex("#000000")("We make app for fun! (ツ)")}
|
|
121
|
+
${chalk.hex("#F77B00")(" █ ███████ █ ")} | ${chalk.hex("#FFCE00")("https://8bitsapps.com")}
|
|
122
|
+
${chalk.hex("#E73100")(" █ █ ")} |
|
|
123
|
+
${chalk.hex("#E73100")(" ██ ██ ")} | ${chalk.gray(`Mode:${mode}`)}
|
|
124
|
+
${chalk.hex("#F77B00")("_".repeat(boxWidth))}\n`);
|
|
125
|
+
}
|
|
126
|
+
//
|
|
127
|
+
/**
|
|
128
|
+
* Shows update notification if a new version is available.
|
|
129
|
+
* @param {{available: boolean, current: string, latest: string}|null} updateInfo
|
|
130
|
+
*/
|
|
131
|
+
function showUpdateNotification(updateInfo) {
|
|
132
|
+
if (!updateInfo || !updateInfo.available) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
//
|
|
136
|
+
const msg = `Update available: ${updateInfo.current} → ${updateInfo.latest}`;
|
|
137
|
+
const cmd = `npm install -g ${packageJson.name}`;
|
|
138
|
+
const sudoWrn = "(sudo might be required)";
|
|
139
|
+
//
|
|
140
|
+
console.log(chalk.white(` ${msg}`));
|
|
141
|
+
console.log(chalk.gray(` Run: ${chalk.cyan(cmd)}`));
|
|
142
|
+
console.log(chalk.gray(` ${chalk.cyan(sudoWrn)}`));
|
|
143
|
+
console.log(chalk.hex("#FFCE00")("_".repeat(boxWidth) + "\n"));
|
|
124
144
|
}
|
|
125
145
|
//
|
|
126
146
|
/**
|
|
@@ -128,6 +148,10 @@ _________________________________________________
|
|
|
128
148
|
*/
|
|
129
149
|
async function main() {
|
|
130
150
|
console.clear();
|
|
151
|
+
//
|
|
152
|
+
// Start update check in background (non-blocking).
|
|
153
|
+
const updateCheckPromise = checkForUpdates();
|
|
154
|
+
//
|
|
131
155
|
// Check if initialized.
|
|
132
156
|
if (!(await isInitialized())) {
|
|
133
157
|
console.log(chalk.yellow("Configuration not found."));
|
|
@@ -147,9 +171,27 @@ async function main() {
|
|
|
147
171
|
return;
|
|
148
172
|
}
|
|
149
173
|
//
|
|
174
|
+
// Wait for update check with timeout (max 2s).
|
|
175
|
+
let updateInfo = null;
|
|
176
|
+
try {
|
|
177
|
+
updateInfo = await Promise.race([
|
|
178
|
+
updateCheckPromise,
|
|
179
|
+
new Promise(resolve => setTimeout(() => resolve(null), 2000))
|
|
180
|
+
]);
|
|
181
|
+
} catch {
|
|
182
|
+
// Ignore errors.
|
|
183
|
+
}
|
|
184
|
+
//
|
|
150
185
|
while (true) {
|
|
151
186
|
console.clear();
|
|
152
187
|
showBanner();
|
|
188
|
+
//
|
|
189
|
+
// Show update notification only on first iteration.
|
|
190
|
+
if (updateInfo) {
|
|
191
|
+
showUpdateNotification(updateInfo);
|
|
192
|
+
updateInfo = null;
|
|
193
|
+
}
|
|
194
|
+
//
|
|
153
195
|
const cmdName = await showMainMenu();
|
|
154
196
|
//
|
|
155
197
|
// ESC pressed in main menu - exit.
|
package/package.json
CHANGED
|
@@ -39,6 +39,24 @@ class ListWithEscapePrompt extends ListPrompt {
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
//
|
|
42
|
+
// Handle delete key - delete selected item (only if deleteFilter allows).
|
|
43
|
+
events.keypress
|
|
44
|
+
.pipe(
|
|
45
|
+
takeUntil(events.line),
|
|
46
|
+
filter(({ key }) => key && key.name === "delete")
|
|
47
|
+
)
|
|
48
|
+
.forEach(() => {
|
|
49
|
+
const selectedValue = this.opt.choices.getChoice(this.selected).value;
|
|
50
|
+
// Check if delete is allowed for this item.
|
|
51
|
+
const deleteFilter = this.opt.deleteFilter;
|
|
52
|
+
if (deleteFilter && !deleteFilter(selectedValue)) {
|
|
53
|
+
return; // Ignore delete key for this item.
|
|
54
|
+
}
|
|
55
|
+
process.stdout.write(`\x1b[${(this.screen.height || 1) - 1}A\x1b[J\x1b[G`);
|
|
56
|
+
this.screen.done();
|
|
57
|
+
this.done({ action: "delete", value: selectedValue });
|
|
58
|
+
});
|
|
59
|
+
//
|
|
42
60
|
return super._run(cb);
|
|
43
61
|
}
|
|
44
62
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const packageJson = require("../package.json");
|
|
3
|
+
//
|
|
4
|
+
const NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
5
|
+
const PACKAGE_NAME = packageJson.name;
|
|
6
|
+
const CURRENT_VERSION = packageJson.version;
|
|
7
|
+
//
|
|
8
|
+
/**
|
|
9
|
+
* Compares two semver versions.
|
|
10
|
+
* @param {string} current - Current version.
|
|
11
|
+
* @param {string} latest - Latest version from registry.
|
|
12
|
+
* @returns {boolean} True if latest is newer than current.
|
|
13
|
+
*/
|
|
14
|
+
function isNewerVersion(current, latest) {
|
|
15
|
+
const currentParts = current.split(".").map(Number);
|
|
16
|
+
const latestParts = latest.split(".").map(Number);
|
|
17
|
+
//
|
|
18
|
+
for (let i = 0; i < 3; i++) {
|
|
19
|
+
if (latestParts[i] > currentParts[i]) return true;
|
|
20
|
+
if (latestParts[i] < currentParts[i]) return false;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
//
|
|
25
|
+
/**
|
|
26
|
+
* Fetches the latest version from npm registry.
|
|
27
|
+
* @returns {Promise<string|null>} Latest version or null on error.
|
|
28
|
+
*/
|
|
29
|
+
async function fetchLatestVersion() {
|
|
30
|
+
try {
|
|
31
|
+
const url = `${NPM_REGISTRY_URL}/${PACKAGE_NAME}/latest`;
|
|
32
|
+
const response = await axios.get(url, { timeout: 3000 });
|
|
33
|
+
return response.data.version || null;
|
|
34
|
+
} catch {
|
|
35
|
+
// Silently fail - network issues should not block the CLI.
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//
|
|
40
|
+
/**
|
|
41
|
+
* Checks for updates and returns update info.
|
|
42
|
+
* @returns {Promise<{available: boolean, current: string, latest: string}|null>}
|
|
43
|
+
*/
|
|
44
|
+
async function checkForUpdates() {
|
|
45
|
+
const latestVersion = await fetchLatestVersion();
|
|
46
|
+
//
|
|
47
|
+
if (!latestVersion) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
//
|
|
51
|
+
const updateAvailable = isNewerVersion(CURRENT_VERSION, latestVersion);
|
|
52
|
+
//
|
|
53
|
+
return {
|
|
54
|
+
available: updateAvailable,
|
|
55
|
+
current: CURRENT_VERSION,
|
|
56
|
+
latest: latestVersion
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//
|
|
60
|
+
module.exports = {
|
|
61
|
+
checkForUpdates,
|
|
62
|
+
isNewerVersion,
|
|
63
|
+
fetchLatestVersion,
|
|
64
|
+
CURRENT_VERSION,
|
|
65
|
+
PACKAGE_NAME
|
|
66
|
+
};
|