@dozimple/abap-adt 1.0.0-rc.1

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +19 -0
  4. package/README.es.md +356 -0
  5. package/README.md +356 -0
  6. package/SECURITY.md +68 -0
  7. package/THIRD_PARTY_NOTICES.md +134 -0
  8. package/config/systems.example.json +39 -0
  9. package/dist/core/activation.js +18 -0
  10. package/dist/core/atc.js +94 -0
  11. package/dist/core/audit.js +93 -0
  12. package/dist/core/catalog.en.js +92 -0
  13. package/dist/core/catalog.js +139 -0
  14. package/dist/core/checks.js +19 -0
  15. package/dist/core/config.js +136 -0
  16. package/dist/core/confirm.js +43 -0
  17. package/dist/core/connection.js +159 -0
  18. package/dist/core/credentials.js +44 -0
  19. package/dist/core/datapolicy.js +175 -0
  20. package/dist/core/diff.js +115 -0
  21. package/dist/core/edits.js +31 -0
  22. package/dist/core/errors.js +68 -0
  23. package/dist/core/feeds.js +60 -0
  24. package/dist/core/notes.js +18 -0
  25. package/dist/core/objects.js +143 -0
  26. package/dist/core/output.js +27 -0
  27. package/dist/core/policy.js +126 -0
  28. package/dist/core/prompts.js +89 -0
  29. package/dist/core/registry.js +316 -0
  30. package/dist/core/revisions.js +97 -0
  31. package/dist/core/sidecar.js +85 -0
  32. package/dist/core/sql.js +54 -0
  33. package/dist/core/telemetry.js +42 -0
  34. package/dist/core/tool.js +5 -0
  35. package/dist/core/transport.js +109 -0
  36. package/dist/index.js +55 -0
  37. package/dist/scripts/audit-verify.js +19 -0
  38. package/dist/scripts/smoke.js +87 -0
  39. package/dist/tools/core/activate.js +27 -0
  40. package/dist/tools/core/api_release_state.js +58 -0
  41. package/dist/tools/core/atc_quickfix.js +159 -0
  42. package/dist/tools/core/co_change.js +73 -0
  43. package/dist/tools/core/create_transport.js +44 -0
  44. package/dist/tools/core/ddic_type_info.js +81 -0
  45. package/dist/tools/core/dumps.js +55 -0
  46. package/dist/tools/core/edit_preflight.js +112 -0
  47. package/dist/tools/core/function_modules.js +75 -0
  48. package/dist/tools/core/gateway_errors.js +45 -0
  49. package/dist/tools/core/get_source.js +70 -0
  50. package/dist/tools/core/inactive_objects.js +29 -0
  51. package/dist/tools/core/object_versions.js +35 -0
  52. package/dist/tools/core/package_contents.js +59 -0
  53. package/dist/tools/core/run_atc.js +117 -0
  54. package/dist/tools/core/run_unit_tests.js +60 -0
  55. package/dist/tools/core/search_objects.js +26 -0
  56. package/dist/tools/core/sql_query.js +25 -0
  57. package/dist/tools/core/support.js +95 -0
  58. package/dist/tools/core/syntax_check.js +35 -0
  59. package/dist/tools/core/table_contents.js +57 -0
  60. package/dist/tools/core/text_elements.js +108 -0
  61. package/dist/tools/core/transaction_info.js +43 -0
  62. package/dist/tools/core/transport_contents.js +49 -0
  63. package/dist/tools/core/transport_diff.js +111 -0
  64. package/dist/tools/core/where_used.js +37 -0
  65. package/dist/tools/core/write_source.js +95 -0
  66. package/dist/tools/docs/docs.js +159 -0
  67. package/dist/tools/local/growth.js +104 -0
  68. package/dist/tools/local/sap_systems.js +65 -0
  69. package/dist/tools/transport-risk/_service.js +63 -0
  70. package/dist/tools/transport-risk/risk_extras.js +90 -0
  71. package/dist/tools/transport-risk/transport_risk.js +42 -0
  72. package/docs/THREAT_MODEL.md +70 -0
  73. package/docs/TOOLS.md +980 -0
  74. package/package.json +68 -0
  75. package/scripts/set-password.sh +31 -0
