@babamba2/mcp-abap-connection 1.5.3 → 1.6.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/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
- package/dist/connection/JwtAbapConnection.d.ts.map +1 -1
- package/dist/connection/RfcAbapConnection.d.ts +7 -5
- package/dist/connection/RfcAbapConnection.d.ts.map +1 -1
- package/dist/connection/RfcAbapConnection.js +21 -25
- package/dist/connection/nco/NcoRfcClient.d.ts +58 -0
- package/dist/connection/nco/NcoRfcClient.d.ts.map +1 -0
- package/dist/connection/nco/NcoRfcClient.js +205 -0
- package/dist/connection/nco/buildBridge.d.ts +25 -0
- package/dist/connection/nco/buildBridge.d.ts.map +1 -0
- package/dist/connection/nco/buildBridge.js +160 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/nco/NcoBridge.cs +275 -0
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,YAAY,EAElB,MAAM,mCAAmC,CAAC;AAM3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9E,uBAAe,sBAAuB,YAAW,cAAc;IAW3D,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAX3C,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,eAAe,CAAU;IAEjC,SAAS,aACU,MAAM,EAAE,SAAS,EACf,MAAM,EAAE,OAAO,GAAG,IAAI,EACzC,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE;IAqBzC;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAUpD;;OAEG;IACH,cAAc,IAAI,WAAW,GAAG,UAAU;IAI1C;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKrC;;OAEG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,SAAS,IAAI,SAAS;IAItB,KAAK,IAAI,IAAI;IAYP,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAevD;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAE3B,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IA4P9B,SAAS,CAAC,QAAQ,CAAC,wBAAwB,IAAI,MAAM;IAErD;;;OAGG;cACa,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAgC,EAC5C,UAAU,GAAE,MAAgC,GAC3C,OAAO,CAAC,MAAM,CAAC;IA2ClB;;OAEG;YACW,0BAA0B;IAsKxC;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIlD;;OAEG;IACH,SAAS,CAAC,UAAU,IAAI,MAAM,GAAG,IAAI;IAIrC,SAAS,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIlD,OAAO,CAAC,yBAAyB;IAkEjC,OAAO,CAAC,gBAAgB;YAqBV,oBAAoB;IAiClC,OAAO,CAAC,eAAe;CA+BxB;AAGD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JwtAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/JwtAbapConnection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"JwtAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/JwtAbapConnection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,sBAAsB;IAC3D,OAAO,CAAC,cAAc,CAAC,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAS;gBAG3B,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,eAAe;IAWlC,SAAS,CAAC,wBAAwB,IAAI,MAAM;IAW5C;;;OAGG;YACW,eAAe;IA0B7B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAyD9B;;OAEG;IACG,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAyD9B;;OAEG;cACa,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,SAAI,EACd,UAAU,SAAO,GAChB,OAAO,CAAC,MAAM,CAAC;IA2ClB,OAAO,CAAC,MAAM,CAAC,cAAc;CAa9B"}
|
|
@@ -5,8 +5,9 @@ import type { AbapConnection } from './AbapConnection.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* RFC-based connection for on-premise SAP systems.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Calls SADT_REST_RFC_ENDPOINT — the same standard SAP FM that Eclipse ADT
|
|
9
|
+
* uses for on-premise ADT operations via JCo — through SAP .NET Connector
|
|
10
|
+
* (nco-bridge.exe, see ./nco). The node-rfc driver was removed in 1.6.0.
|
|
10
11
|
*
|
|
11
12
|
* RFC connections are inherently stateful: one ABAP session persists for the
|
|
12
13
|
* entire connection lifetime. This solves the HTTP 423 "invalid lock handle"
|
|
@@ -16,9 +17,10 @@ import type { AbapConnection } from './AbapConnection.js';
|
|
|
16
17
|
* Connection parameters are derived from the standard ISapConfig.url field:
|
|
17
18
|
* http://saphost:8000 → ashost=saphost, sysnr=00
|
|
18
19
|
*
|
|
19
|
-
* Prerequisites:
|
|
20
|
-
* - SAP
|
|
21
|
-
* -
|
|
20
|
+
* Prerequisites (Windows only):
|
|
21
|
+
* - SAP .NET Connector 3.x for .NET Framework 4.x in the GAC
|
|
22
|
+
* - .NET Framework 4.x csc.exe (present on every Windows install) — the
|
|
23
|
+
* bridge is compiled on first use
|
|
22
24
|
*/
|
|
23
25
|
export declare class RfcAbapConnection implements AbapConnection {
|
|
24
26
|
private readonly config;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RfcAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/RfcAbapConnection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"RfcAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/RfcAbapConnection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,YAAY,EACb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA8F1D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IASpD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IATzB,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,aAAa,CAAuB;gBAGzB,MAAM,EAAE,SAAS,EACjB,MAAM,GAAE,OAAO,GAAG,IAAW;IAa1C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAexB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAInC,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAU9C,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAgM9B;;;OAGG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B,OAAO,CAAC,MAAM,CAAC,cAAc;CAqB9B"}
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RfcAbapConnection = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const NcoRfcClient_js_1 = require("./nco/NcoRfcClient.js");
|
|
5
6
|
/**
|
|
6
7
|
* Derive RFC connection parameters from ISapConfig.
|
|
7
8
|
* Parses hostname from config.url, system number from port (80XX → XX).
|
|
9
|
+
* SAP_NCO_ASHOST / SAP_NCO_SYSNR / SAP_NCO_LANG override the derived values
|
|
10
|
+
* (an HTTP port that does not follow 80XX, or a web dispatcher host).
|
|
8
11
|
*
|
|
9
12
|
* Examples:
|
|
10
13
|
* http://saphost:8000 → ashost=saphost, sysnr=00
|
|
@@ -16,13 +19,14 @@ function buildRfcParams(config) {
|
|
|
16
19
|
const port = Number.parseInt(parsed.port || '8000', 10);
|
|
17
20
|
// SAP HTTP port convention: 80XX where XX = system number
|
|
18
21
|
const sysnr = String(port - 8000).padStart(2, '0');
|
|
22
|
+
const env = process.env;
|
|
19
23
|
return {
|
|
20
|
-
ashost: parsed.hostname,
|
|
21
|
-
sysnr,
|
|
24
|
+
ashost: env.SAP_NCO_ASHOST?.trim() || parsed.hostname,
|
|
25
|
+
sysnr: env.SAP_NCO_SYSNR?.trim() || sysnr,
|
|
22
26
|
client: config.client || '000',
|
|
23
27
|
user: config.username || '',
|
|
24
28
|
passwd: config.password || '',
|
|
25
|
-
lang: 'EN',
|
|
29
|
+
lang: env.SAP_NCO_LANG?.trim() || env.SAP_LANGUAGE?.trim() || 'EN',
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
32
|
/**
|
|
@@ -59,8 +63,9 @@ function detectExceptionStatus(body) {
|
|
|
59
63
|
/**
|
|
60
64
|
* RFC-based connection for on-premise SAP systems.
|
|
61
65
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
66
|
+
* Calls SADT_REST_RFC_ENDPOINT — the same standard SAP FM that Eclipse ADT
|
|
67
|
+
* uses for on-premise ADT operations via JCo — through SAP .NET Connector
|
|
68
|
+
* (nco-bridge.exe, see ./nco). The node-rfc driver was removed in 1.6.0.
|
|
64
69
|
*
|
|
65
70
|
* RFC connections are inherently stateful: one ABAP session persists for the
|
|
66
71
|
* entire connection lifetime. This solves the HTTP 423 "invalid lock handle"
|
|
@@ -70,9 +75,10 @@ function detectExceptionStatus(body) {
|
|
|
70
75
|
* Connection parameters are derived from the standard ISapConfig.url field:
|
|
71
76
|
* http://saphost:8000 → ashost=saphost, sysnr=00
|
|
72
77
|
*
|
|
73
|
-
* Prerequisites:
|
|
74
|
-
* - SAP
|
|
75
|
-
* -
|
|
78
|
+
* Prerequisites (Windows only):
|
|
79
|
+
* - SAP .NET Connector 3.x for .NET Framework 4.x in the GAC
|
|
80
|
+
* - .NET Framework 4.x csc.exe (present on every Windows install) — the
|
|
81
|
+
* bridge is compiled on first use
|
|
76
82
|
*/
|
|
77
83
|
class RfcAbapConnection {
|
|
78
84
|
config;
|
|
@@ -93,21 +99,9 @@ class RfcAbapConnection {
|
|
|
93
99
|
this.logger?.debug(`RfcAbapConnection created for ${this.rfcParams.ashost}:${this.rfcParams.sysnr}, client ${this.rfcParams.client}`);
|
|
94
100
|
}
|
|
95
101
|
async connect() {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Users who need RFC connections must install it manually:
|
|
100
|
-
// npm install node-rfc (+ SAP NW RFC SDK on the machine)
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
102
|
-
const noderfc = require('node-rfc');
|
|
103
|
-
Client = noderfc.Client;
|
|
104
|
-
}
|
|
105
|
-
catch (e) {
|
|
106
|
-
throw new Error('node-rfc is not available. To use RFC connections, install SAP NW RFC SDK ' +
|
|
107
|
-
'and run: npm install node-rfc. ' +
|
|
108
|
-
`Details: ${e instanceof Error ? e.message : String(e)}`);
|
|
109
|
-
}
|
|
110
|
-
this.rfcClient = new Client(this.rfcParams);
|
|
102
|
+
if (this.rfcClient?.alive)
|
|
103
|
+
return;
|
|
104
|
+
this.rfcClient = new NcoRfcClient_js_1.NcoRfcClient(this.rfcParams);
|
|
111
105
|
try {
|
|
112
106
|
await this.rfcClient.open();
|
|
113
107
|
this.logger?.debug('RFC connection opened (stateful session)');
|
|
@@ -137,8 +131,10 @@ class RfcAbapConnection {
|
|
|
137
131
|
}
|
|
138
132
|
async makeAdtRequest(options) {
|
|
139
133
|
if (!this.rfcClient?.alive) {
|
|
140
|
-
|
|
134
|
+
// Lazy open on first request (and reopen after the bridge died).
|
|
135
|
+
await this.connect();
|
|
141
136
|
}
|
|
137
|
+
const rfcClient = this.rfcClient;
|
|
142
138
|
const method = options.method.toUpperCase();
|
|
143
139
|
// Encode query params into URI (axios does this automatically for HTTP,
|
|
144
140
|
// but RFC needs it done manually)
|
|
@@ -188,7 +184,7 @@ class RfcAbapConnection {
|
|
|
188
184
|
if (body)
|
|
189
185
|
this.logger?.debug(`RFC → body: ${body}`);
|
|
190
186
|
try {
|
|
191
|
-
const result = await
|
|
187
|
+
const result = await rfcClient.call('SADT_REST_RFC_ENDPOINT', {
|
|
192
188
|
REQUEST: {
|
|
193
189
|
REQUEST_LINE: {
|
|
194
190
|
METHOD: method,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* node-rfc-shaped RFC client backed by nco-bridge.exe (SAP .NET Connector).
|
|
3
|
+
*
|
|
4
|
+
* Values follow node-rfc: a structure is an object, a table an array of
|
|
5
|
+
* objects, BYTE / XSTRING values are Buffers. One bridge process = one RFC
|
|
6
|
+
* connection in one stateful ABAP session; calls are answered in order.
|
|
7
|
+
*/
|
|
8
|
+
export interface NcoConnectionParams {
|
|
9
|
+
ashost: string;
|
|
10
|
+
sysnr: string;
|
|
11
|
+
client: string;
|
|
12
|
+
user: string;
|
|
13
|
+
passwd: string;
|
|
14
|
+
lang?: string;
|
|
15
|
+
saprouter?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface NcoSystemInfo {
|
|
18
|
+
sysId: string;
|
|
19
|
+
client: string;
|
|
20
|
+
user: string;
|
|
21
|
+
kernel: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class NcoRfcError extends Error {
|
|
24
|
+
readonly kind: string;
|
|
25
|
+
readonly key?: string | undefined;
|
|
26
|
+
readonly ncoName?: string | undefined;
|
|
27
|
+
constructor(message: string, kind: string, key?: string | undefined, ncoName?: string | undefined);
|
|
28
|
+
}
|
|
29
|
+
/** Buffer → {__b64}, recursively (request side). */
|
|
30
|
+
export declare function encodeValue(v: unknown): unknown;
|
|
31
|
+
/** {__b64} → Buffer, recursively (reply side). */
|
|
32
|
+
export declare function decodeValue(v: unknown): unknown;
|
|
33
|
+
export declare class NcoRfcClient {
|
|
34
|
+
private readonly params;
|
|
35
|
+
private readonly options;
|
|
36
|
+
private child;
|
|
37
|
+
private buffer;
|
|
38
|
+
private nextId;
|
|
39
|
+
private readonly pending;
|
|
40
|
+
private opened;
|
|
41
|
+
private exitError;
|
|
42
|
+
systemInfo: NcoSystemInfo | null;
|
|
43
|
+
constructor(params: NcoConnectionParams, options?: {
|
|
44
|
+
exe?: string;
|
|
45
|
+
args?: string[];
|
|
46
|
+
timeoutMs?: number;
|
|
47
|
+
});
|
|
48
|
+
get alive(): boolean;
|
|
49
|
+
open(): Promise<void>;
|
|
50
|
+
call(functionModule: string, params?: Record<string, unknown>): Promise<Record<string, any>>;
|
|
51
|
+
ping(): Promise<void>;
|
|
52
|
+
close(): Promise<void>;
|
|
53
|
+
private kill;
|
|
54
|
+
private request;
|
|
55
|
+
private onData;
|
|
56
|
+
private onLine;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=NcoRfcClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NcoRfcClient.d.ts","sourceRoot":"","sources":["../../../src/connection/nco/NcoRfcClient.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,WAAY,SAAQ,KAAK;IAGlC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM;gBAHzB,OAAO,EAAE,MAAM,EACN,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,YAAA,EACZ,OAAO,CAAC,EAAE,MAAM,YAAA;CAK5B;AAUD,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAW/C;AAED,kDAAkD;AAClD,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAiB/C;AAED,qBAAa,YAAY;IAUrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IACtD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAsB;IACvC,UAAU,EAAE,aAAa,GAAG,IAAI,CAAQ;gBAGrB,MAAM,EAAE,mBAAmB,EAC3B,OAAO,GAAE;QACxB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACf;IAGR,IAAI,KAAK,IAAI,OAAO,CAEnB;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA8CrB,IAAI,CACR,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAUzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5B,OAAO,CAAC,IAAI;IAmBZ,OAAO,CAAC,OAAO;IAuBf,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,MAAM;CAwBf"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NcoRfcClient = exports.NcoRfcError = void 0;
|
|
4
|
+
exports.encodeValue = encodeValue;
|
|
5
|
+
exports.decodeValue = decodeValue;
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const buildBridge_js_1 = require("./buildBridge.js");
|
|
8
|
+
class NcoRfcError extends Error {
|
|
9
|
+
kind;
|
|
10
|
+
key;
|
|
11
|
+
ncoName;
|
|
12
|
+
constructor(message, kind, key, ncoName) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.kind = kind;
|
|
15
|
+
this.key = key;
|
|
16
|
+
this.ncoName = ncoName;
|
|
17
|
+
this.name = 'NcoRfcError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.NcoRfcError = NcoRfcError;
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
22
|
+
/** Buffer → {__b64}, recursively (request side). */
|
|
23
|
+
function encodeValue(v) {
|
|
24
|
+
if (Buffer.isBuffer(v))
|
|
25
|
+
return { __b64: v.toString('base64') };
|
|
26
|
+
if (v instanceof Uint8Array)
|
|
27
|
+
return { __b64: Buffer.from(v).toString('base64') };
|
|
28
|
+
if (Array.isArray(v))
|
|
29
|
+
return v.map(encodeValue);
|
|
30
|
+
if (v && typeof v === 'object') {
|
|
31
|
+
const o = {};
|
|
32
|
+
for (const [k, x] of Object.entries(v))
|
|
33
|
+
o[k] = encodeValue(x);
|
|
34
|
+
return o;
|
|
35
|
+
}
|
|
36
|
+
return v;
|
|
37
|
+
}
|
|
38
|
+
/** {__b64} → Buffer, recursively (reply side). */
|
|
39
|
+
function decodeValue(v) {
|
|
40
|
+
if (Array.isArray(v))
|
|
41
|
+
return v.map(decodeValue);
|
|
42
|
+
if (v && typeof v === 'object') {
|
|
43
|
+
const o = v;
|
|
44
|
+
const keys = Object.keys(o);
|
|
45
|
+
if (keys.length === 1 &&
|
|
46
|
+
keys[0] === '__b64' &&
|
|
47
|
+
typeof o.__b64 === 'string') {
|
|
48
|
+
return Buffer.from(o.__b64, 'base64');
|
|
49
|
+
}
|
|
50
|
+
const out = {};
|
|
51
|
+
for (const [k, x] of Object.entries(o))
|
|
52
|
+
out[k] = decodeValue(x);
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
return v;
|
|
56
|
+
}
|
|
57
|
+
class NcoRfcClient {
|
|
58
|
+
params;
|
|
59
|
+
options;
|
|
60
|
+
child = null;
|
|
61
|
+
buffer = '';
|
|
62
|
+
nextId = 0;
|
|
63
|
+
pending = new Map();
|
|
64
|
+
opened = false;
|
|
65
|
+
exitError = null;
|
|
66
|
+
systemInfo = null;
|
|
67
|
+
constructor(params, options = {}) {
|
|
68
|
+
this.params = params;
|
|
69
|
+
this.options = options;
|
|
70
|
+
}
|
|
71
|
+
get alive() {
|
|
72
|
+
return this.opened && this.child !== null && this.exitError === null;
|
|
73
|
+
}
|
|
74
|
+
async open() {
|
|
75
|
+
if (this.alive)
|
|
76
|
+
return;
|
|
77
|
+
const exe = this.options.exe ?? (0, buildBridge_js_1.ensureNcoBridge)();
|
|
78
|
+
this.exitError = null;
|
|
79
|
+
const child = (0, node_child_process_1.spawn)(exe, this.options.args ?? [], {
|
|
80
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
81
|
+
windowsHide: true,
|
|
82
|
+
});
|
|
83
|
+
this.child = child;
|
|
84
|
+
let stderr = '';
|
|
85
|
+
child.stderr?.on('data', (d) => {
|
|
86
|
+
stderr = (stderr + d.toString()).slice(-2000);
|
|
87
|
+
});
|
|
88
|
+
child.stdout?.setEncoding('utf8');
|
|
89
|
+
child.stdout?.on('data', (d) => this.onData(d));
|
|
90
|
+
const fail = (err) => {
|
|
91
|
+
this.exitError = err;
|
|
92
|
+
this.opened = false;
|
|
93
|
+
this.child = null;
|
|
94
|
+
for (const [, p] of this.pending) {
|
|
95
|
+
clearTimeout(p.timer);
|
|
96
|
+
p.reject(err);
|
|
97
|
+
}
|
|
98
|
+
this.pending.clear();
|
|
99
|
+
};
|
|
100
|
+
child.on('error', (e) => fail(new Error(`nco-bridge failed to start: ${e.message}`)));
|
|
101
|
+
child.on('exit', (code) => fail(new Error(`nco-bridge exited (code ${code})${stderr ? `: ${stderr.trim()}` : ''}`)));
|
|
102
|
+
try {
|
|
103
|
+
this.systemInfo = await this.request('open', {
|
|
104
|
+
params: { lang: 'EN', saprouter: '', ...this.params },
|
|
105
|
+
});
|
|
106
|
+
this.opened = true;
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
this.kill();
|
|
110
|
+
throw e;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async call(functionModule, params = {}) {
|
|
114
|
+
if (!this.alive)
|
|
115
|
+
throw new Error('NCo connection is not open. Call open() first.');
|
|
116
|
+
const result = await this.request('call', {
|
|
117
|
+
fm: functionModule,
|
|
118
|
+
params: encodeValue(params),
|
|
119
|
+
});
|
|
120
|
+
return decodeValue(result);
|
|
121
|
+
}
|
|
122
|
+
async ping() {
|
|
123
|
+
await this.request('ping', {});
|
|
124
|
+
}
|
|
125
|
+
async close() {
|
|
126
|
+
if (!this.child)
|
|
127
|
+
return;
|
|
128
|
+
try {
|
|
129
|
+
if (this.opened)
|
|
130
|
+
await this.request('close', {}, 10_000);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
/* closing anyway */
|
|
134
|
+
}
|
|
135
|
+
this.kill();
|
|
136
|
+
}
|
|
137
|
+
kill() {
|
|
138
|
+
const c = this.child;
|
|
139
|
+
this.opened = false;
|
|
140
|
+
this.child = null;
|
|
141
|
+
if (c) {
|
|
142
|
+
c.removeAllListeners('exit');
|
|
143
|
+
try {
|
|
144
|
+
c.stdin?.end();
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
/* ignore */
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
c.kill();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
/* ignore */
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
request(op, body, timeoutMs) {
|
|
158
|
+
const child = this.child;
|
|
159
|
+
if (!child?.stdin) {
|
|
160
|
+
return Promise.reject(this.exitError ?? new Error('nco-bridge is not running'));
|
|
161
|
+
}
|
|
162
|
+
const id = ++this.nextId;
|
|
163
|
+
const ms = timeoutMs ?? this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
165
|
+
const timer = setTimeout(() => {
|
|
166
|
+
this.pending.delete(id);
|
|
167
|
+
reject(new Error(`nco-bridge ${op} timed out after ${ms} ms`));
|
|
168
|
+
}, ms);
|
|
169
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
170
|
+
child.stdin?.write(`${JSON.stringify({ id, op, ...body })}\n`);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
onData(chunk) {
|
|
174
|
+
this.buffer += chunk;
|
|
175
|
+
let i = this.buffer.indexOf('\n');
|
|
176
|
+
while (i >= 0) {
|
|
177
|
+
const line = this.buffer.slice(0, i).trim();
|
|
178
|
+
this.buffer = this.buffer.slice(i + 1);
|
|
179
|
+
if (line)
|
|
180
|
+
this.onLine(line);
|
|
181
|
+
i = this.buffer.indexOf('\n');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
onLine(line) {
|
|
185
|
+
let msg;
|
|
186
|
+
try {
|
|
187
|
+
msg = JSON.parse(line);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const p = this.pending.get(msg.id);
|
|
193
|
+
if (!p)
|
|
194
|
+
return;
|
|
195
|
+
this.pending.delete(msg.id);
|
|
196
|
+
clearTimeout(p.timer);
|
|
197
|
+
if (msg.ok)
|
|
198
|
+
p.resolve(msg.result);
|
|
199
|
+
else {
|
|
200
|
+
const e = msg.error ?? {};
|
|
201
|
+
p.reject(new NcoRfcError(String(e.message ?? 'RFC call failed'), String(e.kind ?? 'bridge'), e.key, e.name));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.NcoRfcClient = NcoRfcClient;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locates (and on first use builds) nco-bridge.exe — the small C# program in
|
|
3
|
+
* `nco/NcoBridge.cs` that calls RFC function modules through SAP .NET
|
|
4
|
+
* Connector (NCo 3.x).
|
|
5
|
+
*
|
|
6
|
+
* Nothing binary ships with this package: NCo is SAP-licensed and must be
|
|
7
|
+
* installed by the user (SAP GUI installs it on most consultant PCs). The
|
|
8
|
+
* bridge is compiled locally with the csc.exe of .NET Framework 4.x against
|
|
9
|
+
* the sapnco.dll found in the GAC, for the matching platform (GAC_64 → x64,
|
|
10
|
+
* GAC_32 → x86). The exe lands in a cache folder keyed by the source hash,
|
|
11
|
+
* so a changed source never runs a stale build.
|
|
12
|
+
*
|
|
13
|
+
* Override with SAP_NCO_BRIDGE_EXE (path to a prebuilt exe).
|
|
14
|
+
*/
|
|
15
|
+
export interface NcoInstall {
|
|
16
|
+
platform: 'x64' | 'x86';
|
|
17
|
+
sapnco: string;
|
|
18
|
+
sapncoUtils: string;
|
|
19
|
+
version: string;
|
|
20
|
+
}
|
|
21
|
+
/** Finds NCo 3.x in the GAC; 64-bit first. Returns null when NCo is not installed. */
|
|
22
|
+
export declare function findNcoInstall(windir?: string): NcoInstall | null;
|
|
23
|
+
/** Returns the path of a runnable nco-bridge.exe, building it when needed. */
|
|
24
|
+
export declare function ensureNcoBridge(): string;
|
|
25
|
+
//# sourceMappingURL=buildBridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildBridge.d.ts","sourceRoot":"","sources":["../../../src/connection/nco/buildBridge.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,KAAK,GAAG,KAAK,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqBD,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,MAAM,GAAE,MAAe,GAAG,UAAU,GAAG,IAAI,CAmBzE;AAoBD,8EAA8E;AAC9E,wBAAgB,eAAe,IAAI,MAAM,CA2ExC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.findNcoInstall = findNcoInstall;
|
|
37
|
+
exports.ensureNcoBridge = ensureNcoBridge;
|
|
38
|
+
const node_child_process_1 = require("node:child_process");
|
|
39
|
+
const node_crypto_1 = require("node:crypto");
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const os = __importStar(require("node:os"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const WINDIR = process.env.WINDIR || process.env.SystemRoot || 'C:\\Windows';
|
|
44
|
+
function bridgeSourcePath() {
|
|
45
|
+
// dist/connection/nco/buildBridge.js → <package>/nco/NcoBridge.cs
|
|
46
|
+
// src/connection/nco/buildBridge.ts → <package>/nco/NcoBridge.cs (tests)
|
|
47
|
+
return path.resolve(__dirname, '..', '..', '..', 'nco', 'NcoBridge.cs');
|
|
48
|
+
}
|
|
49
|
+
function newestVersionDir(dir) {
|
|
50
|
+
if (!fs.existsSync(dir))
|
|
51
|
+
return null;
|
|
52
|
+
const entries = fs
|
|
53
|
+
.readdirSync(dir)
|
|
54
|
+
.filter((e) => /^v4\.0_\d/.test(e))
|
|
55
|
+
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));
|
|
56
|
+
return entries.length > 0 ? path.join(dir, entries[0]) : null;
|
|
57
|
+
}
|
|
58
|
+
/** Finds NCo 3.x in the GAC; 64-bit first. Returns null when NCo is not installed. */
|
|
59
|
+
function findNcoInstall(windir = WINDIR) {
|
|
60
|
+
const assembly = path.join(windir, 'Microsoft.NET', 'assembly');
|
|
61
|
+
for (const [gac, platform] of [
|
|
62
|
+
['GAC_64', 'x64'],
|
|
63
|
+
['GAC_32', 'x86'],
|
|
64
|
+
]) {
|
|
65
|
+
const ncoDir = newestVersionDir(path.join(assembly, gac, 'sapnco'));
|
|
66
|
+
const utilsDir = newestVersionDir(path.join(assembly, gac, 'sapnco_utils'));
|
|
67
|
+
if (!ncoDir || !utilsDir)
|
|
68
|
+
continue;
|
|
69
|
+
const sapnco = path.join(ncoDir, 'sapnco.dll');
|
|
70
|
+
const sapncoUtils = path.join(utilsDir, 'sapnco_utils.dll');
|
|
71
|
+
if (!fs.existsSync(sapnco) || !fs.existsSync(sapncoUtils))
|
|
72
|
+
continue;
|
|
73
|
+
const version = path
|
|
74
|
+
.basename(ncoDir)
|
|
75
|
+
.replace(/^v4\.0_/, '')
|
|
76
|
+
.split('__')[0];
|
|
77
|
+
return { platform, sapnco, sapncoUtils, version };
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
function findCsc(platform, windir = WINDIR) {
|
|
82
|
+
const fw = platform === 'x64' ? 'Framework64' : 'Framework';
|
|
83
|
+
const candidates = [
|
|
84
|
+
path.join(windir, 'Microsoft.NET', fw, 'v4.0.30319', 'csc.exe'),
|
|
85
|
+
path.join(windir, 'Microsoft.NET', 'Framework', 'v4.0.30319', 'csc.exe'),
|
|
86
|
+
];
|
|
87
|
+
return candidates.find((c) => fs.existsSync(c)) ?? null;
|
|
88
|
+
}
|
|
89
|
+
function cacheRoot() {
|
|
90
|
+
const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
91
|
+
return path.join(base, 'mcp-abap-connection', 'nco-bridge');
|
|
92
|
+
}
|
|
93
|
+
/** Returns the path of a runnable nco-bridge.exe, building it when needed. */
|
|
94
|
+
function ensureNcoBridge() {
|
|
95
|
+
const override = process.env.SAP_NCO_BRIDGE_EXE?.trim();
|
|
96
|
+
if (override) {
|
|
97
|
+
if (!fs.existsSync(override)) {
|
|
98
|
+
throw new Error(`SAP_NCO_BRIDGE_EXE points to a missing file: ${override}`);
|
|
99
|
+
}
|
|
100
|
+
return override;
|
|
101
|
+
}
|
|
102
|
+
if (process.platform !== 'win32') {
|
|
103
|
+
throw new Error('The NCo connection needs Windows (SAP .NET Connector runs on .NET Framework only).');
|
|
104
|
+
}
|
|
105
|
+
const nco = findNcoInstall();
|
|
106
|
+
if (!nco) {
|
|
107
|
+
throw new Error('SAP .NET Connector (NCo 3.x) is not installed: no sapnco.dll in the GAC ' +
|
|
108
|
+
'(GAC_64 or GAC_32). Install NCo 3.1 for .NET Framework 4.x (SAP Support Portal ' +
|
|
109
|
+
'"SAP Connector for Microsoft .NET"; SAP GUI often installs it).');
|
|
110
|
+
}
|
|
111
|
+
const source = bridgeSourcePath();
|
|
112
|
+
if (!fs.existsSync(source)) {
|
|
113
|
+
throw new Error(`nco-bridge source missing: ${source}`);
|
|
114
|
+
}
|
|
115
|
+
const code = fs.readFileSync(source);
|
|
116
|
+
const hash = (0, node_crypto_1.createHash)('sha256')
|
|
117
|
+
.update(code)
|
|
118
|
+
.update(nco.platform)
|
|
119
|
+
.update(nco.version)
|
|
120
|
+
.digest('hex')
|
|
121
|
+
.slice(0, 12);
|
|
122
|
+
const outDir = path.join(cacheRoot(), hash);
|
|
123
|
+
const exe = path.join(outDir, 'nco-bridge.exe');
|
|
124
|
+
if (fs.existsSync(exe))
|
|
125
|
+
return exe;
|
|
126
|
+
const csc = findCsc(nco.platform);
|
|
127
|
+
if (!csc) {
|
|
128
|
+
throw new Error('csc.exe of .NET Framework 4.x not found under %WINDIR%\\Microsoft.NET — cannot build nco-bridge.');
|
|
129
|
+
}
|
|
130
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
131
|
+
const tmpExe = path.join(outDir, `nco-bridge.${process.pid}.tmp.exe`);
|
|
132
|
+
try {
|
|
133
|
+
(0, node_child_process_1.execFileSync)(csc, [
|
|
134
|
+
'/nologo',
|
|
135
|
+
'/codepage:65001',
|
|
136
|
+
`/platform:${nco.platform}`,
|
|
137
|
+
'/target:exe',
|
|
138
|
+
'/optimize+',
|
|
139
|
+
`/out:${tmpExe}`,
|
|
140
|
+
`/r:${nco.sapnco}`,
|
|
141
|
+
`/r:${nco.sapncoUtils}`,
|
|
142
|
+
'/r:System.Web.Extensions.dll',
|
|
143
|
+
source,
|
|
144
|
+
], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
145
|
+
fs.renameSync(tmpExe, exe);
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
try {
|
|
149
|
+
fs.rmSync(tmpExe, { force: true });
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* ignore */
|
|
153
|
+
}
|
|
154
|
+
if (fs.existsSync(exe))
|
|
155
|
+
return exe; // a parallel build won the race
|
|
156
|
+
const out = `${e?.stdout ?? ''}${e?.stderr ?? ''}`.trim();
|
|
157
|
+
throw new Error(`nco-bridge build failed: ${out || e?.message || e}`);
|
|
158
|
+
}
|
|
159
|
+
return exe;
|
|
160
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { createAbapConnection } from './connection/connectionFactory.js';
|
|
|
7
7
|
export { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from './connection/csrfConfig.js';
|
|
8
8
|
export { GenericWebSocketTransport, type IWebSocketFactory, type IWebSocketLike, } from './connection/GenericWebSocketTransport.js';
|
|
9
9
|
export { JwtAbapConnection, JwtAbapConnection as CloudAbapConnection, } from './connection/JwtAbapConnection.js';
|
|
10
|
+
export { ensureNcoBridge, findNcoInstall, type NcoInstall, } from './connection/nco/buildBridge.js';
|
|
11
|
+
export { type NcoConnectionParams, NcoRfcClient, NcoRfcError, type NcoSystemInfo, } from './connection/nco/NcoRfcClient.js';
|
|
10
12
|
export { RfcAbapConnection } from './connection/RfcAbapConnection.js';
|
|
11
13
|
export { SamlAbapConnection } from './connection/SamlAbapConnection.js';
|
|
12
14
|
export type { ILogger } from './logger.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACV,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,IAAI,oBAAoB,GAC3C,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,IAAI,mBAAmB,GACzC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACV,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,IAAI,oBAAoB,GAC3C,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,IAAI,mBAAmB,GACzC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,KAAK,mBAAmB,EACxB,YAAY,EACZ,WAAW,EACX,KAAK,aAAa,GACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Types - re-exported from interfaces package with backward compatibility aliases
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.getTimeoutConfig = exports.getTimeout = exports.SamlAbapConnection = exports.RfcAbapConnection = exports.CloudAbapConnection = exports.JwtAbapConnection = exports.GenericWebSocketTransport = exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = exports.createAbapConnection = exports.OnPremAbapConnection = exports.BaseAbapConnection = exports.sapConfigSignature = void 0;
|
|
4
|
+
exports.getTimeoutConfig = exports.getTimeout = exports.SamlAbapConnection = exports.RfcAbapConnection = exports.NcoRfcError = exports.NcoRfcClient = exports.findNcoInstall = exports.ensureNcoBridge = exports.CloudAbapConnection = exports.JwtAbapConnection = exports.GenericWebSocketTransport = exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = exports.createAbapConnection = exports.OnPremAbapConnection = exports.BaseAbapConnection = exports.sapConfigSignature = void 0;
|
|
5
5
|
// Config utilities
|
|
6
6
|
var sapConfig_js_1 = require("./config/sapConfig.js");
|
|
7
7
|
Object.defineProperty(exports, "sapConfigSignature", { enumerable: true, get: function () { return sapConfig_js_1.sapConfigSignature; } });
|
|
@@ -22,6 +22,12 @@ Object.defineProperty(exports, "GenericWebSocketTransport", { enumerable: true,
|
|
|
22
22
|
var JwtAbapConnection_js_1 = require("./connection/JwtAbapConnection.js");
|
|
23
23
|
Object.defineProperty(exports, "JwtAbapConnection", { enumerable: true, get: function () { return JwtAbapConnection_js_1.JwtAbapConnection; } });
|
|
24
24
|
Object.defineProperty(exports, "CloudAbapConnection", { enumerable: true, get: function () { return JwtAbapConnection_js_1.JwtAbapConnection; } });
|
|
25
|
+
var buildBridge_js_1 = require("./connection/nco/buildBridge.js");
|
|
26
|
+
Object.defineProperty(exports, "ensureNcoBridge", { enumerable: true, get: function () { return buildBridge_js_1.ensureNcoBridge; } });
|
|
27
|
+
Object.defineProperty(exports, "findNcoInstall", { enumerable: true, get: function () { return buildBridge_js_1.findNcoInstall; } });
|
|
28
|
+
var NcoRfcClient_js_1 = require("./connection/nco/NcoRfcClient.js");
|
|
29
|
+
Object.defineProperty(exports, "NcoRfcClient", { enumerable: true, get: function () { return NcoRfcClient_js_1.NcoRfcClient; } });
|
|
30
|
+
Object.defineProperty(exports, "NcoRfcError", { enumerable: true, get: function () { return NcoRfcClient_js_1.NcoRfcError; } });
|
|
25
31
|
var RfcAbapConnection_js_1 = require("./connection/RfcAbapConnection.js");
|
|
26
32
|
Object.defineProperty(exports, "RfcAbapConnection", { enumerable: true, get: function () { return RfcAbapConnection_js_1.RfcAbapConnection; } });
|
|
27
33
|
var SamlAbapConnection_js_1 = require("./connection/SamlAbapConnection.js");
|
package/nco/NcoBridge.cs
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// nco-bridge — calls RFC function modules through SAP .NET Connector (NCo 3.x).
|
|
2
|
+
//
|
|
3
|
+
// Protocol: one JSON object per line on stdin, one JSON reply per line on stdout.
|
|
4
|
+
// {"id":1,"op":"open","params":{"ashost":"h","sysnr":"00","client":"100","user":"U","passwd":"P","lang":"EN","saprouter":""}}
|
|
5
|
+
// {"id":2,"op":"call","fm":"RFC_PING","params":{...}}
|
|
6
|
+
// {"id":3,"op":"close"}
|
|
7
|
+
// Reply: {"id":n,"ok":true,"result":{...}} or {"id":n,"ok":false,"error":{"name":"...","key":"...","message":"..."}}
|
|
8
|
+
//
|
|
9
|
+
// Parameter values follow node-rfc: a structure is an object, a table an array of objects,
|
|
10
|
+
// BYTE / XSTRING values travel as {"__b64":"<base64>"}. Results carry the EXPORTING,
|
|
11
|
+
// CHANGING and TABLES parameters. One RFC connection per process, kept in one stateful
|
|
12
|
+
// context (BeginContext) so that lock / write / unlock run in the same ABAP session.
|
|
13
|
+
// The password only ever arrives on stdin; it is never logged or echoed.
|
|
14
|
+
//
|
|
15
|
+
// Build (Windows, .NET Framework 4.x csc, NCo from the GAC): see src/connection/nco/buildBridge.ts.
|
|
16
|
+
using System;
|
|
17
|
+
using System.Collections;
|
|
18
|
+
using System.Collections.Generic;
|
|
19
|
+
using System.Globalization;
|
|
20
|
+
using System.IO;
|
|
21
|
+
using System.Text;
|
|
22
|
+
using System.Web.Script.Serialization;
|
|
23
|
+
using SAP.Middleware.Connector;
|
|
24
|
+
|
|
25
|
+
namespace NcoBridge
|
|
26
|
+
{
|
|
27
|
+
public static class Program
|
|
28
|
+
{
|
|
29
|
+
static readonly JavaScriptSerializer Json = new JavaScriptSerializer { MaxJsonLength = int.MaxValue, RecursionLimit = 1000 };
|
|
30
|
+
static RfcDestination _dest;
|
|
31
|
+
static bool _inContext;
|
|
32
|
+
|
|
33
|
+
public static int Main(string[] args)
|
|
34
|
+
{
|
|
35
|
+
if (args.Length > 0 && args[0] == "--version")
|
|
36
|
+
{
|
|
37
|
+
Console.WriteLine("nco-bridge 1 / NCo " + typeof(RfcDestination).Assembly.GetName().Version);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
UTF8Encoding utf8 = new UTF8Encoding(false);
|
|
41
|
+
TextReader input = new StreamReader(Console.OpenStandardInput(), utf8);
|
|
42
|
+
StreamWriter output = new StreamWriter(Console.OpenStandardOutput(), utf8);
|
|
43
|
+
output.AutoFlush = true;
|
|
44
|
+
string line;
|
|
45
|
+
while ((line = input.ReadLine()) != null)
|
|
46
|
+
{
|
|
47
|
+
if (line.Trim().Length == 0) continue;
|
|
48
|
+
object id = null;
|
|
49
|
+
string op = "";
|
|
50
|
+
Dictionary<string, object> reply = new Dictionary<string, object>();
|
|
51
|
+
try
|
|
52
|
+
{
|
|
53
|
+
Dictionary<string, object> req = (Dictionary<string, object>)Json.DeserializeObject(line);
|
|
54
|
+
req.TryGetValue("id", out id);
|
|
55
|
+
op = Str(req, "op");
|
|
56
|
+
if (op == "open") reply["result"] = Open(Dict(req, "params"));
|
|
57
|
+
else if (op == "call") reply["result"] = Call(Str(req, "fm"), Dict(req, "params"));
|
|
58
|
+
else if (op == "close") { Close(); reply["result"] = new Dictionary<string, object>(); }
|
|
59
|
+
else if (op == "ping") { Require(); _dest.Ping(); reply["result"] = new Dictionary<string, object>(); }
|
|
60
|
+
else throw new ArgumentException("unknown op '" + op + "'");
|
|
61
|
+
reply["ok"] = true;
|
|
62
|
+
}
|
|
63
|
+
catch (Exception e)
|
|
64
|
+
{
|
|
65
|
+
reply["ok"] = false;
|
|
66
|
+
reply["error"] = ErrorOf(e);
|
|
67
|
+
}
|
|
68
|
+
reply["id"] = id;
|
|
69
|
+
output.WriteLine(Json.Serialize(reply));
|
|
70
|
+
if (op == "close") break;
|
|
71
|
+
}
|
|
72
|
+
Close();
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static Dictionary<string, object> Open(Dictionary<string, object> p)
|
|
77
|
+
{
|
|
78
|
+
if (_dest != null) throw new InvalidOperationException("connection is already open");
|
|
79
|
+
RfcConfigParameters cfg = new RfcConfigParameters();
|
|
80
|
+
cfg[RfcConfigParameters.Name] = "NCOB_" + Guid.NewGuid().ToString("N");
|
|
81
|
+
cfg[RfcConfigParameters.AppServerHost] = Str(p, "ashost");
|
|
82
|
+
cfg[RfcConfigParameters.SystemNumber] = Str(p, "sysnr");
|
|
83
|
+
cfg[RfcConfigParameters.Client] = Str(p, "client");
|
|
84
|
+
cfg[RfcConfigParameters.User] = Str(p, "user");
|
|
85
|
+
cfg[RfcConfigParameters.Password] = Str(p, "passwd");
|
|
86
|
+
string lang = Str(p, "lang");
|
|
87
|
+
cfg[RfcConfigParameters.Language] = lang.Length == 0 ? "EN" : lang;
|
|
88
|
+
string router = Str(p, "saprouter");
|
|
89
|
+
if (router.Length > 0) cfg[RfcConfigParameters.SAPRouter] = router;
|
|
90
|
+
RfcDestination dest = RfcDestinationManager.GetDestination(cfg);
|
|
91
|
+
dest.Ping();
|
|
92
|
+
RfcSessionManager.BeginContext(dest);
|
|
93
|
+
_dest = dest;
|
|
94
|
+
_inContext = true;
|
|
95
|
+
RfcSystemAttributes a = dest.SystemAttributes;
|
|
96
|
+
Dictionary<string, object> info = new Dictionary<string, object>();
|
|
97
|
+
info["sysId"] = a.SystemID;
|
|
98
|
+
info["client"] = a.Client;
|
|
99
|
+
info["user"] = a.User;
|
|
100
|
+
info["kernel"] = a.KernelRelease;
|
|
101
|
+
return info;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static void Close()
|
|
105
|
+
{
|
|
106
|
+
if (_dest == null) return;
|
|
107
|
+
try { if (_inContext) RfcSessionManager.EndContext(_dest); } catch { }
|
|
108
|
+
_inContext = false;
|
|
109
|
+
_dest = null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static void Require()
|
|
113
|
+
{
|
|
114
|
+
if (_dest == null) throw new InvalidOperationException("connection is not open");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static Dictionary<string, object> Call(string fm, Dictionary<string, object> parameters)
|
|
118
|
+
{
|
|
119
|
+
Require();
|
|
120
|
+
if (fm.Length == 0) throw new ArgumentException("fm is required");
|
|
121
|
+
IRfcFunction fn = _dest.Repository.CreateFunction(fm.ToUpperInvariant());
|
|
122
|
+
if (parameters != null)
|
|
123
|
+
foreach (KeyValuePair<string, object> kv in parameters)
|
|
124
|
+
{
|
|
125
|
+
if (kv.Value == null) continue;
|
|
126
|
+
SetValue(fn, kv.Key.ToUpperInvariant(), kv.Value);
|
|
127
|
+
}
|
|
128
|
+
fn.Invoke(_dest);
|
|
129
|
+
Dictionary<string, object> result = new Dictionary<string, object>();
|
|
130
|
+
RfcFunctionMetadata meta = fn.Metadata;
|
|
131
|
+
for (int i = 0; i < meta.ParameterCount; i++)
|
|
132
|
+
{
|
|
133
|
+
RfcParameterMetadata pm = meta[i];
|
|
134
|
+
if (pm.Direction == RfcDirection.IMPORT) continue;
|
|
135
|
+
result[pm.Name] = GetValue(fn, pm.Name, pm.DataType);
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static void SetValue(IRfcDataContainer c, string name, object value)
|
|
141
|
+
{
|
|
142
|
+
RfcElementMetadata em = c.GetElementMetadata(name);
|
|
143
|
+
switch (em.DataType)
|
|
144
|
+
{
|
|
145
|
+
case RfcDataType.STRUCTURE:
|
|
146
|
+
FillStructure(c.GetStructure(name), AsDict(value, name));
|
|
147
|
+
break;
|
|
148
|
+
case RfcDataType.TABLE:
|
|
149
|
+
IRfcTable t = c.GetTable(name);
|
|
150
|
+
foreach (object row in AsList(value, name))
|
|
151
|
+
{
|
|
152
|
+
t.Append();
|
|
153
|
+
FillStructure(t.CurrentRow, AsDict(row, name));
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
case RfcDataType.BYTE:
|
|
157
|
+
case RfcDataType.XSTRING:
|
|
158
|
+
c.SetValue(name, AsBytes(value));
|
|
159
|
+
break;
|
|
160
|
+
default:
|
|
161
|
+
c.SetValue(name, Scalar(value));
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static void FillStructure(IRfcStructure s, Dictionary<string, object> fields)
|
|
167
|
+
{
|
|
168
|
+
foreach (KeyValuePair<string, object> kv in fields)
|
|
169
|
+
{
|
|
170
|
+
if (kv.Value == null) continue;
|
|
171
|
+
SetValue(s, kv.Key.ToUpperInvariant(), kv.Value);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static object GetValue(IRfcDataContainer c, string name, RfcDataType type)
|
|
176
|
+
{
|
|
177
|
+
switch (type)
|
|
178
|
+
{
|
|
179
|
+
case RfcDataType.STRUCTURE:
|
|
180
|
+
return StructureToDict(c.GetStructure(name));
|
|
181
|
+
case RfcDataType.TABLE:
|
|
182
|
+
List<object> rows = new List<object>();
|
|
183
|
+
foreach (IRfcStructure row in c.GetTable(name)) rows.Add(StructureToDict(row));
|
|
184
|
+
return rows;
|
|
185
|
+
case RfcDataType.BYTE:
|
|
186
|
+
case RfcDataType.XSTRING:
|
|
187
|
+
byte[] b = c.GetByteArray(name);
|
|
188
|
+
Dictionary<string, object> wrapped = new Dictionary<string, object>();
|
|
189
|
+
wrapped["__b64"] = b == null ? "" : Convert.ToBase64String(b);
|
|
190
|
+
return wrapped;
|
|
191
|
+
case RfcDataType.INT1:
|
|
192
|
+
case RfcDataType.INT2:
|
|
193
|
+
case RfcDataType.INT4:
|
|
194
|
+
return c.GetInt(name);
|
|
195
|
+
case RfcDataType.INT8:
|
|
196
|
+
return c.GetLong(name);
|
|
197
|
+
case RfcDataType.FLOAT:
|
|
198
|
+
return c.GetDouble(name);
|
|
199
|
+
default:
|
|
200
|
+
return c.GetString(name);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
static Dictionary<string, object> StructureToDict(IRfcStructure s)
|
|
205
|
+
{
|
|
206
|
+
Dictionary<string, object> d = new Dictionary<string, object>();
|
|
207
|
+
RfcStructureMetadata m = s.Metadata;
|
|
208
|
+
for (int i = 0; i < m.FieldCount; i++)
|
|
209
|
+
{
|
|
210
|
+
RfcFieldMetadata f = m[i];
|
|
211
|
+
d[f.Name] = GetValue(s, f.Name, f.DataType);
|
|
212
|
+
}
|
|
213
|
+
return d;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static Dictionary<string, object> ErrorOf(Exception e)
|
|
217
|
+
{
|
|
218
|
+
Dictionary<string, object> err = new Dictionary<string, object>();
|
|
219
|
+
err["name"] = e.GetType().Name;
|
|
220
|
+
err["message"] = e.Message;
|
|
221
|
+
RfcAbapException abap = e as RfcAbapException;
|
|
222
|
+
if (abap != null) err["key"] = abap.Key;
|
|
223
|
+
if (e is RfcLogonException) err["kind"] = "logon";
|
|
224
|
+
else if (e is RfcCommunicationException) err["kind"] = "communication";
|
|
225
|
+
else if (e is RfcAbapRuntimeException) err["kind"] = "abap-runtime";
|
|
226
|
+
else if (e is RfcAbapClassException || e is RfcAbapException || e is RfcAbapMessageException) err["kind"] = "abap";
|
|
227
|
+
else err["kind"] = "bridge";
|
|
228
|
+
return err;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
static object Scalar(object v)
|
|
232
|
+
{
|
|
233
|
+
if (v is string) return v;
|
|
234
|
+
if (v is bool) return (bool)v ? "X" : "";
|
|
235
|
+
if (v is IFormattable) return ((IFormattable)v).ToString(null, CultureInfo.InvariantCulture);
|
|
236
|
+
return Convert.ToString(v, CultureInfo.InvariantCulture);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
static byte[] AsBytes(object v)
|
|
240
|
+
{
|
|
241
|
+
Dictionary<string, object> d = v as Dictionary<string, object>;
|
|
242
|
+
if (d != null && d.ContainsKey("__b64")) return Convert.FromBase64String(Convert.ToString(d["__b64"]));
|
|
243
|
+
string s = v as string;
|
|
244
|
+
if (s != null) return Encoding.UTF8.GetBytes(s);
|
|
245
|
+
throw new ArgumentException("byte value must be {\"__b64\":...} or a string");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static Dictionary<string, object> AsDict(object v, string name)
|
|
249
|
+
{
|
|
250
|
+
Dictionary<string, object> d = v as Dictionary<string, object>;
|
|
251
|
+
if (d == null) throw new ArgumentException(name + ": expected an object");
|
|
252
|
+
return d;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
static IEnumerable AsList(object v, string name)
|
|
256
|
+
{
|
|
257
|
+
if (v is string || !(v is IEnumerable)) throw new ArgumentException(name + ": expected an array");
|
|
258
|
+
return (IEnumerable)v;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
static string Str(Dictionary<string, object> d, string key)
|
|
262
|
+
{
|
|
263
|
+
object v;
|
|
264
|
+
if (d == null || !d.TryGetValue(key, out v) || v == null) return "";
|
|
265
|
+
return Convert.ToString(v, CultureInfo.InvariantCulture);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static Dictionary<string, object> Dict(Dictionary<string, object> d, string key)
|
|
269
|
+
{
|
|
270
|
+
object v;
|
|
271
|
+
if (d == null || !d.TryGetValue(key, out v)) return null;
|
|
272
|
+
return v as Dictionary<string, object>;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@babamba2/mcp-abap-connection",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "ABAP connection layer for MCP ABAP ADT server (fork of @mcp-abap-adt/connection by fr0ster)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
|
+
"nco",
|
|
9
10
|
"bin",
|
|
10
11
|
"README.md",
|
|
11
12
|
"LICENSE"
|