@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,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapCdsDependencies = abapCdsDependencies;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
async function abapCdsDependencies(input) {
|
|
6
|
+
const { object_name, direction = "both" } = input;
|
|
7
|
+
const name = object_name.toUpperCase();
|
|
8
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)("DDLS/DF");
|
|
9
|
+
const uri = `/sap/bc/adt/${adtPath}/${name}`;
|
|
10
|
+
const results = [];
|
|
11
|
+
if (direction === "uses" || direction === "both") {
|
|
12
|
+
// O que essa CDS consome
|
|
13
|
+
const xml = await (0, adt_client_js_1.adtGetXmlWithParams)("/repository/informationsystem/objectreferences", {
|
|
14
|
+
uri,
|
|
15
|
+
direction: "uses",
|
|
16
|
+
});
|
|
17
|
+
const refs = parseReferences(xml);
|
|
18
|
+
results.push({ dir: "Dependências (o que consome)", refs });
|
|
19
|
+
}
|
|
20
|
+
if (direction === "used_by" || direction === "both") {
|
|
21
|
+
// Quem consome essa CDS
|
|
22
|
+
const xml = await (0, adt_client_js_1.adtGetXmlWithParams)("/repository/informationsystem/objectreferences", {
|
|
23
|
+
uri,
|
|
24
|
+
direction: "usedBy",
|
|
25
|
+
});
|
|
26
|
+
const refs = parseReferences(xml);
|
|
27
|
+
results.push({ dir: "Dependentes (quem consome)", refs });
|
|
28
|
+
}
|
|
29
|
+
const lines = [];
|
|
30
|
+
lines.push(`Dependências CDS: ${name}`);
|
|
31
|
+
lines.push("═".repeat(60));
|
|
32
|
+
for (const r of results) {
|
|
33
|
+
lines.push(`\n${r.dir}: ${r.refs.length} referência(s)`);
|
|
34
|
+
lines.push("─".repeat(50));
|
|
35
|
+
if (r.refs.length === 0) {
|
|
36
|
+
lines.push(" (nenhuma)");
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
for (const ref of r.refs) {
|
|
40
|
+
const desc = ref.description ? ` — ${ref.description}` : "";
|
|
41
|
+
lines.push(` ${ref.name} [${ref.type}]${desc}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return lines.join("\n");
|
|
46
|
+
}
|
|
47
|
+
function parseReferences(xml) {
|
|
48
|
+
const refs = [];
|
|
49
|
+
const refRegex = /<(?:adtcore:)?objectReference[^>]*>/gi;
|
|
50
|
+
let match;
|
|
51
|
+
while ((match = refRegex.exec(xml)) !== null) {
|
|
52
|
+
const el = match[0];
|
|
53
|
+
const nameMatch = el.match(/adtcore:name\s*=\s*"([^"]*)"/i);
|
|
54
|
+
const typeMatch = el.match(/adtcore:type\s*=\s*"([^"]*)"/i);
|
|
55
|
+
const descMatch = el.match(/adtcore:description\s*=\s*"([^"]*)"/i);
|
|
56
|
+
if (nameMatch) {
|
|
57
|
+
refs.push({
|
|
58
|
+
name: nameMatch[1],
|
|
59
|
+
type: typeMatch?.[1] ?? "",
|
|
60
|
+
description: descMatch?.[1] ?? "",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return refs;
|
|
65
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapCheck = abapCheck;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
function parseCheckResponse(xml) {
|
|
6
|
+
const messages = [];
|
|
7
|
+
const msgRegex = /<chkl:message[^>]*type="([^"]*)"[^>]*>[\s\S]*?<chkl:shortText[^>]*>([\s\S]*?)<\/chkl:shortText>[\s\S]*?(?:<chkl:uri[^>]*line="(\d+)")?/g;
|
|
8
|
+
let match;
|
|
9
|
+
while ((match = msgRegex.exec(xml)) !== null) {
|
|
10
|
+
messages.push({ type: match[1], text: match[2].trim(), line: match[3] });
|
|
11
|
+
}
|
|
12
|
+
const hasErrors = messages.some((m) => m.type === "E" || m.type === "A");
|
|
13
|
+
return { hasErrors, messages };
|
|
14
|
+
}
|
|
15
|
+
async function abapCheck(input) {
|
|
16
|
+
const { object_type, object_name } = input;
|
|
17
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(object_type);
|
|
18
|
+
const objectUri = `/sap/bc/adt/${adtPath}/${object_name.toUpperCase()}`;
|
|
19
|
+
const csrf = await (0, adt_client_js_1.ensureSession)();
|
|
20
|
+
const payload = `<?xml version="1.0" encoding="UTF-8"?>
|
|
21
|
+
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
22
|
+
<adtcore:objectReference adtcore:uri="${objectUri}" adtcore:type="${object_type}" adtcore:name="${object_name.toUpperCase()}"/>
|
|
23
|
+
</adtcore:objectReferences>`;
|
|
24
|
+
const response = await adt_client_js_1.http.post("/activation", payload, {
|
|
25
|
+
headers: {
|
|
26
|
+
"Content-Type": "application/xml",
|
|
27
|
+
"X-CSRF-Token": csrf,
|
|
28
|
+
},
|
|
29
|
+
params: { method: "activate", preauditRequested: "true" },
|
|
30
|
+
responseType: "text",
|
|
31
|
+
// Aceita 200 (check OK) e 400 (erros de sintaxe)
|
|
32
|
+
validateStatus: (s) => s === 200 || s === 400,
|
|
33
|
+
});
|
|
34
|
+
const { hasErrors, messages } = parseCheckResponse(response.data);
|
|
35
|
+
if (messages.length === 0) {
|
|
36
|
+
return `${object_name.toUpperCase()}: sem erros de sintaxe. (Objeto foi ativado se estava inativo.)`;
|
|
37
|
+
}
|
|
38
|
+
const lines = messages.map((m) => {
|
|
39
|
+
const prefix = m.type === "E" || m.type === "A" ? "ERRO" : m.type === "W" ? "AVISO" : "INFO";
|
|
40
|
+
const loc = m.line ? ` (linha ${m.line})` : "";
|
|
41
|
+
return ` [${prefix}]${loc} ${m.text}`;
|
|
42
|
+
});
|
|
43
|
+
if (hasErrors) {
|
|
44
|
+
return `${object_name.toUpperCase()}: ${messages.filter((m) => m.type === "E" || m.type === "A").length} erro(s) encontrado(s):\n${lines.join("\n")}`;
|
|
45
|
+
}
|
|
46
|
+
return `${object_name.toUpperCase()}: sem erros (${messages.length} aviso(s)):\n${lines.join("\n")}`;
|
|
47
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapCodeCompletion = abapCodeCompletion;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
function resolveSourceUri(objectType, objectName, classInclude) {
|
|
6
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(objectType);
|
|
7
|
+
const name = objectName.toLowerCase();
|
|
8
|
+
if (objectType === "CLAS/OC" && classInclude && classInclude !== "main") {
|
|
9
|
+
return `/sap/bc/adt/${adtPath}/${name}/includes/${classInclude}/source/main`;
|
|
10
|
+
}
|
|
11
|
+
return `/sap/bc/adt/${adtPath}/${name}/source/main`;
|
|
12
|
+
}
|
|
13
|
+
async function abapCodeCompletion(input) {
|
|
14
|
+
const { object_type, object_name, line, column, class_include } = input;
|
|
15
|
+
const uri = resolveSourceUri(object_type, object_name, class_include);
|
|
16
|
+
const csrf = await (0, adt_client_js_1.ensureSession)();
|
|
17
|
+
const response = await adt_client_js_1.http.post("/abapsource/codecompletion/proposal", "", {
|
|
18
|
+
params: { uri, line: String(line), column: String(column) },
|
|
19
|
+
headers: {
|
|
20
|
+
Accept: "application/vnd.sap.as+xml",
|
|
21
|
+
"Content-Type": "text/plain",
|
|
22
|
+
"X-CSRF-Token": csrf,
|
|
23
|
+
},
|
|
24
|
+
responseType: "text",
|
|
25
|
+
validateStatus: (status) => status < 500,
|
|
26
|
+
});
|
|
27
|
+
if (response.status >= 400) {
|
|
28
|
+
const msg = typeof response.data === "string"
|
|
29
|
+
? (response.data.match(/<message[^>]*>([^<]+)<\/message>/i)?.[1] ?? `HTTP ${response.status}`)
|
|
30
|
+
: `HTTP ${response.status}`;
|
|
31
|
+
throw new Error(msg);
|
|
32
|
+
}
|
|
33
|
+
const xml = response.data;
|
|
34
|
+
if (!xml || xml.trim().length === 0) {
|
|
35
|
+
return `Nenhuma sugestão de code completion para ${object_name} na posição ${line}:${column}.`;
|
|
36
|
+
}
|
|
37
|
+
// Parse proposals - formato SAP ABAP XML
|
|
38
|
+
const proposals = [];
|
|
39
|
+
// Pattern: <PROPOSALS><PROPOSAL><IDENTIFIER>name</IDENTIFIER>...</PROPOSAL>
|
|
40
|
+
const propRegex = /<PROPOSAL>([\s\S]*?)<\/PROPOSAL>/gi;
|
|
41
|
+
let match;
|
|
42
|
+
while ((match = propRegex.exec(xml)) !== null) {
|
|
43
|
+
const block = match[1];
|
|
44
|
+
const id = block.match(/<IDENTIFIER>([^<]*)<\/IDENTIFIER>/)?.[1] ?? "";
|
|
45
|
+
const desc = block.match(/<DESCRIPTION>([^<]*)<\/DESCRIPTION>/)?.[1] ?? "";
|
|
46
|
+
if (id)
|
|
47
|
+
proposals.push({ name: id, description: desc });
|
|
48
|
+
}
|
|
49
|
+
// Fallback: formato XML attributes
|
|
50
|
+
if (proposals.length === 0) {
|
|
51
|
+
const attrRegex = /(?:name|identifier)\s*=\s*"([^"]*)"/gi;
|
|
52
|
+
while ((match = attrRegex.exec(xml)) !== null) {
|
|
53
|
+
proposals.push({ name: match[1], description: "" });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (proposals.length === 0) {
|
|
57
|
+
return `Nenhuma sugestão de code completion para ${object_name} na posição ${line}:${column}.`;
|
|
58
|
+
}
|
|
59
|
+
const lines = [];
|
|
60
|
+
lines.push(`Code Completion: ${object_name} (linha ${line}, coluna ${column}) — ${proposals.length} sugestões`);
|
|
61
|
+
lines.push("─".repeat(80));
|
|
62
|
+
for (const p of proposals.slice(0, 50)) {
|
|
63
|
+
const desc = p.description ? ` — ${p.description}` : "";
|
|
64
|
+
lines.push(` ${p.name}${desc}`);
|
|
65
|
+
}
|
|
66
|
+
if (proposals.length > 50) {
|
|
67
|
+
lines.push(` ... +${proposals.length - 50} sugestões omitidas`);
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapCodeCoverage = abapCodeCoverage;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
async function abapCodeCoverage(input) {
|
|
6
|
+
const { object_type, object_name } = input;
|
|
7
|
+
const name = object_name.toUpperCase();
|
|
8
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(object_type);
|
|
9
|
+
const objectUri = `/sap/bc/adt/${adtPath}/${name}`;
|
|
10
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
11
|
+
<aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit">
|
|
12
|
+
<external>
|
|
13
|
+
<coverage active="true">
|
|
14
|
+
<format>statement</format>
|
|
15
|
+
</coverage>
|
|
16
|
+
</external>
|
|
17
|
+
<options>
|
|
18
|
+
<uriType value="semantic"/>
|
|
19
|
+
<testDeterminationStrategy sameProgram="true" assignedTests="false" publicApi="false"/>
|
|
20
|
+
<testRiskLevels harmless="true" dangerous="true" critical="true"/>
|
|
21
|
+
<testDurations short="true" medium="true" long="true"/>
|
|
22
|
+
</options>
|
|
23
|
+
<adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core">
|
|
24
|
+
<objectSet kind="inclusive">
|
|
25
|
+
<adtcore:objectReferences>
|
|
26
|
+
<adtcore:objectReference adtcore:uri="${objectUri}" adtcore:name="${name}"/>
|
|
27
|
+
</adtcore:objectReferences>
|
|
28
|
+
</objectSet>
|
|
29
|
+
</adtcore:objectSets>
|
|
30
|
+
</aunit:runConfiguration>`;
|
|
31
|
+
const { data, status } = await (0, adt_client_js_1.adtPostXml)("/abapunit/testruns", body);
|
|
32
|
+
if (status >= 400) {
|
|
33
|
+
throw new Error(`Code coverage falhou (HTTP ${status}): ${data.slice(0, 500)}`);
|
|
34
|
+
}
|
|
35
|
+
// Parse test results
|
|
36
|
+
const testResults = [];
|
|
37
|
+
const classRegex = /<testClass[^>]*adtcore:name\s*=\s*"([^"]*)"[^>]*>([\s\S]*?)<\/testClass>/gi;
|
|
38
|
+
let classMatch;
|
|
39
|
+
while ((classMatch = classRegex.exec(data)) !== null) {
|
|
40
|
+
const className = classMatch[1];
|
|
41
|
+
const classContent = classMatch[2];
|
|
42
|
+
const methodRegex = /<testMethod[^>]*adtcore:name\s*=\s*"([^"]*)"[^>]*>([\s\S]*?)<\/testMethod>/gi;
|
|
43
|
+
let methodMatch;
|
|
44
|
+
while ((methodMatch = methodRegex.exec(classContent)) !== null) {
|
|
45
|
+
const methodName = methodMatch[1];
|
|
46
|
+
const methodContent = methodMatch[2];
|
|
47
|
+
const hasAlert = /<alert/i.test(methodContent);
|
|
48
|
+
const alertKind = methodContent.match(/kind\s*=\s*"([^"]*)"/i);
|
|
49
|
+
let mStatus = "PASSED";
|
|
50
|
+
if (hasAlert) {
|
|
51
|
+
if (alertKind && /fail|exception|error/i.test(alertKind[1])) {
|
|
52
|
+
mStatus = "FAILED";
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
mStatus = "WARNING";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
testResults.push({ className, method: methodName, status: mStatus });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Parse coverage data
|
|
62
|
+
const coverageLines = [];
|
|
63
|
+
const stmtRegex = /<statement[^>]*?(?:executed|covered)\s*=\s*"([^"]*)"[^>]*?line\s*=\s*"([^"]*)"[^>]*>/gi;
|
|
64
|
+
let stmtMatch;
|
|
65
|
+
while ((stmtMatch = stmtRegex.exec(data)) !== null) {
|
|
66
|
+
coverageLines.push({
|
|
67
|
+
line: parseInt(stmtMatch[2], 10),
|
|
68
|
+
executed: stmtMatch[1] === "true" || stmtMatch[1] === "1",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Fallback: try alternative coverage format
|
|
72
|
+
if (coverageLines.length === 0) {
|
|
73
|
+
const covRegex = /<(?:coverage:)?line[^>]*?nr\s*=\s*"([^"]*)"[^>]*?executed\s*=\s*"([^"]*)"[^>]*>/gi;
|
|
74
|
+
while ((stmtMatch = covRegex.exec(data)) !== null) {
|
|
75
|
+
coverageLines.push({
|
|
76
|
+
line: parseInt(stmtMatch[1], 10),
|
|
77
|
+
executed: stmtMatch[2] === "true" || stmtMatch[2] === "X",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Format output
|
|
82
|
+
const lines = [];
|
|
83
|
+
lines.push(`Code Coverage: ${name}`);
|
|
84
|
+
lines.push("═".repeat(60));
|
|
85
|
+
// Test results summary
|
|
86
|
+
const passed = testResults.filter(t => t.status === "PASSED").length;
|
|
87
|
+
const failed = testResults.filter(t => t.status === "FAILED").length;
|
|
88
|
+
lines.push(`\nTestes: ${passed} passed, ${failed} failed, ${testResults.length} total`);
|
|
89
|
+
for (const t of testResults) {
|
|
90
|
+
const icon = t.status === "PASSED" ? "OK" : t.status === "FAILED" ? "FAIL" : "WARN";
|
|
91
|
+
lines.push(` [${icon}] ${t.className}->${t.method}`);
|
|
92
|
+
}
|
|
93
|
+
// Coverage summary
|
|
94
|
+
if (coverageLines.length > 0) {
|
|
95
|
+
const covered = coverageLines.filter(l => l.executed).length;
|
|
96
|
+
const total = coverageLines.length;
|
|
97
|
+
const pct = total > 0 ? Math.round((covered / total) * 100) : 0;
|
|
98
|
+
lines.push(`\nCobertura: ${covered}/${total} statements (${pct}%)`);
|
|
99
|
+
const uncovered = coverageLines.filter(l => !l.executed).map(l => l.line);
|
|
100
|
+
if (uncovered.length > 0 && uncovered.length <= 50) {
|
|
101
|
+
lines.push(`Linhas não cobertas: ${uncovered.join(", ")}`);
|
|
102
|
+
}
|
|
103
|
+
else if (uncovered.length > 50) {
|
|
104
|
+
lines.push(`Linhas não cobertas: ${uncovered.slice(0, 50).join(", ")} ... (+${uncovered.length - 50})`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
lines.push(`\nCobertura: dados de coverage não retornados pelo servidor.`);
|
|
109
|
+
}
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapCreateAmdp = abapCreateAmdp;
|
|
4
|
+
const create_js_1 = require("./create.js");
|
|
5
|
+
const write_js_1 = require("./write.js");
|
|
6
|
+
const activate_js_1 = require("./activate.js");
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Templates
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
function buildAmdpClass(input) {
|
|
11
|
+
const { class_name, method_name, method_type, importing = [], exporting_table, cds_entity, } = input;
|
|
12
|
+
const cls = class_name.toLowerCase();
|
|
13
|
+
const meth = method_name.toLowerCase();
|
|
14
|
+
const importingParams = importing
|
|
15
|
+
.map((p) => ` VALUE(${p.name}) TYPE ${p.type}`)
|
|
16
|
+
.join("\n");
|
|
17
|
+
if (method_type === "table_function") {
|
|
18
|
+
// AMDP Table Function — used with CDS table function
|
|
19
|
+
return `CLASS ${cls} DEFINITION PUBLIC FINAL CREATE PUBLIC.
|
|
20
|
+
PUBLIC SECTION.
|
|
21
|
+
INTERFACES if_amdp_marker_hdb.
|
|
22
|
+
CLASS-METHODS ${meth}
|
|
23
|
+
FOR TABLE FUNCTION ${cds_entity || class_name.toUpperCase().replace(/^ZCL_/, "ZTF_")}.
|
|
24
|
+
ENDCLASS.
|
|
25
|
+
|
|
26
|
+
CLASS ${cls} IMPLEMENTATION.
|
|
27
|
+
METHOD ${meth} BY DATABASE FUNCTION FOR HDB
|
|
28
|
+
LANGUAGE SQLSCRIPT
|
|
29
|
+
OPTIONS READ-ONLY
|
|
30
|
+
USING ${cds_entity ? cds_entity.toLowerCase() : "/* tabela base */"}.
|
|
31
|
+
|
|
32
|
+
-- TODO: Implementar SQLScript
|
|
33
|
+
RETURN
|
|
34
|
+
SELECT *
|
|
35
|
+
FROM ${cds_entity ? cds_entity.toLowerCase() : "/* tabela base */"};
|
|
36
|
+
|
|
37
|
+
ENDMETHOD.
|
|
38
|
+
ENDCLASS.`;
|
|
39
|
+
}
|
|
40
|
+
// AMDP Procedure
|
|
41
|
+
const exportingLine = exporting_table
|
|
42
|
+
? `\n EXPORTING\n VALUE(et_result) TYPE ${exporting_table}`
|
|
43
|
+
: "";
|
|
44
|
+
return `CLASS ${cls} DEFINITION PUBLIC FINAL CREATE PUBLIC.
|
|
45
|
+
PUBLIC SECTION.
|
|
46
|
+
INTERFACES if_amdp_marker_hdb.
|
|
47
|
+
METHODS ${meth}
|
|
48
|
+
${importingParams ? " IMPORTING\n" + importingParams : ""}${exportingLine}.
|
|
49
|
+
ENDCLASS.
|
|
50
|
+
|
|
51
|
+
CLASS ${cls} IMPLEMENTATION.
|
|
52
|
+
METHOD ${meth} BY DATABASE PROCEDURE FOR HDB
|
|
53
|
+
LANGUAGE SQLSCRIPT
|
|
54
|
+
OPTIONS READ-ONLY
|
|
55
|
+
USING /* tabelas usadas */.
|
|
56
|
+
|
|
57
|
+
-- TODO: Implementar SQLScript
|
|
58
|
+
et_result = SELECT * FROM /* tabela */;
|
|
59
|
+
|
|
60
|
+
ENDMETHOD.
|
|
61
|
+
ENDCLASS.`;
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Main
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
async function abapCreateAmdp(input) {
|
|
67
|
+
const { class_name, method_name, method_type, description, package: pkg = "$TMP", transport_request } = input;
|
|
68
|
+
const name = class_name.toUpperCase();
|
|
69
|
+
const steps = [];
|
|
70
|
+
try {
|
|
71
|
+
await (0, create_js_1.abapCreate)({
|
|
72
|
+
object_type: "CLAS/OC",
|
|
73
|
+
object_name: name,
|
|
74
|
+
description: description || `AMDP ${method_type} - ${method_name}`,
|
|
75
|
+
package: pkg,
|
|
76
|
+
transport_request,
|
|
77
|
+
});
|
|
78
|
+
steps.push(`✓ Classe ${name} criada`);
|
|
79
|
+
const source = buildAmdpClass(input);
|
|
80
|
+
await (0, write_js_1.abapWrite)({
|
|
81
|
+
object_type: "CLAS/OC",
|
|
82
|
+
object_name: name,
|
|
83
|
+
source,
|
|
84
|
+
});
|
|
85
|
+
steps.push(`✓ Source AMDP gravado (${method_type})`);
|
|
86
|
+
await (0, activate_js_1.abapActivate)({
|
|
87
|
+
object_type: "CLAS/OC",
|
|
88
|
+
object_name: name,
|
|
89
|
+
});
|
|
90
|
+
steps.push(`✓ Classe ${name} ativada`);
|
|
91
|
+
steps.push("");
|
|
92
|
+
if (method_type === "table_function") {
|
|
93
|
+
steps.push("📋 Próximos passos:");
|
|
94
|
+
steps.push(" 1. Criar CDS Table Function referenciando esta classe");
|
|
95
|
+
steps.push(" 2. Implementar SQLScript no método");
|
|
96
|
+
steps.push(" → abap_knowledge(action:'get', topics:['abap/amdp'])");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
steps.push("📋 Próximos passos:");
|
|
100
|
+
steps.push(" 1. Ajustar tabelas no USING e implementar SQLScript");
|
|
101
|
+
steps.push(" 2. Chamar via CALL METHOD ou READ ENTITIES");
|
|
102
|
+
steps.push(" → abap_knowledge(action:'get', topics:['abap/amdp'])");
|
|
103
|
+
}
|
|
104
|
+
return steps.join("\n");
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
108
|
+
steps.push(`✗ ERRO: ${message}`);
|
|
109
|
+
return steps.join("\n");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapCreateDcl = abapCreateDcl;
|
|
4
|
+
const create_js_1 = require("./create.js");
|
|
5
|
+
const write_js_1 = require("./write.js");
|
|
6
|
+
const activate_js_1 = require("./activate.js");
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Template builder
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
function buildDclSource(input) {
|
|
11
|
+
const { dcl_name, cds_entity, auth_type, auth_mappings = [], literal_conditions = [], with_user_aspect } = input;
|
|
12
|
+
const conditions = [];
|
|
13
|
+
if (auth_type === "pfcg" || auth_type === "mapping") {
|
|
14
|
+
for (const mapping of auth_mappings) {
|
|
15
|
+
const actvt = mapping.actvt || "03";
|
|
16
|
+
const cdsFields = mapping.cds_fields.join(", ");
|
|
17
|
+
const authFields = [...mapping.auth_fields, `ACTVT = '${actvt}'`].join(", ");
|
|
18
|
+
conditions.push(`(${cdsFields}) = aspect pfcg_auth(${mapping.auth_object}, ${authFields})`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
for (const lit of literal_conditions) {
|
|
22
|
+
conditions.push(lit);
|
|
23
|
+
}
|
|
24
|
+
if (with_user_aspect) {
|
|
25
|
+
conditions.push("created_by ?= aspect user");
|
|
26
|
+
}
|
|
27
|
+
// If no conditions, use a simple grant all
|
|
28
|
+
if (conditions.length === 0) {
|
|
29
|
+
conditions.push("1 = 1");
|
|
30
|
+
}
|
|
31
|
+
const whereClause = conditions.length === 1
|
|
32
|
+
? ` where ${conditions[0]}`
|
|
33
|
+
: ` where ${conditions.join("\n and ")}`;
|
|
34
|
+
return `@EndUserText.label: 'Access Control for ${cds_entity}'
|
|
35
|
+
@MappingRole: true
|
|
36
|
+
define role ${dcl_name} {
|
|
37
|
+
grant select on ${cds_entity}
|
|
38
|
+
${whereClause};
|
|
39
|
+
}`;
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Main
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
async function abapCreateDcl(input) {
|
|
45
|
+
const { dcl_name, cds_entity, package: pkg = "$TMP", transport_request } = input;
|
|
46
|
+
const name = dcl_name.toUpperCase();
|
|
47
|
+
const steps = [];
|
|
48
|
+
try {
|
|
49
|
+
// DCL objects are created as DDLS/DF (they are CDS sources)
|
|
50
|
+
await (0, create_js_1.abapCreate)({
|
|
51
|
+
object_type: "DDLS/DF",
|
|
52
|
+
object_name: name,
|
|
53
|
+
description: `Access Control for ${cds_entity}`,
|
|
54
|
+
package: pkg,
|
|
55
|
+
transport_request,
|
|
56
|
+
});
|
|
57
|
+
steps.push(`✓ DCL ${name} criado`);
|
|
58
|
+
const source = buildDclSource(input);
|
|
59
|
+
await (0, write_js_1.abapWrite)({
|
|
60
|
+
object_type: "DDLS/DF",
|
|
61
|
+
object_name: name,
|
|
62
|
+
source,
|
|
63
|
+
});
|
|
64
|
+
steps.push(`✓ Source DCL gravado`);
|
|
65
|
+
await (0, activate_js_1.abapActivate)({
|
|
66
|
+
object_type: "DDLS/DF",
|
|
67
|
+
object_name: name,
|
|
68
|
+
});
|
|
69
|
+
steps.push(`✓ DCL ${name} ativado`);
|
|
70
|
+
steps.push("");
|
|
71
|
+
steps.push(`📋 DCL ${name} protege a entidade ${cds_entity}`);
|
|
72
|
+
steps.push(` Verifique que ${cds_entity} tem @AccessControl.authorizationCheck: #CHECK`);
|
|
73
|
+
steps.push(` Teste autorizações com SU53 após acesso negado`);
|
|
74
|
+
return steps.join("\n");
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
78
|
+
steps.push(`✗ ERRO: ${message}`);
|
|
79
|
+
return steps.join("\n");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapCreateTransport = abapCreateTransport;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
async function abapCreateTransport(input) {
|
|
6
|
+
const { description, type = "W", target = "" } = input;
|
|
7
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
8
|
+
<tm:root xmlns:tm="http://www.sap.com/cts/adt/tm">
|
|
9
|
+
<tm:request tm:desc="${description}" tm:type="${type}" tm:target="${target}" tm:cts_project=""/>
|
|
10
|
+
</tm:root>`;
|
|
11
|
+
const { data, status, headers } = await (0, adt_client_js_1.adtPostXml)("/cts/transportrequests", body, undefined, "application/vnd.sap.adt.transportorganizer.v1+xml");
|
|
12
|
+
if (status >= 400) {
|
|
13
|
+
const msgMatch = data.match(/<(?:message|shortText)[^>]*>([\s\S]*?)<\/(?:message|shortText)>/i);
|
|
14
|
+
throw new Error(`Erro ao criar transporte (HTTP ${status}): ${msgMatch?.[1] ?? data.slice(0, 500)}`);
|
|
15
|
+
}
|
|
16
|
+
// Extrair número da ordem do response ou Location header
|
|
17
|
+
let trId = "";
|
|
18
|
+
const location = headers["location"] ?? "";
|
|
19
|
+
const locMatch = location.match(/transportrequests\/([A-Z0-9]+)/i);
|
|
20
|
+
if (locMatch) {
|
|
21
|
+
trId = locMatch[1];
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const idMatch = data.match(/(?:tm:)?(?:number|id)\s*=\s*"([^"]*)"/i);
|
|
25
|
+
trId = idMatch?.[1] ?? "";
|
|
26
|
+
}
|
|
27
|
+
// Fallback: buscar no corpo
|
|
28
|
+
if (!trId) {
|
|
29
|
+
const bodyMatch = data.match(/([A-Z]{3,4}K\d{6})/);
|
|
30
|
+
trId = bodyMatch?.[1] ?? "???";
|
|
31
|
+
}
|
|
32
|
+
const typeLabel = type === "W" ? "Workbench" : type === "K" ? "Customizing" : type;
|
|
33
|
+
return `Ordem de transporte criada com sucesso!\n` +
|
|
34
|
+
`Número: ${trId}\n` +
|
|
35
|
+
`Tipo: ${typeLabel}\n` +
|
|
36
|
+
`Descrição: ${description}` +
|
|
37
|
+
(target ? `\nDestino: ${target}` : "");
|
|
38
|
+
}
|