@@ -0,0 +1,81 @@
1
+ import { z } from "zod";
2
+ import { ToolError } from "../../core/errors.js";
3
+ import { sqlLiteral } from "../../core/objects.js";
4
+ import { tsv } from "../../core/output.js";
5
+ import { defineTool } from "../../core/tool.js";
6
+ /** Código de idioma interno de SAP (DDLANGUAGE es LANG de 1 carácter). */
7
+ const LANG = { EN: "E", ES: "S", DE: "D", PT: "P", FR: "F", IT: "I" };
8
+ async function text(sap, table, keyField, key, lang, fields) {
9
+ const r = await sap.query(`SELECT ddlanguage, ${fields} FROM ${table} WHERE ${keyField} = ${sqlLiteral(key)} AND as4local = 'A'`, 20);
10
+ return r.values.find((v) => v.DDLANGUAGE === lang) ?? r.values.find((v) => v.DDLANGUAGE === "E") ?? r.values[0];
11
+ }
12
+ const pick = (row, fields) => fields.filter((f) => row[f] !== undefined && String(row[f]).trim() !== "" && String(row[f]) !== "0")
13
+ .map((f) => `${f.toLowerCase()}=${String(row[f]).trim()}`)
14
+ .join(" · ");
15
+ export default defineTool({
16
+ name: "ddic_type_info",
17
+ title: "Elemento de datos, dominio o tipo tabla",
18
+ description: "Definición de un tipo DDIC: elemento de datos (dominio, tipo, longitud, textos), dominio (tipo, longitud, " +
19
+ "valores fijos, tabla de valores) o tipo tabla (tipo de línea, clave). Lee DD04L/DD01L/DD07L/DD40L: funciona " +
20
+ "igual en ECC 7.50 y en S/4. Detecta el tipo si no se indica.",
21
+ access: "read",
22
+ input: {
23
+ name: z.string().min(1),
24
+ kind: z.enum(["DTEL", "DOMA", "TTYP"]).optional(),
25
+ },
26
+ async run({ name, kind }, { sap, system }) {
27
+ const n = name.trim().toUpperCase();
28
+ const lang = LANG[system.language.toUpperCase()] ?? "E";
29
+ const out = [];
30
+ if (!kind || kind === "DTEL") {
31
+ const r = await sap.query(`SELECT * FROM dd04l WHERE rollname = ${sqlLiteral(n)} AND as4local = 'A'`, 1);
32
+ const d = r.values[0];
33
+ if (d) {
34
+ const t = await text(sap, "dd04t", "rollname", n, lang, "ddtext, reptext, scrtext_m");
35
+ out.push(`Elemento de datos ${n}${t ? ` «${t.DDTEXT}»` : ""}`);
36
+ out.push(" " + pick(d, ["DOMNAME", "DATATYPE", "LENG", "DECIMALS", "OUTPUTLEN", "REFKIND", "REFTYPE", "CONVEXIT", "ENTITYTAB", "MEMORYID", "LOWERCASE"]));
37
+ if (t)
38
+ out.push(` cabecera «${String(t.REPTEXT ?? "").trim()}» · texto medio «${String(t.SCRTEXT_M ?? "").trim()}»`);
39
+ if (d.DOMNAME && !kind)
40
+ out.push("", ...(await domain(sap, String(d.DOMNAME).trim(), lang)));
41
+ return out.join("\n");
42
+ }
43
+ if (kind)
44
+ throw new ToolError("NOT_FOUND", `No hay elemento de datos activo ${n}.`);
45
+ }
46
+ if (!kind || kind === "DOMA") {
47
+ const lines = await domain(sap, n, lang);
48
+ if (lines.length)
49
+ return lines.join("\n");
50
+ if (kind)
51
+ throw new ToolError("NOT_FOUND", `No hay dominio activo ${n}.`);
52
+ }
53
+ const r = await sap.query(`SELECT * FROM dd40l WHERE typename = ${sqlLiteral(n)} AND as4local = 'A'`, 1);
54
+ const tt = r.values[0];
55
+ if (tt) {
56
+ const t = await text(sap, "dd40t", "typename", n, lang, "ddtext");
57
+ return [`Tipo tabla ${n}${t ? ` «${t.DDTEXT}»` : ""}`, " " + pick(tt, ["ROWTYPE", "ROWKIND", "DATATYPE", "LENG", "ACCESSMODE", "KEYDEF", "KEYKIND", "GENERIC"])].join("\n");
58
+ }
59
+ throw new ToolError("NOT_FOUND", `${n} no es un elemento de datos, dominio ni tipo tabla activo.`);
60
+ },
61
+ });
62
+ async function domain(sap, n, lang) {
63
+ const r = await sap.query(`SELECT * FROM dd01l WHERE domname = ${sqlLiteral(n)} AND as4local = 'A'`, 1);
64
+ const d = r.values[0];
65
+ if (!d)
66
+ return [];
67
+ const t = await text(sap, "dd01t", "domname", n, lang, "ddtext");
68
+ const out = [`Dominio ${n}${t ? ` «${t.DDTEXT}»` : ""}`, " " + pick(d, ["DATATYPE", "LENG", "DECIMALS", "OUTPUTLEN", "LOWERCASE", "SIGNFLAG", "CONVEXIT", "ENTITYTAB", "VALEXI"])];
69
+ const fv = await sap.query(`SELECT domvalue_l, domvalue_h, valpos FROM dd07l WHERE domname = ${sqlLiteral(n)} AND as4local = 'A' ORDER BY valpos`, 500);
70
+ if (fv.values.length) {
71
+ const tx = await sap.query(`SELECT ddlanguage, valpos, ddtext FROM dd07t WHERE domname = ${sqlLiteral(n)} AND as4local = 'A'`, 2000);
72
+ const label = (pos) => (tx.values.find((v) => v.VALPOS === pos && v.DDLANGUAGE === lang) ?? tx.values.find((v) => v.VALPOS === pos))?.DDTEXT ?? "";
73
+ out.push(` valores fijos (${fv.values.length}):`);
74
+ out.push(tsv(["valor", "hasta", "texto"], fv.values.map((v) => [v.DOMVALUE_L, v.DOMVALUE_H, label(v.VALPOS)]))
75
+ .split("\n")
76
+ .map((l) => " " + l)
77
+ .join("\n"));
78
+ }
79
+ return out;
80
+ }
81
+ //# sourceMappingURL=ddic_type_info.js.map
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ import { ToolError } from "../../core/errors.js";
3
+ import { decodeEntities, htmlToText } from "../../core/feeds.js";
4
+ import { budget } from "../../core/output.js";
5
+ import { defineTool } from "../../core/tool.js";
6
+ const decode = decodeEntities;
7
+ const stripHtml = htmlToText;
8
+ /** Campo de la tabla «Header Information» del resumen HTML del dump. */
9
+ function headerField(d, label) {
10
+ const m = d.text.match(new RegExp(`<b>${label}&nbsp;</b></td><td[^>]*>([\\s\\S]*?)</td>`));
11
+ return m ? decode(m[1]).trim() : undefined;
12
+ }
13
+ function summary(d, i) {
14
+ const cat = (label) => d.categories.find((c) => c.label === label)?.term;
15
+ return (`${i}. ${headerField(d, "Date/Time")?.replace(" (System)", "") ?? "?"} · ${cat("ABAP runtime error") ?? "?"} · ` +
16
+ `${cat("Terminated ABAP program") ?? "?"} · ${d.author ?? "?"}\n ${headerField(d, "Short Text") ?? ""}`);
17
+ }
18
+ export default defineTool({
19
+ name: "dumps",
20
+ title: "Dumps (ST22)",
21
+ description: "Lista los dumps de ejecución (ST22): fecha, error, programa, usuario y texto corto. Filtra por usuario y por " +
22
+ "texto (error o programa). Con detail=N devuelve el dump N completo (qué pasó, análisis, dónde terminó, fuente, pila).",
23
+ access: "read",
24
+ requires: { adt: ["/sap/bc/adt/runtime/dumps"] },
25
+ input: {
26
+ user: z.string().optional(),
27
+ contains: z.string().optional().describe("Filtra por error o programa, p. ej. CALL_FUNCTION_NOT_FOUND o ZFI_REPORTE"),
28
+ max: z.number().int().min(1).max(200).default(20),
29
+ detail: z.number().int().min(1).optional().describe("Número de la lista para ver el dump completo"),
30
+ },
31
+ async run({ user, contains, max, detail }, { sap }) {
32
+ const c = await sap.adt();
33
+ const feed = await c.dumps(user ? `and( equals( user, ${user.toUpperCase()} ) )` : "");
34
+ const needle = contains?.toUpperCase();
35
+ const list = feed.dumps.filter((d) => !needle || d.categories.some((x) => x.term.toUpperCase().includes(needle)));
36
+ if (!list.length) {
37
+ return `El feed de dumps se leyó (${feed.dumps.length} en total) y ninguno casa${user ? ` con usuario ${user}` : ""}${contains ? ` y «${contains}»` : ""}.`;
38
+ }
39
+ if (detail) {
40
+ const d = list[detail - 1];
41
+ if (!d)
42
+ throw new ToolError("INPUT", `Solo hay ${list.length} dumps en la lista.`);
43
+ const self = d.links.find((l) => l.rel === "self")?.href.replace(/^adt:\/\/[^/]+/, "");
44
+ if (!self)
45
+ return budget(stripHtml(d.text));
46
+ // El recurso ofrece HTML (dump completo) o un XML índice; text/plain da 406.
47
+ const r = await c.httpClient.request(self, { headers: { Accept: "text/html" } });
48
+ return budget(`${summary(d, detail)}\n\n${stripHtml(String(r.body))}`, undefined, "El dump completo está en ST22.");
49
+ }
50
+ const shown = list.slice(0, max);
51
+ return (`${list.length} dumps${needle || user ? " que casan" : ""} (se muestran ${shown.length}; el feed trae los más recientes)\n\n` +
52
+ shown.map((d, i) => summary(d, i + 1)).join("\n"));
53
+ },
54
+ });
55
+ //# sourceMappingURL=dumps.js.map
@@ -0,0 +1,112 @@
1
+ import { z } from "zod";
2
+ import { normalizeError } from "../../core/errors.js";
3
+ import { resolveObject, sqlLiteral, TYPE_HELP } from "../../core/objects.js";
4
+ import { assertTrkorr } from "../../core/policy.js";
5
+ import { describeOrder, isOpen, orderHeaders, transportWarnings } from "../../core/transport.js";
6
+ import { defineTool } from "../../core/tool.js";
7
+ /** Entrada de TADIR que manda sobre el objeto (un FM pertenece a su grupo). */
8
+ async function tadirKey(sap, obj) {
9
+ if (obj.type.startsWith("FUGR/FF")) {
10
+ const r = await sap.query(`SELECT area FROM enlfdir WHERE funcname = ${sqlLiteral(obj.name)}`, 1);
11
+ if (r.values[0]?.AREA)
12
+ return { object: "FUGR", name: r.values[0].AREA };
13
+ }
14
+ return { object: obj.type.slice(0, 4), name: obj.name };
15
+ }
16
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
17
+ const likeEscape = (s) => s.replace(/[#%_]/g, (c) => "#" + c);
18
+ export default defineTool({
19
+ name: "edit_preflight",
20
+ title: "Antes de editar: ¿a qué orden irá?",
21
+ description: "Dice, ANTES de modificar un objeto, en qué orden acabará el cambio y por qué: bloqueo del CTS (TLOCK) de otra " +
22
+ "orden, objeto local ($TMP), reparación (sistema original distinto), o si está libre y qué órdenes tienes abiertas. " +
23
+ "Úsala siempre antes de write_source o de editar a mano en un sistema ajeno.",
24
+ access: "read",
25
+ input: {
26
+ object_name: z.string().min(1),
27
+ object_type: z.string().optional().describe(TYPE_HELP),
28
+ transport: z.string().optional().describe("Orden en la que QUIERES guardar, para comprobar si es posible"),
29
+ },
30
+ async run({ object_name, object_type, transport }, { sap, system }) {
31
+ const c = await sap.adt();
32
+ const obj = await resolveObject(c, object_name, object_type);
33
+ const wanted = transport ? assertTrkorr(transport) : undefined;
34
+ const key = await tadirKey(sap, obj);
35
+ const out = [`${obj.name} (${obj.type})` + (key.name !== obj.name ? ` · pertenece a ${key.object} ${key.name}` : "")];
36
+ const verdict = [];
37
+ // 1. Directorio: paquete y sistema original.
38
+ const tadir = await sap.query(`SELECT devclass, srcsystem, author FROM tadir WHERE pgmid = 'R3TR' AND object = ${sqlLiteral(key.object)} AND obj_name = ${sqlLiteral(key.name)}`, 1);
39
+ const t = tadir.values[0];
40
+ const devclass = t?.DEVCLASS ?? obj.packageName ?? "";
41
+ if (t)
42
+ out.push(`Paquete ${devclass} · sistema original ${t.SRCSYSTEM} · autor ${t.AUTHOR}`);
43
+ else
44
+ out.push(`Sin entrada R3TR ${key.object} ${key.name} en TADIR.`);
45
+ const isLocal = devclass.startsWith("$");
46
+ // 2. Qué dice el propio CTS (lo mismo que el diálogo de orden de Eclipse).
47
+ let info;
48
+ if (!isLocal) {
49
+ try {
50
+ info = await c.transportInfo(obj.uri, devclass, "I");
51
+ }
52
+ catch (e) {
53
+ out.push(`No se pudo consultar el CTS: ${normalizeError(e).message}`);
54
+ }
55
+ }
56
+ // 3. Bloqueos reales del CTS.
57
+ const locks = await sap.query(`SELECT object, lokey, trkorr FROM tlock WHERE lokey LIKE ${sqlLiteral("%" + likeEscape(key.name) + "%")} ESCAPE '#'`, 200);
58
+ const re = new RegExp(`(^|[^A-Z0-9_/])${escapeRegExp(key.name)}($|[^A-Z0-9_])`);
59
+ const mine = locks.values.filter((l) => re.test(String(l.LOKEY).trimEnd()) || String(l.LOKEY).trimEnd().startsWith(key.name));
60
+ const heads = await orderHeaders(sap, mine.map((l) => l.TRKORR));
61
+ const parents = await orderHeaders(sap, [...heads.values()].map((h) => h.parent));
62
+ const lockOrders = new Map(); // orden padre → descripción
63
+ for (const l of mine) {
64
+ const task = heads.get(l.TRKORR);
65
+ const order = task?.parent ? parents.get(task.parent) ?? task : task;
66
+ const id = order?.trkorr ?? l.TRKORR;
67
+ if (!lockOrders.has(id))
68
+ lockOrders.set(id, `${order ? describeOrder(order) : id} [TLOCK ${l.OBJECT} ${String(l.LOKEY).trim()}]`);
69
+ }
70
+ if (lockOrders.size)
71
+ out.push("", "Bloqueos del CTS:", ...[...lockOrders.values()].map((d) => " " + d));
72
+ // 4. Veredicto.
73
+ const sid = system.sid ?? (info?.TRANSPORTS?.[0]?.TRKORR ?? info?.LOCKS?.HEADER?.TRKORR ?? [...heads.keys()][0])?.slice(0, 3);
74
+ if (isLocal)
75
+ verdict.push("Objeto local: el cambio no se registra en ninguna orden y no viaja.");
76
+ if (t && sid && t.SRCSYSTEM && t.SRCSYSTEM !== sid && !isLocal) {
77
+ verdict.push(`REPARACIÓN: el sistema original es ${t.SRCSYSTEM}, no ${sid}. El cambio se registra en una tarea de reparación: ` +
78
+ `si su orden no tiene destino no sale del sistema, y si viaja, lo pisará cualquier transporte que llegue desde ` +
79
+ `${t.SRCSYSTEM}. Si ${sid} debe ser el original: SE03 → Cambiar sistema original.`);
80
+ }
81
+ if (lockOrders.size) {
82
+ const ids = [...lockOrders.keys()];
83
+ verdict.push(`BLOQUEADO en ${ids.join(", ")}: cualquier cambio irá obligatoriamente ahí.`);
84
+ if (wanted && !ids.includes(wanted))
85
+ verdict.push(`No podrás guardar en ${wanted}.`);
86
+ }
87
+ else if (!isLocal) {
88
+ const all = info?.TRANSPORTS ?? [];
89
+ const cands = all.slice(0, 10).map((x) => `${x.TRKORR} «${x.AS4TEXT}» (${x.AS4USER})`);
90
+ verdict.push("Sin bloqueo del CTS: puedes elegir la orden.");
91
+ if (cands.length)
92
+ verdict.push(`Órdenes abiertas que ofrece el CTS (${all.length}${all.length > 10 ? ", se muestran 10" : ""}): ${cands.join("; ")}`);
93
+ if (wanted) {
94
+ const w = (await orderHeaders(sap, [wanted])).get(wanted);
95
+ if (!w)
96
+ verdict.push(`${wanted} no existe en este sistema.`);
97
+ else if (!isOpen(w.trstatus))
98
+ verdict.push(`${wanted} no está modificable (${describeOrder(w)}).`);
99
+ else {
100
+ const warn = await transportWarnings(sap, wanted, system.user);
101
+ verdict.push(warn.length ? `${wanted} está abierta, pero: ${warn.join(" ")}` : `${wanted} está abierta, con destino y es tuya: se puede usar.`);
102
+ }
103
+ }
104
+ }
105
+ for (const m of info?.MESSAGES ?? [])
106
+ verdict.push(`CTS ${m.SEVERITY}: ${m.TEXT}`);
107
+ if (!sid)
108
+ verdict.push(`(No se pudo determinar el SID del sistema para comprobar reparaciones: añade "sid" en systems.json.)`);
109
+ return [...out, "", "Veredicto:", ...verdict.map((v) => " • " + v)].join("\n");
110
+ },
111
+ });
112
+ //# sourceMappingURL=edit_preflight.js.map
@@ -0,0 +1,75 @@
1
+ import { z } from "zod";
2
+ import { ToolError } from "../../core/errors.js";
3
+ import { sqlLiteral } from "../../core/objects.js";
4
+ import { tsv } from "../../core/output.js";
5
+ import { defineTool } from "../../core/tool.js";
6
+ const NAME_RE = /^(\/[A-Z0-9_]+\/)?[A-Z0-9_]+$/;
7
+ /** ISO 639-1 → clave de idioma SAP de un carácter (SPRAS). No es la inicial: español es S, alemán D. */
8
+ const SPRAS = {
9
+ EN: "E", DE: "D", ES: "S", FR: "F", IT: "I", PT: "P", NL: "N", JA: "J", ZH: "1", RU: "R", PL: "L", CS: "C",
10
+ SV: "V", DA: "K", FI: "U", NO: "O", TR: "T", KO: "3", HU: "H", SK: "Q", EL: "G", AR: "A", HE: "B",
11
+ };
12
+ export const sapLanguage = (iso) => SPRAS[iso.trim().toUpperCase()] ?? iso.trim().toUpperCase().slice(0, 1);
13
+ const MODE = { R: "RFC", "": "normal", K: "update (V1)", L: "update (V2)", X: "update" };
14
+ export default defineTool({
15
+ name: "function_modules",
16
+ title: "Módulos de función de un grupo",
17
+ description: "Lista los módulos de función de un grupo de funciones, con su texto y si son RFC o de actualización. Con function " +
18
+ "en vez de group, dice a qué grupo pertenece ese módulo y lista sus hermanos. Úsala cuando un nombre resulte ser un " +
19
+ "grupo y no un módulo, o antes de remediar un grupo entero. Funciona con namespaces (/XXX/).",
20
+ access: "read",
21
+ input: {
22
+ group: z.string().optional().describe("Grupo de funciones, p. ej. ZDEMO_GROUP"),
23
+ function: z.string().optional().describe("En vez del grupo: un módulo; se busca su grupo"),
24
+ },
25
+ async run({ group, function: fm }, { sap, system }) {
26
+ if (!group && !fm)
27
+ throw new ToolError("INPUT", "Indica group o function.");
28
+ let area = group?.trim().toUpperCase();
29
+ let via = "";
30
+ if (!area) {
31
+ const f = fm.trim().toUpperCase();
32
+ if (!NAME_RE.test(f))
33
+ throw new ToolError("INPUT", `Nombre de módulo inválido: ${fm}`);
34
+ const r = await sap.query(`SELECT area FROM enlfdir WHERE funcname = ${sqlLiteral(f)}`, 1);
35
+ area = r.values[0]?.AREA?.trim();
36
+ if (!area)
37
+ throw new ToolError("NOT_FOUND", `No existe el módulo de función ${f}.`, "Si es un grupo, llama con group.");
38
+ via = ` (grupo de ${f})`;
39
+ }
40
+ if (!NAME_RE.test(area))
41
+ throw new ToolError("INPUT", `Nombre de grupo inválido: ${group}`);
42
+ const rows = await sap.query(`SELECT e~funcname, t~fmode FROM enlfdir AS e INNER JOIN tfdir AS t ON t~funcname = e~funcname ` +
43
+ `WHERE e~area = ${sqlLiteral(area)} ORDER BY e~funcname`, 2000);
44
+ if (!rows.values.length) {
45
+ const g = await sap.query(`SELECT obj_name FROM tadir WHERE pgmid = 'R3TR' AND object = 'FUGR' AND obj_name = ${sqlLiteral(area)}`, 1);
46
+ if (!g.values.length)
47
+ throw new ToolError("NOT_FOUND", `No existe el grupo de funciones ${area}.`, "Si es un módulo, llama con function.");
48
+ return `El grupo ${area} existe y no tiene módulos de función.`;
49
+ }
50
+ const names = rows.values.map((r) => String(r.FUNCNAME).trim());
51
+ const lang = sapLanguage(system.language);
52
+ // Idioma de la conexión, después inglés, después cualquiera.
53
+ const rank = (spras) => (spras === lang ? 2 : spras === "E" ? 1 : 0);
54
+ const texts = new Map();
55
+ for (let i = 0; i < names.length; i += 50) {
56
+ const part = names.slice(i, i + 50);
57
+ const t = await sap.query(`SELECT funcname, spras, stext FROM tftit WHERE funcname IN ( ${part.map(sqlLiteral).join(", ")} )`, part.length * 8);
58
+ for (const r of t.values) {
59
+ const k = String(r.FUNCNAME).trim();
60
+ const cur = texts.get(k);
61
+ if (!cur || rank(r.SPRAS) > cur.rank)
62
+ texts.set(k, { text: String(r.STEXT ?? ""), rank: rank(r.SPRAS) });
63
+ }
64
+ }
65
+ const gt = await sap.query(`SELECT spras, areat FROM tlibt WHERE area = ${sqlLiteral(area)}`, 20);
66
+ const groupText = String(gt.values.slice().sort((a, b) => rank(b.SPRAS) - rank(a.SPRAS))[0]?.AREAT ?? "");
67
+ const table = tsv(["módulo", "tipo", "texto"], rows.values.map((r) => {
68
+ const n = String(r.FUNCNAME).trim();
69
+ return [n, MODE[String(r.FMODE ?? "").trim()] ?? String(r.FMODE), texts.get(n)?.text ?? ""];
70
+ }));
71
+ const capped = rows.values.length >= 2000 ? "\n\nTope de 2000 módulos alcanzado: puede haber más." : "";
72
+ return `Grupo ${area}${via}${groupText ? ` «${groupText}»` : ""}: ${rows.values.length} módulos\n\n${table}${capped}`;
73
+ },
74
+ });
75
+ //# sourceMappingURL=function_modules.js.map
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import { htmlToText, parseAtom } from "../../core/feeds.js";
3
+ import { ToolError } from "../../core/errors.js";
4
+ import { budget } from "../../core/output.js";
5
+ import { defineTool } from "../../core/tool.js";
6
+ export default defineTool({
7
+ name: "gateway_errors",
8
+ title: "Errores de SAP Gateway (/IWFND/ERROR_LOG)",
9
+ description: "Lista los errores del log de SAP Gateway (servicios OData): servicio, error, usuario, fecha. Con detail=N trae el " +
10
+ "detalle (contexto, excepción, dónde saltó). Útil para fallos de DPC_EXT y apps Fiori. Solo existe en el sistema " +
11
+ "que hospeda Gateway (en landscapes con Fiori separado, el servidor Fiori).",
12
+ access: "read",
13
+ requires: { adt: ["/sap/bc/adt/gw/errorlog"] },
14
+ input: {
15
+ user: z.string().optional(),
16
+ max: z.number().int().min(1).max(200).default(20),
17
+ detail: z.number().int().min(1).optional().describe("Número de la lista para ver el detalle"),
18
+ },
19
+ async run({ user, max, detail }, { sap }) {
20
+ const c = await sap.adt();
21
+ const qs = { $top: String(Math.max(max, detail ?? 0)) };
22
+ if (user)
23
+ qs.$query = `and( equals( username, ${user.toUpperCase()} ) )`;
24
+ const r = await c.httpClient.request("/sap/bc/adt/gw/errorlog", { headers: { Accept: "application/atom+xml;type=feed" }, qs });
25
+ const entries = parseAtom(String(r.body));
26
+ if (!entries.length)
27
+ return `El log de errores de Gateway se leyó y no tiene entradas${user ? ` de ${user}` : ""}.`;
28
+ if (detail) {
29
+ const e = entries[detail - 1];
30
+ if (!e)
31
+ throw new ToolError("INPUT", `Solo hay ${entries.length} entradas.`);
32
+ const href = e.links.find((l) => /errorlog\//.test(l.href))?.href.replace(/^adt:\/\/[^/]+/, "");
33
+ if (!href)
34
+ return `${e.title}\n${e.summary}`;
35
+ const d = await c.httpClient.request(href, { headers: { Accept: "text/html, application/xhtml+xml, application/xml;q=0.5" } });
36
+ return budget(`${e.title} · ${e.updated} · ${e.author}\n\n${htmlToText(String(d.body))}`);
37
+ }
38
+ return (`${entries.length} errores (los más recientes)\n\n` +
39
+ entries
40
+ .slice(0, max)
41
+ .map((e, i) => `${i + 1}. ${e.updated.replace("T", " ").slice(0, 19)} · ${e.author || "?"} · ${e.categories.map((x) => x.label || x.term).join(", ")}\n ${e.title}`)
42
+ .join("\n"));
43
+ },
44
+ });
45
+ //# sourceMappingURL=gateway_errors.js.map
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ import { normalizeError, ToolError } from "../../core/errors.js";
3
+ import { CLASS_INCLUDES, resolveObject, sourceUrl, sqlLiteral, TYPE_HELP } from "../../core/objects.js";
4
+ import { numbered, tsv } from "../../core/output.js";
5
+ import { defineTool } from "../../core/tool.js";
6
+ const MAX_LINES = 2500;
7
+ /** Campos de una tabla/estructura desde DD03L: funciona en cualquier release. */
8
+ async function ddicFields(sap, name) {
9
+ const r = await sap.query(`SELECT fieldname, position, keyflag, rollname, datatype, leng, decimals, comptype, precfield, checktable ` +
10
+ `FROM dd03l WHERE tabname = ${sqlLiteral(name)} AND as4local = 'A' ORDER BY position`, 2000);
11
+ if (!r.values.length)
12
+ throw new ToolError("NOT_FOUND", `${name} no tiene campos activos en DD03L.`);
13
+ const cols = ["FIELDNAME", "KEYFLAG", "ROLLNAME", "DATATYPE", "LENG", "DECIMALS", "COMPTYPE", "PRECFIELD", "CHECKTABLE"];
14
+ return (`Este release no da fuente ADT para ${name}; campos leídos de DD03L (versión activa):\n\n` +
15
+ tsv(cols.map((c) => c.toLowerCase()), r.values.map((v) => cols.map((c) => v[c]))));
16
+ }
17
+ export default defineTool({
18
+ name: "get_source",
19
+ title: "Leer fuente ABAP",
20
+ description: "Lee la fuente de cualquier objeto: programa, include, clase (y sus includes), interfaz, módulo de función " +
21
+ "(sin necesidad de saber el grupo), CDS, tabla/estructura, etc. Admite rango de líneas para objetos grandes. " +
22
+ "Para tablas en releases sin fuente ADT (7.50) devuelve los campos desde DD03L.",
23
+ access: "read",
24
+ input: {
25
+ object_name: z.string().min(1),
26
+ object_type: z.string().optional().describe(TYPE_HELP + " Si se omite y el nombre es único, se deduce."),
27
+ include: z.enum(CLASS_INCLUDES).default("main").describe("Solo clases: main (clase completa), definitions, implementations, macros, testclasses"),
28
+ version: z.enum(["active", "inactive"]).default("active").describe("inactive = lo último guardado aunque no esté activado"),
29
+ start_line: z.number().int().min(1).optional(),
30
+ line_count: z.number().int().min(1).max(MAX_LINES).optional(),
31
+ },
32
+ async run({ object_name, object_type, include, version, start_line, line_count }, { sap }) {
33
+ const c = await sap.adt();
34
+ const obj = await resolveObject(c, object_name, object_type);
35
+ let source;
36
+ try {
37
+ const url = await sourceUrl(c, obj, include);
38
+ source = await c.getObjectSource(url, { version });
39
+ }
40
+ catch (e) {
41
+ const te = normalizeError(e);
42
+ if (te.kind !== "NOT_FOUND" && te.kind !== "SAP")
43
+ throw te;
44
+ if (!obj.type.startsWith("TABL"))
45
+ throw te;
46
+ // 7.50: las tablas no tienen fuente ADT; primero la vía de estructuras, luego DD03L.
47
+ try {
48
+ source = await c.getObjectSource(`/sap/bc/adt/ddic/structures/${encodeURIComponent(obj.name.toLowerCase())}/source/main`);
49
+ }
50
+ catch {
51
+ return ddicFields(sap, obj.name);
52
+ }
53
+ }
54
+ const all = source.replace(/\r\n/g, "\n").split("\n");
55
+ const head = `${obj.name} (${obj.type})${obj.packageName ? ` · paquete ${obj.packageName}` : ""} · ${all.length} líneas · ${version}`;
56
+ if (start_line || line_count) {
57
+ const from = start_line ?? 1;
58
+ const slice = all.slice(from - 1, from - 1 + (line_count ?? MAX_LINES));
59
+ if (!slice.length)
60
+ return `${head}\n\nEl objeto tiene ${all.length} líneas: no hay nada desde la ${from}.`;
61
+ return `${head}\nLíneas ${from}-${from + slice.length - 1}:\n\n${numbered(slice, from)}`;
62
+ }
63
+ if (all.length > MAX_LINES) {
64
+ return (`${head}\n\n${all.slice(0, MAX_LINES).join("\n")}\n\n` +
65
+ `[… recortado: se muestran ${MAX_LINES} de ${all.length} líneas. Pide start_line=${MAX_LINES + 1} para seguir.]`);
66
+ }
67
+ return `${head}\n\n${all.join("\n")}`;
68
+ },
69
+ });
70
+ //# sourceMappingURL=get_source.js.map
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import { defineTool } from "../../core/tool.js";
3
+ export default defineTool({
4
+ name: "inactive_objects",
5
+ title: "Objetos sin activar",
6
+ description: "Objetos con versión inactiva (guardados sin activar) del usuario de la conexión, con su orden. Revísalo antes de " +
7
+ "liberar: lo inactivo NO viaja en la orden. Con all_users=true incluye los de otros usuarios.",
8
+ access: "read",
9
+ requires: { adt: ["/sap/bc/adt/activation/inactiveobjects"] },
10
+ input: { all_users: z.boolean().default(false) },
11
+ async run({ all_users }, { sap, system }) {
12
+ const c = await sap.adt();
13
+ const recs = await c.inactiveObjects();
14
+ const rows = recs
15
+ .filter((r) => r.object)
16
+ .filter((r) => all_users || r.object.user?.toUpperCase() === system.user.toUpperCase());
17
+ if (!rows.length)
18
+ return `No hay objetos inactivos${all_users ? "" : ` de ${system.user}`}.`;
19
+ const byOrder = new Map();
20
+ for (const r of rows) {
21
+ const o = r.object;
22
+ const key = r.transport?.["adtcore:name"] ?? "(sin orden: local o sin registrar)";
23
+ byOrder.set(key, [...(byOrder.get(key) ?? []), `${o["adtcore:name"]} (${o["adtcore:type"]})${all_users ? ` · ${o.user}` : ""}${o.deleted ? " · BORRADO pendiente" : ""}`]);
24
+ }
25
+ return (`${rows.length} objetos inactivos${all_users ? "" : ` de ${system.user}`}:\n` +
26
+ [...byOrder].map(([t, list]) => `\n${t}\n ${list.join("\n ")}`).join("\n"));
27
+ },
28
+ });
29
+ //# sourceMappingURL=inactive_objects.js.map
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { ToolError } from "../../core/errors.js";
3
+ import { CLASS_INCLUDES, resolveObject, TYPE_HELP } from "../../core/objects.js";
4
+ import { budget } from "../../core/output.js";
5
+ import { defineTool } from "../../core/tool.js";
6
+ export default defineTool({
7
+ name: "object_versions",
8
+ title: "Versiones de un objeto",
9
+ description: "Historial de versiones de un objeto (fecha, autor, orden). Con show=N devuelve la fuente de la versión N de la " +
10
+ "lista (1 = la más reciente), para comparar con la actual.",
11
+ access: "read",
12
+ input: {
13
+ object_name: z.string().min(1),
14
+ object_type: z.string().optional().describe(TYPE_HELP),
15
+ include: z.enum(CLASS_INCLUDES).default("main"),
16
+ show: z.number().int().min(1).optional(),
17
+ },
18
+ async run({ object_name, object_type, include, show }, { sap }) {
19
+ const c = await sap.adt();
20
+ const obj = await resolveObject(c, object_name, object_type);
21
+ const revs = await c.revisions(obj.uri, obj.type.startsWith("CLAS") ? include : undefined);
22
+ if (!revs.length)
23
+ return `${obj.name}: SAP no devuelve versiones para este objeto.`;
24
+ if (show) {
25
+ const r = revs[show - 1];
26
+ if (!r)
27
+ throw new ToolError("INPUT", `Solo hay ${revs.length} versiones.`);
28
+ const src = await c.getObjectSource(r.uri);
29
+ return budget(`${obj.name} · versión ${r.version} · ${r.date} · ${r.author} · ${r.versionTitle}\n\n${src}`);
30
+ }
31
+ return (`${obj.name}: ${revs.length} versiones\n\n` +
32
+ revs.map((r, i) => ` ${i + 1}. ${r.date} ${r.author} ${r.version} ${r.versionTitle}`).join("\n"));
33
+ },
34
+ });
35
+ //# sourceMappingURL=object_versions.js.map
@@ -0,0 +1,59 @@
1
+ import { z } from "zod";
2
+ import { ToolError } from "../../core/errors.js";
3
+ import { sqlLiteral } from "../../core/objects.js";
4
+ import { defineTool } from "../../core/tool.js";
5
+ export default defineTool({
6
+ name: "package_contents",
7
+ title: "Contenido de un paquete",
8
+ description: "Objetos de un paquete de desarrollo agrupados por tipo, con sus subpaquetes (TADIR/TDEVC, cualquier release). " +
9
+ "Con recursive=true incluye los subpaquetes. Dice también si el paquete viaja (conexión al CTS).",
10
+ access: "read",
11
+ input: {
12
+ package: z.string().min(1),
13
+ recursive: z.boolean().default(false),
14
+ object_type: z.string().optional().describe("Filtrar por tipo TADIR: PROG, CLAS, FUGR, TABL, DDLS…"),
15
+ max_objects: z.number().int().min(1).max(5000).default(500),
16
+ },
17
+ async run({ package: pkg, recursive, object_type, max_objects }, { sap }) {
18
+ const root = pkg.trim().toUpperCase();
19
+ const head = await sap.query(`SELECT devclass, parentcl, korrflag, dlvunit, pdevclass FROM tdevc WHERE devclass = ${sqlLiteral(root)}`, 1);
20
+ const h = head.values[0];
21
+ if (!h)
22
+ throw new ToolError("NOT_FOUND", `El paquete ${root} no existe en este sistema.`);
23
+ const txt = await sap.query(`SELECT ctext FROM tdevct WHERE devclass = ${sqlLiteral(root)}`, 1);
24
+ // Árbol de subpaquetes (hasta 5 niveles si es recursivo).
25
+ const packages = [root];
26
+ const subs = [];
27
+ let frontier = [root];
28
+ for (let level = 0; level < (recursive ? 5 : 1) && frontier.length; level++) {
29
+ const r = await sap.query(`SELECT devclass FROM tdevc WHERE parentcl IN ( ${frontier.map(sqlLiteral).join(", ")} )`, 2000);
30
+ frontier = r.values.map((v) => v.DEVCLASS);
31
+ subs.push(...frontier);
32
+ if (recursive)
33
+ packages.push(...frontier);
34
+ }
35
+ const typeFilter = object_type ? ` AND object = ${sqlLiteral(object_type.toUpperCase())}` : "";
36
+ const objs = await sap.query(`SELECT devclass, object, obj_name FROM tadir WHERE pgmid = 'R3TR' AND devclass IN ( ${packages.map(sqlLiteral).join(", ")} )` +
37
+ `${typeFilter} AND object <> 'DEVC' ORDER BY object, obj_name`, max_objects);
38
+ const byType = new Map();
39
+ for (const o of objs.values) {
40
+ const label = recursive && o.DEVCLASS !== root ? `${o.OBJ_NAME} [${o.DEVCLASS}]` : o.OBJ_NAME;
41
+ byType.set(o.OBJECT, [...(byType.get(o.OBJECT) ?? []), label]);
42
+ }
43
+ const lines = [
44
+ `${root} «${txt.values[0]?.CTEXT ?? ""}»` +
45
+ (h.PARENTCL ? ` · dentro de ${h.PARENTCL}` : "") +
46
+ ` · ${h.KORRFLAG === "X" ? "transportable" : "local/sin CTS: sus objetos NO viajan"}` +
47
+ (h.DLVUNIT ? ` · componente ${h.DLVUNIT}` : ""),
48
+ ];
49
+ if (subs.length)
50
+ lines.push(`Subpaquetes${recursive ? "" : " (directos)"}: ${subs.join(", ")}`);
51
+ lines.push("", `${objs.values.length} objetos${objs.values.length >= max_objects ? ` (tope de ${max_objects} alcanzado)` : ""}:`);
52
+ for (const [type, names] of byType)
53
+ lines.push(` ${type} (${names.length}): ${names.join(", ")}`);
54
+ if (!byType.size)
55
+ lines.push(" (sin objetos)");
56
+ return lines.join("\n");
57
+ },
58
+ });
59
+ //# sourceMappingURL=package_contents.js.map