alurkerja-cli 1.0.22 → 1.0.24

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/index.js CHANGED
@@ -5,6 +5,12 @@
5
5
  *
6
6
  * This is a bootstrapper that downloads and executes the Go binary.
7
7
  * As per PRD: npx wrapper should NOT contain business logic.
8
+ *
9
+ * Note: Auto-update functionality has been disabled to prevent
10
+ * automatic version checks and downloads on every startup.
11
+ * Binary will only be downloaded if it doesn't exist locally.
12
+ *
13
+ * Downloads from: https://minio-admin.merapi.javan.id/javan-cli/dist/
8
14
  */
9
15
 
10
16
  const fs = require('fs');
@@ -13,13 +19,11 @@ const https = require('https');
13
19
  const { execSync, spawn } = require('child_process');
14
20
  const os = require('os');
15
21
 
16
- const GITLAB_BASE = 'https://gitlab.javan.co.id/alurkerja/on-premises/toolkits/alurkerja-cli';
17
- const GITLAB_TOKEN = 'javan-Lv77immsAVHiT3RnqrKa'; // GitLab private token
22
+ const MINIO_BASE_URL = 'https://minio.merapi.javan.id/javan-cli/dist';
18
23
  const VERSION = 'v1.0.0'; // Will be replaced during build
19
24
  const INSTALL_DIR = path.join(os.homedir(), '.alurkerja', 'bin');
20
25
  const BINARY_NAME = os.platform() === 'win32' ? 'alurkerja.exe' : 'alurkerja';
21
26
  const BINARY_PATH = path.join(INSTALL_DIR, BINARY_NAME);
22
- const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
23
27
 
24
28
  /**
25
29
  * Detect current platform
@@ -54,15 +58,20 @@ function getVersion() {
54
58
  }
55
59
 
56
60
  /**
57
- * Download binary from GitLab releases
61
+ * Check latest version (simplified - always returns current version)
62
+ */
63
+ async function getLatestVersion() {
64
+ // Since we moved to direct MinIO URLs, version checking is simplified
65
+ // Always return current package version
66
+ return VERSION;
67
+ }
68
+
69
+ /**
70
+ * Download binary from MinIO storage
58
71
  */
