abapgit-agent 1.3.0 → 1.5.0

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
@@ -23,6 +23,74 @@ const TERM_WIDTH = getTermWidth();
23
23
 
24
24
  const COOKIE_FILE = '.abapgit_agent_cookies.txt';
25
25
 
26
+ /**
27
+ * Get CLI version from package.json
28
+ */
29
+ function getCliVersion() {
30
+ const packageJsonPath = pathModule.join(__dirname, '..', 'package.json');
31
+ if (fs.existsSync(packageJsonPath)) {
32
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
33
+ return pkg.version || '1.0.0';
34
+ }
35
+ return '1.0.0';
36
+ }
37
+
38
+ /**
39
+ * Check version compatibility between CLI and ABAP API
40
+ */
41
+ async function checkVersionCompatibility() {
42
+ const cliVersion = getCliVersion();
43
+
44
+ try {
45
+ const config = loadConfig();
46
+ const https = require('https');
47
+ const url = new URL(`/sap/bc/z_abapgit_agent/health`, `https://${config.host}:${config.sapport}`);
48
+
49
+ return new Promise((resolve) => {
50
+ const options = {
51
+ hostname: url.hostname,
52
+ port: url.port,
53
+ path: url.pathname,
54
+ method: 'GET',
55
+ headers: {
56
+ 'Authorization': `Basic ${Buffer.from(`${config.user}:${config.password}`).toString('base64')}`,
57
+ 'sap-client': config.client,
58
+ 'sap-language': config.language || 'EN',
59
+ 'Content-Type': 'application/json'
60
+ },
61
+ agent: new https.Agent({ rejectUnauthorized: false })
62
+ };
63
+
64
+ const req = https.request(options, (res) => {
65
+ let body = '';
66
+ res.on('data', chunk => body += chunk);
67
+ res.on('end', () => {
68
+ try {
69
+ const result = JSON.parse(body);
70
+ const apiVersion = result.version || '1.0.0';
71
+
72
+ if (cliVersion !== apiVersion) {
73
+ console.log(`\n⚠️ Version mismatch: CLI ${cliVersion}, ABAP API ${apiVersion}`);
74
+ console.log(' Some commands may not work correctly.');
75
+ console.log(' Update ABAP code: abapgit-agent pull\n');
76
+ }
77
+ resolve({ cliVersion, apiVersion, compatible: cliVersion === apiVersion });
78
+ } catch (e) {
79
+ resolve({ cliVersion, apiVersion: null, compatible: false, error: e.message });
80
+ }
81
+ });
82
+ });
83
+
84
+ req.on('error', (e) => {
85
+ resolve({ cliVersion, apiVersion: null, compatible: false, error: e.message });
86
+ });
87
+ req.end();
88
+ });
89
+ } catch (error) {
90
+ return { cliVersion, apiVersion: null, compatible: false, error: error.message };
91
+ }
92
+ }
93
+
26
94
  /**
27
95
  * Load configuration from .abapGitAgent in current working directory
28
96
  */
@@ -42,10 +110,19 @@ function loadConfig() {
42
110
  password: process.env.ABAP_PASSWORD,
43
111
  language: process.env.ABAP_LANGUAGE || 'EN',
44
112
  gitUsername: process.env.GIT_USERNAME,
45
- gitPassword: process.env.GIT_PASSWORD
113
+ gitPassword: process.env.GIT_PASSWORD,
114
+ transport: process.env.ABAP_TRANSPORT
46
115
  };
47
116
  }
48
117
 
118
+ /**
119
+ * Get transport request from config or environment
120
+ */
121
+ function getTransport() {
122
+ const config = loadConfig();
123
+ return config.transport;
124
+ }
125
+
49
126
  /**
50
127
  * Check if ABAP integration is configured for this repo
51
128
  */
