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.
- package/README.md +1 -0
- package/abap/guidelines/00_index.md +35 -0
- package/abap/guidelines/01_sql.md +72 -0
- package/abap/guidelines/02_exceptions.md +108 -0
- package/abap/guidelines/03_testing.md +252 -0
- package/abap/guidelines/04_cds.md +120 -0
- package/abap/guidelines/05_classes.md +50 -0
- package/abap/guidelines/06_objects.md +103 -0
- package/abap/guidelines/07_json.md +22 -0
- package/abap/guidelines/08_abapgit.md +193 -0
- package/bin/abapgit-agent +467 -30
- package/bin/abgagt +24 -0
- package/package.json +8 -2
- package/src/abap-client.js +65 -2
- package/src/agent.js +57 -3
- package/src/ref-search.js +989 -0
- package/.abapGitAgent.example +0 -11
- package/.github/workflows/release.yml +0 -60
- package/API.md +0 -710
- package/CLAUDE.md +0 -1058
- package/CLAUDE_MEM.md +0 -88
- package/ERROR_HANDLING.md +0 -30
- package/INSTALL.md +0 -155
- package/RELEASE_NOTES.md +0 -143
- package/abap/CLAUDE.md +0 -1010
- package/abap/copilot-instructions.md +0 -79
- package/abap/package.devc.xml +0 -10
- package/abap/zcl_abgagt_agent.clas.abap +0 -420
- package/abap/zcl_abgagt_agent.clas.xml +0 -15
- package/abap/zcl_abgagt_cmd_factory.clas.abap +0 -48
- package/abap/zcl_abgagt_cmd_factory.clas.xml +0 -15
- package/abap/zcl_abgagt_command_create.clas.abap +0 -95
- package/abap/zcl_abgagt_command_create.clas.xml +0 -15
- package/abap/zcl_abgagt_command_import.clas.abap +0 -138
- package/abap/zcl_abgagt_command_import.clas.xml +0 -15
- package/abap/zcl_abgagt_command_inspect.clas.abap +0 -456
- package/abap/zcl_abgagt_command_inspect.clas.testclasses.abap +0 -121
- package/abap/zcl_abgagt_command_inspect.clas.xml +0 -16
- package/abap/zcl_abgagt_command_preview.clas.abap +0 -386
- package/abap/zcl_abgagt_command_preview.clas.xml +0 -15
- package/abap/zcl_abgagt_command_pull.clas.abap +0 -80
- package/abap/zcl_abgagt_command_pull.clas.testclasses.abap +0 -87
- package/abap/zcl_abgagt_command_pull.clas.xml +0 -16
- package/abap/zcl_abgagt_command_tree.clas.abap +0 -237
- package/abap/zcl_abgagt_command_tree.clas.xml +0 -15
- package/abap/zcl_abgagt_command_unit.clas.abap +0 -297
- package/abap/zcl_abgagt_command_unit.clas.xml +0 -15
- package/abap/zcl_abgagt_command_view.clas.abap +0 -240
- package/abap/zcl_abgagt_command_view.clas.xml +0 -15
- package/abap/zcl_abgagt_resource_create.clas.abap +0 -71
- package/abap/zcl_abgagt_resource_create.clas.xml +0 -15
- package/abap/zcl_abgagt_resource_health.clas.abap +0 -25
- package/abap/zcl_abgagt_resource_health.clas.xml +0 -15
- package/abap/zcl_abgagt_resource_import.clas.abap +0 -66
- package/abap/zcl_abgagt_resource_import.clas.xml +0 -15
- package/abap/zcl_abgagt_resource_inspect.clas.abap +0 -63
- package/abap/zcl_abgagt_resource_inspect.clas.xml +0 -15
- package/abap/zcl_abgagt_resource_preview.clas.abap +0 -67
- package/abap/zcl_abgagt_resource_preview.clas.xml +0 -15
- package/abap/zcl_abgagt_resource_pull.clas.abap +0 -71
- package/abap/zcl_abgagt_resource_pull.clas.xml +0 -15
- package/abap/zcl_abgagt_resource_tree.clas.abap +0 -70
- package/abap/zcl_abgagt_resource_tree.clas.xml +0 -15
- package/abap/zcl_abgagt_resource_unit.clas.abap +0 -64
- package/abap/zcl_abgagt_resource_unit.clas.xml +0 -15
- package/abap/zcl_abgagt_resource_view.clas.abap +0 -68
- package/abap/zcl_abgagt_resource_view.clas.xml +0 -15
- package/abap/zcl_abgagt_rest_handler.clas.abap +0 -32
- package/abap/zcl_abgagt_rest_handler.clas.xml +0 -15
- package/abap/zcl_abgagt_util.clas.abap +0 -93
- package/abap/zcl_abgagt_util.clas.testclasses.abap +0 -84
- package/abap/zcl_abgagt_util.clas.xml +0 -16
- package/abap/zcl_abgagt_viewer_clas.clas.abap +0 -58
- package/abap/zcl_abgagt_viewer_clas.clas.xml +0 -15
- package/abap/zcl_abgagt_viewer_ddls.clas.abap +0 -83
- package/abap/zcl_abgagt_viewer_ddls.clas.xml +0 -15
- package/abap/zcl_abgagt_viewer_dtel.clas.abap +0 -98
- package/abap/zcl_abgagt_viewer_dtel.clas.xml +0 -15
- package/abap/zcl_abgagt_viewer_factory.clas.abap +0 -41
- package/abap/zcl_abgagt_viewer_factory.clas.xml +0 -15
- package/abap/zcl_abgagt_viewer_intf.clas.abap +0 -58
- package/abap/zcl_abgagt_viewer_intf.clas.xml +0 -15
- package/abap/zcl_abgagt_viewer_stru.clas.abap +0 -59
- package/abap/zcl_abgagt_viewer_stru.clas.xml +0 -15
- package/abap/zcl_abgagt_viewer_tabl.clas.abap +0 -59
- package/abap/zcl_abgagt_viewer_tabl.clas.xml +0 -15
- package/abap/zcl_abgagt_viewer_ttyp.clas.abap +0 -93
- package/abap/zcl_abgagt_viewer_ttyp.clas.xml +0 -15
- package/abap/zif_abgagt_agent.intf.abap +0 -53
- package/abap/zif_abgagt_agent.intf.xml +0 -15
- package/abap/zif_abgagt_cmd_factory.intf.abap +0 -7
- package/abap/zif_abgagt_cmd_factory.intf.xml +0 -15
- package/abap/zif_abgagt_command.intf.abap +0 -26
- package/abap/zif_abgagt_command.intf.xml +0 -15
- package/abap/zif_abgagt_util.intf.abap +0 -28
- package/abap/zif_abgagt_util.intf.xml +0 -15
- package/abap/zif_abgagt_viewer.intf.abap +0 -12
- package/abap/zif_abgagt_viewer.intf.xml +0 -15
- package/docs/commands.md +0 -142
- package/docs/create-command.md +0 -129
- package/docs/health-command.md +0 -89
- package/docs/import-command.md +0 -195
- package/docs/init-command.md +0 -189
- package/docs/inspect-command.md +0 -169
- package/docs/list-command.md +0 -289
- package/docs/preview-command.md +0 -528
- package/docs/pull-command.md +0 -202
- package/docs/status-command.md +0 -68
- package/docs/tree-command.md +0 -303
- package/docs/unit-command.md +0 -167
- package/docs/view-command.md +0 -501
- package/img/claude.png +0 -0
- package/scripts/claude-integration.js +0 -351
- package/scripts/release.js +0 -298
- package/scripts/release.sh +0 -60
- package/scripts/test-integration.js +0 -139
- 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();
|
package/scripts/release.js
DELETED
|
@@ -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
|
-
}
|
package/scripts/release.sh
DELETED
|
@@ -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
|