@patimweb/pi-abap-fs 1.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.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * abap_where_used tool — Find where an ABAP object is used.
3
+ */
4
+
5
+ import { Type } from "typebox";
6
+ import { resolveObject, withClient } from "../client.ts";
7
+ import { resolveConfig } from "../config.ts";
8
+ import { formatUsageReferences } from "../formatting/formatters.ts";
9
+
10
+ export const AbapWhereUsedTool = {
11
+ name: "abap_where_used",
12
+ label: "Find ABAP Object Usage",
13
+ description:
14
+ "Find where an ABAP object is referenced (where-used list). Provide the object name or URL. Returns the list of referencing objects.",
15
+ parameters: Type.Object({
16
+ profile: Type.Optional(
17
+ Type.String({
18
+ description: "Profile name to use. Uses active profile if omitted.",
19
+ }),
20
+ ),
21
+ name: Type.Optional(
22
+ Type.String({ description: "Object name, e.g. 'ZCL_MY_CLASS'" }),
23
+ ),
24
+ objectUrl: Type.Optional(
25
+ Type.String({
26
+ description:
27
+ "Object URL, e.g. '/sap/bc/adt/oo/classes/zcl_my_class'. Takes precedence over name.",
28
+ }),
29
+ ),
30
+ type: Type.Optional(
31
+ Type.String({ description: "Optional type hint for search, e.g. 'CLAS/OC'." }),
32
+ ),
33
+ }),
34
+
35
+ async execute(
36
+ _toolCallId: string,
37
+ params: { profile?: string; name?: string; objectUrl?: string; type?: string },
38
+ _signal: AbortSignal,
39
+ ) {
40
+ const config = resolveConfig(params.profile);
41
+ const { resolved, references } = await withClient(config, async (client) => {
42
+ const resolved = await resolveObject(client, params);
43
+ const references = await client.usageReferences(resolved.mainInclude);
44
+ return { resolved, references };
45
+ });
46
+
47
+ return {
48
+ content: [
49
+ {
50
+ type: "text" as const,
51
+ text: formatUsageReferences(references),
52
+ },
53
+ ],
54
+ details: {
55
+ name: resolved.name,
56
+ count: references.length,
57
+ references: references.map((r) => ({
58
+ name: r["adtcore:name"] || r.objectIdentifier,
59
+ type: r["adtcore:type"],
60
+ description: r["adtcore:description"],
61
+ })),
62
+ },
63
+ };
64
+ },
65
+ };
@@ -0,0 +1,120 @@
1
+ /**
2
+ * abap_write tool — Write source code to an ABAP object.
3
+ *
4
+ * Self-contained: resolves the object, locks it, writes the source, then
5
+ * unlocks it. If a lockHandle is provided, the caller owns the lock and it
6
+ * is NOT unlocked automatically.
7
+ */
8
+
9
+ import { Type } from "typebox";
10
+ import { resolveObject, withClient } from "../client.ts";
11
+ import { resolveConfig } from "../config.ts";
12
+
13
+ export const AbapWriteTool = {
14
+ name: "abap_write",
15
+ label: "Write ABAP Source",
16
+ description:
17
+ "Write (save) source code to an ABAP object. Provide the object (name or objectUrl), the full new source, and optionally a transport request number. If a lockHandle from a previous abap_lock call is provided, that lock is used and left in place; otherwise the object is locked, written, and unlocked automatically. Use abap_activate afterwards to activate the change.",
18
+ parameters: Type.Object({
19
+ profile: Type.Optional(
20
+ Type.String({
21
+ description: "Profile name to use. Uses active profile if omitted.",
22
+ }),
23
+ ),
24
+ name: Type.Optional(
25
+ Type.String({ description: "Object name, e.g. 'ZCL_MY_CLASS'" }),
26
+ ),
27
+ objectUrl: Type.Optional(
28
+ Type.String({
29
+ description:
30
+ "Object URL, e.g. '/sap/bc/adt/oo/classes/zcl_my_class'. Takes precedence over name.",
31
+ }),
32
+ ),
33
+ type: Type.Optional(
34
+ Type.String({ description: "Optional type hint for search, e.g. 'CLAS/OC'." }),
35
+ ),
36
+ source: Type.String({
37
+ description: "The full new source code to write.",
38
+ }),
39
+ transport: Type.Optional(
40
+ Type.String({
41
+ description:
42
+ "Transport / correction request number, e.g. 'DEVK900123'. Only used when a new lock is taken.",
43
+ }),
44
+ ),
45
+ lockHandle: Type.Optional(
46
+ Type.String({
47
+ description:
48
+ "Optional lock handle from a previous abap_lock call. When provided, the lock is used and NOT released automatically.",
49
+ }),
50
+ ),
51
+ }),
52
+
53
+ async execute(
54
+ _toolCallId: string,
55
+ params: {
56
+ profile?: string;
57
+ name?: string;
58
+ objectUrl?: string;
59
+ type?: string;
60
+ source: string;
61
+ transport?: string;
62
+ lockHandle?: string;
63
+ },
64
+ _signal: AbortSignal,
65
+ ) {
66
+ const config = resolveConfig(params.profile);
67
+
68
+ const result = await withClient(
69
+ config,
70
+ async (client) => {
71
+ const resolved = await resolveObject(client, params);
72
+
73
+ let lockHandle = params.lockHandle;
74
+ let selfLocked = false;
75
+ if (!lockHandle) {
76
+ const lock = await client.lock(resolved.objectUrl, "MODIFY");
77
+ lockHandle = lock.LOCK_HANDLE;
78
+ selfLocked = true;
79
+ }
80
+
81
+ try {
82
+ await client.setObjectSource(
83
+ resolved.mainInclude,
84
+ params.source,
85
+ lockHandle,
86
+ params.transport,
87
+ );
88
+ } finally {
89
+ if (selfLocked) {
90
+ await client.unLock(resolved.objectUrl, lockHandle);
91
+ }
92
+ }
93
+
94
+ return { resolved, selfLocked };
95
+ },
96
+ { stateful: true },
97
+ );
98
+
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text" as const,
103
+ text:
104
+ `Source written to ${result.resolved.name}. ` +
105
+ (result.selfLocked
106
+ ? "Lock released automatically."
107
+ : "Lock left in place (caller owns it).") +
108
+ " Use abap_activate to activate the change.",
109
+ },
110
+ ],
111
+ details: {
112
+ name: result.resolved.name,
113
+ type: result.resolved.type,
114
+ objectUrl: result.resolved.objectUrl,
115
+ lines: params.source.split("\n").length,
116
+ lockReleased: result.selfLocked,
117
+ },
118
+ };
119
+ },
120
+ };
package/src/types.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Data types for the pi ABAP FS extension.
3
+ *
4
+ * All domain data is represented as plain immutable-shaped interfaces.
5
+ * No behavior — just data.
6
+ */
7
+
8
+ // Configuration
9
+
10
+ export interface AbapProfile {
11
+ /** ADT base URL, e.g. http://vhcalnplci.bti.local:8000 */
12
+ readonly url: string;
13
+ /** SAP logon user (e.g. DEVELOPER) */
14
+ readonly username: string;
15
+ /** Password (stored in ~/.pi/abap-config.json with 0600 permissions) */
16
+ readonly password: string;
17
+ /** SAP client / mandant, e.g. "001" */
18
+ readonly client?: string;
19
+ /** Logon language key, e.g. "EN" */
20
+ readonly language?: string;
21
+ /** Accept self-signed TLS certificates (on-premise dev systems). Defaults to false. */
22
+ readonly allowUnauthorized?: boolean;
23
+ }
24
+
25
+ export interface AbapProfiles {
26
+ readonly profiles: Record<string, AbapProfile>;
27
+ readonly activeProfile: string | null;
28
+ }
29
+
30
+ // Operation errors
31
+
32
+ export class AbapNotConfiguredError extends Error {
33
+ constructor() {
34
+ super(
35
+ "No SAP connection configured. Use the abap_setup tool first.",
36
+ );
37
+ this.name = "AbapNotConfiguredError";
38
+ }
39
+ }
40
+
41
+ export class AbapObjectNotFoundError extends Error {
42
+ constructor(name: string) {
43
+ super(`No ABAP object found matching "${name}".`);
44
+ this.name = "AbapObjectNotFoundError";
45
+ }
46
+ }
47
+
48
+ export class AbapProfileNotFoundError extends Error {
49
+ constructor(name: string, available: ReadonlyArray<string>) {
50
+ super(
51
+ `Profile "${name}" not found. Available: ${available.join(", ") || "none"}`,
52
+ );
53
+ this.name = "AbapProfileNotFoundError";
54
+ }
55
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "types": ["node"],
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "esModuleInterop": true,
14
+ "forceConsistentCasingInFileNames": true
15
+ },
16
+ "include": ["index.ts", "src/**/*.ts", "tests/**/*.ts"]
17
+ }