@mseep/bw-modeling-mcp 0.8.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/CHANGELOG.md +140 -0
- package/LICENSE +21 -0
- package/README.md +598 -0
- package/dist/bw-client.js +774 -0
- package/dist/index.js +2199 -0
- package/dist/tools/activation.js +171 -0
- package/dist/tools/adso.js +895 -0
- package/dist/tools/composite_provider.js +169 -0
- package/dist/tools/cp_components.js +347 -0
- package/dist/tools/dataflow.js +148 -0
- package/dist/tools/datasource.js +536 -0
- package/dist/tools/delete.js +22 -0
- package/dist/tools/dtp.js +602 -0
- package/dist/tools/infoarea.js +117 -0
- package/dist/tools/infoobject.js +447 -0
- package/dist/tools/infosource.js +225 -0
- package/dist/tools/processchain.js +154 -0
- package/dist/tools/processvariant.js +49 -0
- package/dist/tools/push.js +100 -0
- package/dist/tools/query.js +631 -0
- package/dist/tools/reporting.js +558 -0
- package/dist/tools/repository.js +84 -0
- package/dist/tools/request_monitor.js +174 -0
- package/dist/tools/roles.js +503 -0
- package/dist/tools/search.js +107 -0
- package/dist/tools/transformation.js +1392 -0
- package/package.json +51 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { MEDIA_TYPES, createClientFromEnv } from '../bw-client.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse all <atom:title> entries from an activation/atom feed response.
|
|
4
|
+
* Used to extract success messages and deactivated DTP names.
|
|
5
|
+
*/
|
|
6
|
+
export function parseActivationMessages(xml) {
|
|
7
|
+
const messages = [];
|
|
8
|
+
const regex = /<atom:title>([^<]+)<\/atom:title>/g;
|
|
9
|
+
let match;
|
|
10
|
+
while ((match = regex.exec(xml)) !== null) {
|
|
11
|
+
messages.push(match[1]);
|
|
12
|
+
}
|
|
13
|
+
return messages;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse DTP names that were deactivated by impact analysis from activation response.
|
|
17
|
+
* Matches both German and English SAP system messages containing a DTP name and a deactivation keyword.
|
|
18
|
+
*/
|
|
19
|
+
export function parseDtpsDeactivated(xml) {
|
|
20
|
+
const dtps = [];
|
|
21
|
+
const messages = parseActivationMessages(xml);
|
|
22
|
+
for (const msg of messages) {
|
|
23
|
+
// Match DTP name pattern in German or English activation messages
|
|
24
|
+
const dtpMatch = msg.match(/\b(DTP_[A-Z0-9]+)\b/i);
|
|
25
|
+
if (dtpMatch && (msg.toLowerCase().includes('deaktiv') || msg.toLowerCase().includes('deactiv'))) {
|
|
26
|
+
dtps.push(dtpMatch[1].toUpperCase());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return dtps;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* bw_activate — activate one BW object (aDSO, Transformation, or DTP).
|
|
33
|
+
*
|
|
34
|
+
* Sequence:
|
|
35
|
+
* 1. POST /sap/bw/modeling/activation (with lockHandle in body)
|
|
36
|
+
* 2. POST ?action=unlock (skipped for DTP — no unlock needed)
|
|
37
|
+
*
|
|
38
|
+
* For DTP activation pass lock_handle="" (empty string).
|
|
39
|
+
* The lockHandle is obtained from bw_update_adso or bw_update_transformation.
|
|
40
|
+
*
|
|
41
|
+
* Returns all messages from the activation response, including any DTPs
|
|
42
|
+
* that were deactivated by impact analysis (these must be re-activated with bw_activate).
|
|
43
|
+
*/
|
|
44
|
+
export async function bwActivate(client, objectType, objectName, lockHandle, corrNr, sourceSystem) {
|
|
45
|
+
const typeLower = objectType.toLowerCase();
|
|
46
|
+
// Validate object type
|
|
47
|
+
if (!['adso', 'trfn', 'dtpa', 'iobj', 'trcs', 'rsds'].includes(typeLower)) {
|
|
48
|
+
return JSON.stringify({
|
|
49
|
+
success: false,
|
|
50
|
+
message: `Unknown object type: ${objectType}. Supported: adso, trfn, dtpa, iobj, trcs, rsds`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// RSDS (DataSource) has a compound key — the source system is mandatory.
|
|
54
|
+
if (typeLower === 'rsds' && !sourceSystem) {
|
|
55
|
+
return JSON.stringify({
|
|
56
|
+
success: false,
|
|
57
|
+
message: `object_type "rsds" requires source_system (a DataSource is identified by DataSource name plus source system).`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Step 2: Activate
|
|
61
|
+
// trfn and dtpa activate in a fresh SAP session. The shared module-level client carries a
|
|
62
|
+
// stale session buffer that still sees the transformation as inactive (the state from DTP
|
|
63
|
+
// creation time); a fresh session reads the current DB state and avoids the false
|
|
64
|
+
// "transformation inactive" rejection, as well as cross-call session/CSRF collisions.
|
|
65
|
+
const activationClient = (typeLower === 'trfn' || typeLower === 'dtpa')
|
|
66
|
+
? createClientFromEnv()
|
|
67
|
+
: client;
|
|
68
|
+
// Step 1: For trfn/dtpa, GET the object first to prime SAP's internal HANA cache refresh.
|
|
69
|
+
// This mirrors Eclipse's behavior of GETting before activating. For dtpa the priming GET must
|
|
70
|
+
// run in the SAME fresh session as the activation POST, otherwise the buffer it refreshes is
|
|
71
|
+
// not the one performing the activation. trfn keeps its historical throwaway-GET behavior.
|
|
72
|
+
if (typeLower === 'trfn' || typeLower === 'dtpa') {
|
|
73
|
+
const mediaKey = typeLower;
|
|
74
|
+
const primingClient = typeLower === 'dtpa' ? activationClient : createClientFromEnv();
|
|
75
|
+
await primingClient.get(`/sap/bw/modeling/${typeLower}/${objectName.toLowerCase()}/m`, MEDIA_TYPES[mediaKey]);
|
|
76
|
+
}
|
|
77
|
+
const activationXml = await activationClient.activate(typeLower, objectName, lockHandle, corrNr, sourceSystem);
|
|
78
|
+
// Step 3: Unlock (skipped for dtpa and rsds — both are standalone activations with no lock)
|
|
79
|
+
// Always use the original client session — BW locks are session-bound and can only
|
|
80
|
+
// be released by the session that acquired them. activationClient is a fresh session
|
|
81
|
+
// for trfn and would silently fail to release the lock.
|
|
82
|
+
if (lockHandle && typeLower !== 'dtpa' && typeLower !== 'rsds') {
|
|
83
|
+
await client.unlock(typeLower, objectName);
|
|
84
|
+
}
|
|
85
|
+
// Parse result messages
|
|
86
|
+
const messages = parseActivationMessages(activationXml);
|
|
87
|
+
const deactivatedDtps = parseDtpsDeactivated(activationXml);
|
|
88
|
+
// Check for errors in the response
|
|
89
|
+
const hasError = activationXml.includes('messageType="Error"') ||
|
|
90
|
+
activationXml.includes("messageType='Error'");
|
|
91
|
+
const hasWarning = activationXml.includes('messageType="Warning"') ||
|
|
92
|
+
activationXml.includes("messageType='Warning'");
|
|
93
|
+
// BW pattern: when a transformation contains a mapping rule for a field that no
|
|
94
|
+
// longer exists in the target aDSO, BW fails the first activation with an Error
|
|
95
|
+
// but simultaneously deletes the invalid rule. A single retry then succeeds.
|
|
96
|
+
// Detect this by looking for "is not valid and is being deleted" in the messages.
|
|
97
|
+
const hasDeletedRule = messages.some((m) => m.toLowerCase().includes('is not valid and is being deleted'));
|
|
98
|
+
if (hasError && hasDeletedRule && typeLower === 'trfn') {
|
|
99
|
+
const retryClient = createClientFromEnv();
|
|
100
|
+
const retryXml = await retryClient.activate(typeLower, objectName, lockHandle, corrNr);
|
|
101
|
+
if (lockHandle) {
|
|
102
|
+
await client.unlock(typeLower, objectName);
|
|
103
|
+
}
|
|
104
|
+
const retryMessages = parseActivationMessages(retryXml);
|
|
105
|
+
const retryDeactivatedDtps = parseDtpsDeactivated(retryXml);
|
|
106
|
+
const retryHasError = retryXml.includes('messageType="Error"') ||
|
|
107
|
+
retryXml.includes("messageType='Error'");
|
|
108
|
+
const retryHasWarning = retryXml.includes('messageType="Warning"') ||
|
|
109
|
+
retryXml.includes("messageType='Warning'");
|
|
110
|
+
const retryResult = {
|
|
111
|
+
success: !retryHasError,
|
|
112
|
+
object_type: objectType.toUpperCase(),
|
|
113
|
+
object_name: objectName.toUpperCase(),
|
|
114
|
+
messages: retryMessages,
|
|
115
|
+
retried: true,
|
|
116
|
+
};
|
|
117
|
+
if (retryHasWarning)
|
|
118
|
+
retryResult['warning'] = true;
|
|
119
|
+
if (retryDeactivatedDtps.length > 0) {
|
|
120
|
+
retryResult['dtps_deactivated_by_impact_analysis'] = retryDeactivatedDtps;
|
|
121
|
+
retryResult['next_step'] =
|
|
122
|
+
`Re-activate the deactivated DTPs using bw_activate with object_type="dtpa" and lock_handle="".`;
|
|
123
|
+
}
|
|
124
|
+
return JSON.stringify(retryResult, null, 2);
|
|
125
|
+
}
|
|
126
|
+
// BW pattern: when activating a trfn fails and a non-empty lockHandle was passed,
|
|
127
|
+
// retry once with an empty lockHandle after a short delay. This handles cases where
|
|
128
|
+
// the transformation was never explicitly locked by the caller (stale lockHandle).
|
|
129
|
+
if (hasError && typeLower === 'trfn' && lockHandle) {
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
131
|
+
const retryClient = createClientFromEnv();
|
|
132
|
+
const retryXml = await retryClient.activate(typeLower, objectName, '', corrNr);
|
|
133
|
+
// No unlock needed here — retrying with empty lockHandle means the object was not locked
|
|
134
|
+
const retryMessages = parseActivationMessages(retryXml);
|
|
135
|
+
const retryDeactivatedDtps = parseDtpsDeactivated(retryXml);
|
|
136
|
+
const retryHasError = retryXml.includes('messageType="Error"') ||
|
|
137
|
+
retryXml.includes("messageType='Error'");
|
|
138
|
+
const retryHasWarning = retryXml.includes('messageType="Warning"') ||
|
|
139
|
+
retryXml.includes("messageType='Warning'");
|
|
140
|
+
const retryResult = {
|
|
141
|
+
success: !retryHasError,
|
|
142
|
+
object_type: objectType.toUpperCase(),
|
|
143
|
+
object_name: objectName.toUpperCase(),
|
|
144
|
+
messages: retryMessages,
|
|
145
|
+
retried: true,
|
|
146
|
+
};
|
|
147
|
+
if (retryHasWarning)
|
|
148
|
+
retryResult['warning'] = true;
|
|
149
|
+
if (retryDeactivatedDtps.length > 0) {
|
|
150
|
+
retryResult['dtps_deactivated_by_impact_analysis'] = retryDeactivatedDtps;
|
|
151
|
+
retryResult['next_step'] =
|
|
152
|
+
`Re-activate the deactivated DTPs using bw_activate with object_type="dtpa" and lock_handle="".`;
|
|
153
|
+
}
|
|
154
|
+
return JSON.stringify(retryResult, null, 2);
|
|
155
|
+
}
|
|
156
|
+
const result = {
|
|
157
|
+
success: !hasError,
|
|
158
|
+
object_type: objectType.toUpperCase(),
|
|
159
|
+
object_name: objectName.toUpperCase(),
|
|
160
|
+
messages,
|
|
161
|
+
};
|
|
162
|
+
if (hasWarning) {
|
|
163
|
+
result['warning'] = true;
|
|
164
|
+
}
|
|
165
|
+
if (deactivatedDtps.length > 0) {
|
|
166
|
+
result['dtps_deactivated_by_impact_analysis'] = deactivatedDtps;
|
|
167
|
+
result['next_step'] =
|
|
168
|
+
`Re-activate the deactivated DTPs using bw_activate with object_type="dtpa" and lock_handle="".`;
|
|
169
|
+
}
|
|
170
|
+
return JSON.stringify(result, null, 2);
|
|
171
|
+
}
|