@linkup-ai/abap-ai 0.1.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 +389 -0
- package/dist/adt-client.js +383 -0
- package/dist/cli/activate.js +127 -0
- package/dist/cli/init.js +559 -0
- package/dist/cli/link.js +148 -0
- package/dist/cli/remove.js +83 -0
- package/dist/cli/status.js +231 -0
- package/dist/cli/systems.js +72 -0
- package/dist/cli.js +92 -0
- package/dist/index.js +1442 -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/license-guard.js +81 -0
- package/dist/logger.js +114 -0
- package/dist/postinstall.js +165 -0
- package/dist/security-audit.js +136 -0
- package/dist/security-policy.js +322 -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 +71 -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 +172 -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 +271 -0
- package/dist/tools/traces.js +66 -0
- package/dist/tools/transport-contents.js +83 -0
- package/dist/tools/transports.js +68 -0
- package/dist/tools/unit-test.js +135 -0
- package/dist/tools/where-used.js +59 -0
- package/dist/tools/write.js +120 -0
- package/package.json +50 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapDoc = abapDoc;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
function resolveSourceUri(objectType, objectName) {
|
|
6
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(objectType);
|
|
7
|
+
return `/${adtPath}/${objectName.toLowerCase()}/source/main`;
|
|
8
|
+
}
|
|
9
|
+
function extractAbapDoc(source, methodName) {
|
|
10
|
+
const lines = source.split("\n");
|
|
11
|
+
const docs = [];
|
|
12
|
+
let currentComments = [];
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
// ABAP Doc comment: starts with "!
|
|
16
|
+
if (trimmed.startsWith('"!') || trimmed.startsWith('"! ')) {
|
|
17
|
+
currentComments.push(trimmed.replace(/^"!\s?/, ""));
|
|
18
|
+
}
|
|
19
|
+
else if (currentComments.length > 0) {
|
|
20
|
+
// Próxima linha após bloco de comentários — é o target
|
|
21
|
+
// Extrair nome do método/atributo/parâmetro
|
|
22
|
+
const nameMatch = trimmed.match(/(?:methods|data|class-data|constants|types|class-methods)\s+(\w+)/i)
|
|
23
|
+
|| trimmed.match(/(?:interface|class)\s+(\w+)/i);
|
|
24
|
+
const target = nameMatch?.[1] ?? trimmed.substring(0, 60);
|
|
25
|
+
docs.push({ target, comments: [...currentComments] });
|
|
26
|
+
currentComments = [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Filtrar por método se especificado
|
|
30
|
+
if (methodName) {
|
|
31
|
+
const filtered = docs.filter((d) => d.target.toUpperCase() === methodName.toUpperCase());
|
|
32
|
+
if (filtered.length === 0) {
|
|
33
|
+
return `Nenhuma documentação ABAP Doc encontrada para ${methodName}.`;
|
|
34
|
+
}
|
|
35
|
+
return filtered.map((d) => d.comments.join("\n")).join("\n\n");
|
|
36
|
+
}
|
|
37
|
+
if (docs.length === 0) {
|
|
38
|
+
return "Nenhuma documentação ABAP Doc encontrada neste objeto.";
|
|
39
|
+
}
|
|
40
|
+
const result = [];
|
|
41
|
+
for (const d of docs) {
|
|
42
|
+
result.push(`▸ ${d.target}`);
|
|
43
|
+
for (const c of d.comments) {
|
|
44
|
+
result.push(` ${c}`);
|
|
45
|
+
}
|
|
46
|
+
result.push("");
|
|
47
|
+
}
|
|
48
|
+
return result.join("\n");
|
|
49
|
+
}
|
|
50
|
+
async function abapDoc(input) {
|
|
51
|
+
const { object_type, object_name, method_name } = input;
|
|
52
|
+
const name = object_name.toUpperCase();
|
|
53
|
+
await (0, adt_client_js_1.ensureSession)();
|
|
54
|
+
const uri = resolveSourceUri(object_type, name);
|
|
55
|
+
const response = await adt_client_js_1.http.get(uri, {
|
|
56
|
+
headers: { Accept: "text/plain" },
|
|
57
|
+
responseType: "text",
|
|
58
|
+
validateStatus: (status) => status < 500,
|
|
59
|
+
});
|
|
60
|
+
if (response.status === 404) {
|
|
61
|
+
throw new Error(`Objeto ${name} (${object_type}) não encontrado.`);
|
|
62
|
+
}
|
|
63
|
+
const source = response.data;
|
|
64
|
+
const target = method_name ? `${name}->${method_name}` : name;
|
|
65
|
+
const doc = extractAbapDoc(source, method_name);
|
|
66
|
+
const lines = [
|
|
67
|
+
`ABAP Doc: ${target}`,
|
|
68
|
+
"─".repeat(50),
|
|
69
|
+
doc,
|
|
70
|
+
];
|
|
71
|
+
return lines.join("\n");
|
|
72
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapGitRepos = abapGitRepos;
|
|
4
|
+
exports.abapGitPull = abapGitPull;
|
|
5
|
+
exports.abapGitStage = abapGitStage;
|
|
6
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
7
|
+
// ─── abap_git_repos ─────────────────────────────────────────────
|
|
8
|
+
async function abapGitRepos() {
|
|
9
|
+
await (0, adt_client_js_1.ensureSession)();
|
|
10
|
+
const response = await adt_client_js_1.http.get("/abapgit/repos", {
|
|
11
|
+
headers: { Accept: "application/json" },
|
|
12
|
+
responseType: "text",
|
|
13
|
+
validateStatus: (status) => status < 500,
|
|
14
|
+
});
|
|
15
|
+
if (response.status === 404) {
|
|
16
|
+
return "abapGit não está disponível neste sistema. Verifique se o plugin abapGit está instalado.";
|
|
17
|
+
}
|
|
18
|
+
const data = response.data;
|
|
19
|
+
// Try JSON parsing first
|
|
20
|
+
try {
|
|
21
|
+
const json = JSON.parse(data);
|
|
22
|
+
const repos = Array.isArray(json) ? json : json.repositories ?? json.repos ?? [];
|
|
23
|
+
if (repos.length === 0) {
|
|
24
|
+
return "Nenhum repositório abapGit vinculado ao sistema.";
|
|
25
|
+
}
|
|
26
|
+
const lines = [];
|
|
27
|
+
lines.push(`abapGit: ${repos.length} repositório(s)`);
|
|
28
|
+
lines.push("═".repeat(60));
|
|
29
|
+
for (const r of repos) {
|
|
30
|
+
lines.push(` ${r.package ?? r.sapPackage ?? "?"}`);
|
|
31
|
+
lines.push(` URL: ${r.url ?? r.remoteUrl ?? "?"}`);
|
|
32
|
+
lines.push(` Branch: ${r.branch ?? r.branchName ?? "main"}`);
|
|
33
|
+
if (r.status)
|
|
34
|
+
lines.push(` Status: ${r.status}`);
|
|
35
|
+
}
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Fall back to XML parsing
|
|
40
|
+
const repos = [];
|
|
41
|
+
const repoRegex = /<(?:abapgit:)?repo[^>]*>([\s\S]*?)<\/(?:abapgit:)?repo>/gi;
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = repoRegex.exec(data)) !== null) {
|
|
44
|
+
const content = match[1];
|
|
45
|
+
const pkgMatch = content.match(/<(?:abapgit:)?package[^>]*>([\s\S]*?)<\/(?:abapgit:)?package>/i);
|
|
46
|
+
const urlMatch = content.match(/<(?:abapgit:)?url[^>]*>([\s\S]*?)<\/(?:abapgit:)?url>/i);
|
|
47
|
+
const branchMatch = content.match(/<(?:abapgit:)?branch[^>]*>([\s\S]*?)<\/(?:abapgit:)?branch>/i);
|
|
48
|
+
repos.push({
|
|
49
|
+
pkg: pkgMatch?.[1]?.trim() ?? "",
|
|
50
|
+
url: urlMatch?.[1]?.trim() ?? "",
|
|
51
|
+
branch: branchMatch?.[1]?.trim() ?? "main",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (repos.length === 0) {
|
|
55
|
+
return "Nenhum repositório abapGit encontrado ou formato de resposta não reconhecido.";
|
|
56
|
+
}
|
|
57
|
+
const lines = [];
|
|
58
|
+
lines.push(`abapGit: ${repos.length} repositório(s)`);
|
|
59
|
+
lines.push("═".repeat(60));
|
|
60
|
+
for (const r of repos) {
|
|
61
|
+
lines.push(` ${r.pkg}`);
|
|
62
|
+
lines.push(` URL: ${r.url}`);
|
|
63
|
+
lines.push(` Branch: ${r.branch}`);
|
|
64
|
+
}
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function abapGitPull(input) {
|
|
69
|
+
const { repo_id, branch = "main", transport_request } = input;
|
|
70
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
71
|
+
<abapgit:pull xmlns:abapgit="http://www.sap.com/adt/abapgit">
|
|
72
|
+
<abapgit:branch>${branch}</abapgit:branch>
|
|
73
|
+
${transport_request ? `<abapgit:transportRequest>${transport_request}</abapgit:transportRequest>` : ""}
|
|
74
|
+
</abapgit:pull>`;
|
|
75
|
+
const { data, status, headers } = await (0, adt_client_js_1.adtPostXml)(`/abapgit/repos/${repo_id}/pull`, body);
|
|
76
|
+
if (status === 404) {
|
|
77
|
+
throw new Error(`Repositório abapGit "${repo_id}" não encontrado.`);
|
|
78
|
+
}
|
|
79
|
+
// Handle async operation (202 Accepted)
|
|
80
|
+
if (status === 202) {
|
|
81
|
+
const location = headers["location"] ?? "";
|
|
82
|
+
return `Pull iniciado (operação assíncrona).\n` +
|
|
83
|
+
`Repositório: ${repo_id}\n` +
|
|
84
|
+
`Branch: ${branch}\n` +
|
|
85
|
+
(location ? `Status URL: ${location}` : "Verifique o status no ADT ou abapGit.");
|
|
86
|
+
}
|
|
87
|
+
if (status >= 400) {
|
|
88
|
+
const msgMatch = data.match(/<(?:message|shortText)[^>]*>([\s\S]*?)<\/(?:message|shortText)>/i);
|
|
89
|
+
throw new Error(`Pull falhou (HTTP ${status}): ${msgMatch?.[1] ?? data.slice(0, 500)}`);
|
|
90
|
+
}
|
|
91
|
+
return `Pull concluído com sucesso!\nRepositório: ${repo_id}\nBranch: ${branch}`;
|
|
92
|
+
}
|
|
93
|
+
async function abapGitStage(input) {
|
|
94
|
+
const { repo_id, action = "status", message, transport_request } = input;
|
|
95
|
+
if (action === "status") {
|
|
96
|
+
await (0, adt_client_js_1.ensureSession)();
|
|
97
|
+
const response = await adt_client_js_1.http.get(`/abapgit/repos/${repo_id}/stage`, {
|
|
98
|
+
headers: { Accept: "application/xml" },
|
|
99
|
+
responseType: "text",
|
|
100
|
+
validateStatus: (status) => status < 500,
|
|
101
|
+
});
|
|
102
|
+
if (response.status === 404) {
|
|
103
|
+
throw new Error(`Repositório abapGit "${repo_id}" não encontrado.`);
|
|
104
|
+
}
|
|
105
|
+
const xml = response.data;
|
|
106
|
+
// Parse staged/unstaged objects
|
|
107
|
+
const objects = [];
|
|
108
|
+
const objRegex = /<(?:abapgit:)?object[^>]*>/gi;
|
|
109
|
+
let match;
|
|
110
|
+
while ((match = objRegex.exec(xml)) !== null) {
|
|
111
|
+
const el = match[0];
|
|
112
|
+
const nameMatch = el.match(/name\s*=\s*"([^"]*)"/i);
|
|
113
|
+
const typeMatch = el.match(/type\s*=\s*"([^"]*)"/i);
|
|
114
|
+
const statusMatch = el.match(/status\s*=\s*"([^"]*)"/i);
|
|
115
|
+
if (nameMatch) {
|
|
116
|
+
objects.push({
|
|
117
|
+
name: nameMatch[1],
|
|
118
|
+
type: typeMatch?.[1] ?? "",
|
|
119
|
+
status: statusMatch?.[1] ?? "",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const lines = [];
|
|
124
|
+
lines.push(`abapGit Stage: ${repo_id} — ${objects.length} objeto(s)`);
|
|
125
|
+
lines.push("═".repeat(60));
|
|
126
|
+
if (objects.length === 0) {
|
|
127
|
+
lines.push("Nenhuma alteração pendente.");
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
for (const o of objects) {
|
|
131
|
+
const st = o.status ? ` [${o.status}]` : "";
|
|
132
|
+
lines.push(` ${o.type} ${o.name}${st}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
|
137
|
+
else if (action === "push") {
|
|
138
|
+
if (!message) {
|
|
139
|
+
throw new Error("Parâmetro 'message' é obrigatório para push.");
|
|
140
|
+
}
|
|
141
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
142
|
+
<abapgit:push xmlns:abapgit="http://www.sap.com/adt/abapgit">
|
|
143
|
+
<abapgit:message>${message}</abapgit:message>
|
|
144
|
+
${transport_request ? `<abapgit:transportRequest>${transport_request}</abapgit:transportRequest>` : ""}
|
|
145
|
+
</abapgit:push>`;
|
|
146
|
+
const { data, status, headers } = await (0, adt_client_js_1.adtPostXml)(`/abapgit/repos/${repo_id}/push`, body);
|
|
147
|
+
if (status === 202) {
|
|
148
|
+
const location = headers["location"] ?? "";
|
|
149
|
+
return `Push iniciado (operação assíncrona).\n` +
|
|
150
|
+
`Repositório: ${repo_id}\n` +
|
|
151
|
+
`Mensagem: ${message}\n` +
|
|
152
|
+
(location ? `Status URL: ${location}` : "Verifique o status no ADT.");
|
|
153
|
+
}
|
|
154
|
+
if (status >= 400) {
|
|
155
|
+
const msgMatch = data.match(/<(?:message|shortText)[^>]*>([\s\S]*?)<\/(?:message|shortText)>/i);
|
|
156
|
+
throw new Error(`Push falhou (HTTP ${status}): ${msgMatch?.[1] ?? data.slice(0, 500)}`);
|
|
157
|
+
}
|
|
158
|
+
return `Push concluído com sucesso!\nRepositório: ${repo_id}\nMensagem: ${message}`;
|
|
159
|
+
}
|
|
160
|
+
throw new Error(`Ação desconhecida: ${action}. Use "status" ou "push".`);
|
|
161
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapActivateTool = void 0;
|
|
4
|
+
exports.abapActivate = abapActivate;
|
|
5
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
6
|
+
function parseActivationResponse(xml) {
|
|
7
|
+
const executed = /activationExecuted="true"/.test(xml);
|
|
8
|
+
const checked = /checkExecuted="true"/.test(xml);
|
|
9
|
+
const messages = [];
|
|
10
|
+
const msgRegex = /<chkl:message[^>]*type="([^"]*)"[^>]*>[\s\S]*?<chkl:shortText[^>]*>([\s\S]*?)<\/chkl:shortText>[\s\S]*?(?:<chkl:uri[^>]*line="(\d+)")?/g;
|
|
11
|
+
let match;
|
|
12
|
+
while ((match = msgRegex.exec(xml)) !== null) {
|
|
13
|
+
messages.push({ type: match[1], text: match[2].trim(), line: match[3] });
|
|
14
|
+
}
|
|
15
|
+
// Se não executou check nem ativação e não há erros → objeto já estava ativo
|
|
16
|
+
const alreadyActive = !executed && !checked && messages.length === 0;
|
|
17
|
+
return { success: executed || alreadyActive, alreadyActive, messages };
|
|
18
|
+
}
|
|
19
|
+
async function abapActivate(input) {
|
|
20
|
+
const { object_type, object_name } = input;
|
|
21
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(object_type);
|
|
22
|
+
const objectUri = `/sap/bc/adt/${adtPath}/${object_name.toUpperCase()}`;
|
|
23
|
+
const csrf = await (0, adt_client_js_1.ensureSession)();
|
|
24
|
+
const payload = `<?xml version="1.0" encoding="UTF-8"?>
|
|
25
|
+
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
|
|
26
|
+
<adtcore:objectReference adtcore:uri="${objectUri}" adtcore:type="${object_type}" adtcore:name="${object_name.toUpperCase()}"/>
|
|
27
|
+
</adtcore:objectReferences>`;
|
|
28
|
+
const response = await adt_client_js_1.http.post("/activation", payload, {
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/xml",
|
|
31
|
+
"X-CSRF-Token": csrf,
|
|
32
|
+
},
|
|
33
|
+
params: { method: "activate", preauditRequested: "true" },
|
|
34
|
+
responseType: "text",
|
|
35
|
+
// Aceita 200 (ativação OK) e 400 (erros de sintaxe) — sem isso, axios rejeita non-2xx
|
|
36
|
+
// e o erro pode não ser propagado corretamente pelo MCP SDK
|
|
37
|
+
validateStatus: (status) => status === 200 || status === 400,
|
|
38
|
+
});
|
|
39
|
+
const { success, alreadyActive, messages } = parseActivationResponse(response.data);
|
|
40
|
+
if (!success) {
|
|
41
|
+
const errors = messages.map((m) => `[${m.type}]${m.line ? ` linha ${m.line}` : ""}: ${m.text}`).join("\n");
|
|
42
|
+
throw new Error(`Ativação falhou:\n${errors || "Erro desconhecido — verifique o código"}`);
|
|
43
|
+
}
|
|
44
|
+
if (alreadyActive) {
|
|
45
|
+
return `Objeto ${object_name.toUpperCase()} já estava ativo — nenhuma ação necessária.`;
|
|
46
|
+
}
|
|
47
|
+
if (messages.length === 0) {
|
|
48
|
+
return `Objeto ${object_name.toUpperCase()} ativado com sucesso.`;
|
|
49
|
+
}
|
|
50
|
+
const warnings = messages.map((m) => `[${m.type}]${m.line ? ` linha ${m.line}` : ""}: ${m.text}`).join("\n");
|
|
51
|
+
return `Objeto ${object_name.toUpperCase()} ativado com avisos:\n${warnings}`;
|
|
52
|
+
}
|
|
53
|
+
exports.abapActivateTool = {
|
|
54
|
+
name: "abap_activate",
|
|
55
|
+
description: "Ativa um objeto ABAP no sistema SAP via ADT API. Retorna erros de sintaxe se a ativação falhar.",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
object_type: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Tipo do objeto SAP. Exemplos: PROG/P (report), CLAS/OC (classe).",
|
|
62
|
+
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"],
|
|
63
|
+
},
|
|
64
|
+
object_name: {
|
|
65
|
+
type: "string",
|
|
66
|
+
description: "Nome do objeto ABAP (ex: ZTESTEDENIS). Case insensitive.",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
required: ["object_type", "object_name"],
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapAtcCheck = abapAtcCheck;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
function buildAtcPayload(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
|
+
<atc:run xmlns:atc="http://www.sap.com/adt/atc">
|
|
10
|
+
<objectSets xmlns:adtcore="http://www.sap.com/adt/core">
|
|
11
|
+
<objectSet kind="inclusive">
|
|
12
|
+
<adtcore:objectReferences>
|
|
13
|
+
<adtcore:objectReference adtcore:uri="${objectUri}" adtcore:name="${objectName}" adtcore:type="${objectType}"/>
|
|
14
|
+
</adtcore:objectReferences>
|
|
15
|
+
</objectSet>
|
|
16
|
+
</objectSets>
|
|
17
|
+
</atc:run>`;
|
|
18
|
+
}
|
|
19
|
+
function parseAtcFindings(xml) {
|
|
20
|
+
const findings = [];
|
|
21
|
+
// Extrair findings individuais
|
|
22
|
+
const findingRegex = /<finding[^>]*?>([\s\S]*?)<\/finding>/gi;
|
|
23
|
+
let match;
|
|
24
|
+
while ((match = findingRegex.exec(xml)) !== null) {
|
|
25
|
+
const content = match[0];
|
|
26
|
+
const priorityMatch = content.match(/priority\s*=\s*"([^"]*)"/i);
|
|
27
|
+
const checkTitleMatch = content.match(/checkTitle\s*=\s*"([^"]*)"/i);
|
|
28
|
+
const msgTitleMatch = content.match(/messageTitle\s*=\s*"([^"]*)"/i);
|
|
29
|
+
const uriMatch = content.match(/uri\s*=\s*"([^"]*)"/i);
|
|
30
|
+
const lineMatch = content.match(/line\s*=\s*"([^"]*)"/i) || content.match(/location[^>]*?line\s*=\s*"([^"]*)"/i);
|
|
31
|
+
findings.push({
|
|
32
|
+
priority: priorityMatch?.[1] ?? "3",
|
|
33
|
+
checkTitle: checkTitleMatch?.[1] ?? "",
|
|
34
|
+
messageTitle: msgTitleMatch?.[1] ?? "",
|
|
35
|
+
uri: uriMatch?.[1] ?? "",
|
|
36
|
+
line: lineMatch?.[1] ?? "",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return findings;
|
|
40
|
+
}
|
|
41
|
+
function priorityLabel(p) {
|
|
42
|
+
switch (p) {
|
|
43
|
+
case "1": return "ERRO";
|
|
44
|
+
case "2": return "AVISO";
|
|
45
|
+
case "3": return "INFO";
|
|
46
|
+
default: return `P${p}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function formatAtcResults(objectName, findings) {
|
|
50
|
+
if (findings.length === 0) {
|
|
51
|
+
return `ATC Check: ${objectName} — nenhum finding encontrado. Código OK!`;
|
|
52
|
+
}
|
|
53
|
+
const errors = findings.filter((f) => f.priority === "1").length;
|
|
54
|
+
const warnings = findings.filter((f) => f.priority === "2").length;
|
|
55
|
+
const infos = findings.filter((f) => f.priority === "3").length;
|
|
56
|
+
const lines = [
|
|
57
|
+
`ATC Check: ${objectName} — ${findings.length} findings (${errors} erros, ${warnings} avisos, ${infos} info)`,
|
|
58
|
+
"─".repeat(70),
|
|
59
|
+
];
|
|
60
|
+
// Ordenar por prioridade
|
|
61
|
+
const sorted = [...findings].sort((a, b) => a.priority.localeCompare(b.priority));
|
|
62
|
+
for (const f of sorted) {
|
|
63
|
+
const label = priorityLabel(f.priority);
|
|
64
|
+
const loc = f.line ? ` linha ${f.line}` : "";
|
|
65
|
+
lines.push(`[${label}]${loc}: ${f.messageTitle}`);
|
|
66
|
+
if (f.checkTitle)
|
|
67
|
+
lines.push(` Check: ${f.checkTitle}`);
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
71
|
+
async function abapAtcCheck(input) {
|
|
72
|
+
const { object_type, object_name } = input;
|
|
73
|
+
const name = object_name.toUpperCase();
|
|
74
|
+
const csrf = await (0, adt_client_js_1.ensureSession)();
|
|
75
|
+
const payload = buildAtcPayload(object_type, name);
|
|
76
|
+
// Passo 1: Criar worklist
|
|
77
|
+
const wlResponse = await adt_client_js_1.http.post("/atc/worklists", "", {
|
|
78
|
+
headers: {
|
|
79
|
+
Accept: "text/plain",
|
|
80
|
+
"X-CSRF-Token": csrf,
|
|
81
|
+
},
|
|
82
|
+
responseType: "text",
|
|
83
|
+
validateStatus: () => true,
|
|
84
|
+
});
|
|
85
|
+
// Extrair worklistId do Location header
|
|
86
|
+
const wlLocation = (wlResponse.headers["location"] ?? "");
|
|
87
|
+
const wlIdMatch = wlLocation.match(/worklistId\/([^/?]+)/i)
|
|
88
|
+
|| wlLocation.match(/worklists\/([^/?]+)/i);
|
|
89
|
+
const worklistId = wlIdMatch?.[1];
|
|
90
|
+
if (!worklistId) {
|
|
91
|
+
throw new Error("Não foi possível criar worklist ATC. Location: " + wlLocation);
|
|
92
|
+
}
|
|
93
|
+
// Passo 2: Executar run ATC
|
|
94
|
+
const runResponse = await adt_client_js_1.http.post("/atc/runs", payload, {
|
|
95
|
+
headers: {
|
|
96
|
+
"Content-Type": "application/xml",
|
|
97
|
+
Accept: "application/xml",
|
|
98
|
+
"X-CSRF-Token": csrf,
|
|
99
|
+
},
|
|
100
|
+
params: { worklistId },
|
|
101
|
+
responseType: "text",
|
|
102
|
+
validateStatus: (status) => status < 500,
|
|
103
|
+
});
|
|
104
|
+
if (runResponse.status >= 400) {
|
|
105
|
+
const body = typeof runResponse.data === "string" ? runResponse.data : JSON.stringify(runResponse.data);
|
|
106
|
+
throw new Error(`ATC run falhou (HTTP ${runResponse.status}): ${body.slice(0, 500)}`);
|
|
107
|
+
}
|
|
108
|
+
// Passo 3: Obter resultados do worklist
|
|
109
|
+
const resultsResponse = await adt_client_js_1.http.get(`/atc/worklists/${worklistId}`, {
|
|
110
|
+
headers: { Accept: "application/atc.worklist.v1+xml" },
|
|
111
|
+
responseType: "text",
|
|
112
|
+
validateStatus: (status) => status < 500,
|
|
113
|
+
});
|
|
114
|
+
const resultsXml = resultsResponse.data;
|
|
115
|
+
const findings = parseAtcFindings(resultsXml);
|
|
116
|
+
return formatAtcResults(name, findings);
|
|
117
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapAuthObject = abapAuthObject;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
async function abapAuthObject(input) {
|
|
6
|
+
const { object_name } = input;
|
|
7
|
+
const name = object_name.toUpperCase();
|
|
8
|
+
await (0, adt_client_js_1.ensureSession)();
|
|
9
|
+
const response = await adt_client_js_1.http.get(`/aps/iam/suso/${name}`, {
|
|
10
|
+
headers: { Accept: "text/html" },
|
|
11
|
+
responseType: "text",
|
|
12
|
+
validateStatus: (status) => status < 500,
|
|
13
|
+
});
|
|
14
|
+
if (response.status === 404) {
|
|
15
|
+
throw new Error(`Objeto de autorização ${name} não encontrado.`);
|
|
16
|
+
}
|
|
17
|
+
const html = response.data;
|
|
18
|
+
// Parse HTML para extrair informações
|
|
19
|
+
const titleMatch = html.match(/<title>([^<]*)<\/title>/i);
|
|
20
|
+
const title = titleMatch?.[1] ?? name;
|
|
21
|
+
// Extrair campos de autorização das tabelas HTML
|
|
22
|
+
// Pattern: <td>FIELD_NAME</td><td>Description</td>
|
|
23
|
+
const fields = [];
|
|
24
|
+
const tdRegex = /<tr[^>]*>\s*<td[^>]*>([A-Z_][A-Z0-9_]*)<\/td>\s*<td[^>]*>([^<]*)<\/td>/gi;
|
|
25
|
+
let match;
|
|
26
|
+
while ((match = tdRegex.exec(html)) !== null) {
|
|
27
|
+
fields.push({ name: match[1], description: match[2].trim() });
|
|
28
|
+
}
|
|
29
|
+
// Fallback: buscar campos em qualquer formato de tabela
|
|
30
|
+
if (fields.length === 0) {
|
|
31
|
+
const simpleRegex = />([A-Z][A-Z0-9_]{2,})<\/(?:td|span|div|a)/gi;
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
while ((match = simpleRegex.exec(html)) !== null) {
|
|
34
|
+
const val = match[1];
|
|
35
|
+
if (!seen.has(val) && val !== name && val.length <= 30) {
|
|
36
|
+
seen.add(val);
|
|
37
|
+
fields.push({ name: val, description: "" });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const lines = [];
|
|
42
|
+
lines.push(`Objeto de Autorização: ${title}`);
|
|
43
|
+
lines.push("═".repeat(50));
|
|
44
|
+
if (fields.length > 0) {
|
|
45
|
+
lines.push(`\nCampos (${fields.length}):`);
|
|
46
|
+
lines.push("─".repeat(40));
|
|
47
|
+
for (const f of fields) {
|
|
48
|
+
const desc = f.description ? ` — ${f.description}` : "";
|
|
49
|
+
lines.push(` ${f.name}${desc}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
lines.push("\n(Campos não encontrados no formato HTML. Verifique no SAP GUI.)");
|
|
54
|
+
}
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapBreakpoints = abapBreakpoints;
|
|
4
|
+
const adt_client_js_1 = require("../adt-client.js");
|
|
5
|
+
async function abapBreakpoints(input) {
|
|
6
|
+
const { action = "list", object_type, object_name, line, breakpoint_id } = input;
|
|
7
|
+
if (action === "list") {
|
|
8
|
+
const xml = await (0, adt_client_js_1.adtGetXml)("/debugger/breakpoints");
|
|
9
|
+
// Parse breakpoints
|
|
10
|
+
const breakpoints = [];
|
|
11
|
+
const bpRegex = /<(?:dbg:)?breakpoint[^>]*>/gi;
|
|
12
|
+
let match;
|
|
13
|
+
while ((match = bpRegex.exec(xml)) !== null) {
|
|
14
|
+
const el = match[0];
|
|
15
|
+
const idMatch = el.match(/(?:dbg:)?id\s*=\s*"([^"]*)"/i);
|
|
16
|
+
const uriMatch = el.match(/(?:adtcore:)?uri\s*=\s*"([^"]*)"/i);
|
|
17
|
+
const lineMatch = el.match(/line\s*=\s*"([^"]*)"/i);
|
|
18
|
+
const typeMatch = el.match(/(?:dbg:)?type\s*=\s*"([^"]*)"/i);
|
|
19
|
+
breakpoints.push({
|
|
20
|
+
id: idMatch?.[1] ?? "",
|
|
21
|
+
uri: uriMatch?.[1] ?? "",
|
|
22
|
+
line: lineMatch?.[1] ?? "",
|
|
23
|
+
type: typeMatch?.[1] ?? "line",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (breakpoints.length === 0) {
|
|
27
|
+
return "Nenhum breakpoint externo ativo.";
|
|
28
|
+
}
|
|
29
|
+
const lines = [];
|
|
30
|
+
lines.push(`Breakpoints: ${breakpoints.length} ativo(s)`);
|
|
31
|
+
lines.push("═".repeat(50));
|
|
32
|
+
for (const bp of breakpoints) {
|
|
33
|
+
lines.push(` [${bp.id}] Linha ${bp.line} (${bp.type})`);
|
|
34
|
+
if (bp.uri)
|
|
35
|
+
lines.push(` URI: ${bp.uri}`);
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
39
|
+
else if (action === "set") {
|
|
40
|
+
if (!object_type || !object_name || !line) {
|
|
41
|
+
throw new Error("object_type, object_name e line são obrigatórios para definir um breakpoint.");
|
|
42
|
+
}
|
|
43
|
+
const name = object_name.toUpperCase();
|
|
44
|
+
const adtPath = (0, adt_client_js_1.resolveAdtPath)(object_type);
|
|
45
|
+
const uri = `/sap/bc/adt/${adtPath}/${name}/source/main`;
|
|
46
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
47
|
+
<dbg:breakpoint xmlns:dbg="http://www.sap.com/adt/debugger"
|
|
48
|
+
xmlns:adtcore="http://www.sap.com/adt/core">
|
|
49
|
+
<dbg:type>line</dbg:type>
|
|
50
|
+
<dbg:line>${line}</dbg:line>
|
|
51
|
+
<adtcore:objectReference adtcore:uri="${uri}"/>
|
|
52
|
+
</dbg:breakpoint>`;
|
|
53
|
+
const { data, status } = await (0, adt_client_js_1.adtPostXml)("/debugger/breakpoints", body);
|
|
54
|
+
if (status >= 400) {
|
|
55
|
+
const msgMatch = data.match(/<(?:message|shortText)[^>]*>([\s\S]*?)<\/(?:message|shortText)>/i);
|
|
56
|
+
throw new Error(`Falha ao definir breakpoint (HTTP ${status}): ${msgMatch?.[1] ?? data.slice(0, 500)}`);
|
|
57
|
+
}
|
|
58
|
+
const idMatch = data.match(/(?:dbg:)?id\s*=\s*"([^"]*)"/i);
|
|
59
|
+
return `Breakpoint definido com sucesso!\n` +
|
|
60
|
+
`Objeto: ${name} (${object_type})\n` +
|
|
61
|
+
`Linha: ${line}` +
|
|
62
|
+
(idMatch ? `\nID: ${idMatch[1]}` : "");
|
|
63
|
+
}
|
|
64
|
+
else if (action === "remove") {
|
|
65
|
+
if (!breakpoint_id) {
|
|
66
|
+
throw new Error("breakpoint_id é obrigatório para remover um breakpoint.");
|
|
67
|
+
}
|
|
68
|
+
const csrf = await (0, adt_client_js_1.ensureSession)();
|
|
69
|
+
await adt_client_js_1.http.delete(`/debugger/breakpoints/${breakpoint_id}`, {
|
|
70
|
+
headers: { "X-CSRF-Token": csrf },
|
|
71
|
+
validateStatus: (status) => status < 500,
|
|
72
|
+
});
|
|
73
|
+
return `Breakpoint ${breakpoint_id} removido com sucesso.`;
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Ação desconhecida: ${action}. Use "list", "set" ou "remove".`);
|
|
76
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abapCallHierarchy = abapCallHierarchy;
|
|
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.toUpperCase();
|
|
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 abapCallHierarchy(input) {
|
|
14
|
+
const { object_type, object_name, method_name, direction = "callers", class_include } = input;
|
|
15
|
+
const name = object_name.toUpperCase();
|
|
16
|
+
let uri = resolveSourceUri(object_type, name, class_include);
|
|
17
|
+
if (method_name) {
|
|
18
|
+
uri += `#name=${method_name}`;
|
|
19
|
+
}
|
|
20
|
+
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
|
21
|
+
<callhierarchy:callHierarchy
|
|
22
|
+
xmlns:callhierarchy="http://www.sap.com/adt/callhierarchy"
|
|
23
|
+
xmlns:adtcore="http://www.sap.com/adt/core">
|
|
24
|
+
<adtcore:objectReference adtcore:uri="${uri}" adtcore:name="${method_name || name}"/>
|
|
25
|
+
</callhierarchy:callHierarchy>`;
|
|
26
|
+
const params = {};
|
|
27
|
+
if (direction === "callees")
|
|
28
|
+
params.direction = "callees";
|
|
29
|
+
const { data, status } = await (0, adt_client_js_1.adtPostXml)("/callhierarchy", body, params);
|
|
30
|
+
if (status >= 400) {
|
|
31
|
+
throw new Error(`Call hierarchy falhou (HTTP ${status}): ${data.slice(0, 500)}`);
|
|
32
|
+
}
|
|
33
|
+
// Parse entries
|
|
34
|
+
const entries = [];
|
|
35
|
+
const entryRegex = /<(?:callhierarchy:)?entry[^>]*>([\s\S]*?)<\/(?:callhierarchy:)?entry>/gi;
|
|
36
|
+
let match;
|
|
37
|
+
while ((match = entryRegex.exec(data)) !== null) {
|
|
38
|
+
const content = match[0];
|
|
39
|
+
const nameMatch = content.match(/adtcore:name\s*=\s*"([^"]*)"/i);
|
|
40
|
+
const typeMatch = content.match(/adtcore:type\s*=\s*"([^"]*)"/i);
|
|
41
|
+
const uriMatch = content.match(/adtcore:uri\s*=\s*"([^"]*)"/i);
|
|
42
|
+
const descMatch = content.match(/adtcore:description\s*=\s*"([^"]*)"/i);
|
|
43
|
+
if (nameMatch) {
|
|
44
|
+
entries.push({
|
|
45
|
+
name: nameMatch[1],
|
|
46
|
+
type: typeMatch?.[1] ?? "",
|
|
47
|
+
uri: uriMatch?.[1] ?? "",
|
|
48
|
+
description: descMatch?.[1] ?? "",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Fallback: parse objectReference elements
|
|
53
|
+
if (entries.length === 0) {
|
|
54
|
+
const refRegex = /<adtcore:objectReference[^>]*>/gi;
|
|
55
|
+
while ((match = refRegex.exec(data)) !== null) {
|
|
56
|
+
const el = match[0];
|
|
57
|
+
const nameMatch = el.match(/adtcore:name\s*=\s*"([^"]*)"/i);
|
|
58
|
+
const typeMatch = el.match(/adtcore:type\s*=\s*"([^"]*)"/i);
|
|
59
|
+
const uriMatch = el.match(/adtcore:uri\s*=\s*"([^"]*)"/i);
|
|
60
|
+
const descMatch = el.match(/adtcore:description\s*=\s*"([^"]*)"/i);
|
|
61
|
+
if (nameMatch) {
|
|
62
|
+
entries.push({
|
|
63
|
+
name: nameMatch[1],
|
|
64
|
+
type: typeMatch?.[1] ?? "",
|
|
65
|
+
uri: uriMatch?.[1] ?? "",
|
|
66
|
+
description: descMatch?.[1] ?? "",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (entries.length === 0) {
|
|
72
|
+
return `Nenhum ${direction === "callers" ? "chamador" : "chamado"} encontrado para ${method_name || name}.`;
|
|
73
|
+
}
|
|
74
|
+
const dirLabel = direction === "callers" ? "Chamadores (quem chama)" : "Chamados (quem é chamado)";
|
|
75
|
+
const lines = [];
|
|
76
|
+
lines.push(`Call Hierarchy — ${dirLabel}: ${method_name || name}`);
|
|
77
|
+
lines.push("─".repeat(70));
|
|
78
|
+
for (const e of entries) {
|
|
79
|
+
const desc = e.description ? ` — ${e.description}` : "";
|
|
80
|
+
lines.push(` ${e.name} [${e.type}]${desc}`);
|
|
81
|
+
}
|
|
82
|
+
lines.push(`\nTotal: ${entries.length} referência(s)`);
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|