abapgit-agent 1.8.6 → 1.8.8

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/bin/abapgit-agent CHANGED
@@ -24,7 +24,7 @@ const gitUtils = require('../src/utils/git-utils');
24
24
  const versionCheck = require('../src/utils/version-check');
25
25
  const validators = require('../src/utils/validators');
26
26
  const { AbapHttp } = require('../src/utils/abap-http');
27
- const { loadConfig, getTransport, isAbapIntegrationEnabled } = require('../src/config');
27
+ const { loadConfig, getTransport, isAbapIntegrationEnabled, getSafeguards } = require('../src/config');
28
28
 
29
29
  // Get terminal width for responsive table
30
30
  const getTermWidth = () => process.stdout.columns || 80;
@@ -52,7 +52,8 @@ async function main() {
52
52
  where: require('../src/commands/where'),
53
53
  ref: require('../src/commands/ref'),
54
54
  init: require('../src/commands/init'),
55
- pull: require('../src/commands/pull')
55
+ pull: require('../src/commands/pull'),
56
+ upgrade: require('../src/commands/upgrade')
56
57
  };
57
58
 
58
59
  // Check if this is a modular command
@@ -98,11 +99,17 @@ To enable integration:
98
99
  isAbapIntegrationEnabled,
99
100
  loadConfig,
100
101
  AbapHttp,
101
- getTransport
102
+ getTransport,
103
+ getSafeguards
102
104
  };
103
105
 
104
106
  // Execute command
105
107
  await cmd.execute(args.slice(1), context);
108
+
109
+ // Show new version reminder (non-blocking, cached daily)
110
+ if (command !== 'upgrade') {
111
+ await versionCheck.showNewVersionReminder();
112
+ }
106
113
  return;
107
114
  }
108
115
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abapgit-agent",
3
- "version": "1.8.6",
3
+ "version": "1.8.8",
4
4
  "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -33,6 +33,7 @@
33
33
  "test:cmd:view": "node tests/run-all.js --cmd --command=view",
34
34
  "test:cmd:preview": "node tests/run-all.js --cmd --command=preview",
35
35
  "test:cmd:tree": "node tests/run-all.js --cmd --command=tree",
36
+ "test:cmd:upgrade": "node tests/run-all.js --cmd --command=upgrade",
36
37
  "test:lifecycle": "node tests/run-all.js --lifecycle",
37
38
  "test:pull": "node tests/run-all.js --pull",
38
39
  "pull": "node bin/abapgit-agent",
@@ -120,9 +120,15 @@ Examples:
120
120
  resultData = { filesStaged: 'unknown', commitMessage: commitMessage };
121
121
  }
122
122
 
123
+ if (resultData.success !== 'X' && resultData.success !== true) {
124
+ const errorMsg = resultData.error || resultData.ERROR || 'Unknown error';
125
+ console.log(`❌ Import failed: ${errorMsg}`);
126
+ process.exit(1);
127
+ }
128
+
123
129
  console.log(`✅ Import completed successfully!`);
124
- console.log(` Files staged: ${resultData.filesStaged || resultData.FILES_STAGED || 'unknown'}`);
125
- console.log(` Commit: ${resultData.commitMessage || resultData.COMMIT_MESSAGE || commitMessage || 'Initial import'}`);
130
+ console.log(` Files staged: ${resultData.filesStaged || resultData.FILES_STAGED || resultData.files_staged || 'unknown'}`);
131
+ console.log(` Commit: ${resultData.commitMessage || resultData.COMMIT_MESSAGE || resultData.commit_message || commitMessage || 'Initial import'}`);
126
132
  console.log(``);
127
133
 
128
134
  // Calculate time spent
