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
+ };
0 commit comments