abapgit-agent 1.4.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/.github/workflows/release.yml +4 -1
- package/CLAUDE.md +27 -0
- package/README.md +1 -0
- package/abap/CLAUDE.md +112 -0
- package/abap/zcl_abgagt_command_inspect.clas.abap +57 -12
- package/abap/zcl_abgagt_resource_health.clas.abap +1 -1
- package/abap/zcl_abgagt_resource_inspect.clas.abap +1 -0
- package/bin/abapgit-agent +133 -17
- package/docs/inspect-command.md +12 -1
- package/docs/list-command.md +289 -0
- package/docs/pull-command.md +15 -1
- package/package.json +4 -2
- package/scripts/release.js +298 -0
- package/scripts/unrelease.js +277 -0
- package/src/agent.js +1 -1
- package/src/config.js +9 -2
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# list Command Requirements
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
List ABAP objects in a package with filtering and pagination capabilities. This command provides a flat list of objects with support for filtering by type, name pattern, and pagination.
|
|
6
|
+
|
|
7
|
+
## Command
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# List all objects in a package
|
|
11
|
+
abapgit-agent list --package $ZMY_PACKAGE
|
|
12
|
+
|
|
13
|
+
# Filter by object type
|
|
14
|
+
abapgit-agent list --package $ZMY_PACKAGE --type CLAS,INTF
|
|
15
|
+
|
|
16
|
+
# Filter by name pattern
|
|
17
|
+
abapgit-agent list --package $ZMY_PACKAGE --name ZCL_*
|
|
18
|
+
|
|
19
|
+
# Limit results
|
|
20
|
+
abapgit-agent list --package $ZMY_PACKAGE --limit 50
|
|
21
|
+
|
|
22
|
+
# Paginate results
|
|
23
|
+
abapgit-agent list --package $ZMY_PACKAGE --offset 100 --limit 50
|
|
24
|
+
|
|
25
|
+
# JSON output for scripting
|
|
26
|
+
abapgit-agent list --package $ZMY_PACKAGE --json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Prerequisite
|
|
30
|
+
|
|
31
|
+
- `.abapGitAgent` exists with valid credentials
|
|
32
|
+
- Package must exist in the ABAP system
|
|
33
|
+
|
|
34
|
+
## Parameters
|
|
35
|
+
|
|
36
|
+
| Parameter | Required | Default | Description |
|
|
37
|
+
|-----------|----------|---------|-------------|
|
|
38
|
+
| `--package` | Yes | - | Package name (e.g., `$ZMY_PACKAGE`, `ZMY_PACKAGE`) |
|
|
39
|
+
| `--type` | No | All types | Comma-separated object types (e.g., `CLAS,INTF,PROG`) |
|
|
40
|
+
| `--name` | No | - | Name pattern with wildcard support (e.g., `ZCL_*`) |
|
|
41
|
+
| `--limit` | No | 100 | Maximum objects to return (max: 1000) |
|
|
42
|
+
| `--offset` | No | 0 | Number of objects to skip |
|
|
43
|
+
| `--json` | No | false | Output raw JSON only |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Tasks
|
|
48
|
+
|
|
49
|
+
### 1. Validate Parameters
|
|
50
|
+
|
|
51
|
+
- `--package` must be specified
|
|
52
|
+
- Package name must be valid (1-30 characters)
|
|
53
|
+
- `--limit` must be between 1 and 1000
|
|
54
|
+
|
|
55
|
+
### 2. Load Configuration
|
|
56
|
+
|
|
57
|
+
Read `.abapGitAgent` for credentials
|
|
58
|
+
|
|
59
|
+
### 3. Fetch CSRF Token
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
GET /health (with X-CSRF-Token: fetch)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 4. Make List Request
|
|
66
|
+
|
|
67
|
+
**Endpoint:** `POST /list`
|
|
68
|
+
|
|
69
|
+
**Request Body:**
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"package": "$ZMY_PACKAGE",
|
|
73
|
+
"type": "CLAS,INTF",
|
|
74
|
+
"name": "ZCL_*",
|
|
75
|
+
"limit": 100,
|
|
76
|
+
"offset": 0
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 5. Display Results
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Output
|
|
85
|
+
|
|
86
|
+
### Human-Readable Output
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
📋 Objects in $ZMY_PACKAGE (Total: 15)
|
|
90
|
+
|
|
91
|
+
CLAS (5)
|
|
92
|
+
ZCL_CLASS1
|
|
93
|
+
ZCL_CLASS2
|
|
94
|
+
ZCL_CLASS3
|
|
95
|
+
ZCL_CLASS4
|
|
96
|
+
ZCL_CLASS5
|
|
97
|
+
|
|
98
|
+
INTF (2)
|
|
99
|
+
ZIF_INTERFACE1
|
|
100
|
+
ZIF_INTERFACE2
|
|
101
|
+
|
|
102
|
+
PROG (3)
|
|
103
|
+
ZPROG1
|
|
104
|
+
ZPROG2
|
|
105
|
+
ZPROG3
|
|
106
|
+
|
|
107
|
+
TABL (5)
|
|
108
|
+
ZTABLE1
|
|
109
|
+
ZTABLE2
|
|
110
|
+
ZTABLE3
|
|
111
|
+
ZTABLE4
|
|
112
|
+
ZTABLE5
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### With Type Filter
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
📋 Objects in $ZMY_PACKAGE (CLAS only, Total: 5)
|
|
119
|
+
|
|
120
|
+
CLAS (5)
|
|
121
|
+
ZCL_CLASS1
|
|
122
|
+
ZCL_CLASS2
|
|
123
|
+
ZCL_CLASS3
|
|
124
|
+
ZCL_CLASS4
|
|
125
|
+
ZCL_CLASS5
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### JSON Output
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"SUCCESS": true,
|
|
133
|
+
"COMMAND": "LIST",
|
|
134
|
+
"PACKAGE": "$ZMY_PACKAGE",
|
|
135
|
+
"TOTAL": 15,
|
|
136
|
+
"LIMIT": 100,
|
|
137
|
+
"OFFSET": 0,
|
|
138
|
+
"OBJECTS": [
|
|
139
|
+
{ "TYPE": "CLAS", "NAME": "ZCL_CLASS1" },
|
|
140
|
+
{ "TYPE": "CLAS", "NAME": "ZCL_CLASS2" },
|
|
141
|
+
{ "TYPE": "CLAS", "NAME": "ZCL_CLASS3" },
|
|
142
|
+
{ "TYPE": "CLAS", "NAME": "ZCL_CLASS4" },
|
|
143
|
+
{ "TYPE": "CLAS", "NAME": "ZCL_CLASS5" },
|
|
144
|
+
{ "TYPE": "INTF", "NAME": "ZIF_INTERFACE1" },
|
|
145
|
+
{ "TYPE": "INTF", "NAME": "ZIF_INTERFACE2" },
|
|
146
|
+
{ "TYPE": "PROG", "NAME": "ZPROG1" },
|
|
147
|
+
{ "TYPE": "PROG", "NAME": "ZPROG2" },
|
|
148
|
+
{ "TYPE": "PROG", "NAME": "ZPROG3" },
|
|
149
|
+
{ "TYPE": "TABL", "NAME": "ZTABLE1" },
|
|
150
|
+
{ "TYPE": "TABL", "NAME": "ZTABLE2" },
|
|
151
|
+
{ "TYPE": "TABL", "NAME": "ZTABLE3" },
|
|
152
|
+
{ "TYPE": "TABL", "NAME": "ZTABLE4" },
|
|
153
|
+
{ "TYPE": "TABL", "NAME": "ZTABLE5" }
|
|
154
|
+
],
|
|
155
|
+
"BY_TYPE": [
|
|
156
|
+
{ "TYPE": "CLAS", "COUNT": 5 },
|
|
157
|
+
{ "TYPE": "INTF", "COUNT": 2 },
|
|
158
|
+
{ "TYPE": "PROG", "COUNT": 3 },
|
|
159
|
+
{ "TYPE": "TABL", "COUNT": 5 }
|
|
160
|
+
],
|
|
161
|
+
"ERROR": ""
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Response Fields:**
|
|
166
|
+
|
|
167
|
+
| Field | Type | Description |
|
|
168
|
+
|-------|------|-------------|
|
|
169
|
+
| `SUCCESS` | boolean | Whether the request succeeded |
|
|
170
|
+
| `COMMAND` | string | Command name ("LIST") |
|
|
171
|
+
| `PACKAGE` | string | Package name |
|
|
172
|
+
| `TOTAL` | number | Total objects matching filter |
|
|
173
|
+
| `LIMIT` | number | Requested limit |
|
|
174
|
+
| `OFFSET` | number | Requested offset |
|
|
175
|
+
| `OBJECTS` | array | List of objects [{TYPE, NAME}] |
|
|
176
|
+
| `BY_TYPE` | array | Object counts by type [{TYPE, COUNT}] |
|
|
177
|
+
| `ERROR` | string | Error message (empty if success) |
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Error Handling
|
|
182
|
+
|
|
183
|
+
| Error | Message |
|
|
184
|
+
|-------|---------|
|
|
185
|
+
| Package not specified | `Package parameter is required` |
|
|
186
|
+
| Package not found | `Package <name> does not exist` |
|
|
187
|
+
| Invalid type | `Invalid object type: <type>` |
|
|
188
|
+
| Limit too high | `Limit value too high (max: 1000)` |
|
|
189
|
+
|
|
190
|
+
### Error Output
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
❌ Package not found: $ZNONEXISTENT
|
|
194
|
+
|
|
195
|
+
Error: Package $ZNONEXISTENT does not exist in the system.
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Example
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# List all objects
|
|
204
|
+
abapgit-agent list --package $ZMY_PACKAGE
|
|
205
|
+
|
|
206
|
+
# Filter by type
|
|
207
|
+
abapgit-agent list --package $ZMY_PACKAGE --type CLAS,INTF
|
|
208
|
+
|
|
209
|
+
# Filter by name pattern
|
|
210
|
+
abapgit-agent list --package $ZMY_PACKAGE --name ZCL_*
|
|
211
|
+
|
|
212
|
+
# Paginate
|
|
213
|
+
abapgit-agent list --package $ZMY_PACKAGE --limit 50 --offset 50
|
|
214
|
+
|
|
215
|
+
# JSON for scripting
|
|
216
|
+
abapgit-agent list --package $ZMY_PACKAGE --json > objects.json
|
|
217
|
+
|
|
218
|
+
# CI/CD: Count classes
|
|
219
|
+
CLASS_COUNT=$(abapgit-agent list --package $ZMY_PACKAGE --type CLAS --json | jq '.TOTAL')
|
|
220
|
+
echo "Package has $CLASS_COUNT classes"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Implementation
|
|
226
|
+
|
|
227
|
+
### ABAP Tables Used
|
|
228
|
+
|
|
229
|
+
| Table | Purpose |
|
|
230
|
+
|-------|---------|
|
|
231
|
+
| **TDEVC** | Package definitions (validate package exists) |
|
|
232
|
+
| **TADIR** | Object directory (fetch objects in package) |
|
|
233
|
+
|
|
234
|
+
### Supported Object Types
|
|
235
|
+
|
|
236
|
+
| Type | Description |
|
|
237
|
+
|------|-------------|
|
|
238
|
+
| CLAS | Class |
|
|
239
|
+
| INTF | Interface |
|
|
240
|
+
| PROG | Program |
|
|
241
|
+
| FUGR | Function Group |
|
|
242
|
+
| TABL | Table |
|
|
243
|
+
| STRU | Structure |
|
|
244
|
+
| DTEL | Data Element |
|
|
245
|
+
| TTYP | Table Type |
|
|
246
|
+
| DDLS | CDS View |
|
|
247
|
+
| DDLX | CDS View Entity |
|
|
248
|
+
|
|
249
|
+
### Query Logic
|
|
250
|
+
|
|
251
|
+
```abap
|
|
252
|
+
" Validate package exists
|
|
253
|
+
SELECT SINGLE devclass FROM tdevc
|
|
254
|
+
INTO lv_package
|
|
255
|
+
WHERE devclass = iv_package.
|
|
256
|
+
|
|
257
|
+
" Get objects with filters
|
|
258
|
+
SELECT object obj_name FROM tadir
|
|
259
|
+
INTO TABLE lt_objects
|
|
260
|
+
WHERE devclass = iv_package
|
|
261
|
+
AND object IN lt_types
|
|
262
|
+
AND obj_name LIKE lv_name_pattern
|
|
263
|
+
ORDER BY object obj_name
|
|
264
|
+
LIMIT iv_limit
|
|
265
|
+
OFFSET iv_offset.
|
|
266
|
+
|
|
267
|
+
" Get counts by type (for summary)
|
|
268
|
+
SELECT object COUNT(*) AS count FROM tadir
|
|
269
|
+
INTO TABLE lt_counts
|
|
270
|
+
WHERE devclass = iv_package
|
|
271
|
+
AND object IN lt_types
|
|
272
|
+
GROUP BY object.
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Files to Create
|
|
276
|
+
|
|
277
|
+
| File | Description |
|
|
278
|
+
|------|-------------|
|
|
279
|
+
| `zcl_abgagt_command_list.clas.abap` | Command implementation |
|
|
280
|
+
| `zcl_abgagt_command_list.clas.xml` | Class metadata |
|
|
281
|
+
| `zcl_abgagt_resource_list.clas.abap` | REST resource handler |
|
|
282
|
+
| `zcl_abgagt_resource_list.clas.xml` | Resource metadata |
|
|
283
|
+
|
|
284
|
+
### Files to Modify
|
|
285
|
+
|
|
286
|
+
| File | Description |
|
|
287
|
+
|------|-------------|
|
|
288
|
+
| `zif_abgagt_command.intf.abap` | Add LIST constant |
|
|
289
|
+
| `zcl_abgagt_cmd_factory.clas.abap` | Add LIST command mapping |
|
package/docs/pull-command.md
CHANGED
|
@@ -24,6 +24,9 @@ abapgit-agent pull --transport DEVK900001
|
|
|
24
24
|
|
|
25
25
|
# Combined options
|
|
26
26
|
abapgit-agent pull --branch develop --files src/zcl_my_class.clas.abap --transport DEVK900001
|
|
27
|
+
|
|
28
|
+
# Using transport from config/environment (no --transport flag needed)
|
|
29
|
+
abapgit-agent pull
|
|
27
30
|
```
|
|
28
31
|
|
|
29
32
|
## Prerequisite
|
|
@@ -39,7 +42,18 @@ abapgit-agent pull --branch develop --files src/zcl_my_class.clas.abap --transpo
|
|
|
39
42
|
| `--url` | No | Git repository URL (auto-detected if not specified) |
|
|
40
43
|
| `--branch` | No | Branch name (default: current branch) |
|
|
41
44
|
| `--files` | No | Comma-separated list of files to pull |
|
|
42
|
-
| `--transport` | No | Transport request
|
|
45
|
+
| `--transport` | No | Transport request (config/env takes priority if not specified) |
|
|
46
|
+
|
|
47
|
+
## Transport Request Precedence
|
|
48
|
+
|
|
49
|
+
The transport request is determined in this order:
|
|
50
|
+
|
|
51
|
+
| Priority | Source | Example |
|
|
52
|
+
|----------|--------|---------|
|
|
53
|
+
| 1 | CLI `--transport` argument | `--transport DEVK900001` |
|
|
54
|
+
| 2 | Config file `transport` | `"transport": "DEVK900001"` in `.abapGitAgent` |
|
|
55
|
+
| 3 | Environment variable `ABAP_TRANSPORT` | `export ABAP_TRANSPORT="DEVK900001"` |
|
|
56
|
+
| 4 (default) | Not set | abapGit creates/uses default |
|
|
43
57
|
|
|
44
58
|
---
|
|
45
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "abapgit-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
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
|
"bin": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"start": "node src/server.js",
|
|
11
11
|
"dev": "nodemon src/server.js",
|
|
12
12
|
"test": "jest",
|
|
13
|
-
"pull": "node bin/abapgit-agent"
|
|
13
|
+
"pull": "node bin/abapgit-agent",
|
|
14
|
+
"release": "node scripts/release.js",
|
|
15
|
+
"unrelease": "node scripts/unrelease.js"
|
|
14
16
|
},
|
|
15
17
|
"dependencies": {
|
|
16
18
|
"cors": "^2.8.5",
|
|
@@ -0,0 +1,298 @@
|
|
|
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
|
+
}
|