@linkup-ai/abap-ai 2.0.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 +384 -0
- package/dist/adt-client.js +364 -0
- package/dist/cli/activate.js +113 -0
- package/dist/cli/init.js +333 -0
- package/dist/cli/remove.js +80 -0
- package/dist/cli/status.js +229 -0
- package/dist/cli/systems.js +68 -0
- package/dist/cli.js +81 -0
- package/dist/index.js +1318 -0
- package/dist/knowledge/abap/abap-dictionary.md +199 -0
- package/dist/knowledge/abap/abap-sql.md +296 -0
- package/dist/knowledge/abap/amdp.md +273 -0
- package/dist/knowledge/abap/clean-code.md +293 -0
- package/dist/knowledge/abap/cloud-background-processing.md +250 -0
- package/dist/knowledge/abap/cloud-communication.md +265 -0
- package/dist/knowledge/abap/cloud-development.md +176 -0
- package/dist/knowledge/abap/cloud-extensibility.md +252 -0
- package/dist/knowledge/abap/cloud-released-apis.md +261 -0
- package/dist/knowledge/abap/constructor-expressions.md +289 -0
- package/dist/knowledge/abap/enhancements.md +232 -0
- package/dist/knowledge/abap/exceptions.md +271 -0
- package/dist/knowledge/abap/internal-tables.md +205 -0
- package/dist/knowledge/abap/object-orientation.md +298 -0
- package/dist/knowledge/abap/performance.md +216 -0
- package/dist/knowledge/abap/rap-abstract-entities.md +206 -0
- package/dist/knowledge/abap/rap-business-events.md +216 -0
- package/dist/knowledge/abap/rap-draft.md +191 -0
- package/dist/knowledge/abap/rap-eml.md +453 -0
- package/dist/knowledge/abap/rap-end-to-end.md +486 -0
- package/dist/knowledge/abap/rap-feature-control.md +185 -0
- package/dist/knowledge/abap/rap-numbering.md +280 -0
- package/dist/knowledge/abap/rap-service-exposure.md +163 -0
- package/dist/knowledge/abap/rap-unmanaged.md +468 -0
- package/dist/knowledge/abap/string-processing.md +180 -0
- package/dist/knowledge/abap/unit-testing.md +303 -0
- package/dist/knowledge/abap-cds/access-control.md +241 -0
- package/dist/knowledge/abap-cds/annotations.md +331 -0
- package/dist/knowledge/abap-cds/associations.md +254 -0
- package/dist/knowledge/abap-cds/expressions.md +230 -0
- package/dist/knowledge/abap-cds/functions.md +245 -0
- package/dist/knowledge/abap-cds/metadata-extensions.md +294 -0
- package/dist/knowledge/cap/authentication.md +278 -0
- package/dist/knowledge/cap/cdl-syntax.md +247 -0
- package/dist/knowledge/cap/cql-queries.md +266 -0
- package/dist/knowledge/cap/deployment.md +343 -0
- package/dist/knowledge/cap/event-handlers.md +287 -0
- package/dist/knowledge/cap/fiori-integration.md +303 -0
- package/dist/knowledge/cap/service-definitions.md +287 -0
- package/dist/knowledge/fiori/annotations.md +347 -0
- package/dist/knowledge/fiori/deployment.md +340 -0
- package/dist/knowledge/fiori/fiori-elements.md +332 -0
- package/dist/knowledge/fiori/fiori-side-effects.md +107 -0
- package/dist/knowledge/fiori/fiori-valuelist.md +144 -0
- package/dist/knowledge/fiori/ui5-controllers.md +358 -0
- package/dist/knowledge/fiori/ui5-data-binding.md +311 -0
- package/dist/knowledge/fiori/ui5-fragments-dialogs.md +330 -0
- package/dist/knowledge/fiori/ui5-manifest.md +411 -0
- package/dist/knowledge/fiori/ui5-routing.md +303 -0
- package/dist/knowledge/fiori/ui5-xml-views.md +294 -0
- package/dist/logger.js +114 -0
- package/dist/system-profile.js +207 -0
- package/dist/tools/abap-doc.js +72 -0
- package/dist/tools/abapgit.js +161 -0
- package/dist/tools/activate.js +68 -0
- package/dist/tools/atc-check.js +117 -0
- package/dist/tools/auth-object.js +56 -0
- package/dist/tools/breakpoints.js +76 -0
- package/dist/tools/call-hierarchy.js +84 -0
- package/dist/tools/cds-annotations.js +98 -0
- package/dist/tools/cds-dependencies.js +65 -0
- package/dist/tools/check.js +47 -0
- package/dist/tools/code-completion.js +70 -0
- package/dist/tools/code-coverage.js +111 -0
- package/dist/tools/create-amdp.js +111 -0
- package/dist/tools/create-dcl.js +81 -0
- package/dist/tools/create-transport.js +38 -0
- package/dist/tools/create.js +285 -0
- package/dist/tools/data-preview.js +37 -0
- package/dist/tools/delete.js +45 -0
- package/dist/tools/deploy-bsp.js +298 -0
- package/dist/tools/discovery.js +59 -0
- package/dist/tools/element-info.js +93 -0
- package/dist/tools/enhancements.js +186 -0
- package/dist/tools/extract-method.js +44 -0
- package/dist/tools/function-group.js +59 -0
- package/dist/tools/knowledge.js +275 -0
- package/dist/tools/lock-object.js +75 -0
- package/dist/tools/message-class.js +67 -0
- package/dist/tools/navigate.js +80 -0
- package/dist/tools/number-range.js +57 -0
- package/dist/tools/object-documentation.js +43 -0
- package/dist/tools/object-structure.js +78 -0
- package/dist/tools/object-versions.js +57 -0
- package/dist/tools/package-contents.js +60 -0
- package/dist/tools/pretty-printer.js +35 -0
- package/dist/tools/publish-binding.js +49 -0
- package/dist/tools/quick-fix.js +69 -0
- package/dist/tools/read.js +167 -0
- package/dist/tools/refactor-rename.js +60 -0
- package/dist/tools/release-transport.js +24 -0
- package/dist/tools/released-apis.js +51 -0
- package/dist/tools/repository-tree.js +90 -0
- package/dist/tools/scaffold-rap.js +642 -0
- package/dist/tools/search.js +73 -0
- package/dist/tools/shared/data-format.js +101 -0
- package/dist/tools/sql-console.js +17 -0
- package/dist/tools/system-info.js +270 -0
- package/dist/tools/traces.js +66 -0
- package/dist/tools/transport-contents.js +83 -0
- package/dist/tools/transports.js +67 -0
- package/dist/tools/unit-test.js +135 -0
- package/dist/tools/where-used.js +59 -0
- package/dist/tools/write.js +101 -0
- package/package.json +49 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapListTransports = abapListTransports;
|
|
4
|
+
exports.abapAssignTransport = abapAssignTransport;
|
|
5
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
6
|
+
const SAP_USER = process.env.SAP_USER ?? "";
|
|
7
|
+
function parseTransportResponse(xml) {
|
|
8
|
+
const results = [];
|
|
9
|
+
// Formato ADT: <tm:request tm:number="ECDK..." tm:desc="..." tm:type="K" tm:status="D|R" ...>
|
|
10
|
+
const reqRegex = /<tm:request[^>]*tm:number="([^"]*)"[^>]*tm:desc="([^"]*)"[^>]*tm:type="([^"]*)"[^>]*tm:status="([^"]*)"[^>]*>/gi;
|
|
11
|
+
let match;
|
|
12
|
+
while ((match = reqRegex.exec(xml)) !== null) {
|
|
13
|
+
const corrNr = match[1];
|
|
14
|
+
const description = match[2];
|
|
15
|
+
const type = match[3];
|
|
16
|
+
const status = match[4];
|
|
17
|
+
results.push({ corrNr, description, type, taskNr: undefined });
|
|
18
|
+
}
|
|
19
|
+
return results;
|
|
20
|
+
}
|
|
21
|
+
async function abapListTransports(input) {
|
|
22
|
+
const { user = SAP_USER, status = "D" } = input;
|
|
23
|
+
await (0, adt_client_js_1.ensureSession)();
|
|
24
|
+
const response = await adt_client_js_1.http.get("/cts/transportrequests", {
|
|
25
|
+
headers: { Accept: "application/vnd.sap.adt.transportorganizertree.v1+xml" },
|
|
26
|
+
params: { user: user.toUpperCase(), status },
|
|
27
|
+
responseType: "text",
|
|
28
|
+
validateStatus: (s) => s < 500,
|
|
29
|
+
});
|
|
30
|
+
const requests = parseTransportResponse(response.data);
|
|
31
|
+
if (requests.length === 0) {
|
|
32
|
+
return `Nenhuma ordem de transporte aberta encontrada para ${user.toUpperCase()}.`;
|
|
33
|
+
}
|
|
34
|
+
const header = `Ordens de transporte abertas — ${user.toUpperCase()} (${requests.length}):\n`;
|
|
35
|
+
const rows = requests.map((r) => {
|
|
36
|
+
const task = r.taskNr ? ` tarefa: ${r.taskNr}` : "";
|
|
37
|
+
const type = r.type === "W" ? "Workbench" : "Customizing";
|
|
38
|
+
return ` ${r.corrNr} [${type}] ${r.description}${task}`;
|
|
39
|
+
});
|
|
40
|
+
return header + rows.join("\n");
|
|
41
|
+
}
|
|
42
|
+
async function abapAssignTransport(input) {
|
|
43
|
+
const { transport_request, object_type, object_name } = input;
|
|
44
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(object_type);
|
|
45
|
+
const objectUri = `/sap/bc/adt/${adtPath}/${object_name.toUpperCase()}`;
|
|
46
|
+
const csrf = await (0, adt_client_js_1.ensureSession)();
|
|
47
|
+
const payload = `<?xml version="1.0" encoding="UTF-8"?>
|
|
48
|
+
<tm:root xmlns:tm="http://www.sap.com/cts/adt/tm" xmlns:adtcore="http://www.sap.com/adt/core">
|
|
49
|
+
<tm:workbenchObjects>
|
|
50
|
+
<tm:abapObject>
|
|
51
|
+
<adtcore:objectReference adtcore:uri="${objectUri}" adtcore:type="${object_type}" adtcore:name="${object_name.toUpperCase()}"/>
|
|
52
|
+
</tm:abapObject>
|
|
53
|
+
</tm:workbenchObjects>
|
|
54
|
+
</tm:root>`;
|
|
55
|
+
const response = await adt_client_js_1.http.put(`/cts/transportrequests/${transport_request}/tasks/objects`, payload, {
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/xml",
|
|
58
|
+
"X-CSRF-Token": csrf,
|
|
59
|
+
},
|
|
60
|
+
validateStatus: (s) => s >= 200 && s < 500,
|
|
61
|
+
});
|
|
62
|
+
if (response.status === 200 || response.status === 201 || response.status === 204) {
|
|
63
|
+
return `Objeto ${object_name.toUpperCase()} (${object_type}) vinculado à ordem ${transport_request} com sucesso.`;
|
|
64
|
+
}
|
|
65
|
+
const errorBody = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
|
|
66
|
+
throw new Error(`Erro ao vincular objeto à ordem (HTTP ${response.status}): ${errorBody.slice(0, 500)}`);
|
|
67
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapUnitTest = abapUnitTest;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
function buildUnitTestPayload(objectType, objectName) {
|
|
6
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(objectType);
|
|
7
|
+
const objectUri = `/sap/bc/adt/${adtPath}/${objectName}`;
|
|
8
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
9
|
+
<aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit">
|
|
10
|
+
<external>
|
|
11
|
+
<coverage active="false"/>
|
|
12
|
+
</external>
|
|
13
|
+
<options>
|
|
14
|
+
<uriType value="semantic"/>
|
|
15
|
+
<testDeterminationStrategy sameProgram="true" assignedTests="false" publicTestClasses="true"/>
|
|
16
|
+
<testRiskLevels harmless="true" dangerous="true" critical="true"/>
|
|
17
|
+
<testDurations short="true" medium="true" long="true"/>
|
|
18
|
+
</options>
|
|
19
|
+
<adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core">
|
|
20
|
+
<objectSet kind="inclusive">
|
|
21
|
+
<adtcore:objectReferences>
|
|
22
|
+
<adtcore:objectReference adtcore:uri="${objectUri}" adtcore:name="${objectName}"/>
|
|
23
|
+
</adtcore:objectReferences>
|
|
24
|
+
</objectSet>
|
|
25
|
+
</adtcore:objectSets>
|
|
26
|
+
</aunit:runConfiguration>`;
|
|
27
|
+
}
|
|
28
|
+
function parseUnitTestResults(xml) {
|
|
29
|
+
const results = [];
|
|
30
|
+
// Extrair test classes
|
|
31
|
+
const classRegex = /<testClass[^>]*?adtcore:name\s*=\s*"([^"]*)"[^>]*>([\s\S]*?)<\/testClass>/gi;
|
|
32
|
+
let classMatch;
|
|
33
|
+
while ((classMatch = classRegex.exec(xml)) !== null) {
|
|
34
|
+
const className = classMatch[1];
|
|
35
|
+
const classContent = classMatch[2];
|
|
36
|
+
const methods = [];
|
|
37
|
+
// Extrair test methods dentro de cada classe
|
|
38
|
+
const methodRegex = /<testMethod[^>]*?adtcore:name\s*=\s*"([^"]*)"[^>]*>([\s\S]*?)<\/testMethod>/gi;
|
|
39
|
+
let methodMatch;
|
|
40
|
+
while ((methodMatch = methodRegex.exec(classContent)) !== null) {
|
|
41
|
+
const methodName = methodMatch[1];
|
|
42
|
+
const methodContent = methodMatch[2];
|
|
43
|
+
// Extrair duração
|
|
44
|
+
const durationMatch = methodContent.match(/executionTime\s*=\s*"([^"]*)"/i) ||
|
|
45
|
+
methodContent.match(/duration\s*=\s*"([^"]*)"/i);
|
|
46
|
+
// Verificar alertas/falhas
|
|
47
|
+
const alertRegex = /<alert[^>]*?kind\s*=\s*"([^"]*)"[^>]*>[\s\S]*?<title>([\s\S]*?)<\/title>[\s\S]*?(?:<detail>([\s\S]*?)<\/detail>)?/gi;
|
|
48
|
+
let alertMatch;
|
|
49
|
+
let status = "passed";
|
|
50
|
+
let message = "";
|
|
51
|
+
while ((alertMatch = alertRegex.exec(methodContent)) !== null) {
|
|
52
|
+
const kind = alertMatch[1]; // failedAssertion, exception, etc.
|
|
53
|
+
const title = alertMatch[2].trim();
|
|
54
|
+
const detail = alertMatch[3]?.trim() ?? "";
|
|
55
|
+
if (kind === "failedAssertion" || kind === "exception" || kind === "failure") {
|
|
56
|
+
status = "failed";
|
|
57
|
+
}
|
|
58
|
+
else if (kind === "warning") {
|
|
59
|
+
if (status === "passed")
|
|
60
|
+
status = "warning";
|
|
61
|
+
}
|
|
62
|
+
message = detail ? `${title}: ${detail}` : title;
|
|
63
|
+
}
|
|
64
|
+
methods.push({
|
|
65
|
+
name: methodName,
|
|
66
|
+
status,
|
|
67
|
+
message,
|
|
68
|
+
duration: durationMatch?.[1] ?? "",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
results.push({ className, methods });
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
function formatUnitTestResults(objectName, results) {
|
|
76
|
+
if (results.length === 0) {
|
|
77
|
+
return `${objectName}: nenhum teste unitário encontrado ou executado.`;
|
|
78
|
+
}
|
|
79
|
+
let totalPassed = 0;
|
|
80
|
+
let totalFailed = 0;
|
|
81
|
+
let totalWarning = 0;
|
|
82
|
+
for (const cls of results) {
|
|
83
|
+
for (const m of cls.methods) {
|
|
84
|
+
if (m.status === "passed")
|
|
85
|
+
totalPassed++;
|
|
86
|
+
else if (m.status === "failed")
|
|
87
|
+
totalFailed++;
|
|
88
|
+
else
|
|
89
|
+
totalWarning++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const total = totalPassed + totalFailed + totalWarning;
|
|
93
|
+
const summary = totalFailed > 0
|
|
94
|
+
? `FALHOU — ${totalPassed}/${total} passaram, ${totalFailed} falharam`
|
|
95
|
+
: `OK — ${totalPassed}/${total} passaram`;
|
|
96
|
+
const lines = [
|
|
97
|
+
`ABAP Unit: ${objectName}`,
|
|
98
|
+
`Resultado: ${summary}`,
|
|
99
|
+
"─".repeat(60),
|
|
100
|
+
];
|
|
101
|
+
for (const cls of results) {
|
|
102
|
+
lines.push("");
|
|
103
|
+
lines.push(`Classe: ${cls.className}`);
|
|
104
|
+
for (const m of cls.methods) {
|
|
105
|
+
const icon = m.status === "passed" ? "[OK]" : m.status === "failed" ? "[FAIL]" : "[WARN]";
|
|
106
|
+
const dur = m.duration ? ` (${m.duration}ms)` : "";
|
|
107
|
+
lines.push(` ${icon} ${m.name}${dur}`);
|
|
108
|
+
if (m.message) {
|
|
109
|
+
lines.push(` ${m.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return lines.join("\n");
|
|
114
|
+
}
|
|
115
|
+
async function abapUnitTest(input) {
|
|
116
|
+
const { object_type, object_name } = input;
|
|
117
|
+
const name = object_name.toUpperCase();
|
|
118
|
+
const csrf = await (0, adt_client_js_1.ensureSession)();
|
|
119
|
+
const payload = buildUnitTestPayload(object_type, name);
|
|
120
|
+
const response = await adt_client_js_1.http.post("/abapunit/testruns", payload, {
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/xml",
|
|
123
|
+
"Accept": "application/xml",
|
|
124
|
+
"X-CSRF-Token": csrf,
|
|
125
|
+
},
|
|
126
|
+
responseType: "text",
|
|
127
|
+
validateStatus: (status) => status < 500,
|
|
128
|
+
});
|
|
129
|
+
if (response.status >= 300) {
|
|
130
|
+
const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
|
|
131
|
+
throw new Error(`ABAP Unit falhou (HTTP ${response.status}): ${body.slice(0, 500)}`);
|
|
132
|
+
}
|
|
133
|
+
const results = parseUnitTestResults(response.data);
|
|
134
|
+
return formatUnitTestResults(name, results);
|
|
135
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapWhereUsed = abapWhereUsed;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
function parseWhereUsedResponse(xml) {
|
|
6
|
+
const refs = [];
|
|
7
|
+
// Total de resultados
|
|
8
|
+
const totalMatch = xml.match(/numberOfResults="(\d+)"/);
|
|
9
|
+
const totalResults = totalMatch ? parseInt(totalMatch[1], 10) : 0;
|
|
10
|
+
// Formato ADT: <usageReferences:referencedObject uri="...">
|
|
11
|
+
// <usageReferences:adtObject adtcore:name="..." adtcore:type="...">
|
|
12
|
+
// <adtcore:packageRef adtcore:name="..."/>
|
|
13
|
+
const refRegex = /<usageReferences:referencedObject\b([^>]*)>[\s\S]*?<usageReferences:adtObject\b([^>]*)>[\s\S]*?(?:<adtcore:packageRef\b([^/]*)\/?>)?[\s\S]*?<\/usageReferences:referencedObject>/gi;
|
|
14
|
+
let match;
|
|
15
|
+
while ((match = refRegex.exec(xml)) !== null) {
|
|
16
|
+
const outerAttrs = match[1];
|
|
17
|
+
const adtAttrs = match[2];
|
|
18
|
+
const pkgAttrs = match[3] ?? "";
|
|
19
|
+
const uri = /uri="([^"]*)"/.exec(outerAttrs)?.[1] ?? "";
|
|
20
|
+
const name = /adtcore:name="([^"]*)"/.exec(adtAttrs)?.[1];
|
|
21
|
+
const type = /adtcore:type="([^"]*)"/.exec(adtAttrs)?.[1];
|
|
22
|
+
const pkg = /adtcore:name="([^"]*)"/.exec(pkgAttrs)?.[1];
|
|
23
|
+
if (name && type) {
|
|
24
|
+
refs.push({ name, type, uri, packageName: pkg });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { refs, totalResults };
|
|
28
|
+
}
|
|
29
|
+
async function abapWhereUsed(input) {
|
|
30
|
+
const { object_type, object_name, max_results = 50 } = input;
|
|
31
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(object_type);
|
|
32
|
+
const objectUri = `/sap/bc/adt/${adtPath}/${object_name.toUpperCase()}`;
|
|
33
|
+
const csrf = await (0, adt_client_js_1.ensureSession)();
|
|
34
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
35
|
+
<usageReferenceRequest xmlns="http://www.sap.com/adt/ris/usageReferences">
|
|
36
|
+
<objectUri>${objectUri}</objectUri>
|
|
37
|
+
</usageReferenceRequest>`;
|
|
38
|
+
const response = await adt_client_js_1.http.post("/repository/informationsystem/usageReferences", body, {
|
|
39
|
+
headers: {
|
|
40
|
+
Accept: "application/vnd.sap.adt.repository.usagereferences.result.v1+xml",
|
|
41
|
+
"Content-Type": "application/vnd.sap.adt.repository.usagereferences.request.v1+xml",
|
|
42
|
+
"X-CSRF-Token": csrf,
|
|
43
|
+
},
|
|
44
|
+
params: { uri: objectUri, maxResults: max_results },
|
|
45
|
+
responseType: "text",
|
|
46
|
+
validateStatus: (status) => status < 500,
|
|
47
|
+
});
|
|
48
|
+
const { refs, totalResults } = parseWhereUsedResponse(response.data);
|
|
49
|
+
if (refs.length === 0) {
|
|
50
|
+
return `Nenhuma referência encontrada para ${object_name.toUpperCase()} (${object_type}).`;
|
|
51
|
+
}
|
|
52
|
+
const showing = refs.length < totalResults ? ` (mostrando ${refs.length} de ${totalResults})` : "";
|
|
53
|
+
const header = `Referências a ${object_name.toUpperCase()} (${object_type}) — ${totalResults} resultado(s)${showing}:\n`;
|
|
54
|
+
const rows = refs.map((r) => {
|
|
55
|
+
const pkg = r.packageName ? ` [${r.packageName}]` : "";
|
|
56
|
+
return ` ${r.name.padEnd(40)} ${r.type}${pkg}`;
|
|
57
|
+
});
|
|
58
|
+
return header + rows.join("\n");
|
|
59
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapWriteTool = void 0;
|
|
4
|
+
exports.abapWrite = abapWrite;
|
|
5
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
6
|
+
const system_profile_js_1 = require("../system-profile.js");
|
|
7
|
+
function resolveSourcePath(adtPath, name, objectType, classInclude) {
|
|
8
|
+
if (objectType === "CLAS/OC" && classInclude && classInclude !== "main") {
|
|
9
|
+
return `/${adtPath}/${name}/includes/${classInclude}/source/main`;
|
|
10
|
+
}
|
|
11
|
+
return `/${adtPath}/${name}/source/main`;
|
|
12
|
+
}
|
|
13
|
+
async function abapWrite(input) {
|
|
14
|
+
const { object_type, object_name, source, class_include, transport_request } = input;
|
|
15
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(object_type);
|
|
16
|
+
const name = object_name.toUpperCase();
|
|
17
|
+
const objectPath = `/${adtPath}/${name}`;
|
|
18
|
+
const sourcePath = resolveSourcePath(adtPath, name, object_type, class_include);
|
|
19
|
+
// Lock is best-effort — some object types return 406 but write still works
|
|
20
|
+
let locked = false;
|
|
21
|
+
try {
|
|
22
|
+
await (0, adt_client_js_1.adtLock)(objectPath);
|
|
23
|
+
locked = true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Lock not supported for this object type — proceed without it
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const { etag } = await (0, adt_client_js_1.adtGetWithEtag)(sourcePath);
|
|
30
|
+
try {
|
|
31
|
+
await (0, adt_client_js_1.adtPut)(sourcePath, source, etag, transport_request);
|
|
32
|
+
}
|
|
33
|
+
catch (putError) {
|
|
34
|
+
// Workaround: em on-premise S/4, o GET retorna ETag com sufixo diferente do esperado pelo PUT.
|
|
35
|
+
// Se o PUT falha com 412, extrai o ETag correto da mensagem de erro e tenta novamente.
|
|
36
|
+
if ((0, system_profile_js_1.getProfile)().quirks.etagRequiresManualFix && is412Error(putError)) {
|
|
37
|
+
const correctEtag = extractServerEtag(putError);
|
|
38
|
+
if (correctEtag && correctEtag !== etag) {
|
|
39
|
+
await (0, adt_client_js_1.adtPut)(sourcePath, source, correctEtag, transport_request);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
throw putError;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
throw putError;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
if (locked) {
|
|
52
|
+
await (0, adt_client_js_1.adtUnlock)(objectPath).catch(() => { });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return `Objeto ${object_name} gravado com sucesso.`;
|
|
56
|
+
}
|
|
57
|
+
function is412Error(error) {
|
|
58
|
+
return typeof error === "object" && error !== null &&
|
|
59
|
+
"response" in error &&
|
|
60
|
+
error.response?.status === 412;
|
|
61
|
+
}
|
|
62
|
+
function extractServerEtag(error) {
|
|
63
|
+
// A mensagem de erro 412 do SAP contém: "does not match the object ETag XXXX in the server"
|
|
64
|
+
const data = error.response?.data;
|
|
65
|
+
if (typeof data !== "string")
|
|
66
|
+
return null;
|
|
67
|
+
const match = data.match(/object ETag (\S+) in the server/);
|
|
68
|
+
return match?.[1] || null;
|
|
69
|
+
}
|
|
70
|
+
exports.abapWriteTool = {
|
|
71
|
+
name: "abap_write",
|
|
72
|
+
description: "Grava código-fonte em um objeto ABAP existente no sistema SAP via ADT API.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
object_type: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "Tipo do objeto SAP. Ex: PROG/P (report), CLAS/OC (classe), DDLS/DF (CDS view).",
|
|
79
|
+
enum: ["PROG/P", "CLAS/OC", "FUGR/FF", "DOMA/D", "DTEL/D", "TABL/DT", "INTF/OI", "DDLS/DF", "DDLX/MX", "SRVD/SRV", "BDEF/BDO", "SRVB/SVB"],
|
|
80
|
+
},
|
|
81
|
+
object_name: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Nome do objeto ABAP (ex: ZTESTEDENIS).",
|
|
84
|
+
},
|
|
85
|
+
source: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description: "Código-fonte ABAP completo para gravar no objeto.",
|
|
88
|
+
},
|
|
89
|
+
class_include: {
|
|
90
|
+
description: "Include da classe a gravar. Apenas para CLAS/OC. Padrão: main (definição da classe).",
|
|
91
|
+
type: "string",
|
|
92
|
+
enum: ["main", "locals_def", "locals_imp", "testclasses", "macros"],
|
|
93
|
+
},
|
|
94
|
+
transport_request: {
|
|
95
|
+
description: "Ordem de transporte (ex: ECDK900123). Obrigatória para objetos em pacotes não-locais.",
|
|
96
|
+
type: "string",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ["object_type", "object_name", "source"],
|
|
100
|
+
},
|
|
101
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@linkup-ai/abap-ai",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "LKPABAP.ia — AI-powered ABAP development tools for SAP S/4HANA via ADT REST API",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"abap-ai": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "restricted"
|
|
15
|
+
},
|
|
16
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"sap",
|
|
19
|
+
"abap",
|
|
20
|
+
"mcp",
|
|
21
|
+
"claude",
|
|
22
|
+
"adt",
|
|
23
|
+
"fiori",
|
|
24
|
+
"rap",
|
|
25
|
+
"s4hana",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"ai",
|
|
28
|
+
"copilot"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc && rm -rf dist/knowledge && cp -r src/knowledge dist/knowledge",
|
|
32
|
+
"start": "node dist/index.js",
|
|
33
|
+
"dev": "tsc --watch",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
38
|
+
"axios": "^1.7.0",
|
|
39
|
+
"axios-cookiejar-support": "^4.0.7",
|
|
40
|
+
"prompts": "^2.4.2",
|
|
41
|
+
"tough-cookie": "^4.1.4"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.0.0",
|
|
45
|
+
"@types/prompts": "^2.4.9",
|
|
46
|
+
"@types/tough-cookie": "^4.0.5",
|
|
47
|
+
"typescript": "^5.7.0"
|
|
48
|
+
}
|
|
49
|
+
}
|