abapgit-agent 1.5.0 → 1.6.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.
Files changed (117) hide show
  1. package/README.md +1 -0
  2. package/abap/guidelines/00_index.md +35 -0
  3. package/abap/guidelines/01_sql.md +72 -0
  4. package/abap/guidelines/02_exceptions.md +108 -0
  5. package/abap/guidelines/03_testing.md +252 -0
  6. package/abap/guidelines/04_cds.md +120 -0
  7. package/abap/guidelines/05_classes.md +50 -0
  8. package/abap/guidelines/06_objects.md +103 -0
  9. package/abap/guidelines/07_json.md +22 -0
  10. package/abap/guidelines/08_abapgit.md +193 -0
  11. package/bin/abapgit-agent +467 -30
  12. package/bin/abgagt +24 -0
  13. package/package.json +8 -2
  14. package/src/abap-client.js +65 -2
  15. package/src/agent.js +57 -3
  16. package/src/ref-search.js +989 -0
  17. package/.abapGitAgent.example +0 -11
  18. package/.github/workflows/release.yml +0 -60
  19. package/API.md +0 -710
  20. package/CLAUDE.md +0 -1058
  21. package/CLAUDE_MEM.md +0 -88
  22. package/ERROR_HANDLING.md +0 -30
  23. package/INSTALL.md +0 -155
  24. package/RELEASE_NOTES.md +0 -143
  25. package/abap/CLAUDE.md +0 -1010
  26. package/abap/copilot-instructions.md +0 -79
  27. package/abap/package.devc.xml +0 -10
  28. package/abap/zcl_abgagt_agent.clas.abap +0 -420
  29. package/abap/zcl_abgagt_agent.clas.xml +0 -15
  30. package/abap/zcl_abgagt_cmd_factory.clas.abap +0 -48
  31. package/abap/zcl_abgagt_cmd_factory.clas.xml +0 -15
  32. package/abap/zcl_abgagt_command_create.clas.abap +0 -95
  33. package/abap/zcl_abgagt_command_create.clas.xml +0 -15
  34. package/abap/zcl_abgagt_command_import.clas.abap +0 -138
  35. package/abap/zcl_abgagt_command_import.clas.xml +0 -15
  36. package/abap/zcl_abgagt_command_inspect.clas.abap +0 -456
  37. package/abap/zcl_abgagt_command_inspect.clas.testclasses.abap +0 -121
  38. package/abap/zcl_abgagt_command_inspect.clas.xml +0 -16
  39. package/abap/zcl_abgagt_command_preview.clas.abap +0 -386
  40. package/abap/zcl_abgagt_command_preview.clas.xml +0 -15
  41. package/abap/zcl_abgagt_command_pull.clas.abap +0 -80
  42. package/abap/zcl_abgagt_command_pull.clas.testclasses.abap +0 -87
  43. package/abap/zcl_abgagt_command_pull.clas.xml +0 -16
  44. package/abap/zcl_abgagt_command_tree.clas.abap +0 -237
  45. package/abap/zcl_abgagt_command_tree.clas.xml +0 -15
  46. package/abap/zcl_abgagt_command_unit.clas.abap +0 -297
  47. package/abap/zcl_abgagt_command_unit.clas.xml +0 -15
  48. package/abap/zcl_abgagt_command_view.clas.abap +0 -240
  49. package/abap/zcl_abgagt_command_view.clas.xml +0 -15
  50. package/abap/zcl_abgagt_resource_create.clas.abap +0 -71
  51. package/abap/zcl_abgagt_resource_create.clas.xml +0 -15
  52. package/abap/zcl_abgagt_resource_health.clas.abap +0 -25
  53. package/abap/zcl_abgagt_resource_health.clas.xml +0 -15
  54. package/abap/zcl_abgagt_resource_import.clas.abap +0 -66
  55. package/abap/zcl_abgagt_resource_import.clas.xml +0 -15
  56. package/abap/zcl_abgagt_resource_inspect.clas.abap +0 -63
  57. package/abap/zcl_abgagt_resource_inspect.clas.xml +0 -15
  58. package/abap/zcl_abgagt_resource_preview.clas.abap +0 -67
  59. package/abap/zcl_abgagt_resource_preview.clas.xml +0 -15
  60. package/abap/zcl_abgagt_resource_pull.clas.abap +0 -71
  61. package/abap/zcl_abgagt_resource_pull.clas.xml +0 -15
  62. package/abap/zcl_abgagt_resource_tree.clas.abap +0 -70
  63. package/abap/zcl_abgagt_resource_tree.clas.xml +0 -15
  64. package/abap/zcl_abgagt_resource_unit.clas.abap +0 -64
  65. package/abap/zcl_abgagt_resource_unit.clas.xml +0 -15
  66. package/abap/zcl_abgagt_resource_view.clas.abap +0 -68
  67. package/abap/zcl_abgagt_resource_view.clas.xml +0 -15
  68. package/abap/zcl_abgagt_rest_handler.clas.abap +0 -32
  69. package/abap/zcl_abgagt_rest_handler.clas.xml +0 -15
  70. package/abap/zcl_abgagt_util.clas.abap +0 -93
  71. package/abap/zcl_abgagt_util.clas.testclasses.abap +0 -84
  72. package/abap/zcl_abgagt_util.clas.xml +0 -16
  73. package/abap/zcl_abgagt_viewer_clas.clas.abap +0 -58
  74. package/abap/zcl_abgagt_viewer_clas.clas.xml +0 -15
  75. package/abap/zcl_abgagt_viewer_ddls.clas.abap +0 -83
  76. package/abap/zcl_abgagt_viewer_ddls.clas.xml +0 -15
  77. package/abap/zcl_abgagt_viewer_dtel.clas.abap +0 -98
  78. package/abap/zcl_abgagt_viewer_dtel.clas.xml +0 -15
  79. package/abap/zcl_abgagt_viewer_factory.clas.abap +0 -41
  80. package/abap/zcl_abgagt_viewer_factory.clas.xml +0 -15
  81. package/abap/zcl_abgagt_viewer_intf.clas.abap +0 -58
  82. package/abap/zcl_abgagt_viewer_intf.clas.xml +0 -15
  83. package/abap/zcl_abgagt_viewer_stru.clas.abap +0 -59
  84. package/abap/zcl_abgagt_viewer_stru.clas.xml +0 -15
  85. package/abap/zcl_abgagt_viewer_tabl.clas.abap +0 -59
  86. package/abap/zcl_abgagt_viewer_tabl.clas.xml +0 -15
  87. package/abap/zcl_abgagt_viewer_ttyp.clas.abap +0 -93
  88. package/abap/zcl_abgagt_viewer_ttyp.clas.xml +0 -15
  89. package/abap/zif_abgagt_agent.intf.abap +0 -53
  90. package/abap/zif_abgagt_agent.intf.xml +0 -15
  91. package/abap/zif_abgagt_cmd_factory.intf.abap +0 -7
  92. package/abap/zif_abgagt_cmd_factory.intf.xml +0 -15
  93. package/abap/zif_abgagt_command.intf.abap +0 -26
  94. package/abap/zif_abgagt_command.intf.xml +0 -15
  95. package/abap/zif_abgagt_util.intf.abap +0 -28
  96. package/abap/zif_abgagt_util.intf.xml +0 -15
  97. package/abap/zif_abgagt_viewer.intf.abap +0 -12
  98. package/abap/zif_abgagt_viewer.intf.xml +0 -15
  99. package/docs/commands.md +0 -142
  100. package/docs/create-command.md +0 -129
  101. package/docs/health-command.md +0 -89
  102. package/docs/import-command.md +0 -195
  103. package/docs/init-command.md +0 -189
  104. package/docs/inspect-command.md +0 -169
  105. package/docs/list-command.md +0 -289
  106. package/docs/preview-command.md +0 -528
  107. package/docs/pull-command.md +0 -202
  108. package/docs/status-command.md +0 -68
  109. package/docs/tree-command.md +0 -303
  110. package/docs/unit-command.md +0 -167
  111. package/docs/view-command.md +0 -501
  112. package/img/claude.png +0 -0
  113. package/scripts/claude-integration.js +0 -351
  114. package/scripts/release.js +0 -298
  115. package/scripts/release.sh +0 -60
  116. package/scripts/test-integration.js +0 -139
  117. package/scripts/unrelease.js +0 -277