59
72
  async function downloadBinary(version, platform) {
60
73
  const binaryName = `alurkerja-${platform.os}-${platform.arch}${platform.os === 'windows' ? '.exe' : ''}`;
61
-
62
- // Use GitLab API v4 to get raw file with PRIVATE-TOKEN authentication
63
- const projectPath = encodeURIComponent('alurkerja/on-premises/toolkits/alurkerja-cli');
64
- const filePath = encodeURIComponent(`dist/${binaryName}`);
65
- const downloadUrl = `https://gitlab.javan.co.id/api/v4/projects/${projectPath}/repository/files/${filePath}/raw?ref=master`;
74
+ const downloadUrl = `${MINIO_BASE_URL}/${binaryName}`;
66
75
 
67
76
  console.log(`📥 Downloading Alurkerja CLI ${version} for ${platform.os}-${platform.arch}...`);
68
77
 
@@ -76,59 +85,52 @@ async function downloadBinary(version, platform) {
76
85
  const file = fs.createWriteStream(tempPath);
77
86
  let downloadComplete = false;
78
87
 
79
- const downloadFile = (targetUrl) => {
80
- const url = new URL(targetUrl);
81
- const options = {
82
- hostname: url.hostname,
83
- path: url.pathname + url.search,
84
- headers: {
85
- 'PRIVATE-TOKEN': GITLAB_TOKEN
86
- },
87
- followRedirect: true
88
- };
89
-
90
- const req = https.get(options, (res) => {
91
- if (res.statusCode === 302 || res.statusCode === 301) {
92
- // Follow redirect with PRIVATE-TOKEN header
93
- const redirectUrl = res.headers.location.startsWith('http')
94
- ? res.headers.location
95
- : `https://${url.hostname}${res.headers.location}`;
96
-
97
- return downloadFile(redirectUrl);
98
- }
99
-
100
- if (res.statusCode !== 200) {
101
- file.close();
102
- try { fs.unlinkSync(tempPath); } catch {}
103
- reject(new Error(`Download failed with status ${res.statusCode}`));
104
- return;
88
+ const url = new URL(downloadUrl);
89
+ const options = {
90
+ hostname: url.hostname,
91
+ path: url.pathname,
92
+ method: 'GET'
93
+ };
94
+
95
+ const req = https.get(options, (res) => {
96
+ // Handle redirects
97
+ if (res.statusCode === 302 || res.statusCode === 301) {
98
+ const redirectUrl = res.headers.location;
99
+ if (redirectUrl) {
100
+ const newUrl = redirectUrl.startsWith('http') ? redirectUrl : `https://${url.hostname}${redirectUrl}`;
101
+ return downloadBinary(version, platform).then(resolve).catch(reject);
105
102
  }
106
-
107
- res.pipe(file);
108
- res.on('end', () => {
109
- downloadComplete = true;
110
- });
111
- });
103
+ }
112
104
 
113
- req.on('error', (err) => {
114
- if (!downloadComplete) {
115
- file.close();
116
- try { fs.unlinkSync(tempPath); } catch {}
117
- reject(err);
118
- }
119
- });
105
+ if (res.statusCode !== 200) {
106
+ file.close();
107
+ try { fs.unlinkSync(tempPath); } catch {}
108
+ reject(new Error(`Download failed with status ${res.statusCode}: ${downloadUrl}`));
109
+ return;
110
+ }
120
111
 
121
- req.setTimeout(30000, () => {
122
- if (!downloadComplete) {
123
- req.abort();
124
- file.close();
125
- try { fs.unlinkSync(tempPath); } catch {}
126
- reject(new Error('Download timeout'));
127
- }
112
+ res.pipe(file);
113
+ res.on('end', () => {
114
+ downloadComplete = true;
128
115
  });
129
- };
116
+ });
117
+
118
+ req.on('error', (err) => {
119
+ if (!downloadComplete) {
120
+ file.close();
121
+ try { fs.unlinkSync(tempPath); } catch {}
122
+ reject(new Error(`Download failed: ${err.message}`));
123
+ }
124
+ });
130
125
 
131
- downloadFile(downloadUrl);
126
+ req.setTimeout(30000, () => {
127
+ if (!downloadComplete) {
128
+ req.abort();
129
+ file.close();
130
+ try { fs.unlinkSync(tempPath); } catch {}
131
+ reject(new Error('Download timeout'));
132
+ }
133
+ });
132
134
 
133
135
  file.on('error', (err) => {
134
136
  try { fs.unlinkSync(tempPath); } catch {}
@@ -147,7 +149,7 @@ async function downloadBinary(version, platform) {
147
149
  const stats = fs.statSync(tempPath);
148
150
  if (stats.size < 1000) {
149
151
  try { fs.unlinkSync(tempPath); } catch {}
150
- reject(new Error(`Download failed: received invalid file (${stats.size} bytes)`));
152
+ reject(new Error(`Download failed: invalid file size (${stats.size} bytes)`));
151
153
  return;
152
154
  }
153
155
 
@@ -191,29 +193,19 @@ function binaryExists() {
191
193
  }
192
194
 
193
195
  /**
194
- * Get version of installed binary
196
+ * Get version of installed binary (disabled to prevent auto-updates)
195
197
  */
196
198
  function getInstalledVersion() {
197
- try {
198
- const output = execSync(`"${BINARY_PATH}" --version`, { encoding: 'utf8', timeout: 5000 });
199
- const match = output.match(/v?(\d+\.\d+\.\d+)/);
200
- return match ? match[1] : null;
201
- } catch {
202
- return null;
203
- }
199
+ // Always return current version to prevent version-based updates
200
+ return VERSION.replace('v', '');
204
201
  }
205
202
 
206
203
  /**
207
- * Check if binary file is older than cache duration
204
+ * Check if binary file is older than cache duration (disabled to prevent auto-updates)
208
205
  */
209
206
  function isBinaryStale() {
210
- try {
211
- const stats = fs.statSync(BINARY_PATH);
212
- const ageMs = Date.now() - stats.mtimeMs;
213
- return ageMs > CACHE_DURATION_MS;
214
- } catch {
215
- return true; // If can't read stats, consider it stale
216
- }
207
+ // Always return false to prevent time-based updates
208
+ return false;
217
209
  }
218
210
 
219
211
  /**
@@ -223,6 +215,38 @@ function hasForceDownloadFlag() {
223
215
  return process.argv.includes('--force-download');
224
216
  }
225
217
 
218
+ /**
219
+ * Check if update command is requested
220
+ */
221
+ function isUpdateCommand() {
222
+ return process.argv.includes('update') && !process.argv.includes('--help');
223
+ }
224
+
225
+ /**
226
+ * Show update information
227
+ */
228
+ async function showUpdateInfo() {
229
+ try {
230
+ const currentVersion = VERSION;
231
+ const latestVersion = await getLatestVersion();
232
+ const forceDownload = hasForceDownloadFlag();
233
+
234
+ console.log(`📋 Current version: ${currentVersion}`);
235
+ console.log(`🔄 Latest version: ${latestVersion}`);
236
+
237
+ if (currentVersion === latestVersion && !forceDownload) {
238
+ console.log('✅ You already have the latest version!');
239
+ return false;
240
+ }
241
+
242
+ console.log('🔄 Update available!');
243
+ return true;
244
+ } catch (error) {
245
+ console.log(`⚠️ Could not check latest version: ${error.message}`);
246
+ return hasForceDownloadFlag();
247
+ }
248
+ }
249
+
226
250
  /**
227
251
  * Execute the Go binary with provided arguments
228
252
  */
@@ -249,18 +273,37 @@ async function bootstrap() {
249
273
  try {
250
274
  const platform = detectPlatform();
251
275
  const version = getVersion();
252
- const installedVersion = getInstalledVersion();
253
276
  const forceDownload = hasForceDownloadFlag();
277
+ const isUpdate = isUpdateCommand();
278
+
279
+ // Handle update command specially
280
+ if (isUpdate) {
281
+ const shouldUpdate = await showUpdateInfo();
282
+
283
+ if (shouldUpdate || forceDownload) {
284
+ console.log('📥 Downloading latest version...');
285
+ await downloadBinary(version, platform);
286
+ console.log('✅ Update completed successfully!');
287
+ console.log('🎉 Alurkerja CLI has been updated to the latest version');
288
+ }
289
+
290
+ // For update command, don't continue to execute the binary
291
+ // Show current version and exit
292
+ setTimeout(() => {
293
+ const args = ['--version'];
294
+ executeBinary(args);
295
+ }, 100);
296
+ return;
297
+ }
254
298
 
255
- // Check if we need to download the binary
256
- const needsDownload = forceDownload ||
257
- !binaryExists() ||
258
- (installedVersion !== version && version !== 'dev') ||
259
- isBinaryStale();
299
+ // Only download if binary doesn't exist or force download is requested
300
+ const needsDownload = forceDownload || !binaryExists();
260
301
 
261
302
  if (needsDownload) {
262
303
  if (forceDownload) {
263
304
  console.log('🔄 Force download requested...');
305
+ } else {
306
+ console.log('📥 Binary not found, downloading...');
264
307
  }
265
308
  await downloadBinary(version, platform);
266
309
  console.log(`✅ Alurkerja CLI ${version} ready!`);
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Alurkerja CLI - npx Wrapper
5
+ *
6
+ * This is a bootstrapper that downloads and executes the Go binary.
7
+ * As per PRD: npx wrapper should NOT contain business logic.
8
+ *
9
+ * Note: Auto-update functionality has been disabled to prevent
10
+ * automatic version checks and downloads on every startup.
11
+ * Binary will only be downloaded if it doesn't exist locally.
12
+ *
13
+ * Downloads from: https://minio-admin.merapi.javan.id/javan-cli/dist/
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const https = require('https');
19
+ const { execSync, spawn } = require('child_process');
20
+ const os = require('os');
21
+
22
+ const MINIO_BASE_URL = 'https://minio.merapi.javan.id/javan-cli/dist';
23
+ const VERSION = 'v1.0.0'; // Will be replaced during build
24
+ const INSTALL_DIR = path.join(os.homedir(), '.alurkerja', 'bin');
25
+ const BINARY_NAME = os.platform() === 'win32' ? 'alurkerja.exe' : 'alurkerja';
26
+ const BINARY_PATH = path.join(INSTALL_DIR, BINARY_NAME);
27
+
28
+ /**
29
+ * Detect current platform
30
+ */
31
+ function detectPlatform() {
32
+ const platform = os.platform();
33
+ const arch = os.arch();
34
+
35
+ let osName;
36
+ switch (platform) {
37
+ case 'linux': osName = 'linux'; break;
38
+ case 'darwin': osName = 'darwin'; break;
39
+ case 'win32': osName = 'windows'; break;
40
+ default: throw new Error(`Unsupported platform: ${platform}`);
41
+ }
42
+
43
+ let archName;
44
+ switch (arch) {
45
+ case 'x64': archName = 'amd64'; break;
46
+ case 'arm64': archName = 'arm64'; break;
47
+ default: throw new Error(`Unsupported architecture: ${arch}`);
48
+ }
49
+
50
+ return { os: osName, arch: archName };
51
+ }
52
+
53
+ /**
54
+ * Get the version to download
55
+ */
56
+ function getVersion() {
57
+ return VERSION;
58
+ }
59
+
60
+ /**
61
+ * Check latest version (simplified - always returns current version)
62
+ */
63
+ async function getLatestVersion() {
64
+ // Since we moved to direct MinIO URLs, version checking is simplified
65
+ // Always return current package version
66
+ return VERSION;
67
+ }
68
+
69
+ /**
70
+ * Download binary from MinIO storage
71
+ */
72
+ async function downloadBinary(version, platform) {
73
+ const binaryName = `alurkerja-${platform.os}-${platform.arch}${platform.os === 'windows' ? '.exe' : ''}`;
74
+ const downloadUrl = `${MINIO_BASE_URL}/${binaryName}`;
75
+
76
+ console.log(`📥 Downloading Alurkerja CLI ${version} for ${platform.os}-${platform.arch}...`);
77
+
78
+ // Ensure install directory exists
79
+ fs.mkdirSync(INSTALL_DIR, { recursive: true });
80
+
81
+ // Use temporary file to avoid ETXTBSY error
82
+ const tempPath = `${BINARY_PATH}.tmp`;
83
+
84
+ return new Promise((resolve, reject) => {
85
+ const file = fs.createWriteStream(tempPath);
86
+ let downloadComplete = false;
87
+
88
+ const url = new URL(downloadUrl);
89
+ const options = {
90
+ hostname: url.hostname,
91
+ path: url.pathname,
92
+ method: 'GET'
93
+ };
94
+
95
+ const req = https.get(options, (res) => {
96
+ // Handle redirects
97
+ if (res.statusCode === 302 || res.statusCode === 301) {
98
+ const redirectUrl = res.headers.location;
99
+ if (redirectUrl) {
100
+ const newUrl = redirectUrl.startsWith('http') ? redirectUrl : `https://${url.hostname}${redirectUrl}`;
101
+ return downloadBinary(version, platform).then(resolve).catch(reject);
102
+ }
103
+ }
104
+
105
+ if (res.statusCode !== 200) {
106
+ file.close();
107
+ try { fs.unlinkSync(tempPath); } catch {}
108
+ reject(new Error(`Download failed with status ${res.statusCode}: ${downloadUrl}`));
109
+ return;
110
+ }
111
+
112
+ res.pipe(file);
113
+ res.on('end', () => {
114
+ downloadComplete = true;
115
+ });
116
+ });
117
+
118
+ req.on('error', (err) => {
119
+ if (!downloadComplete) {
120
+ file.close();
121
+ try { fs.unlinkSync(tempPath); } catch {}
122
+ reject(new Error(`Download failed: ${err.message}`));
123
+ }
124
+ });
125
+
126
+ req.setTimeout(30000, () => {
127
+ if (!downloadComplete) {
128
+ req.abort();
129
+ file.close();
130
+ try { fs.unlinkSync(tempPath); } catch {}
131
+ reject(new Error('Download timeout'));
132
+ }
133
+ });
134
+
135
+ file.on('error', (err) => {
136
+ try { fs.unlinkSync(tempPath); } catch {}
137
+ reject(err);
138
+ });
139
+
140
+ file.on('finish', () => {
141
+ file.close((err) => {
142
+ if (err) {
143
+ try { fs.unlinkSync(tempPath); } catch {}
144
+ reject(err);
145
+ return;
146
+ }
147
+
148
+ // Validate the downloaded file
149
+ const stats = fs.statSync(tempPath);
150
+ if (stats.size < 1000) {
151
+ try { fs.unlinkSync(tempPath); } catch {}
152
+ reject(new Error(`Download failed: invalid file size (${stats.size} bytes)`));
153
+ return;
154
+ }
155
+
156
+ // Make executable on Unix systems
157
+ if (platform.os !== 'windows') {
158
+ try {
159
+ fs.chmodSync(tempPath, '755');
160
+ } catch (err) {
161
+ try { fs.unlinkSync(tempPath); } catch {}
162
+ reject(new Error(`Failed to make binary executable: ${err.message}`));
163
+ return;
164
+ }
165
+ }
166
+
167
+ // Rename temp file to final path
168
+ try {
169
+ if (fs.existsSync(BINARY_PATH)) {
170
+ fs.unlinkSync(BINARY_PATH);
171
+ }
172
+ fs.renameSync(tempPath, BINARY_PATH);
173
+ resolve();
174
+ } catch (err) {
175
+ try { fs.unlinkSync(tempPath); } catch {}
176
+ reject(new Error(`Failed to install binary: ${err.message}`));
177
+ }
178
+ });
179
+ });
180
+ });
181
+ }
182
+
183
+ /**
184
+ * Check if binary exists and is executable
185
+ */
186
+ function binaryExists() {
187
+ try {
188
+ fs.accessSync(BINARY_PATH, fs.constants.F_OK | fs.constants.X_OK);
189
+ return true;
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Get version of installed binary (disabled to prevent auto-updates)
197
+ */
198
+ function getInstalledVersion() {
199
+ // Always return current version to prevent version-based updates
200
+ return VERSION.replace('v', '');
201
+ }
202
+
203
+ /**
204
+ * Check if binary file is older than cache duration (disabled to prevent auto-updates)
205
+ */
206
+ function isBinaryStale() {
207
+ // Always return false to prevent time-based updates
208
+ return false;
209
+ }
210
+
211
+ /**
212
+ * Check if force download flag is present
213
+ */
214
+ function hasForceDownloadFlag() {
215
+ return process.argv.includes('--force-download');
216
+ }
217
+
218
+ /**
219
+ * Check if update command is requested
220
+ */
221
+ function isUpdateCommand() {
222
+ return process.argv.includes('update') && !process.argv.includes('--help');
223
+ }
224
+
225
+ /**
226
+ * Show update information
227
+ */
228
+ async function showUpdateInfo() {
229
+ try {
230
+ const currentVersion = VERSION;
231
+ const latestVersion = await getLatestVersion();
232
+ const forceDownload = hasForceDownloadFlag();
233
+
234
+ console.log(`📋 Current version: ${currentVersion}`);
235
+ console.log(`🔄 Latest version: ${latestVersion}`);
236
+
237
+ if (currentVersion === latestVersion && !forceDownload) {
238
+ console.log('✅ You already have the latest version!');
239
+ return false;
240
+ }
241
+
242
+ console.log('🔄 Update available!');
243
+ return true;
244
+ } catch (error) {
245
+ console.log(`⚠️ Could not check latest version: ${error.message}`);
246
+ return hasForceDownloadFlag();
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Execute the Go binary with provided arguments
252
+ */
253
+ function executeBinary(args) {
254
+ const child = spawn(BINARY_PATH, args, {
255
+ stdio: 'inherit',
256
+ shell: false
257
+ });
258
+
259
+ child.on('error', (err) => {
260
+ console.error(`❌ Failed to execute binary: ${err.message}`);
261
+ process.exit(1);
262
+ });
263
+
264
+ child.on('exit', (code) => {
265
+ process.exit(code || 0);
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Main bootstrap logic
271
+ */
272
+ async function bootstrap() {
273
+ try {
274
+ const platform = detectPlatform();
275
+ const version = getVersion();
276
+ const forceDownload = hasForceDownloadFlag();
277
+ const isUpdate = isUpdateCommand();
278
+
279
+ // Handle update command specially
280
+ if (isUpdate) {
281
+ const shouldUpdate = await showUpdateInfo();
282
+
283
+ if (shouldUpdate || forceDownload) {
284
+ console.log('📥 Downloading latest version...');
285
+ await downloadBinary(version, platform);
286
+ console.log('✅ Update completed successfully!');
287
+ console.log('🎉 Alurkerja CLI has been updated to the latest version');
288
+ }
289
+
290
+ // For update command, don't continue to execute the binary
291
+ // Show current version and exit
292
+ setTimeout(() => {
293
+ const args = ['--version'];
294
+ executeBinary(args);
295
+ }, 100);
296
+ return;
297
+ }
298
+
299
+ // Only download if binary doesn't exist or force download is requested
300
+ const needsDownload = forceDownload || !binaryExists();
301
+
302
+ if (needsDownload) {
303
+ if (forceDownload) {
304
+ console.log('🔄 Force download requested...');
305
+ } else {
306
+ console.log('📥 Binary not found, downloading...');
307
+ }
308
+ await downloadBinary(version, platform);
309
+ console.log(`✅ Alurkerja CLI ${version} ready!`);
310
+ // Small delay to ensure file system sync after download
311
+ await new Promise(resolve => setTimeout(resolve, 100));
312
+ }
313
+
314
+ // Execute the binary with all arguments passed to this script
315
+ // Filter out --force-download flag before passing to binary
316
+ const args = process.argv.slice(2).filter(arg => arg !== '--force-download');
317
+ executeBinary(args);
318
+
319
+ } catch (error) {
320
+ console.error(`❌ Bootstrap failed: ${error.message}`);
321
+ console.log('💡 Try installing directly: curl -fsSL https://alurkerja.com/install.sh | bash');
322
+ process.exit(1);
323
+ }
324
+ }
325
+
326
+ // Handle termination signals
327
+ process.on('SIGINT', () => process.exit(0));
328
+ process.on('SIGTERM', () => process.exit(0));
329
+
330
+ // Start bootstrap
331
+ bootstrap();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alurkerja-cli",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Alurkerja CLI - npx wrapper for Go binary",
5
5
  "main": "index.js",
6
6
  "private": false,
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "alurkerja-cli",
3
+ "version": "1.0.23",
4
+ "description": "Alurkerja CLI - npx wrapper for Go binary",
5
+ "main": "index.js",
6
+ "private": false,
7
+ "bin": {
8
+ "alurkerja": "./index.js"
9
+ },
10
+ "keywords": [
11
+ "alurkerja",
12
+ "cli",
13
+ "addon",
14
+ "ci",
15
+ "cd"
16
+ ],
17
+ "author": "Alurkerja Team",
18
+ "license": "MIT",
19
+ "engines": {
20
+ "node": ">=14.0.0"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/alurkerja/alurkerja-cli"
25
+ },
26
+ "homepage": "https://alurkerja.com",
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "dependencies": {}
31
+ }