Skip to content

Commit d1b5823

Browse files
committed
feat: add version checking with update prompt to CLI -v flag
- Add checkVersionAndUpdate() function that displays current version and checks for latest version from npm registry - Show update prompt with 'appwrite update' suggestion when newer version is available - Extract version checking functions to utils.js.twig for reusability between -v flag and update command - Intercept -v/--version flags before Commander.js processing to enable custom version display - Handle network errors gracefully, always show current version even when offline - Display appropriate messages for up-to-date, pre-release, and newer versions available scenarios
1 parent bce5297 commit d1b5823

File tree

3 files changed

+114
-97
lines changed

3 files changed

+114
-97
lines changed

templates/cli/index.js.twig

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const chalk = require("chalk");
1010
const { version } = require("./package.json");
1111
const { commandDescriptions, cliConfig } = require("./lib/parser");
1212
const { client } = require("./lib/commands/generic");
13+
const { getLatestVersion, compareVersions } = require("./lib/utils");
1314
const inquirer = require("inquirer");
1415
{% if sdk.test != "true" %}
1516
const { login, logout, whoami, migrate, register } = require("./lib/commands/generic");
@@ -28,6 +29,41 @@ const { {{ service.name | caseLower }} } = require("./lib/commands/{{ service.na
2829

2930
inquirer.registerPrompt('search-list', require('inquirer-search-list'));
3031

32+
/**
33+
* Check for updates and show version information
34+
*/
35+
async function checkVersionAndUpdate() {
36+
process.stdout.write(chalk.bold(`{{ language.params.executableName|caseLower }} version ${version}`) + '\n');
37+
38+
try {
39+
const latestVersion = await getLatestVersion();
40+
const comparison = compareVersions(version, latestVersion);
41+
42+
if (comparison > 0) {
43+
// Current version is older than latest
44+
process.stdout.write(chalk.yellow(`\n⚠️ A newer version is available: ${chalk.bold(latestVersion)}`) + '\n');
45+
process.stdout.write(chalk.cyan(`💡 Run '${chalk.bold('{{ language.params.executableName|caseLower }} update')}' to update to the latest version.`) + '\n');
46+
} else if (comparison === 0) {
47+
process.stdout.write(chalk.green('\n✅ You are running the latest version!') + '\n');
48+
} else {
49+
// Current version is newer than latest (pre-release/dev)
50+
process.stdout.write(chalk.blue('\n🚀 You are running a pre-release or development version.') + '\n');
51+
}
52+
} catch (error) {
53+
// Silently fail version check, just show current version
54+
process.stdout.write(chalk.gray('\n(Unable to check for updates)') + '\n');
55+
}
56+
}
57+
58+
// Intercept version flag before Commander.js processes it
59+
if (process.argv.includes('-v') || process.argv.includes('--version')) {
60+
(async () => {
61+
await checkVersionAndUpdate();
62+
process.exit(0);
63+
})();
64+
return;
65+
}
66+
3167
program
3268
.description(commandDescriptions['main'])
3369
.configureHelp({
Lines changed: 40 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
const fs = require("fs");
22
const path = require("path");
3-
const os = require("os");
43
const { spawn } = require("child_process");
54
const { Command } = require("commander");
65
const { fetch } = require("undici");
76
const chalk = require("chalk");
87
const inquirer = require("inquirer");
98
const { success, log, warn, error, hint, actionRunner, commandDescriptions } = require("../parser");
9+
const { getLatestVersion, compareVersions } = require("../utils");
1010
const { version } = require("../../package.json");
1111

1212
/**
@@ -15,13 +15,11 @@ const { version } = require("../../package.json");
1515
const isInstalledViaNpm = () => {
1616
try {
1717
const scriptPath = process.argv[1];
18-
19-
// Common indicators of npm global installation
18+
2019
if (scriptPath.includes('node_modules') && scriptPath.includes('appwrite-cli')) {
2120
return true;
2221
}
23-
24-
// Check for npm global paths
22+
2523
if (scriptPath.includes('/usr/local/lib/node_modules/') ||
2624
scriptPath.includes('/opt/homebrew/lib/node_modules/') ||
2725
scriptPath.includes('/.npm-global/') ||
@@ -47,39 +45,7 @@ const isInstalledViaHomebrew = () => {
4745
}
4846
};
4947

50-
/**
51-
* Get the latest version from npm registry
52-
*/
53-
const getLatestVersion = async () => {
54-
try {
55-
const response = await fetch('https://registry.npmjs.org/{{ language.params.npmPackage|caseDash }}/latest');
56-
if (!response.ok) {
57-
throw new Error(`HTTP ${response.status}`);
58-
}
59-
const data = await response.json();
60-
return data.version;
61-
} catch (e) {
62-
throw new Error(`Failed to fetch latest version: ${e.message}`);
63-
}
64-
};
6548

66-
/**
67-
* Compare versions using semantic versioning
68-
*/
69-
const compareVersions = (current, latest) => {
70-
const currentParts = current.split('.').map(Number);
71-
const latestParts = latest.split('.').map(Number);
72-
73-
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
74-
const currentPart = currentParts[i] || 0;
75-
const latestPart = latestParts[i] || 0;
76-
77-
if (latestPart > currentPart) return 1; // Latest is newer
78-
if (latestPart < currentPart) return -1; // Current is newer
79-
}
80-
81-
return 0; // Same version
82-
};
8349

8450
/**
8551
* Execute command and return promise
@@ -115,7 +81,6 @@ const updateViaNpm = async () => {
11581
success("Updated to latest version via npm!");
11682
hint("Run '{{ language.params.executableName|caseLower }} --version' to verify the new version.");
11783
} catch (e) {
118-
// Check if error is due to file already existing (likely already latest version)
11984
if (e.message.includes('EEXIST') || e.message.includes('file already exists')) {
12085
success("Latest version is already installed via npm!");
12186
hint("The CLI is up to date. Run '{{ language.params.executableName|caseLower }} --version' to verify.");
@@ -135,7 +100,6 @@ const updateViaHomebrew = async () => {
135100
success("Updated to latest version via Homebrew!");
136101
hint("Run '{{ language.params.executableName|caseLower }} --version' to verify the new version.");
137102
} catch (e) {
138-
// Check if error is due to package already being up-to-date
139103
if (e.message.includes('already installed') || e.message.includes('up-to-date')) {
140104
success("Latest version is already installed via Homebrew!");
141105
hint("The CLI is up to date. Run '{{ language.params.executableName|caseLower }} --version' to verify.");
@@ -146,42 +110,11 @@ const updateViaHomebrew = async () => {
146110
}
147111
};
148112

149-
/**
150-
* Update via installation script
151-
*/
152-
const updateViaScript = async () => {
153-
const platform = os.platform();
154-
155-
try {
156-
if (platform === 'win32') {
157-
// Windows PowerShell script
158-
await execCommand('powershell', ['-Command', 'iwr -useb https://appwrite.io/cli/install.ps1 | iex']);
159-
} else {
160-
// Linux/macOS bash script
161-
await execCommand('sh', ['-c', 'wget -q https://appwrite.io/cli/install.sh -O - | /bin/bash']);
162-
}
163-
success("Updated to latest version via installation script!");
164-
hint("Run '{{ language.params.executableName|caseLower }} --version' to verify the new version.");
165-
} catch (e) {
166-
// Check if error indicates already up-to-date
167-
if (e.message.includes('already exists') || e.message.includes('up-to-date')) {
168-
success("Latest version is already installed!");
169-
hint("The CLI is up to date. Run '{{ language.params.executableName|caseLower }} --version' to verify.");
170-
} else {
171-
error(`Failed to update via installation script: ${e.message}`);
172-
if (platform === 'win32') {
173-
hint("Try running: iwr -useb https://appwrite.io/cli/install.ps1 | iex");
174-
} else {
175-
hint("Try running: wget -q https://appwrite.io/cli/install.sh -O - | /bin/bash");
176-
}
177-
}
178-
}
179-
};
180-
181113
/**
182114
* Show manual update instructions
183115
*/
184116
const showManualInstructions = (latestVersion) => {
117+
console.log("");
185118
log("Manual update options:");
186119
console.log("");
187120

@@ -193,17 +126,40 @@ const showManualInstructions = (latestVersion) => {
193126
console.log(` brew upgrade {{ language.params.executableName|caseLower }}`);
194127
console.log("");
195128

196-
log(`${chalk.bold("Option 3: Installation Script")}`);
197-
if (os.platform() === 'win32') {
198-
console.log(` iwr -useb https://appwrite.io/cli/install.ps1 | iex`);
199-
} else {
200-
console.log(` wget -q https://appwrite.io/cli/install.sh -O - | /bin/bash`);
129+
log(`${chalk.bold("Option 3: Download Binary")}`);
130+
console.log(` Visit: https://github.com/{{ language.params.npmPackage|caseDash }}/releases/tag/${latestVersion}`);
131+
};
132+
133+
/**
134+
* Show interactive menu for choosing update method
135+
*/
136+
const chooseUpdateMethod = async (latestVersion) => {
137+
const choices = [
138+
{ name: 'NPM', value: 'npm' },
139+
{ name: 'Homebrew (macOS)', value: 'homebrew' },
140+
{ name: 'Show manual instructions', value: 'manual' }
141+
];
142+
143+
const { method } = await inquirer.prompt([
144+
{
145+
type: 'list',
146+
name: 'method',
147+
message: 'Could not detect installation method. How would you like to update?',
148+
choices: choices
149+
}
150+
]);
151+
152+
switch (method) {
153+
case 'npm':
154+
await updateViaNpm();
155+
break;
156+
case 'homebrew':
157+
await updateViaHomebrew();
158+
break;
159+
case 'manual':
160+
showManualInstructions(latestVersion);
161+
break;
201162
}
202-
console.log("");
203-
204-
log(`${chalk.bold("Option 4: Download Binary")}`);
205-
console.log(` Visit: https://github.com/appwrite/sdk-for-cli/releases/tag/${latestVersion}`);
206-
console.log("");
207163
};
208164

209165
/**
@@ -225,35 +181,23 @@ const updateCli = async ({ manual } = {}) => {
225181
}
226182

227183
log(`Updating from ${chalk.blue(version)} to ${chalk.green(latestVersion)}...`);
228-
console.log("");
229184

230185
if (manual) {
231186
showManualInstructions(latestVersion);
232187
return;
233188
}
234189

235-
// Auto-detect installation method and update automatically
236190
if (isInstalledViaNpm()) {
237191
await updateViaNpm();
238192
} else if (isInstalledViaHomebrew()) {
239193
await updateViaHomebrew();
240194
} else {
241-
// Unknown installation method - try npm first, then show manual instructions
242-
try {
243-
await updateViaNpm();
244-
} catch (e) {
245-
// If npm failed and it's not because the version is already installed
246-
if (!e.message.includes('EEXIST') && !e.message.includes('file already exists')) {
247-
error("Could not detect installation method or update failed.");
248-
showManualInstructions(latestVersion);
249-
}
250-
// If it's EEXIST error, updateViaNpm already handled it with success message
251-
}
195+
await chooseUpdateMethod(latestVersion);
252196
}
253197

254198
} catch (e) {
255199
error(`Failed to check for updates: ${e.message}`);
256-
hint("You can manually check for updates at: https://github.com/appwrite/sdk-for-cli/releases");
200+
hint("You can manually check for updates at: https://github.com/{{ language.params.npmPackage|caseDash }}/releases");
257201
}
258202
};
259203

@@ -264,4 +208,4 @@ const update = new Command("update")
264208

265209
module.exports = {
266210
update
267-
};
211+
};

templates/cli/lib/utils.js.twig

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,43 @@ const path = require("path");
33
const net = require("net");
44
const childProcess = require('child_process');
55
const chalk = require('chalk');
6+
const { fetch } = require("undici");
67
const { localConfig, globalConfig } = require("./config");
78

9+
/**
10+
* Get the latest version from npm registry
11+
*/
12+
async function getLatestVersion() {
13+
try {
14+
const response = await fetch('https://registry.npmjs.org/{{ language.params.npmPackage|caseDash }}/latest');
15+
if (!response.ok) {
16+
throw new Error(`HTTP ${response.status}`);
17+
}
18+
const data = await response.json();
19+
return data.version;
20+
} catch (e) {
21+
throw new Error(`Failed to fetch latest version: ${e.message}`);
22+
}
23+
}
24+
25+
/**
26+
* Compare versions using semantic versioning
27+
*/
28+
function compareVersions(current, latest) {
29+
const currentParts = current.split('.').map(Number);
30+
const latestParts = latest.split('.').map(Number);
31+
32+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
33+
const currentPart = currentParts[i] || 0;
34+
const latestPart = latestParts[i] || 0;
35+
36+
if (latestPart > currentPart) return 1; // Latest is newer
37+
if (latestPart < currentPart) return -1; // Current is newer
38+
}
39+
40+
return 0; // Same version
41+
}
42+
843
function getAllFiles(folder) {
944
const files = [];
1045
for (const pathDir of fs.readdirSync(folder)) {
@@ -285,5 +320,7 @@ module.exports = {
285320
systemHasCommand,
286321
checkDeployConditions,
287322
showConsoleLink,
288-
isCloud
323+
isCloud,
324+
getLatestVersion,
325+
compareVersions
289326
};

0 commit comments

Comments
 (0)