@@ -9,7 +9,21 @@ module.exports = {
9
9
  requiresVersionCheck: true,
10
10
 
11
11
  async execute(args, context) {
12
- const { loadConfig, AbapHttp, gitUtils, getTransport } = context;
12
+ const { loadConfig, AbapHttp, gitUtils, getTransport, getSafeguards } = context;
13
+
14
+ // Check project-level safeguards
15
+ const safeguards = getSafeguards();
16
+
17
+ // SAFEGUARD 1: Check if pull is completely disabled
18
+ if (safeguards.disablePull) {
19
+ console.error('❌ Error: pull command is disabled for this project\n');
20
+ if (safeguards.reason) {
21
+ console.error(`Reason: ${safeguards.reason}\n`);
22
+ }
23
+ console.error('The pull command has been disabled in .abapgit-agent.json');
24
+ console.error('Please contact the project maintainer to enable it.');
25
+ process.exit(1);
26
+ }
13
27
 
14
28
  const urlArgIndex = args.indexOf('--url');
15
29
  const branchArgIndex = args.indexOf('--branch');
@@ -36,6 +50,18 @@ module.exports = {
36
50
  files = args[filesArgIndex + 1].split(',').map(f => f.trim());
37
51
  }
38
52
 
53
+ // SAFEGUARD 2: Check if files are required but not provided
54
+ if (safeguards.requireFilesForPull && !files) {
55
+ console.error('❌ Error: --files parameter is required for this project\n');
56
+ if (safeguards.reason) {
57
+ console.error(`Reason: ${safeguards.reason}\n`);
58
+ }
59
+ console.error('Usage: abapgit-agent pull --files <file1>,<file2>\n');
60
+ console.error('This safeguard is configured in .abapgit-agent.json');
61
+ console.error('Contact the project maintainer if you need to change this setting.');
62
+ process.exit(1);
63
+ }
64
+
39
65
  if (!gitUrl) {
40
66
  gitUrl = gitUtils.getRemoteUrl();
41
67
  if (!gitUrl) {
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Upgrade command - Upgrade CLI and/or ABAP backend to latest or specific version
3
+ */
4
+
5
+ const { execSync } = require('child_process');
6
+ const readline = require('readline');
7
+
8
+ module.exports = {
9
+ name: 'upgrade',
10
+ description: 'Upgrade CLI and/or ABAP backend to latest or specific version',
11
+ requiresAbapConfig: false, // Checked conditionally
12
+ requiresVersionCheck: false, // Does its own version checks
13
+
14
+ async execute(args, context) {
15
+ const { versionCheck, loadConfig, isAbapIntegrationEnabled } = context;
16
+
17
+ // Parse flags
18
+ const flags = this.parseFlags(args);
19
+
20
+ // Validate flag combinations
21
+ try {
22
+ this.validateFlags(flags);
23
+ } catch (error) {
24
+ console.error(`❌ Error: ${error.message}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ // Check if ABAP config is needed
29
+ const needsAbapConfig = !flags.cliOnly && !flags.checkOnly;
30
+ if (needsAbapConfig && !isAbapIntegrationEnabled()) {
31
+ console.error('❌ Error: .abapGitAgent config file not found');
32
+ console.error(' ABAP upgrade requires configuration.');
33
+ console.error(' Run: abapgit-agent init');
34
+ console.error('');
35
+ console.error(' Or use --cli-only to upgrade CLI package only.');
36
+ process.exit(1);
37
+ }
38
+
39
+ // Get current versions
40
+ const cliVersion = versionCheck.getCliVersion();
41
+ let abapVersion = null;
42
+
43
+ if (needsAbapConfig) {
44
+ try {
45
+ const config = loadConfig();
46
+ const { apiVersion } = await versionCheck.checkCompatibility(config);
47
+ abapVersion = apiVersion;
48
+ } catch (e) {
49
+ console.error(`⚠️ Could not fetch ABAP version: ${e.message}`);
50
+ }
51
+ }
52
+
53
+ // Get latest version from npm
54
+ const latestVersion = await versionCheck.getLatestNpmVersion();
55
+ if (!latestVersion && !flags.version && !flags.match) {
56
+ console.error('❌ Error: Could not fetch latest version from npm registry');
57
+ console.error(' Please check your internet connection or specify --version X.X.X');
58
+ process.exit(1);
59
+ }
60
+
61
+ // Validate specified version exists in npm registry
62
+ if (flags.version && !flags.abapOnly) {
63
+ const versionExists = await this.validateVersionExists(flags.version);
64
+ if (!versionExists) {
65
+ console.error(`❌ Error: Version ${flags.version} not found in npm registry`);
66
+ console.error(' Please check available versions at: https://www.npmjs.com/package/abapgit-agent?activeTab=versions');
67
+ process.exit(1);
68
+ }
69
+ }
70
+
71
+ // Determine target versions
72
+ const targets = this.determineTargets(flags, cliVersion, abapVersion, latestVersion);
73
+
74
+ // Check-only mode
75
+ if (flags.checkOnly) {
76
+ this.showCheckReport(cliVersion, abapVersion, latestVersion);
77
+ return;
78
+ }
79
+
80
+ // Dry-run mode
81
+ if (flags.dryRun) {
82
+ this.showDryRunPlan(cliVersion, abapVersion, targets, flags);
83
+ return;
84
+ }
85
+
86
+ // Show upgrade plan
87
+ if (!flags.yes) {
88
+ const proceed = await this.confirmUpgrade(cliVersion, abapVersion, targets, flags);
89
+ if (!proceed) {
90
+ console.log('Upgrade cancelled.');
91
+ return;
92
+ }
93
+ }
94
+
95
+ // Execute upgrade
96
+ await this.performUpgrade(targets, flags, context);
97
+
98
+ // Verify upgrade
99
+ await this.verifyUpgrade(targets, flags, context);
100
+ },
101
+
102
+ /**
103
+ * Parse command-line flags
104
+ */
105
+ parseFlags(args) {
106
+ return {
107
+ checkOnly: args.includes('--check'),
108
+ cliOnly: args.includes('--cli-only'),
109
+ abapOnly: args.includes('--abap-only'),
110
+ match: args.includes('--match'),
111
+ version: this.getArgValue(args, '--version'),
112
+ latest: args.includes('--latest'),
113
+ yes: args.includes('--yes') || args.includes('-y'),
114
+ dryRun: args.includes('--dry-run'),
115
+ transport: this.getArgValue(args, '--transport')
116
+ };
117
+ },
118
+
119
+ /**
120
+ * Get argument value following a flag
121
+ */
122
+ getArgValue(args, flag) {
123
+ const index = args.indexOf(flag);
124
+ if (index !== -1 && index + 1 < args.length) {
125
+ return args[index + 1];
126
+ }
127
+ return null;
128
+ },
129
+
130
+ /**
131
+ * Validate flag combinations
132
+ */
133
+ validateFlags(flags) {
134
+ // Invalid combinations
135
+ if (flags.match && flags.version) {
136
+ throw new Error('Cannot use --match and --version together');
137
+ }
138
+
139
+ if (flags.match && flags.cliOnly) {
140
+ throw new Error('Cannot use --match with --cli-only. --match upgrades ABAP to match CLI version');
141
+ }
142
+
143
+ if (flags.cliOnly && flags.abapOnly) {
144
+ throw new Error('Cannot use --cli-only and --abap-only together');
145
+ }
146
+ },
147
+
148
+ /**
149
+ * Validate that a version exists in npm registry
150
+ */
151
+ async validateVersionExists(version) {
152
+ return new Promise((resolve) => {
153
+ const { execSync } = require('child_process');
154
+ try {
155
+ // Use npm view to check if version exists
156
+ const output = execSync(`npm view abapgit-agent@${version} version 2>/dev/null`, {
157
+ encoding: 'utf8',
158
+ stdio: ['pipe', 'pipe', 'ignore']
159
+ }).trim();
160
+
161
+ // If npm view returns the version, it exists
162
+ resolve(output === version);
163
+ } catch (e) {
164
+ // Version doesn't exist or npm command failed
165
+ resolve(false);
166
+ }
167
+ });
168
+ },
169
+
170
+ /**
171
+ * Determine target versions for CLI and ABAP
172
+ */
173
+ determineTargets(flags, cliVersion, abapVersion, latestVersion) {
174
+ let cliTarget = null;
175
+ let abapTarget = null;
176
+
177
+ if (flags.match) {
178
+ // Match ABAP to CLI version
179
+ abapTarget = cliVersion;
180
+ } else if (flags.version) {
181
+ // Specific version
182
+ cliTarget = flags.cliOnly ? flags.version : (flags.abapOnly ? null : flags.version);
183
+ abapTarget = flags.abapOnly ? flags.version : (flags.cliOnly ? null : flags.version);
184
+ } else {
185
+ // Latest version (default)
186
+ cliTarget = flags.abapOnly ? null : latestVersion;
187
+ abapTarget = flags.cliOnly ? null : latestVersion;
188
+ }
189
+
190
+ return { cliTarget, abapTarget };
191
+ },
192
+
193
+ /**
194
+ * Show check-only report
195
+ */
196
+ showCheckReport(cliVersion, abapVersion, latestVersion) {
197
+ console.log('');
198
+ console.log('Current versions:');
199
+ console.log(` CLI: v${cliVersion}`);
200
+ if (abapVersion) {
201
+ console.log(` ABAP: v${abapVersion}`);
202
+ }
203
+ console.log('');
204
+
205
+ if (latestVersion) {
206
+ console.log(`Latest available: v${latestVersion}`);
207
+ console.log('');
208
+ }
209
+
210
+ const needsCliUpgrade = latestVersion && cliVersion !== latestVersion;
211
+ const needsAbapUpgrade = abapVersion && latestVersion && abapVersion !== latestVersion;
212
+ const versionMismatch = abapVersion && cliVersion !== abapVersion;
213
+
214
+ if (versionMismatch) {
215
+ console.log('⚠️ Version mismatch detected');
216
+ console.log('');
217
+ } else if (!needsCliUpgrade && !needsAbapUpgrade) {
218
+ console.log('✅ All components are up to date');
219
+ console.log('');
220
+ return;
221
+ }
222
+
223
+ console.log('To upgrade:');
224
+ console.log(' Both: abapgit-agent upgrade');
225
+ console.log(' CLI only: abapgit-agent upgrade --cli-only');
226
+ if (abapVersion) {
227
+ console.log(' ABAP only: abapgit-agent upgrade --abap-only');
228
+ console.log(' Match: abapgit-agent upgrade --match');
229
+ }
230
+ console.log('');
231
+ },
232
+
233
+ /**
234
+ * Show dry-run plan
235
+ */
236
+ showDryRunPlan(cliVersion, abapVersion, targets, flags) {
237
+ console.log('');
238
+ console.log('🔹 DRY RUN - No changes will be made');
239
+ console.log('');
240
+ console.log('Current versions:');
241
+ console.log(` CLI: v${cliVersion}`);
242
+ if (abapVersion) {
243
+ console.log(` ABAP: v${abapVersion}`);
244
+ }
245
+ console.log('');
246
+
247
+ console.log('Target versions:');
248
+ if (targets.cliTarget) {
249
+ console.log(` CLI: v${targets.cliTarget}`);
250
+ }
251
+ if (targets.abapTarget) {
252
+ console.log(` ABAP: v${targets.abapTarget}`);
253
+ }
254
+ console.log('');
255
+
256
+ console.log('Would execute:');
257
+ let step = 1;
258
+ if (targets.cliTarget) {
259
+ console.log(` ${step}. npm install -g abapgit-agent@${targets.cliTarget}`);
260
+ step++;
261
+ }
262
+ if (targets.abapTarget) {
263
+ console.log(` ${step}. abapgit-agent pull --branch v${targets.abapTarget}`);
264
+ console.log(' (via existing abapGit repository)');
265
+ step++;
266
+ }
267
+ console.log(` ${step}. Verify versions match`);
268
+ console.log('');
269
+ console.log('No changes made.');
270
+ console.log('');
271
+ },
272
+
273
+ /**
274
+ * Confirm upgrade with user
275
+ */
276
+ async confirmUpgrade(cliVersion, abapVersion, targets, flags) {
277
+ console.log('');
278
+ console.log('📦 Upgrade Plan:');
279
+ console.log('');
280
+ console.log('Current versions:');
281
+ console.log(` CLI: v${cliVersion}`);
282
+ if (abapVersion) {
283
+ console.log(` ABAP: v${abapVersion}`);
284
+ }
285
+ console.log('');
286
+
287
+ console.log('Target versions:');
288
+ if (targets.cliTarget) {
289
+ console.log(` CLI: v${targets.cliTarget}`);
290
+ }
291
+ if (targets.abapTarget) {
292
+ console.log(` ABAP: v${targets.abapTarget}`);
293
+ }
294
+ console.log('');
295
+
296
+ console.log('This will:');
297
+ let step = 1;
298
+ if (targets.cliTarget) {
299
+ console.log(` ${step}. Upgrade npm package: abapgit-agent@${targets.cliTarget}`);
300
+ step++;
301
+ }
302
+ if (targets.abapTarget) {
303
+ console.log(` ${step}. Pull ABAP code from git tag v${targets.abapTarget}`);
304
+ step++;
305
+ console.log(` ${step}. Activate all backend components`);
306
+ step++;
307
+ }
308
+ console.log('');
309
+
310
+ return new Promise((resolve) => {
311
+ const rl = readline.createInterface({
312
+ input: process.stdin,
313
+ output: process.stdout
314
+ });
315
+
316
+ rl.question('Do you want to continue? [Y/n] ', (answer) => {
317
+ rl.close();
318
+ const normalized = answer.trim().toLowerCase();
319
+ resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
320
+ });
321
+ });
322
+ },
323
+
324
+ /**
325
+ * Perform upgrade
326
+ */
327
+ async performUpgrade(targets, flags, context) {
328
+ console.log('');
329
+ console.log('🚀 Starting upgrade...');
330
+ console.log('');
331
+
332
+ // Upgrade CLI
333
+ if (targets.cliTarget) {
334
+ await this.upgradeCliPackage(targets.cliTarget);
335
+ }
336
+
337
+ // Upgrade ABAP
338
+ if (targets.abapTarget) {
339
+ await this.upgradeAbapBackend(targets.abapTarget, flags.transport, context);
340
+ }
341
+ },
342
+
343
+ /**
344
+ * Upgrade CLI package via npm
345
+ */
346
+ async upgradeCliPackage(version) {
347
+ console.log(`📦 Upgrading CLI to v${version}...`);
348
+
349
+ try {
350
+ // Check if npm is available
351
+ try {
352
+ execSync('npm --version', { stdio: 'ignore' });
353
+ } catch (e) {
354
+ console.error('❌ Error: npm is not installed or not in PATH');
355
+ console.error(' Please install Node.js and npm: https://nodejs.org/');
356
+ process.exit(1);
357
+ }
358
+
359
+ // Run npm install
360
+ const command = `npm install -g abapgit-agent@${version}`;
361
+ console.log(` Running: ${command}`);
362
+
363
+ execSync(command, {
364
+ stdio: 'inherit',
365
+ encoding: 'utf8'
366
+ });
367
+
368
+ console.log(`✅ CLI upgraded to v${version}`);
369
+ console.log('');
370
+ } catch (error) {
371
+ console.error(`❌ Failed to upgrade CLI: ${error.message}`);
372
+ console.error('');
373
+ console.error('This may be due to:');
374
+ console.error(' - Version not found in npm registry');
375
+ console.error(' - Permission issues (try with sudo)');
376
+ console.error(' - Network connectivity issues');
377
+ process.exit(1);
378
+ }
379
+ },
380
+
381
+ /**
382
+ * Upgrade ABAP backend via pull command
383
+ */
384
+ async upgradeAbapBackend(version, transport, context) {
385
+ console.log(`📦 Upgrading ABAP backend to v${version}...`);
386
+ console.log(` Using git tag: v${version}`);
387
+ console.log('');
388
+
389
+ try {
390
+ // Build pull command args
391
+ const pullArgs = ['--branch', `v${version}`];
392
+ if (transport) {
393
+ pullArgs.push('--transport', transport);
394
+ }
395
+
396
+ // Execute pull command
397
+ const pullCommand = require('./pull');
398
+ await pullCommand.execute(pullArgs, context);
399
+
400
+ console.log('');
401
+ console.log(`✅ ABAP backend upgraded to v${version}`);
402
+ console.log('');
403
+ } catch (error) {
404
+ console.error(`❌ Failed to upgrade ABAP backend: ${error.message}`);
405
+ console.error('');
406
+ console.error('This may be due to:');
407
+ console.error(' - Git tag not found in repository');
408
+ console.error(' - ABAP activation errors');
409
+ console.error(' - Connection issues with ABAP system');
410
+ process.exit(1);
411
+ }
412
+ },
413
+
414
+ /**
415
+ * Verify upgrade success
416
+ */
417
+ async verifyUpgrade(targets, flags, context) {
418
+ console.log('🔍 Verifying upgrade...');
419
+ console.log('');
420
+
421
+ const { versionCheck, loadConfig } = context;
422
+
423
+ // Check CLI version
424
+ let cliVersion = null;
425
+ if (targets.cliTarget) {
426
+ cliVersion = versionCheck.getCliVersion();
427
+ if (cliVersion === targets.cliTarget) {
428
+ console.log(`✅ CLI version verified: v${cliVersion}`);
429
+ } else {
430
+ console.log(`⚠️ CLI version mismatch: expected v${targets.cliTarget}, got v${cliVersion}`);
431
+ console.log(' You may need to restart your terminal or reinstall globally');
432
+ }
433
+ }
434
+
435
+ // Check ABAP version
436
+ if (targets.abapTarget && !flags.cliOnly) {
437
+ try {
438
+ const config = loadConfig();
439
+ const { apiVersion: abapVersion } = await versionCheck.checkCompatibility(config);
440
+
441
+ if (abapVersion === targets.abapTarget) {
442
+ console.log(`✅ ABAP version verified: v${abapVersion}`);
443
+ } else {
444
+ console.log(`⚠️ ABAP version mismatch: expected v${targets.abapTarget}, got v${abapVersion}`);
445
+ console.log(' Some components may have failed to activate');
446
+ }
447
+ } catch (e) {
448
+ console.log(`⚠️ Could not verify ABAP version: ${e.message}`);
449
+ }
450
+ }
451
+
452
+ console.log('');
453
+ console.log('✅ Upgrade complete!');
454
+ console.log('');
455
+ }
456
+ };
package/src/config.js CHANGED
@@ -88,11 +88,67 @@ function getWorkflowConfig() {
88
88
  };
89
89
  }
90
90
 
91
+ /**
92
+ * Load project-level configuration (.abapgit-agent.json)
93
+ * This file is checked into version control and contains project settings
94
+ * @returns {Object|null} Project config or null if not found
95
+ */
96
+ function loadProjectConfig() {
97
+ const projectConfigPath = path.join(process.cwd(), '.abapgit-agent.json');
98
+
99
+ if (fs.existsSync(projectConfigPath)) {
100
+ try {
101
+ return JSON.parse(fs.readFileSync(projectConfigPath, 'utf8'));
102
+ } catch (error) {
103
+ console.warn(`⚠️ Warning: Failed to parse .abapgit-agent.json: ${error.message}`);
104
+ return null;
105
+ }
106
+ }
107
+
108
+ return null;
109
+ }
110
+
111
+ /**
112
+ * Get safeguard configuration from project-level config
113
+ * Project-level safeguards CANNOT be overridden by user config
114
+ * @returns {Object} Safeguards config with requireFilesForPull, disablePull, and reason
115
+ */
116
+ function getSafeguards() {
117
+ const projectConfig = loadProjectConfig();
118
+
119
+ if (projectConfig?.safeguards) {
120
+ return {
121
+ requireFilesForPull: projectConfig.safeguards.requireFilesForPull === true,
122
+ disablePull: projectConfig.safeguards.disablePull === true,
123
+ reason: projectConfig.safeguards.reason || null
124
+ };
125
+ }
126
+
127
+ // Default: no safeguards (backward compatible)
128
+ return {
129
+ requireFilesForPull: false,
130
+ disablePull: false,
131
+ reason: null
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Get project information from project-level config
137
+ * @returns {Object|null} Project info (name, description) or null
138
+ */
139
+ function getProjectInfo() {
140
+ const projectConfig = loadProjectConfig();
141
+ return projectConfig?.project || null;
142
+ }
143
+
91
144
  module.exports = {
92
145
  loadConfig,
93
146
  getAbapConfig,
94
147
  getAgentConfig,
95
148
  getTransport,
96
149
  isAbapIntegrationEnabled,
97
- getWorkflowConfig
150
+ getWorkflowConfig,
151
+ getSafeguards,
152
+ getProjectInfo,
153
+ loadProjectConfig
98
154
  };
@@ -58,7 +58,7 @@ async function pollForCompletion(http, endpoint, jobNumber, options = {}) {
58
58
  let lastProgress = -1;
59
59
  let pollCount = 0;
60
60
 
61
- while (status === 'running' || status === 'scheduled') {
61
+ while (status === 'running' || status === 'scheduled' || status === 'STARTING') {
62
62
  // Wait before polling (except first time)
63
63
  if (pollCount > 0) {
64
64
  await sleep(pollInterval);
@@ -3,8 +3,30 @@
3
3
  */
4
4
  const pathModule = require('path');
5
5
  const fs = require('fs');
6
+ const os = require('os');
6
7
  const https = require('https');
7
8
 
9
+ /**
10
+ * Get cache directory for abapgit-agent
11
+ * Follows OS-specific conventions:
12
+ * - Windows: %LOCALAPPDATA%\abapgit-agent\cache
13
+ * - Linux/macOS: ~/.cache/abapgit-agent (respects XDG_CACHE_HOME)
14
+ * @returns {string} Cache directory path
15
+ */
16
+ function getCacheDir() {
17
+ const homeDir = os.homedir();
18
+
19
+ // Windows: %LOCALAPPDATA%\abapgit-agent\cache
20
+ if (process.platform === 'win32') {
21
+ const localAppData = process.env.LOCALAPPDATA || pathModule.join(homeDir, 'AppData', 'Local');
22
+ return pathModule.join(localAppData, 'abapgit-agent', 'cache');
23
+ }
24
+
25
+ // Linux/macOS: respect XDG_CACHE_HOME or use ~/.cache
26
+ const xdgCache = process.env.XDG_CACHE_HOME || pathModule.join(homeDir, '.cache');
27
+ return pathModule.join(xdgCache, 'abapgit-agent');
28
+ }
29
+
8
30
  /**
9
31
  * Get CLI version from package.json
10
32
  * @returns {string} CLI version or '1.0.0' as default
@@ -74,7 +96,166 @@ async function checkCompatibility(config) {
74
96
  }
75
97
  }
76
98
 
99
+ /**
100
+ * Get configured npm registry URL
101
+ * Respects user's npm config (e.g., corporate mirrors, regional mirrors)
102
+ * @returns {string} Registry URL
103
+ */
104
+ function getNpmRegistry() {
105
+ try {
106
+ const { execSync } = require('child_process');
107
+ const registry = execSync('npm config get registry', {
108
+ encoding: 'utf8',
109
+ stdio: ['pipe', 'pipe', 'ignore'] // Suppress stderr
110
+ }).trim();
111
+
112
+ // Validate registry URL
113
+ if (registry && registry.startsWith('http')) {
114
+ return registry;
115
+ }
116
+ } catch (e) {
117
+ // Fall back to default if npm config fails
118
+ }
119
+
120
+ // Default npm registry
121
+ return 'https://registry.npmjs.org/';
122
+ }
123
+
124
+ /**
125
+ * Get latest version from npm registry
126
+ * Respects user's configured npm registry (mirrors, corporate proxies, etc.)
127
+ * @returns {Promise<string|null>} Latest version or null on error
128
+ */
129
+ async function getLatestNpmVersion() {
130
+ return new Promise((resolve) => {
131
+ try {
132
+ const registry = getNpmRegistry();
133
+ // Ensure registry ends with /
134
+ const baseUrl = registry.endsWith('/') ? registry : registry + '/';
135
+ const url = `${baseUrl}abapgit-agent/latest`;
136
+
137
+ https.get(url, (res) => {
138
+ let body = '';
139
+ res.on('data', chunk => body += chunk);
140
+ res.on('end', () => {
141
+ try {
142
+ const data = JSON.parse(body);
143
+ resolve(data.version || null);
144
+ } catch (e) {
145
+ resolve(null);
146
+ }
147
+ });
148
+ }).on('error', () => resolve(null));
149
+ } catch (e) {
150
+ resolve(null);
151
+ }
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Compare two semver versions
157
+ * @param {string} a - Version A (e.g., "1.9.0")
158
+ * @param {string} b - Version B (e.g., "1.8.6")
159
+ * @returns {number} 1 if a > b, -1 if a < b, 0 if equal
160
+ */
161
+ function compareVersions(a, b) {
162
+ const aParts = a.split('.').map(Number);
163
+ const bParts = b.split('.').map(Number);
164
+
165
+ for (let i = 0; i < 3; i++) {
166
+ if (aParts[i] > bParts[i]) return 1;
167
+ if (aParts[i] < bParts[i]) return -1;
168
+ }
169
+ return 0;
170
+ }
171
+
172
+ /**
173
+ * Check for new version availability with daily caching
174
+ * Cache file location (OS-specific):
175
+ * - Linux/macOS: ~/.cache/abapgit-agent/version-check.json
176
+ * - Windows: %LOCALAPPDATA%\abapgit-agent\cache\version-check.json
177
+ * Format: { lastCheck: timestamp, latestVersion: "1.9.0" }
178
+ * @returns {Promise<object>} { hasNewVersion, latestVersion, currentVersion }
179
+ */
180
+ async function checkForNewVersion() {
181
+ const cliVersion = getCliVersion();
182
+ const cacheFile = pathModule.join(getCacheDir(), 'version-check.json');
183
+ const now = Date.now();
184
+ const ONE_DAY = 24 * 60 * 60 * 1000;
185
+
186
+ // Read cache if exists and less than 24 hours old
187
+ if (fs.existsSync(cacheFile)) {
188
+ try {
189
+ const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
190
+ if (now - cache.lastCheck < ONE_DAY && cache.latestVersion) {
191
+ // Use cached result
192
+ return {
193
+ hasNewVersion: compareVersions(cache.latestVersion, cliVersion) > 0,
194
+ latestVersion: cache.latestVersion,
195
+ currentVersion: cliVersion
196
+ };
197
+ }
198
+ } catch (e) {
199
+ // Invalid cache, continue to fetch
200
+ }
201
+ }
202
+
203
+ // Fetch latest version from npm
204
+ const latestVersion = await getLatestNpmVersion();
205
+
206
+ if (latestVersion) {
207
+ // Write cache
208
+ try {
209
+ const cacheDir = pathModule.dirname(cacheFile);
210
+ if (!fs.existsSync(cacheDir)) {
211
+ fs.mkdirSync(cacheDir, { recursive: true });
212
+ }
213
+ fs.writeFileSync(cacheFile, JSON.stringify({
214
+ lastCheck: now,
215
+ latestVersion: latestVersion
216
+ }));
217
+ } catch (e) {
218
+ // Ignore cache write errors
219
+ }
220
+
221
+ return {
222
+ hasNewVersion: compareVersions(latestVersion, cliVersion) > 0,
223
+ latestVersion: latestVersion,
224
+ currentVersion: cliVersion
225
+ };
226
+ }
227
+
228
+ // Failed to fetch, return no update
229
+ return {
230
+ hasNewVersion: false,
231
+ latestVersion: null,
232
+ currentVersion: cliVersion
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Show new version reminder at end of command output
238
+ * Non-blocking, silent on error
239
+ */
240
+ async function showNewVersionReminder() {
241
+ try {
242
+ const { hasNewVersion, latestVersion, currentVersion } = await checkForNewVersion();
243
+
244
+ if (hasNewVersion) {
245
+ console.log('');
246
+ console.log(`💡 New version available: ${latestVersion} (current: ${currentVersion})`);
247
+ console.log(` Run: abapgit-agent upgrade`);
248
+ }
249
+ } catch (e) {
250
+ // Silent - don't interrupt user's workflow
251
+ }
252
+ }
253
+
77
254
  module.exports = {
78
255
  getCliVersion,
79
- checkCompatibility
256
+ checkCompatibility,
257
+ getLatestNpmVersion,
258
+ compareVersions,
259
+ checkForNewVersion,
260
+ showNewVersionReminder
80
261
  };