@@ -1,351 +0,0 @@
1
- /**
2
- * Claude Integration - Shell script for Claude Code
3
- *
4
- * This script can be run from any ABAP git repo with .abapGitAgent config.
5
- *
6
- * Usage in Claude:
7
- * 1. Run: node scripts/claude-integration.js pull --url <git-url> [--branch <branch>]
8
- * 2. Parse response for activation result
9
- * 3. Display errors if any
10
- *
11
- * Alternatively, use the CLI directly:
12
- * ./bin/abapgit-agent pull [--branch <branch>]
13
- */
14
-
15
- const http = require('http');
16
- const https = require('https');
17
- const path = require('path');
18
- const fs = require('fs');
19
-
20
- const COOKIE_FILE = path.join(__dirname, '..', '.abapgit_agent_cookies.txt');
21
-
22
- /**
23
- * Check if ABAP AI integration is configured for this repo
24
- * Looks for .abapGitAgent in current working directory
25
- */
26
- function isAbapIntegrationEnabled() {
27
- // Check in current working directory (repo root)
28
- const repoConfigPath = path.join(process.cwd(), '.abapGitAgent');
29
- if (fs.existsSync(repoConfigPath)) {
30
- return true;
31
- }
32
- // Also check if repo has abap/ folder with ABAP objects
33
- const abapFolder = path.join(process.cwd(), 'abap');
34
- if (fs.existsSync(abapFolder)) {
35
- const files = fs.readdirSync(abapFolder);
36
- return files.some(f => f.endsWith('.abap') || f.endsWith('.clas.abap') || f.endsWith('.fugr.abap'));
37
- }
38
- return false;
39
- }
40
-
41
- /**
42
- * Load configuration from .abapGitAgent
43
- */
44
- function loadConfig() {
45
- // First check current working directory (repo root)
46
- const repoConfigPath = path.join(process.cwd(), '.abapGitAgent');
47
-
48
- if (fs.existsSync(repoConfigPath)) {
49
- console.log(`📁 Found .abapGitAgent in: ${repoConfigPath}`);
50
- return JSON.parse(fs.readFileSync(repoConfigPath, 'utf8'));
51
- }
52
-
53
- // Fallback to abapgit-agent parent directory
54
- const bridgeConfigPath = path.join(__dirname, '..', '.abapGitAgent');
55
- if (fs.existsSync(bridgeConfigPath)) {
56
- return JSON.parse(fs.readFileSync(bridgeConfigPath, 'utf8'));
57
- }
58
-
59
- // Fallback to environment variables
60
- return {
61
- host: process.env.ABAP_HOST,
62
- sapport: parseInt(process.env.ABAP_PORT, 10) || 443,
63
- client: process.env.ABAP_CLIENT || '100',
64
- user: process.env.ABAP_USER,
65
- password: process.env.ABAP_PASSWORD,
66
- language: process.env.ABAP_LANGUAGE || 'EN',
67
- gitUsername: process.env.GIT_USERNAME,
68
- gitPassword: process.env.GIT_PASSWORD
69
- };
70
- }
71
-
72
- /**
73
- * Read cookies from Netscape format cookie file
74
- */
75
- function readNetscapeCookies() {
76
- if (!fs.existsSync(COOKIE_FILE)) return '';
77
-
78
- const content = fs.readFileSync(COOKIE_FILE, 'utf8');
79
- const lines = content.split('\n');
80
- const cookies = [];
81
-
82
- for (const line of lines) {
83
- const trimmed = line.trim();
84
- // Skip empty lines and header comments but NOT HttpOnly cookies
85
- if (!trimmed || (trimmed.startsWith('#') && !trimmed.startsWith('#HttpOnly'))) continue;
86
-
87
- const parts = trimmed.split('\t');
88
- if (parts.length >= 7) {
89
- cookies.push(`${parts[5]}=${parts[6]}`);
90
- }
91
- }
92
-
93
- return cookies.join('; ');
94
- }
95
-
96
- /**
97
- * Fetch CSRF token using GET /pull with X-CSRF-Token: fetch
98
- */
99
- async function fetchCsrfToken(config) {
100
- const url = new URL(`/sap/bc/z_abapgit_agent/pull`, `https://${config.host}:${config.sapport}`);
101
-
102
- return new Promise((resolve, reject) => {
103
- const cookieHeader = readNetscapeCookies();
104
-
105
- const options = {
106
- hostname: url.hostname,
107
- port: url.port,
108
- path: url.pathname,
109
- method: 'GET',
110
- headers: {
111
- 'Authorization': `Basic ${Buffer.from(`${config.user}:${config.password}`).toString('base64')}`,
112
- 'sap-client': config.client,
113
- 'sap-language': config.language || 'EN',
114
- 'X-CSRF-Token': 'fetch',
115
- 'Content-Type': 'application/json',
116
- ...(cookieHeader && { 'Cookie': cookieHeader })
117
- },
118
- agent: new https.Agent({ rejectUnauthorized: false })
119
- };
120
-
121
- const req = https.request(options, (res) => {
122
- const csrfToken = res.headers['x-csrf-token'];
123
-
124
- // Save new cookies from response - the CSRF token is tied to this new session!
125
- const setCookie = res.headers['set-cookie'];
126
- if (setCookie) {
127
- const cookies = Array.isArray(setCookie)
128
- ? setCookie.map(c => c.split(';')[0]).join('; ')
129
- : setCookie.split(';')[0];
130
- fs.writeFileSync(COOKIE_FILE, cookies);
131
- }
132
-
133
- let body = '';
134
- res.on('data', chunk => body += chunk);
135
- res.on('end', () => {
136
- resolve(csrfToken);
137
- });
138
- });
139
-
140
- req.on('error', reject);
141
- req.end();
142
- });
143
- }
144
-
145
- /**
146
- * Make HTTP request to ABAP REST endpoint
147
- */
148
- function request(method, path, data = null, options = {}) {
149
- return new Promise((resolve, reject) => {
150
- const config = loadConfig();
151
- const url = new URL(path, `https://${config.host}:${config.sapport}`);
152
-
153
- const headers = {
154
- 'Content-Type': 'application/json',
155
- 'sap-client': config.client,
156
- 'sap-language': config.language || 'EN',
157
- ...options.headers
158
- };
159
-
160
- // Add authorization
161
- headers['Authorization'] = `Basic ${Buffer.from(`${config.user}:${config.password}`).toString('base64')}`;
162
-
163
- // Add CSRF token for POST
164
- if (method === 'POST' && options.csrfToken) {
165
- headers['X-CSRF-Token'] = options.csrfToken;
166
- }
167
-
168
- // Add cookies if available
169
- const cookieHeader = readNetscapeCookies();
170
- if (cookieHeader) {
171
- headers['Cookie'] = cookieHeader;
172
- }
173
-
174
- const reqOptions = {
175
- hostname: url.hostname,
176
- port: url.port,
177
- path: url.pathname,
178
- method,
179
- headers,
180
- agent: new https.Agent({ rejectUnauthorized: false })
181
- };
182
-
183
- const req = (url.protocol === 'https:' ? https : http).request(reqOptions, (res) => {
184
- let body = '';
185
- res.on('data', chunk => body += chunk);
186
- res.on('end', () => {
187
- try {
188
- resolve(JSON.parse(body));
189
- } catch (e) {
190
- resolve(body);
191
- }
192
- });
193
- });
194
-
195
- req.on('error', reject);
196
-
197
- if (data) {
198
- req.write(JSON.stringify(data));
199
- }
200
- req.end();
201
- });
202
- }
203
-
204
- /**
205
- * Pull and activate repository
206
- */
207
- async function pull(gitUrl, branch = 'main') {
208
- console.log(`\n🚀 Starting pull for: ${gitUrl}`);
209
- console.log(` Branch: ${branch}`);
210
-
211
- try {
212
- const config = loadConfig();
213
-
214
- // Fetch CSRF token first
215
- const csrfToken = await fetchCsrfToken(config);
216
-
217
- // Prepare request data with git credentials
218
- const data = {
219
- url: gitUrl,
220
- branch: branch,
221
- username: config.gitUsername,
222
- password: config.gitPassword
223
- };
224
-
225
- const result = await request('POST', '/sap/bc/z_abapgit_agent/pull', data, { csrfToken });
226
-
227
- console.log('\n');
228
-
229
- if (result.success === 'X' || result.success === true) {
230
- console.log(`✅ Pull completed successfully!`);
231
- console.log(` Job ID: ${result.job_id}`);
232
- console.log(` Message: ${result.message}`);
233
- } else {
234
- console.log(`❌ Pull completed with errors!`);
235
- console.log(` Job ID: ${result.job_id}`);
236
- console.log(` Message: ${result.message}`);
237
-
238
- if (result.error_detail) {
239
- console.log(`\n📋 Error Details:`);
240
- console.log(result.error_detail);
241
- }
242
- }
243
-
244
- return result;
245
- } catch (error) {
246
- console.error(`\n❌ Error: ${error.message}`);
247
- process.exit(1);
248
- }
249
- }
250
-
251
- /**
252
- * Check agent health
253
- */
254
- async function healthCheck() {
255
- try {
256
- const result = await request('GET', '/sap/bc/z_abapgit_agent/health');
257
- return result;
258
- } catch (error) {
259
- return { status: 'unreachable', error: error.message };
260
- }
261
- }
262
-
263
- /**
264
- * Main CLI
265
- */
266
- async function main() {
267
- const args = process.argv.slice(2);
268
- const command = args[0];
269
-
270
- // Check if ABAP integration is enabled for this repo
271
- if (!isAbapIntegrationEnabled()) {
272
- console.log(`
273
- ⚠️ ABAP AI Integration not configured for this repository.
274
-
275
- To enable integration:
276
- 1. Create a .abapGitAgent file in the repo root with ABAP connection details:
277
- {
278
- "host": "your-sap-system.com",
279
- "sapport": 443,
280
- "client": "100",
281
- "user": "TECH_USER",
282
- "password": "your-password",
283
- "language": "EN"
284
- }
285
-
286
- 2. Or set environment variables:
287
- - ABAP_HOST, ABAP_PORT, ABAP_CLIENT, ABAP_USER, ABAP_PASSWORD
288
- `);
289
- if (command !== 'help' && command !== '--help' && command !== '-h') {
290
- process.exit(1);
291
- }
292
- }
293
-
294
- try {
295
- switch (command) {
296
- case 'pull':
297
- const urlIndex = args.indexOf('--url') !== -1 ? args.indexOf('--url') + 1 : 1;
298
- const branchIndex = args.indexOf('--branch');
299
-
300
- if (!args[urlIndex]) {
301
- console.error('Error: --url is required');
302
- console.error('Usage: node scripts/claude-integration.js pull --url <git-url> [--branch <branch>]');
303
- process.exit(1);
304
- }
305
-
306
- await pull(args[urlIndex], branchIndex !== -1 ? args[branchIndex + 1] : 'main');
307
- break;
308
-
309
- case 'health':
310
- const health = await healthCheck();
311
- console.log(JSON.stringify(health, null, 2));
312
- break;
313
-
314
- case 'status':
315
- if (isAbapIntegrationEnabled()) {
316
- console.log('✅ ABAP AI Integration is ENABLED');
317
- console.log(' Config location:', path.join(process.cwd(), '.abapGitAgent'));
318
- } else {
319
- console.log('❌ ABAP AI Integration is NOT configured');
320
- }
321
- break;
322
-
323
- default:
324
- console.log(`
325
- ABAP AI Bridge - Claude Integration
326
-
327
- Usage:
328
- node scripts/claude-integration.js <command> [options]
329
-
330
- Commands:
331
- pull --url <git-url> [--branch <branch>]
332
- Pull and activate a repository in ABAP system
333
-
334
- health
335
- Check if ABAP REST API is healthy
336
-
337
- status
338
- Check if ABAP integration is configured for this repo
339
-
340
- Examples:
341
- node scripts/claude-integration.js pull --url https://github.com/user/repo --branch main
342
- node scripts/claude-integration.js health
343
- `);
344
- }
345
- } catch (error) {
346
- console.error(`Error: ${error.message}`);
347
- process.exit(1);
348
- }
349
- }
350
-
351
- main();
@@ -1,298 +0,0 @@
1
- /**
2
- * Release script - Creates release for github.com
3
- *
4
- * Usage: npm run release [--dry-run]
5
- *
6
- * Options:
7
- * --dry-run Test the release flow without pushing to github.com
8
- *
9
- * This script:
10
- * 1. Reads version from package.json
11
- * 2. Updates the ABAP health resource with the new version
12
- * 3. Uses Claude CLI to generate release notes from commits
13
- * 4. Updates RELEASE_NOTES.md with new version notes
14
- * 5. Pushes to github.com to trigger GitHub Actions (unless --dry-run)
15
- * 6. GitHub Actions will publish to npm and create GitHub release
16
- */
17
-
18
- const fs = require('fs');
19
- const path = require('path');
20
- const { execSync } = require('child_process');
21
-
22
- const packageJsonPath = path.join(__dirname, '..', 'package.json');
23
- const abapHealthPath = path.join(__dirname, '..', 'abap', 'zcl_abgagt_resource_health.clas.abap');
24
- const releaseNotesPath = path.join(__dirname, '..', 'RELEASE_NOTES.md');
25
- const repoRoot = path.join(__dirname, '..');
26
-
27
- // Check for --dry-run flag
28
- const args = process.argv.slice(2);
29
- const dryRun = args.includes('--dry-run');
30
-
31
- if (dryRun) {
32
- console.log('🔹 DRY RUN MODE - No actual release will be created\n');
33
- }
34
-
35
- // Read version from package.json
36
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
37
- const version = pkg.version;
38
-
39
- console.log(`Current version: ${version}`);
40
- console.log('');
41
-
42
- // Check if version has been bumped - version must be greater than or equal to latest tag
43
- const allTags = execSync('git tag --sort=-v:refname', { cwd: repoRoot, encoding: 'utf8' });
44
- const tagList = allTags.trim().split('\n').filter(t => t.startsWith('v'));
45
- const latestTag = tagList[0] ? tagList[0].replace('v', '') : '';
46
-
47
- const versionTag = `v${version}`;
48
-
49
- if (version === latestTag) {
50
- // Check if the tag points to HEAD (new release we just created)
51
- try {
52
- const tagRef = execSync(`git rev-parse ${versionTag}^{commit}`, { cwd: repoRoot, encoding: 'utf8' }).trim();
53
- const headRef = execSync('git rev-parse HEAD', { cwd: repoRoot, encoding: 'utf8' }).trim();
54
-
55
- if (tagRef !== headRef) {
56
- console.log(`Version ${version} has already been released (tag v${version} exists on different commit)`);
57
- console.log('');
58
- console.log('To bump version, run one of:');
59
- console.log(' npm version patch # e.g., 1.4.0 -> 1.4.1');
60
- console.log(' npm version minor # e.g., 1.4.0 -> 1.5.0');
61
- console.log(' npm version major # e.g., 1.4.0 -> 2.0.0');
62
- console.log('');
63
- process.exit(1);
64
- }
65
- // If tag points to HEAD, this is the new release we just created - continue
66
- } catch (e) {
67
- // Tag doesn't exist - this is a new version
68
- }
69
- }
70
-
71
- console.log(`Current version: ${version} (tag: ${versionTag})`);
72
- console.log('');
73
-
74
- // Check if there's a remote for github.com
75
- let remoteName = 'origin';
76
- try {
77
- const remotes = execSync('git remote -v', { cwd: repoRoot, encoding: 'utf8' });
78
- if (remotes.includes('github.com') && !remotes.includes('github.tools.sap')) {
79
- remoteName = 'origin';
80
- } else if (remotes.includes('public') && remotes.includes('github.com')) {
81
- remoteName = 'public';
82
- }
83
- console.log(`Using remote: ${remoteName}`);
84
- } catch (e) {
85
- console.log('Could not determine remote, using origin');
86
- }
87
-
88
- console.log('');
89
-
90
- // Read ABAP health resource file
91
- let abapContent = fs.readFileSync(abapHealthPath, 'utf8');
92
-
93
- // Update version in ABAP file (replace existing version)
94
- const oldVersionMatch = abapContent.match(/version":"(\d+\.\d+\.\d+)"/);
95
- if (oldVersionMatch) {
96
- const oldVersion = oldVersionMatch[1];
97
- abapContent = abapContent.replace(
98
- `version":"${oldVersion}"`,
99
- `version":"${version}"`
100
- );
101
-
102
- // Write updated content
103
- fs.writeFileSync(abapHealthPath, abapContent);
104
- console.log(`📦 ABAP version: ${oldVersion} -> ${version}`);
105
- if (dryRun) {
106
- console.log(' (file modified, not committed)');
107
- }
108
- console.log('');
109
- } else {
110
- console.error('Could not find version in ABAP file');
111
- process.exit(1);
112
- }
113
-
114
- // Get commits since last tag for release notes
115
- console.log('Generating release notes with Claude...');
116
- console.log('');
117
-
118
- let releaseNotesContent = '';
119
-
120
- try {
121
- // Find previous tag
122
- const allTags = execSync('git tag --sort=-v:refname', { cwd: repoRoot, encoding: 'utf8' });
123
- const tagList = allTags.trim().split('\n').filter(t => t.startsWith('v'));
124
- const previousTag = tagList[1] || 'HEAD~10';
125
-
126
- // Get commits since last release
127
- const commits = execSync(`git log ${previousTag}..HEAD --oneline`, { cwd: repoRoot, encoding: 'utf8' });
128
-
129
- if (commits.trim()) {
130
- console.log(`Found ${commits.trim().split('\n').length} commits since last release`);
131
- console.log('');
132
-
133
- // Create Claude prompt - escape for shell
134
- const commitsEscaped = commits.replace(/"/g, '\\"').replace(/\n/g, '\\n');
135
- const prompt = `Generate concise release notes for version ${version} of a Node.js CLI tool called abapgit-agent.
136
-
137
- Commits since last release:
138
- ${commitsEscaped}
139
-
140
- Instructions:
141
- 1. IGNORE commits that revert, undo, or remove previous changes
142
- 2. IGNORE commits that fix/improve the release process itself
143
- 3. Focus on actual USER-FACING features and fixes
144
- 4. Use 2-4 bullet points MAX per category
145
- 5. Keep each bullet brief (under 10 words)
146
- 6. START your response with "## v${version}" and END with "---"
147
- 7. OUTPUT ONLY the release notes - do NOT add any intro text, explanation, or commentary
148
- 8. Use this exact format with blank lines between categories:
149
- ## v${version}
150
-
151
- ### New Features
152
-
153
- - Brief feature description
154
-
155
- ### Bug Fixes
156
-
157
- - Brief fix description
158
-
159
- ### Improvements
160
-
161
- - Brief improvement
162
-
163
- ### Documentation
164
-
165
- - Brief doc update
166
-
167
- ---
168
-
169
- OMIT any category that has no items.`;
170
-
171
- // Call Claude CLI
172
- try {
173
- releaseNotesContent = execSync(`claude --print "${prompt}"`, { cwd: repoRoot, encoding: 'utf8', timeout: 60000 });
174
- releaseNotesContent = releaseNotesContent.trim();
175
- console.log('Generated release notes:');
176
- console.log(releaseNotesContent);
177
- console.log('');
178
- } catch (e) {
179
- console.log('Claude CLI not available, using fallback');
180
- releaseNotesContent = `## v${version}\n\nSee commit history for changes.`;
181
- }
182
- } else {
183
- console.log('No commits since last release');
184
- releaseNotesContent = `## v${version}\n\nSee commit history for changes.`;
185
- }
186
- } catch (e) {
187
- console.log('Could not generate release notes:', e.message);
188
- releaseNotesContent = `## v${version}\n\nSee commit history for changes.`;
189
- }
190
-
191
- // Show release notes in dry-run mode
192
- if (dryRun) {
193
- console.log('📝 Generated Release Notes:');
194
- console.log('─'.repeat(50));
195
- console.log(releaseNotesContent);
196
- console.log('─'.repeat(50));
197
- console.log('');
198
- }
199
-
200
- // Update RELEASE_NOTES.md
201
- if (fs.existsSync(releaseNotesPath)) {
202
- const existingContent = fs.readFileSync(releaseNotesPath, 'utf8');
203
-
204
- // Check if version already exists
205
- if (existingContent.includes(`## v${version}`)) {
206
- console.log(`Release notes for v${version} already exist`);
207
- } else {
208
- let newContent;
209
-
210
- // Check if there's a "# Release Notes" header - insert after it if present
211
- if (existingContent.startsWith('# Release Notes')) {
212
- // Find the position after "# Release Notes" and any following content
213
- const lines = existingContent.split('\n');
214
- let insertIndex = 0;
215
- for (let i = 0; i < lines.length; i++) {
216
- if (lines[i].match(/^## v\d+\.\d+\.\d+/)) {
217
- insertIndex = i;
218
- break;
219
- }
220
- }
221
- // Insert new content before the first version header
222
- const before = lines.slice(0, insertIndex).join('\n');
223
- const after = lines.slice(insertIndex).join('\n');
224
- newContent = before + '\n\n' + releaseNotesContent + '\n\n---\n\n' + after;
225
- } else {
226
- // Add new version at the top
227
- newContent = releaseNotesContent + '\n\n---\n\n' + existingContent;
228
- }
229
-
230
- fs.writeFileSync(releaseNotesPath, newContent);
231
- if (dryRun) {
232
- console.log('📄 RELEASE_NOTES.md: would add new version at top');
233
- } else {
234
- console.log(`Updated RELEASE_NOTES.md with v${version}`);
235
- }
236
- }
237
- } else {
238
- // Create new RELEASE_NOTES.md
239
- fs.writeFileSync(releaseNotesPath, releaseNotesContent);
240
- console.log(`Created RELEASE_NOTES.md with v${version}`);
241
- }
242
- console.log('');
243
-
244
- // Check git status and commit (skip in dry-run)
245
- const status = execSync('git status --porcelain', { cwd: repoRoot, encoding: 'utf8' });
246
-
247
- if (status.trim()) {
248
- if (dryRun) {
249
- console.log('🔹 DRY RUN - Would create commit with changes:');
250
- console.log(status);
251
- console.log('');
252
- } else {
253
- // Stage and commit
254
- try {
255
- execSync('git add abap/zcl_abgagt_resource_health.clas.abap package.json RELEASE_NOTES.md', { cwd: repoRoot });
256
- execSync(`git commit -m "chore: release v${version}"`, { cwd: repoRoot });
257
- console.log('Created git commit for version update');
258
- console.log('');
259
- } catch (e) {
260
- console.log('No changes to commit or commit failed');
261
- }
262
- }
263
- } else {
264
- console.log('No changes to commit (version already up to date)');
265
- console.log('');
266
- }
267
-
268
- // Push to trigger GitHub Actions
269
- if (dryRun) {
270
- console.log('🔹 DRY RUN - Skipping push to github.com');
271
- console.log('');
272
- console.log('To actually release, run:');
273
- console.log(` git push ${remoteName} master --follow-tags`);
274
- console.log('');
275
- } else {
276
- console.log('Pushing to github.com to trigger release...');
277
- console.log('');
278
-
279
- try {
280
- // Push master and tags to github.com
281
- execSync(`git push ${remoteName} master --follow-tags`, { cwd: repoRoot });
282
- console.log('Pushed to github.com successfully!');
283
- console.log('');
284
- } catch (e) {
285
- console.log('Push failed, please push manually');
286
- console.log('');
287
- }
288
-
289
- console.log('Release workflow triggered!');
290
- console.log('');
291
- console.log('The GitHub Actions workflow will:');
292
- console.log('1. Run tests');
293
- console.log('2. Publish to npm');
294
- console.log('3. Create GitHub release with Claude-generated notes');
295
- console.log('');
296
- console.log('Next step for ABAP system:');
297
- console.log(' abapgit-agent pull --files abap/zcl_abgagt_resource_health.clas.abap');
298
- }
@@ -1,60 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Release script for abapgit-agent
4
- # Usage: ./scripts/release.sh
5
-
6
- set -e
7
-
8
- echo "=== abapgit-agent Release Script ==="
9
- echo
10
-
11
- # Get current version
12
- CURRENT_VERSION=$(node -p "require('./package.json').version")
13
- echo "Current version: $CURRENT_VERSION"
14
- echo
15
-
16
- # Ask for version bump type
17
- echo "Select version bump type:"
18
- echo " 1) patch (${CURRENT_VERSION} -> ${CURRENT_VERSION%.*}.$((${CURRENT_VERSION##*.}+1)))"
19
- echo " 2) minor (${CURRENT_VERSION} -> $(((${CURRENT_VERSION%%.*})+1)).0)"
20
- echo " 3) major ($(((${CURRENT_VERSION%%.*})+1)).0.0)"
21
- echo " 4) custom"
22
- read -p "Enter choice (1-4): " CHOICE
23
-
24
- case $CHOICE in
25
- 1) npm version patch ;;
26
- 2) npm version minor ;;
27
- 3) npm version major ;;
28
- 4)
29
- read -p "Enter new version: " NEW_VERSION
30
- npm version $NEW_VERSION
31
- ;;
32
- *)
33
- echo "Invalid choice"
34
- exit 1
35
- ;;
36
- esac
37
-
38
- # Get the new version tag
39
- NEW_TAG="v$(node -p "require('./package.json').version')"
40
- echo
41
- echo "Created tag: $NEW_TAG"
42
- echo
43
-
44
- # Push to trigger workflow
45
- read -p "Push to GitHub to trigger release? (y/n): " CONFIRM
46
- if [ "$CONFIRM" = "y" ]; then
47
- echo "Pushing to GitHub..."
48
- git push public --follow-tags
49
- echo
50
- echo "=== Release triggered! ==="
51
- echo "Tag: $NEW_TAG"
52
- echo
53
- echo "Next steps:"
54
- echo "1. Go to https://github.com/SylvosCai/abapgit-agent/releases"
55
- echo "2. Edit the release to add 'What's New' information"
56
- echo "3. Publish the release"
57
- else
58
- echo "Aborted. Tag $NEW_TAG created locally."
59
- echo "To push manually: git push public --follow-tags"
60
- fi