@@ -385,7 +462,7 @@ async function syntaxCheckSource(sourceFile, csrfToken, config) {
385
462
  /**
386
463
  * Inspect all files in one request
387
464
  */
388
- async function inspectAllFiles(files, csrfToken, config) {
465
+ async function inspectAllFiles(files, csrfToken, config, variant = null) {
389
466
  // Convert files to uppercase names (same as syntaxCheckSource does)
390
467
  const fileNames = files.map(f => {
391
468
  const baseName = pathModule.basename(f).toUpperCase();
@@ -398,6 +475,11 @@ async function inspectAllFiles(files, csrfToken, config) {
398
475
  files: fileNames
399
476
  };
400
477
 
478
+ // Add variant if specified
479
+ if (variant) {
480
+ data.variant = variant;
481
+ }
482
+
401
483
  const result = await request('POST', '/sap/bc/z_abapgit_agent/inspect', data, { csrfToken: csrfToken });
402
484
 
403
485
  // Handle both table result and old single result
@@ -1195,6 +1277,12 @@ To enable integration:
1195
1277
  }
1196
1278
  }
1197
1279
 
1280
+ // Version compatibility check for commands that interact with ABAP
1281
+ const abapCommands = ['create', 'import', 'pull', 'inspect', 'unit', 'tree', 'view', 'preview'];
1282
+ if (command && abapCommands.includes(command)) {
1283
+ await checkVersionCompatibility();
1284
+ }
1285
+
1198
1286
  try {
1199
1287
  switch (command) {
1200
1288
  case 'init':
@@ -1390,16 +1478,21 @@ Examples:
1390
1478
  let gitUrl = urlArgIndex !== -1 ? args[urlArgIndex + 1] : null;
1391
1479
  let branch = branchArgIndex !== -1 ? args[branchArgIndex + 1] : getGitBranch();
1392
1480
  let files = null;
1481
+
1482
+ // Transport: CLI arg takes priority, then config/environment, then null
1393
1483
  let transportRequest = null;
1484
+ if (transportArgIndex !== -1 && transportArgIndex + 1 < args.length) {
1485
+ // Explicit --transport argument
1486
+ transportRequest = args[transportArgIndex + 1];
1487
+ } else {
1488
+ // Fall back to config or environment variable
1489
+ transportRequest = getTransport();
1490
+ }
1394
1491
 
1395
1492
  if (filesArgIndex !== -1 && filesArgIndex + 1 < args.length) {
1396
1493
  files = args[filesArgIndex + 1].split(',').map(f => f.trim());
1397
1494
  }
1398
1495
 
1399
- if (transportArgIndex !== -1 && transportArgIndex + 1 < args.length) {
1400
- transportRequest = args[transportArgIndex + 1];
1401
- }
1402
-
1403
1496
  if (!gitUrl) {
1404
1497
  gitUrl = getGitRemoteUrl();
1405
1498
  if (!gitUrl) {
@@ -1427,31 +1520,32 @@ Examples:
1427
1520
  break;
1428
1521
 
1429
1522
  case 'inspect': {
1430
- // TODO: Implement full inspect feature with:
1431
- // - Syntax check (currently implemented via /inspect)
1432
- // - Code Inspector checks (SE51, SCI)
1433
- // - ATC checks (SATC)
1434
- // - Custom rule checks
1435
- // Add --check-type parameter to specify which check to run
1436
-
1437
1523
  const filesArgIndex = args.indexOf('--files');
1438
1524
  if (filesArgIndex === -1 || filesArgIndex + 1 >= args.length) {
1439
1525
  console.error('Error: --files parameter required');
1440
- console.error('Usage: abapgit-agent inspect --files <file1>,<file2>,...');
1526
+ console.error('Usage: abapgit-agent inspect --files <file1>,<file2>,... [--variant <check-variant>]');
1441
1527
  console.error('Example: abapgit-agent inspect --files zcl_my_class.clas.abap');
1528
+ console.error('Example: abapgit-agent inspect --files zcl_my_class.clas.abap --variant ALL_CHECKS');
1442
1529
  process.exit(1);
1443
1530
  }
1444
1531
 
1445
1532
  const filesSyntaxCheck = args[filesArgIndex + 1].split(',').map(f => f.trim());
1446
1533
 
1534
+ // Parse optional --variant parameter
1535
+ const variantArgIndex = args.indexOf('--variant');
1536
+ const variant = variantArgIndex !== -1 ? args[variantArgIndex + 1] : null;
1537
+
1447
1538
  console.log(`\n Inspect for ${filesSyntaxCheck.length} file(s)`);
1539
+ if (variant) {
1540
+ console.log(` Using variant: ${variant}`);
1541
+ }
1448
1542
  console.log('');
1449
1543
 
1450
1544
  const config = loadConfig();
1451
1545
  const csrfToken = await fetchCsrfToken(config);
1452
1546
 
1453
1547
  // Send all files in one request
1454
- const results = await inspectAllFiles(filesSyntaxCheck, csrfToken, config);
1548
+ const results = await inspectAllFiles(filesSyntaxCheck, csrfToken, config, variant);
1455
1549
 
1456
1550
  // Process results
1457
1551
  for (const result of results) {
@@ -1485,15 +1579,37 @@ Examples:
1485
1579
 
1486
1580
  case 'tree': {
1487
1581
  const packageArgIndex = args.indexOf('--package');
1488
- if (packageArgIndex === -1 || packageArgIndex + 1 >= args.length) {
1582
+ if (packageArgIndex === -1) {
1489
1583
  console.error('Error: --package parameter required');
1490
1584
  console.error('Usage: abapgit-agent tree --package <package> [--depth <n>] [--include-objects] [--json]');
1491
- console.error('Example: abapgit-agent tree --package $ZMY_PACKAGE');
1585
+ console.error('Example: abapgit-agent tree --package ZMY_PACKAGE');
1586
+ process.exit(1);
1587
+ }
1588
+
1589
+ // Check if package value is missing (happens when shell variable expands to empty)
1590
+ if (packageArgIndex + 1 >= args.length) {
1591
+ console.error('Error: --package parameter value is missing');
1592
+ console.error('');
1593
+ console.error('Tip: If you are using a shell variable, make sure to quote it:');
1594
+ console.error(' abapgit-agent tree --package "$ZMY_PACKAGE"');
1595
+ console.error(' or escape the $ character:');
1596
+ console.error(' abapgit-agent tree --package \\$ZMY_PACKAGE');
1597
+ console.error('');
1598
+ console.error('Usage: abapgit-agent tree --package <package> [--depth <n>] [--include-objects] [--json]');
1599
+ console.error('Example: abapgit-agent tree --package ZMY_PACKAGE');
1492
1600
  process.exit(1);
1493
1601
  }
1494
1602
 
1495
1603
  const packageName = args[packageArgIndex + 1];
1496
1604
 
1605
+ // Check for empty/whitespace-only package name
1606
+ if (!packageName || packageName.trim() === '') {
1607
+ console.error('Error: --package parameter cannot be empty');
1608
+ console.error('Usage: abapgit-agent tree --package <package> [--depth <n>] [--include-objects] [--json]');
1609
+ console.error('Example: abapgit-agent tree --package ZMY_PACKAGE');
1610
+ process.exit(1);
1611
+ }
1612
+
1497
1613
  // Optional depth parameter
1498
1614
  const depthArgIndex = args.indexOf('--depth');
1499
1615
  let depth = 3;
@@ -1726,6 +1842,243 @@ Examples:
1726
1842
  break;
1727
1843
  }
1728
1844
 
1845
+ case 'preview': {
1846
+ const objectsArgIndex = args.indexOf('--objects');
1847
+ if (objectsArgIndex === -1 || objectsArgIndex + 1 >= args.length) {
1848
+ console.error('Error: --objects parameter required');
1849
+ console.error('Usage: abapgit-agent preview --objects <table1>,<view1>,... [--type <type>] [--limit <n>] [--where <condition>] [--columns <cols>] [--vertical] [--compact] [--json]');
1850
+ console.error('Example: abapgit-agent preview --objects SFLIGHT');
1851
+ console.error('Example: abapgit-agent preview --objects ZC_MY_CDS_VIEW --type DDLS');
1852
+ console.error('Example: abapgit-agent preview --objects SFLIGHT --where "CARRID = \'AA\'"');
1853
+ process.exit(1);
1854
+ }
1855
+
1856
+ const objects = args[objectsArgIndex + 1].split(',').map(o => o.trim().toUpperCase());
1857
+ const typeArg = args.indexOf('--type');
1858
+ const type = typeArg !== -1 ? args[typeArg + 1].toUpperCase() : null;
1859
+ const limitArg = args.indexOf('--limit');
1860
+ const limit = limitArg !== -1 ? parseInt(args[limitArg + 1], 10) : 10;
1861
+ const whereArg = args.indexOf('--where');
1862
+ const where = whereArg !== -1 ? args[whereArg + 1] : null;
1863
+ const columnsArg = args.indexOf('--columns');
1864
+ const columns = columnsArg !== -1 ? args[columnsArg + 1].split(',').map(c => c.trim().toUpperCase()) : null;
1865
+ const verticalOutput = args.includes('--vertical');
1866
+ const compactOutput = args.includes('--compact');
1867
+ const jsonOutput = args.includes('--json');
1868
+
1869
+ console.log(`\n Previewing ${objects.length} object(s)`);
1870
+
1871
+ const config = loadConfig();
1872
+ const csrfToken = await fetchCsrfToken(config);
1873
+
1874
+ const data = {
1875
+ objects: objects,
1876
+ limit: Math.min(Math.max(1, limit), 100)
1877
+ };
1878
+
1879
+ if (type) {
1880
+ data.type = type;
1881
+ }
1882
+
1883
+ if (where) {
1884
+ data.where = where;
1885
+ }
1886
+
1887
+ if (columns) {
1888
+ data.columns = columns;
1889
+ }
1890
+
1891
+ const result = await request('POST', '/sap/bc/z_abapgit_agent/preview', data, { csrfToken });
1892
+
1893
+ // Handle uppercase keys from ABAP
1894
+ const success = result.SUCCESS || result.success;
1895
+ const previewObjects = result.OBJECTS || result.objects || [];
1896
+ const message = result.MESSAGE || result.message || '';
1897
+ const error = result.ERROR || result.error;
1898
+
1899
+ if (!success || error) {
1900
+ console.error(`\n Error: ${error || 'Failed to preview objects'}`);
1901
+ break;
1902
+ }
1903
+
1904
+ if (jsonOutput) {
1905
+ console.log(JSON.stringify(result, null, 2));
1906
+ } else {
1907
+ console.log(`\n ${message}`);
1908
+ console.log('');
1909
+
1910
+ // Track if columns were explicitly specified
1911
+ const columnsExplicit = columns !== null;
1912
+
1913
+ for (let i = 0; i < previewObjects.length; i++) {
1914
+ const obj = previewObjects[i];
1915
+ const objName = obj.NAME || obj.name || `Object ${i + 1}`;
1916
+ const objType = obj.TYPE || obj.type || '';
1917
+ const objTypeText = obj.TYPE_TEXT || obj.type_text || '';
1918
+ // Parse rows - could be a JSON string or array
1919
+ let rows = obj.ROWS || obj.rows || [];
1920
+ if (typeof rows === 'string') {
1921
+ try {
1922
+ rows = JSON.parse(rows);
1923
+ } catch (e) {
1924
+ rows = [];
1925
+ }
1926
+ }
1927
+ const fields = obj.FIELDS || obj.fields || [];
1928
+ const rowCount = obj.ROW_COUNT || obj.row_count || 0;
1929
+ const totalRows = obj.TOTAL_ROWS || obj.total_rows || 0;
1930
+ const notFound = obj.NOT_FOUND || obj.not_found || false;
1931
+ const accessDenied = obj.ACCESS_DENIED || obj.access_denied || false;
1932
+
1933
+ // Check if object was not found
1934
+ if (notFound) {
1935
+ console.log(` ❌ ${objName} (${objTypeText})`);
1936
+ console.log(` Object not found: ${objName}`);
1937
+ continue;
1938
+ }
1939
+
1940
+ // Check if access denied
1941
+ if (accessDenied) {
1942
+ console.log(` ❌ ${objName} (${objTypeText})`);
1943
+ console.log(` Access denied to: ${objName}`);
1944
+ continue;
1945
+ }
1946
+
1947
+ console.log(` 📊 Preview: ${objName} (${objTypeText})`);
1948
+
1949
+ // Check for errors first
1950
+ const objError = obj.ERROR || obj.error;
1951
+ if (objError) {
1952
+ console.log(` ❌ Error: ${objError}`);
1953
+ continue;
1954
+ }
1955
+
1956
+ if (rows.length === 0) {
1957
+ console.log(' No data found');
1958
+ continue;
1959
+ }
1960
+
1961
+ // Get all unique field names from all rows
1962
+ const allFields = new Set();
1963
+ rows.forEach(row => {
1964
+ Object.keys(row).forEach(key => allFields.add(key));
1965
+ });
1966
+ const allFieldNames = Array.from(allFields);
1967
+
1968
+ // Display as table - use fields metadata only if --columns was explicitly specified
1969
+ let fieldNames;
1970
+ let columnsAutoSelected = false;
1971
+ if (columnsExplicit && fields && fields.length > 0) {
1972
+ // Use fields from metadata (filtered by explicit --columns)
1973
+ fieldNames = fields.map(f => f.FIELD || f.field);
1974
+ } else {
1975
+ // Use all fields - let terminal handle wrapping if needed
1976
+ // Terminal width detection is unreliable without a proper TTY
1977
+ fieldNames = allFieldNames;
1978
+ }
1979
+
1980
+ // Calculate column widths - use reasonable defaults
1981
+ const colWidths = {};
1982
+ const maxColWidth = compactOutput ? 10 : 20;
1983
+ fieldNames.forEach(field => {
1984
+ let maxWidth = field.length;
1985
+ rows.forEach(row => {
1986
+ const value = String(row[field] || '');
1987
+ maxWidth = Math.max(maxWidth, value.length);
1988
+ });
1989
+ // Cap at maxColWidth (truncates both headers and data in compact mode)
1990
+ colWidths[field] = Math.min(maxWidth, maxColWidth);
1991
+ });
1992
+
1993
+ // Render output - either vertical or table
1994
+ if (verticalOutput) {
1995
+ // Vertical format: each field on its own line
1996
+ rows.forEach((row, rowIndex) => {
1997
+ if (rows.length > 1) {
1998
+ console.log(`\n Row ${rowIndex + 1}:`);
1999
+ console.log(' ' + '─'.repeat(30));
2000
+ }
2001
+ fieldNames.forEach(field => {
2002
+ const value = String(row[field] || '');
2003
+ console.log(` ${field}: ${value}`);
2004
+ });
2005
+ });
2006
+ console.log('');
2007
+ continue;
2008
+ }
2009
+
2010
+ // Build table header
2011
+ let headerLine = ' ┌';
2012
+ let separatorLine = ' ├';
2013
+ fieldNames.forEach(field => {
2014
+ const width = colWidths[field];
2015
+ headerLine += '─'.repeat(width + 2) + '┬';
2016
+ separatorLine += '─'.repeat(width + 2) + '┼';
2017
+ });
2018
+ headerLine = headerLine.slice(0, -1) + '┐';
2019
+ separatorLine = separatorLine.slice(0, -1) + '┤';
2020
+
2021
+ // Build header row
2022
+ let headerRow = ' │';
2023
+ fieldNames.forEach(field => {
2024
+ const width = colWidths[field];
2025
+ let displayField = String(field);
2026
+ if (displayField.length > width) {
2027
+ displayField = displayField.slice(0, width - 3) + '...';
2028
+ }
2029
+ headerRow += ' ' + displayField.padEnd(width) + ' │';
2030
+ });
2031
+
2032
+ console.log(headerLine);
2033
+ console.log(headerRow);
2034
+ console.log(separatorLine);
2035
+
2036
+ // Build data rows
2037
+ rows.forEach(row => {
2038
+ let dataRow = ' │';
2039
+ fieldNames.forEach(field => {
2040
+ const width = colWidths[field];
2041
+ const value = String(row[field] || '');
2042
+ const displayValue = value.length > width ? value.slice(0, width - 3) + '...' : value;
2043
+ dataRow += ' ' + displayValue.padEnd(width) + ' │';
2044
+ });
2045
+ console.log(dataRow);
2046
+ });
2047
+
2048
+ // Build bottom border
2049
+ let bottomLine = ' └';
2050
+ fieldNames.forEach(field => {
2051
+ const width = colWidths[field];
2052
+ bottomLine += '─'.repeat(width + 2) + '┴';
2053
+ });
2054
+ bottomLine = bottomLine.slice(0, -1) + '┘';
2055
+ console.log(bottomLine);
2056
+
2057
+ // Show row count
2058
+ if (totalRows > rowCount) {
2059
+ console.log(`\n Showing ${rowCount} of ${totalRows} rows`);
2060
+ } else {
2061
+ console.log(`\n ${rowCount} row(s)`);
2062
+ }
2063
+
2064
+ // Note about hidden columns only when --columns was explicitly specified
2065
+ const columnsDisplayed = fieldNames.length;
2066
+ let columnsHidden = [];
2067
+
2068
+ if (columnsExplicit) {
2069
+ columnsHidden = obj.COLUMNS_HIDDEN || obj.columns_hidden || [];
2070
+ if (columnsHidden.length > 0) {
2071
+ console.log(`\n ⚠️ ${columnsHidden.length} more columns hidden (${columnsHidden.join(', ')})`);
2072
+ console.log(' Use --json for full data');
2073
+ }
2074
+ }
2075
+
2076
+ console.log('');
2077
+ }
2078
+ }
2079
+ break;
2080
+ }
2081
+
1729
2082
  case 'help':
1730
2083
  case '--help':
1731
2084
  case '-h':
package/docs/commands.md CHANGED
@@ -14,6 +14,7 @@ All available CLI commands for abapGit Agent.
14
14
  | [tree](tree-command.md) | ✅ | Display package hierarchy tree |
15
15
  | [unit](unit-command.md) | ✅ | Run AUnit tests |
16
16
  | [view](view-command.md) | ✅ | View ABAP object source code from system |
17
+ | [preview](preview-command.md) | 🔄 | Preview table/CDS view data |
17
18
  | [health](health-command.md) | ✅ | Health check |
18
19
  | [status](status-command.md) | ✅ | Status check |
19
20
 
@@ -70,6 +71,10 @@ abapgit-agent tree --package $MY_PACKAGE
70
71
  abapgit-agent view --objects ZCL_MY_CLASS
71
72
  abapgit-agent view --objects SFLIGHT --type TABL
72
73
 
74
+ # Preview table/CDS view data
75
+ abapgit-agent preview --objects SFLIGHT
76
+ abapgit-agent preview --objects ZC_MY_CDS_VIEW --type DDLS
77
+
73
78
  # Check configuration
74
79
  abapgit-agent status
75
80
 
@@ -15,6 +15,12 @@ abapgit-agent inspect --files zcl_my_class.clas.abap,zcl_other.clas.abap
15
15
 
16
16
  # With path
17
17
  abapgit-agent inspect --files abap/zcl_my_class.clas.abap
18
+
19
+ # With Code Inspector variant
20
+ abapgit-agent inspect --files abap/zcl_my_class.clas.abap --variant ALL_CHECKS
21
+
22
+ # With no variant (uses default SAP standard checks)
23
+ abapgit-agent inspect --files abap/zcl_my_class.clas.abap --variant EMPTY
18
24
  ```
19
25
 
20
26
  ## Prerequisite
@@ -27,6 +33,7 @@ abapgit-agent inspect --files abap/zcl_my_class.clas.abap
27
33
  | Parameter | Required | Description |
28
34
  |-----------|----------|-------------|
29
35
  | `--files` | Yes | Comma-separated list of files to inspect |
36
+ | `--variant` | No | Code Inspector variant name (e.g., `ALL_CHECKS`, `EMPTY`) |
30
37
 
31
38
  ---
32
39
 
@@ -54,7 +61,8 @@ GET /health (with X-CSRF-Token: fetch)
54
61
  **Request Body:**
55
62
  ```json
56
63
  {
57
- "files": ["ZCL_MY_CLASS.CLASS.ABAP"]
64
+ "files": ["ZCL_MY_CLASS.CLASS.ABAP"],
65
+ "variant": "ALL_CHECKS"
58
66
  }
59
67
  ```
60
68
 
@@ -140,6 +148,9 @@ abapgit-agent inspect --files zcl_my_class.clas.abap
140
148
 
141
149
  # Multiple files
142
150
  abapgit-agent inspect --files abap/zcl_my_class.clas.abap,abap/zcl_other.clas.abap
151
+
152
+ # With Code Inspector variant
153
+ abapgit-agent inspect --files abap/zcl_my_class.clas.abap --variant ALL_CHECKS
143
154
  ```
144
155
 
145
156
  ## Use Case