Skip to content

Commit 02c515a

Browse files
cursoragentchirag
andcommitted
Add update command to CLI for checking and performing version upgrades
Co-authored-by: chirag <[email protected]>
1 parent ad31e7a commit 02c515a

File tree

3 files changed

+288
-0
lines changed

3 files changed

+288
-0
lines changed

templates/cli/index.js.twig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const { types } = require("./lib/commands/types");
1818
const { pull } = require("./lib/commands/pull");
1919
const { run } = require("./lib/commands/run");
2020
const { push, deploy } = require("./lib/commands/push");
21+
const { update } = require("./lib/commands/update");
2122
{% else %}
2223
const { migrate } = require("./lib/commands/generic");
2324
{% endif %}
@@ -72,6 +73,7 @@ program
7273
.addCommand(types)
7374
.addCommand(deploy)
7475
.addCommand(run)
76+
.addCommand(update)
7577
.addCommand(logout)
7678
{% endif %}
7779
{% for service in spec.services %}
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const os = require("os");
4+
const { spawn } = require("child_process");
5+
const { Command } = require("commander");
6+
const { fetch } = require("undici");
7+
const chalk = require("chalk");
8+
const inquirer = require("inquirer");
9+
const { success, log, warn, error, hint, actionRunner, commandDescriptions } = require("../parser");
10+
const { version } = require("../../package.json");
11+
12+
/**
13+
* Check if the CLI was installed via npm
14+
*/
15+
const isInstalledViaNpm = () => {
16+
try {
17+
// Check if we're in a global npm installation
18+
const execPath = process.argv[0];
19+
const scriptPath = process.argv[1];
20+
21+
// Common indicators of npm global installation
22+
if (scriptPath.includes('node_modules') && scriptPath.includes('appwrite-cli')) {
23+
return true;
24+
}
25+
26+
// Check if npm is available and appwrite-cli is installed globally
27+
return false;
28+
} catch (e) {
29+
return false;
30+
}
31+
};
32+
33+
/**
34+
* Check if the CLI was installed via Homebrew
35+
*/
36+
const isInstalledViaHomebrew = () => {
37+
try {
38+
const scriptPath = process.argv[1];
39+
return scriptPath.includes('/opt/homebrew/') || scriptPath.includes('/usr/local/Cellar/');
40+
} catch (e) {
41+
return false;
42+
}
43+
};
44+
45+
/**
46+
* Get the latest version from npm registry
47+
*/
48+
const getLatestVersion = async () => {
49+
try {
50+
const response = await fetch('https://registry.npmjs.org/{{ language.params.npmPackage|caseDash }}/latest');
51+
if (!response.ok) {
52+
throw new Error(`HTTP ${response.status}`);
53+
}
54+
const data = await response.json();
55+
return data.version;
56+
} catch (e) {
57+
throw new Error(`Failed to fetch latest version: ${e.message}`);
58+
}
59+
};
60+
61+
/**
62+
* Compare versions using semantic versioning
63+
*/
64+
const compareVersions = (current, latest) => {
65+
const currentParts = current.split('.').map(Number);
66+
const latestParts = latest.split('.').map(Number);
67+
68+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
69+
const currentPart = currentParts[i] || 0;
70+
const latestPart = latestParts[i] || 0;
71+
72+
if (latestPart > currentPart) return 1; // Latest is newer
73+
if (latestPart < currentPart) return -1; // Current is newer
74+
}
75+
76+
return 0; // Same version
77+
};
78+
79+
/**
80+
* Execute command and return promise
81+
*/
82+
const execCommand = (command, args = [], options = {}) => {
83+
return new Promise((resolve, reject) => {
84+
const child = spawn(command, args, {
85+
stdio: 'inherit',
86+
shell: true,
87+
...options
88+
});
89+
90+
child.on('close', (code) => {
91+
if (code === 0) {
92+
resolve();
93+
} else {
94+
reject(new Error(`Command failed with exit code ${code}`));
95+
}
96+
});
97+
98+
child.on('error', (err) => {
99+
reject(err);
100+
});
101+
});
102+
};
103+
104+
/**
105+
* Update via npm
106+
*/
107+
const updateViaNpm = async () => {
108+
log("Updating via npm...");
109+
try {
110+
await execCommand('npm', ['install', '-g', '{{ language.params.npmPackage|caseDash }}@latest']);
111+
success("Successfully updated {{ language.params.executableName|caseLower }} via npm!");
112+
hint("Run '{{ language.params.executableName|caseLower }} --version' to verify the new version.");
113+
} catch (e) {
114+
error(`Failed to update via npm: ${e.message}`);
115+
hint("Try running: npm install -g {{ language.params.npmPackage|caseDash }}@latest");
116+
}
117+
};
118+
119+
/**
120+
* Update via Homebrew
121+
*/
122+
const updateViaHomebrew = async () => {
123+
log("Updating via Homebrew...");
124+
try {
125+
await execCommand('brew', ['upgrade', '{{ language.params.executableName|caseLower }}']);
126+
success("Successfully updated {{ language.params.executableName|caseLower }} via Homebrew!");
127+
hint("Run '{{ language.params.executableName|caseLower }} --version' to verify the new version.");
128+
} catch (e) {
129+
error(`Failed to update via Homebrew: ${e.message}`);
130+
hint("Try running: brew upgrade {{ language.params.executableName|caseLower }}");
131+
}
132+
};
133+
134+
/**
135+
* Update via installation script
136+
*/
137+
const updateViaScript = async () => {
138+
log("Updating via installation script...");
139+
const platform = os.platform();
140+
141+
try {
142+
if (platform === 'win32') {
143+
// Windows PowerShell script
144+
await execCommand('powershell', ['-Command', 'iwr -useb https://appwrite.io/cli/install.ps1 | iex']);
145+
} else {
146+
// Linux/macOS bash script
147+
await execCommand('sh', ['-c', 'wget -q https://appwrite.io/cli/install.sh -O - | /bin/bash']);
148+
}
149+
success("Successfully updated {{ language.params.executableName|caseLower }} via installation script!");
150+
hint("Run '{{ language.params.executableName|caseLower }} --version' to verify the new version.");
151+
} catch (e) {
152+
error(`Failed to update via installation script: ${e.message}`);
153+
if (platform === 'win32') {
154+
hint("Try running: iwr -useb https://appwrite.io/cli/install.ps1 | iex");
155+
} else {
156+
hint("Try running: wget -q https://appwrite.io/cli/install.sh -O - | /bin/bash");
157+
}
158+
}
159+
};
160+
161+
/**
162+
* Show manual update instructions
163+
*/
164+
const showManualInstructions = (latestVersion) => {
165+
log("Manual update options:");
166+
console.log("");
167+
168+
log(`${chalk.bold("Option 1: NPM")}`);
169+
console.log(` npm install -g {{ language.params.npmPackage|caseDash }}@latest`);
170+
console.log("");
171+
172+
log(`${chalk.bold("Option 2: Homebrew (macOS)")}`);
173+
console.log(` brew upgrade {{ language.params.executableName|caseLower }}`);
174+
console.log("");
175+
176+
log(`${chalk.bold("Option 3: Installation Script")}`);
177+
if (os.platform() === 'win32') {
178+
console.log(` iwr -useb https://appwrite.io/cli/install.ps1 | iex`);
179+
} else {
180+
console.log(` wget -q https://appwrite.io/cli/install.sh -O - | /bin/bash`);
181+
}
182+
console.log("");
183+
184+
log(`${chalk.bold("Option 4: Download Binary")}`);
185+
console.log(` Visit: https://github.com/appwrite/sdk-for-cli/releases/tag/${latestVersion}`);
186+
console.log("");
187+
};
188+
189+
/**
190+
* Main update function
191+
*/
192+
const updateCli = async ({ manual } = {}) => {
193+
log(`Current version: ${chalk.bold(version)}`);
194+
195+
try {
196+
log("Checking for updates...");
197+
const latestVersion = await getLatestVersion();
198+
199+
const comparison = compareVersions(version, latestVersion);
200+
201+
if (comparison === 0) {
202+
success(`You're already running the latest version (${chalk.bold(version)})!`);
203+
return;
204+
} else if (comparison < 0) {
205+
warn(`You're running a newer version (${chalk.bold(version)}) than the latest released version (${chalk.bold(latestVersion)}).`);
206+
hint("This might be a pre-release or development version.");
207+
return;
208+
}
209+
210+
log(`Latest version: ${chalk.bold(latestVersion)}`);
211+
log(`${chalk.green('→')} A new version is available!`);
212+
console.log("");
213+
214+
if (manual) {
215+
showManualInstructions(latestVersion);
216+
return;
217+
}
218+
219+
// Auto-detect installation method and suggest appropriate update
220+
if (isInstalledViaNpm()) {
221+
const answer = await inquirer.prompt([{
222+
type: 'confirm',
223+
name: 'update',
224+
message: 'Update via npm?',
225+
default: true
226+
}]);
227+
228+
if (answer.update) {
229+
await updateViaNpm();
230+
}
231+
} else if (isInstalledViaHomebrew()) {
232+
const answer = await inquirer.prompt([{
233+
type: 'confirm',
234+
name: 'update',
235+
message: 'Update via Homebrew?',
236+
default: true
237+
}]);
238+
239+
if (answer.update) {
240+
await updateViaHomebrew();
241+
}
242+
} else {
243+
// Unknown installation method - show options
244+
const answer = await inquirer.prompt([{
245+
type: 'list',
246+
name: 'method',
247+
message: 'How would you like to update?',
248+
choices: [
249+
{ name: 'NPM (npm install -g)', value: 'npm' },
250+
{ name: 'Homebrew (macOS)', value: 'homebrew' },
251+
{ name: 'Installation Script', value: 'script' },
252+
{ name: 'Show manual instructions', value: 'manual' }
253+
]
254+
}]);
255+
256+
switch (answer.method) {
257+
case 'npm':
258+
await updateViaNpm();
259+
break;
260+
case 'homebrew':
261+
await updateViaHomebrew();
262+
break;
263+
case 'script':
264+
await updateViaScript();
265+
break;
266+
case 'manual':
267+
showManualInstructions(latestVersion);
268+
break;
269+
}
270+
}
271+
272+
} catch (e) {
273+
error(`Failed to check for updates: ${e.message}`);
274+
hint("You can manually check for updates at: https://github.com/appwrite/sdk-for-cli/releases");
275+
}
276+
};
277+
278+
const update = new Command("update")
279+
.description("Update the {{ spec.title|caseUcfirst }} CLI to the latest version")
280+
.option("--manual", "Show manual update instructions instead of auto-updating")
281+
.action(actionRunner(updateCli));
282+
283+
module.exports = {
284+
update
285+
};

templates/cli/lib/parser.js.twig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ const commandDescriptions = {
216216
"sites": `The sites command allows you to view, create and manage your Appwrite Sites.`,
217217
"storage": `The storage command allows you to manage your project files.`,
218218
"teams": `The teams command allows you to group users of your project to enable them to share read and write access to your project resources.`,
219+
"update": `The update command allows you to update the {{ spec.title|caseUcfirst }} CLI to the latest version.`,
219220
"users": `The users command allows you to manage your project users.`,
220221
"client": `The client command allows you to configure your CLI`,
221222
"login": `The login command allows you to authenticate and manage a user account.`,

0 commit comments

Comments
 (0)