@mcp-abap-adt/auth-stores 0.1.5 → 0.1.7

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 (33) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +91 -5
  3. package/dist/parsers/abap/AbapServiceKeyParser.d.ts +7 -0
  4. package/dist/parsers/abap/AbapServiceKeyParser.js +20 -2
  5. package/dist/parsers/xsuaa/XsuaaServiceKeyParser.d.ts +7 -0
  6. package/dist/parsers/xsuaa/XsuaaServiceKeyParser.js +21 -7
  7. package/dist/storage/abap/envLoader.d.ts +3 -1
  8. package/dist/storage/abap/envLoader.js +10 -1
  9. package/dist/storage/abap/tokenStorage.d.ts +3 -1
  10. package/dist/storage/abap/tokenStorage.js +14 -3
  11. package/dist/storage/xsuaa/xsuaaEnvLoader.d.ts +3 -1
  12. package/dist/storage/xsuaa/xsuaaEnvLoader.js +13 -2
  13. package/dist/storage/xsuaa/xsuaaTokenStorage.d.ts +3 -1
  14. package/dist/storage/xsuaa/xsuaaTokenStorage.js +14 -3
  15. package/dist/stores/abap/AbapServiceKeyStore.d.ts +4 -2
  16. package/dist/stores/abap/AbapServiceKeyStore.js +68 -8
  17. package/dist/stores/abap/AbapSessionStore.d.ts +6 -2
  18. package/dist/stores/abap/AbapSessionStore.js +62 -4
  19. package/dist/stores/abap/SafeAbapSessionStore.d.ts +7 -2
  20. package/dist/stores/abap/SafeAbapSessionStore.js +61 -15
  21. package/dist/stores/btp/BtpServiceKeyStore.d.ts +4 -2
  22. package/dist/stores/btp/BtpServiceKeyStore.js +16 -2
  23. package/dist/stores/btp/BtpSessionStore.d.ts +4 -2
  24. package/dist/stores/btp/BtpSessionStore.js +56 -8
  25. package/dist/stores/btp/SafeBtpSessionStore.d.ts +7 -2
  26. package/dist/stores/btp/SafeBtpSessionStore.js +58 -19
  27. package/dist/stores/xsuaa/SafeXsuaaSessionStore.d.ts +7 -2
  28. package/dist/stores/xsuaa/SafeXsuaaSessionStore.js +58 -19
  29. package/dist/stores/xsuaa/XsuaaServiceKeyStore.d.ts +4 -2
  30. package/dist/stores/xsuaa/XsuaaServiceKeyStore.js +16 -2
  31. package/dist/stores/xsuaa/XsuaaSessionStore.d.ts +4 -2
  32. package/dist/stores/xsuaa/XsuaaSessionStore.js +56 -8
  33. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,82 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.7] - 2025-12-08
11
+
12
+ ### Added
13
+ - **Broker Usage Tests**: Added comprehensive test suites for broker usage scenarios
14
+ - Tests verify stores work correctly when used as in `AuthBroker` (without `saveSession`)
15
+ - Tests cover `setConnectionConfig` and `setAuthorizationConfig` on empty stores
16
+ - Tests verify session creation and updates in broker flow scenarios
17
+ - Test files: `*SessionStore.broker.test.ts` for all store types
18
+
19
+ ### Changed
20
+ - **Session Store Initialization**: File-based session stores now automatically create directory in constructor
21
+ - `AbapSessionStore`, `BtpSessionStore`, `XsuaaSessionStore` create directory if it doesn't exist
22
+ - Stores are ready to use immediately after construction
23
+ - Directory creation is logged at debug level
24
+ - **Session Creation Logic**: Session stores now automatically create sessions when calling `setConnectionConfig` or `setAuthorizationConfig`
25
+ - No need to call `saveSession` first - stores handle session creation internally
26
+ - `setConnectionConfig` creates new session if none exists (requires `serviceUrl` for ABAP)
27
+ - `setAuthorizationConfig` creates new session if none exists (for BTP/XSUAA, `mcpUrl` is optional)
28
+ - For ABAP: `setAuthorizationConfig` requires existing `serviceUrl` (from `setConnectionConfig` or throws error)
29
+ - This matches how `AuthBroker` uses stores - stores are now fully ready after construction
30
+ - **Token Validation**: Updated validation to allow empty string for `jwtToken` in BTP/XSUAA stores
31
+ - Empty token is allowed (can be set later via `setConnectionConfig`)
32
+ - Only `undefined` or `null` tokens are rejected
33
+ - This enables creating sessions with authorization config first, then adding connection config
34
+
35
+ ### Fixed
36
+ - **getConnectionConfig**: Fixed to allow empty string tokens (not just non-empty strings)
37
+ - Returns `null` only if token is `undefined` or `null`
38
+ - Empty string tokens are valid (can be set later)
39
+ - **setConnectionConfig Updates**: Fixed to preserve existing token when updating connection config
40
+ - Only updates `jwtToken` if `authorizationToken` is provided in config
41
+ - Preserves existing token if `authorizationToken` is `undefined`
42
+ - **Safe Session Stores**: Fixed session creation in `setConnectionConfig` and `setAuthorizationConfig`
43
+ - Now saves directly to Map (internal format) instead of calling `saveSession` with wrong format
44
+ - This fixes issues where `mcpUrl`/`serviceUrl` was not being saved correctly
45
+ - **loadXsuaaEnvFile**: Fixed to allow empty string for `jwtToken` (can be set later)
46
+ - Only rejects `undefined` or `null` tokens
47
+ - Empty string tokens are valid and can be set later via `setConnectionConfig`
48
+ - **testLogger**: Fixed to not output by default in test environment
49
+ - Now requires explicit enable via `DEBUG_AUTH_STORES=true` or `DEBUG=true`
50
+ - No longer enables logging automatically when `NODE_ENV === 'test'`
51
+
52
+ ## [0.1.6] - 2025-12-08
53
+
54
+ ### Added
55
+ - **Comprehensive Logging System**: Added optional logging support throughout the package
56
+ - All stores, parsers, and storage functions now accept optional `ILogger` parameter
57
+ - Logging shows detailed information: file paths, file sizes, parsed data structure, operation results
58
+ - Logging works by default in test environment (`NODE_ENV === 'test'`)
59
+ - Controlled via environment variables: `DEBUG_AUTH_STORES`, `LOG_LEVEL`
60
+ - **Test Logger Helper**: Added `createTestLogger` helper for tests
61
+ - Respects `DEBUG_AUTH_STORES`, `DEBUG`, and `LOG_LEVEL` environment variables
62
+ - Formats messages and meta into single-line output
63
+ - Shows stack traces for debug and error levels
64
+ - **Logging in Parsers**: Added logging to `AbapServiceKeyParser` and `XsuaaServiceKeyParser`
65
+ - Logs parsing operations, validation checks, and results
66
+ - Shows structure of parsed data (keys, fields, validation results)
67
+ - **Logging in Storage**: Added logging to all storage functions
68
+ - `loadEnvFile`, `saveTokenToEnv` (ABAP)
69
+ - `loadXsuaaEnvFile`, `saveXsuaaTokenToEnv` (XSUAA)
70
+ - Logs file operations: reading, writing, file sizes, preserved variables
71
+
72
+ ### Changed
73
+ - **Store Constructors**: All stores now accept optional `log?: ILogger` parameter
74
+ - `AbapServiceKeyStore`, `BtpServiceKeyStore`, `XsuaaServiceKeyStore`
75
+ - `AbapSessionStore`, `BtpSessionStore`, `XsuaaSessionStore`
76
+ - `SafeAbapSessionStore`, `SafeBtpSessionStore`, `SafeXsuaaSessionStore`
77
+ - **Parser Constructors**: Parsers now accept optional `log?: ILogger` parameter
78
+ - `AbapServiceKeyParser`, `XsuaaServiceKeyParser`
79
+ - **Storage Functions**: Storage functions now accept optional `log?: ILogger` parameter
80
+ - All storage functions pass logger through to enable detailed logging
81
+ - **Logging Format**: All log messages are concise, single-line strings with embedded key data
82
+ - Example: `Reading service key file: /path/to/file.json`
83
+ - Example: `File read successfully, size: 121 bytes, keys: uaa`
84
+ - Example: `Session saved: token(2263 chars), hasRefreshToken(true), sapUrl(https://...)`
85
+
10
86
  ## [0.1.5] - 2025-12-07
11
87
 
12
88
  ### Changed
package/README.md CHANGED
@@ -145,11 +145,12 @@ All stores accept a single directory path in the constructor:
145
145
  // Single directory path
146
146
  const store = new BtpServiceKeyStore('/path/to/service-keys');
147
147
 
148
- // Directory will be created automatically if it doesn't exist when saving files
149
- const sessionStore = new AbapSessionStore('/path/to/sessions');
150
- await sessionStore.saveSession('TRIAL', config); // Creates directory if needed
148
+ // File-based session stores automatically create directory in constructor if it doesn't exist
149
+ const sessionStore = new AbapSessionStore('/path/to/sessions'); // Directory created automatically
151
150
  ```
152
151
 
152
+ **Note**: File-based session stores (`AbapSessionStore`, `BtpSessionStore`, `XsuaaSessionStore`) automatically create the directory in the constructor if it doesn't exist. Stores are ready to use immediately after construction.
153
+
153
154
  ### Service Key Format
154
155
 
155
156
  **ABAP Service Key** (with nested `uaa` object):
@@ -235,6 +236,80 @@ const abapKey = await loadServiceKey('TRIAL', '/path/to/service-keys');
235
236
  const xsuaaKey = await loadXSUAAServiceKey('mcp', '/path/to/service-keys');
236
237
  ```
237
238
 
239
+ ## Debug Logging
240
+
241
+ Stores support optional logging through the `ILogger` interface. To enable detailed logging:
242
+
243
+ ### Using Logger in Code
244
+
245
+ ```typescript
246
+ import { AbapServiceKeyStore } from '@mcp-abap-adt/auth-stores';
247
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
248
+
249
+ // Create logger (or use your own implementation)
250
+ const logger: ILogger = {
251
+ debug: (msg) => console.debug(msg),
252
+ info: (msg) => console.info(msg),
253
+ warn: (msg) => console.warn(msg),
254
+ error: (msg) => console.error(msg),
255
+ };
256
+
257
+ // Pass logger to store constructor
258
+ const store = new AbapServiceKeyStore('/path/to/service-keys', logger);
259
+ const sessionStore = new AbapSessionStore('/path/to/sessions', logger);
260
+ ```
261
+
262
+ ### Using Test Logger in Tests
263
+
264
+ For tests, use the `createTestLogger` helper which respects environment variables:
265
+
266
+ ```typescript
267
+ import { createTestLogger } from './__tests__/helpers/testLogger';
268
+
269
+ // Logger will output only if DEBUG_AUTH_STORES=true is set
270
+ const logger = createTestLogger('MY-TEST');
271
+ const store = new AbapServiceKeyStore('/path/to/service-keys', logger);
272
+ ```
273
+
274
+ ### Environment Variables
275
+
276
+ To enable logging in tests or when using `createTestLogger`:
277
+
278
+ ```bash
279
+ # Enable logging for auth stores
280
+ DEBUG_AUTH_STORES=true npm test
281
+
282
+ # Or enable via general DEBUG variable
283
+ DEBUG=true npm test
284
+
285
+ # Or include in DEBUG list
286
+ DEBUG=auth-stores npm test
287
+
288
+ # Set log level (debug, info, warn, error)
289
+ LOG_LEVEL=debug npm test
290
+ ```
291
+
292
+ **Note**: Logging is enabled by default in test environment (`NODE_ENV === 'test'`). To disable, set `DEBUG_AUTH_STORES=false`.
293
+
294
+ Logging shows:
295
+ - **File operations**: Which files are read/written, file sizes, file paths
296
+ - **Parsing operations**: Structure of parsed data, validation results, keys found
297
+ - **Storage operations**: What data is saved/loaded, token lengths, refresh token presence, URLs
298
+ - **Errors**: Detailed error information with context
299
+
300
+ Example output with `LOG_LEVEL=debug`:
301
+ ```
302
+ [TEST-STORE] [DEBUG] Reading service key file: /path/to/TRIAL.json
303
+ [TEST-STORE] [DEBUG] File read successfully, size: 121 bytes, keys: uaa
304
+ [TEST-STORE] [DEBUG] Parsed service key structure: hasUaa(true), uaaKeys(url, clientid, clientsecret)
305
+ [TEST-STORE] Authorization config loaded from /path/to/TRIAL.json: uaaUrl(https://...authentication...), clientId(test-client...)
306
+ [TEST-STORE] [DEBUG] Reading env file: /path/to/TRIAL.env
307
+ [TEST-STORE] [DEBUG] Env file read successfully, size: 245 bytes
308
+ [TEST-STORE] Session loaded for TRIAL: token(2263 chars), hasRefreshToken(true), sapUrl(https://...abap...)
309
+ ```
310
+
311
+ **Note**: Logging only works when a logger is explicitly provided. Stores will not output anything to console if no logger is passed.
312
+
238
313
  ## Testing
239
314
 
240
315
  The package includes both unit tests (with mocked file system) and integration tests (with real files).
@@ -281,12 +356,23 @@ Integration tests will skip if `test-config.yaml` is not configured or contains
281
356
 
282
357
  - All stores implement `IServiceKeyStore` or `ISessionStore` interfaces from `@mcp-abap-adt/auth-broker`
283
358
  - Stores accept a single directory path in constructor
284
- - File-based stores automatically create directories when saving
359
+ - File-based session stores automatically create directories in constructor if they don't exist
360
+ - Session stores automatically create sessions when calling `setConnectionConfig` or `setAuthorizationConfig` (no need to call `saveSession` first)
285
361
  - In-memory stores (`Safe*SessionStore`) don't persist data to disk
286
362
 
363
+ ### Session Store Behavior
364
+
365
+ Session stores are designed to work seamlessly with `AuthBroker`:
366
+
367
+ - **Ready after construction**: File-based stores create directory automatically, stores are ready to use immediately
368
+ - **Automatic session creation**: Calling `setConnectionConfig` or `setAuthorizationConfig` on an empty store creates a new session
369
+ - **ABAP stores**: Require `serviceUrl` when creating new session via `setConnectionConfig` or `setAuthorizationConfig`
370
+ - **BTP/XSUAA stores**: `mcpUrl` is optional - can create session with authorization config first, then add connection config
371
+ - **Token updates**: `setConnectionConfig` updates token if provided, preserves existing token if not provided
372
+
287
373
  ## Dependencies
288
374
 
289
- - `@mcp-abap-adt/auth-broker` (^0.1.6) - Interface definitions
375
+ - `@mcp-abap-adt/interfaces` (^0.1.3) - Interface definitions (`IServiceKeyStore`, `ISessionStore`, `IConfig`, `IConnectionConfig`, `IAuthorizationConfig`, `ILogger`)
290
376
  - `dotenv` - Environment variable parsing
291
377
 
292
378
  ## License
@@ -15,10 +15,17 @@
15
15
  * ...
16
16
  * }
17
17
  */
18
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
18
19
  /**
19
20
  * Parser for standard ABAP service key format
20
21
  */
21
22
  export declare class AbapServiceKeyParser {
23
+ private log?;
24
+ /**
25
+ * Create a new AbapServiceKeyParser instance
26
+ * @param log Optional logger for logging operations
27
+ */
28
+ constructor(log?: ILogger);
22
29
  /**
23
30
  * Check if this parser can handle the given raw service key data
24
31
  * @param rawData Raw JSON data from service key file
@@ -22,13 +22,23 @@ exports.AbapServiceKeyParser = void 0;
22
22
  * Parser for standard ABAP service key format
23
23
  */
24
24
  class AbapServiceKeyParser {
25
+ log;
26
+ /**
27
+ * Create a new AbapServiceKeyParser instance
28
+ * @param log Optional logger for logging operations
29
+ */
30
+ constructor(log) {
31
+ this.log = log;
32
+ }
25
33
  /**
26
34
  * Check if this parser can handle the given raw service key data
27
35
  * @param rawData Raw JSON data from service key file
28
36
  * @returns true if data has nested uaa object, false otherwise
29
37
  */
30
38
  canParse(rawData) {
31
- return rawData && typeof rawData === 'object' && rawData.uaa && typeof rawData.uaa === 'object';
39
+ const result = rawData && typeof rawData === 'object' && rawData.uaa && typeof rawData.uaa === 'object';
40
+ this.log?.debug(`canParse check: hasUaa(${!!rawData?.uaa}), result(${result})`);
41
+ return result;
32
42
  }
33
43
  /**
34
44
  * Parse raw service key data
@@ -37,13 +47,21 @@ class AbapServiceKeyParser {
37
47
  * @throws Error if data cannot be parsed or is invalid
38
48
  */
39
49
  parse(rawData) {
50
+ this.log?.debug(`Parsing ABAP service key: hasUaa(${!!rawData?.uaa}), keys(${rawData ? Object.keys(rawData).join(', ') : 'none'})`);
40
51
  if (!this.canParse(rawData)) {
52
+ this.log?.error(`Service key does not match ABAP format: missing uaa object`);
41
53
  throw new Error('Service key does not match ABAP format (missing uaa object)');
42
54
  }
43
55
  // Validate UAA configuration
44
- if (!rawData.uaa.url || !rawData.uaa.clientid || !rawData.uaa.clientsecret) {
56
+ const hasUrl = !!rawData.uaa.url;
57
+ const hasClientId = !!rawData.uaa.clientid;
58
+ const hasClientSecret = !!rawData.uaa.clientsecret;
59
+ this.log?.debug(`UAA validation: url(${hasUrl}), clientid(${hasClientId}), clientsecret(${hasClientSecret})`);
60
+ if (!hasUrl || !hasClientId || !hasClientSecret) {
61
+ this.log?.error(`Service key uaa object missing required fields: url(${hasUrl}), clientid(${hasClientId}), clientsecret(${hasClientSecret})`);
45
62
  throw new Error('Service key "uaa" object missing required fields: url, clientid, clientsecret');
46
63
  }
64
+ this.log?.debug(`ABAP service key parsed successfully: uaaUrl(${rawData.uaa.url.substring(0, 40)}...), hasAbap(${!!rawData.abap})`);
47
65
  return rawData;
48
66
  }
49
67
  }
@@ -10,10 +10,17 @@
10
10
  * ...
11
11
  * }
12
12
  */
13
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
13
14
  /**
14
15
  * Parser for direct XSUAA service key format from BTP
15
16
  */
16
17
  export declare class XsuaaServiceKeyParser {
18
+ private log?;
19
+ /**
20
+ * Create a new XsuaaServiceKeyParser instance
21
+ * @param log Optional logger for logging operations
22
+ */
23
+ constructor(log?: ILogger);
17
24
  /**
18
25
  * Check if this parser can handle the given raw service key data
19
26
  * @param rawData Raw JSON data from service key file
@@ -17,6 +17,14 @@ exports.XsuaaServiceKeyParser = void 0;
17
17
  * Parser for direct XSUAA service key format from BTP
18
18
  */
19
19
  class XsuaaServiceKeyParser {
20
+ log;
21
+ /**
22
+ * Create a new XsuaaServiceKeyParser instance
23
+ * @param log Optional logger for logging operations
24
+ */
25
+ constructor(log) {
26
+ this.log = log;
27
+ }
20
28
  /**
21
29
  * Check if this parser can handle the given raw service key data
22
30
  * @param rawData Raw JSON data from service key file
@@ -24,19 +32,21 @@ class XsuaaServiceKeyParser {
24
32
  */
25
33
  canParse(rawData) {
26
34
  if (!rawData || typeof rawData !== 'object' || Array.isArray(rawData)) {
35
+ this.log?.debug(`canParse check: invalid type, result(false)`);
27
36
  return false;
28
37
  }
29
38
  // Check for nested uaa object (ABAP format) - should not have it
30
39
  if (rawData.uaa) {
40
+ this.log?.debug(`canParse check: has nested uaa (ABAP format), result(false)`);
31
41
  return false;
32
42
  }
33
43
  // Check for required XSUAA fields at root level
34
- return (typeof rawData.url === 'string' &&
35
- typeof rawData.clientid === 'string' &&
36
- typeof rawData.clientsecret === 'string' &&
37
- rawData.url.length > 0 &&
38
- rawData.clientid.length > 0 &&
39
- rawData.clientsecret.length > 0);
44
+ const hasUrl = typeof rawData.url === 'string' && rawData.url.length > 0;
45
+ const hasClientId = typeof rawData.clientid === 'string' && rawData.clientid.length > 0;
46
+ const hasClientSecret = typeof rawData.clientsecret === 'string' && rawData.clientsecret.length > 0;
47
+ const result = hasUrl && hasClientId && hasClientSecret;
48
+ this.log?.debug(`canParse check: url(${hasUrl}), clientid(${hasClientId}), clientsecret(${hasClientSecret}), result(${result})`);
49
+ return result;
40
50
  }
41
51
  /**
42
52
  * Parse raw service key data
@@ -45,14 +55,16 @@ class XsuaaServiceKeyParser {
45
55
  * @throws Error if data cannot be parsed or is invalid
46
56
  */
47
57
  parse(rawData) {
58
+ this.log?.debug(`Parsing XSUAA service key: hasUrl(${!!rawData?.url}), hasClientId(${!!rawData?.clientid}), hasClientSecret(${!!rawData?.clientsecret}), keys(${rawData ? Object.keys(rawData).join(', ') : 'none'})`);
48
59
  if (!this.canParse(rawData)) {
60
+ this.log?.error(`Service key does not match XSUAA format: missing required fields at root level`);
49
61
  throw new Error('Service key does not match XSUAA format (missing url, clientid, or clientsecret at root level)');
50
62
  }
51
63
  // Normalize to standard format
52
64
  // For authorization (OAuth2 authorize endpoint), use 'url' (not 'apiurl')
53
65
  // 'apiurl' is for token endpoint, but authorization uses base 'url'
54
66
  const uaaUrl = rawData.url;
55
- return {
67
+ const result = {
56
68
  uaa: {
57
69
  url: uaaUrl,
58
70
  clientid: rawData.clientid,
@@ -68,6 +80,8 @@ class XsuaaServiceKeyParser {
68
80
  sap_client: rawData.sap_client,
69
81
  language: rawData.language,
70
82
  };
83
+ this.log?.debug(`XSUAA service key parsed successfully: uaaUrl(${uaaUrl.substring(0, 40)}...), hasAbap(${!!rawData.abap})`);
84
+ return result;
71
85
  }
72
86
  }
73
87
  exports.XsuaaServiceKeyParser = XsuaaServiceKeyParser;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Environment file loader - loads .env files by destination name for ABAP
3
3
  */
4
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
4
5
  interface EnvConfig {
5
6
  sapUrl: string;
6
7
  sapClient?: string;
@@ -15,7 +16,8 @@ interface EnvConfig {
15
16
  * Load environment configuration from {destination}.env file
16
17
  * @param destination Destination name
17
18
  * @param directory Directory where the file is located
19
+ * @param log Optional logger for logging operations
18
20
  * @returns EnvConfig object or null if file not found
19
21
  */
20
- export declare function loadEnvFile(destination: string, directory: string): Promise<EnvConfig | null>;
22
+ export declare function loadEnvFile(destination: string, directory: string, log?: ILogger): Promise<EnvConfig | null>;
21
23
  export {};
@@ -45,22 +45,29 @@ const constants_1 = require("../../utils/constants");
45
45
  * Load environment configuration from {destination}.env file
46
46
  * @param destination Destination name
47
47
  * @param directory Directory where the file is located
48
+ * @param log Optional logger for logging operations
48
49
  * @returns EnvConfig object or null if file not found
49
50
  */
50
- async function loadEnvFile(destination, directory) {
51
+ async function loadEnvFile(destination, directory, log) {
51
52
  const fileName = `${destination}.env`;
52
53
  const envFilePath = path.join(directory, fileName);
54
+ log?.debug(`Reading env file: ${envFilePath}`);
53
55
  if (!fs.existsSync(envFilePath)) {
56
+ log?.debug(`Env file not found: ${envFilePath}`);
54
57
  return null;
55
58
  }
56
59
  try {
57
60
  // Read and parse .env file
58
61
  const envContent = fs.readFileSync(envFilePath, 'utf8');
62
+ log?.debug(`Env file read successfully, size: ${envContent.length} bytes`);
59
63
  const parsed = dotenv.parse(envContent);
64
+ log?.debug(`Parsed env variables: ${Object.keys(parsed).join(', ')}`);
60
65
  // Extract required fields
61
66
  const sapUrl = parsed[constants_1.ABAP_CONNECTION_VARS.SERVICE_URL];
62
67
  const jwtToken = parsed[constants_1.ABAP_CONNECTION_VARS.AUTHORIZATION_TOKEN];
68
+ log?.debug(`Extracted fields: hasSapUrl(${!!sapUrl}), hasJwtToken(${!!jwtToken})`);
63
69
  if (!sapUrl || !jwtToken) {
70
+ log?.warn(`Env file missing required fields: sapUrl(${!!sapUrl}), jwtToken(${!!jwtToken})`);
64
71
  return null;
65
72
  }
66
73
  const config = {
@@ -86,9 +93,11 @@ async function loadEnvFile(destination, directory) {
86
93
  if (parsed[constants_1.ABAP_CONNECTION_VARS.SAP_LANGUAGE]) {
87
94
  config.language = parsed[constants_1.ABAP_CONNECTION_VARS.SAP_LANGUAGE].trim();
88
95
  }
96
+ log?.info(`Env config loaded from ${envFilePath}: sapUrl(${config.sapUrl.substring(0, 50)}...), token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl})`);
89
97
  return config;
90
98
  }
91
99
  catch (error) {
100
+ log?.error(`Failed to load env file from ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`);
92
101
  throw new Error(`Failed to load environment file for destination "${destination}": ${error instanceof Error ? error.message : String(error)}`);
93
102
  }
94
103
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Token storage - saves tokens to .env files for ABAP
3
3
  */
4
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
4
5
  interface EnvConfig {
5
6
  sapUrl: string;
6
7
  sapClient?: string;
@@ -16,9 +17,10 @@ interface EnvConfig {
16
17
  * @param destination Destination name
17
18
  * @param savePath Path where to save the file
18
19
  * @param config Configuration to save
20
+ * @param log Optional logger for logging operations
19
21
  */
20
22
  export declare function saveTokenToEnv(destination: string, savePath: string, config: Partial<EnvConfig> & {
21
23
  sapUrl?: string;
22
24
  jwtToken: string;
23
- }): Promise<void>;
25
+ }, log?: ILogger): Promise<void>;
24
26
  export {};
@@ -45,18 +45,26 @@ const constants_1 = require("../../utils/constants");
45
45
  * @param destination Destination name
46
46
  * @param savePath Path where to save the file
47
47
  * @param config Configuration to save
48
+ * @param log Optional logger for logging operations
48
49
  */
49
- async function saveTokenToEnv(destination, savePath, config) {
50
+ async function saveTokenToEnv(destination, savePath, config, log) {
51
+ const envFilePath = path.join(savePath, `${destination}.env`);
52
+ const tempFilePath = `${envFilePath}.tmp`;
53
+ log?.debug(`Saving token to env file: ${envFilePath}`);
54
+ log?.debug(`Config to save: hasSapUrl(${!!config.sapUrl}), token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl})`);
50
55
  // Ensure directory exists
51
56
  if (!fs.existsSync(savePath)) {
57
+ log?.debug(`Creating directory: ${savePath}`);
52
58
  fs.mkdirSync(savePath, { recursive: true });
53
59
  }
54
- const envFilePath = path.join(savePath, `${destination}.env`);
55
- const tempFilePath = `${envFilePath}.tmp`;
56
60
  // Read existing .env file if it exists
57
61
  let existingContent = '';
58
62
  if (fs.existsSync(envFilePath)) {
59
63
  existingContent = fs.readFileSync(envFilePath, 'utf8');
64
+ log?.debug(`Reading existing env file, size: ${existingContent.length} bytes`);
65
+ }
66
+ else {
67
+ log?.debug(`Env file does not exist, creating new one`);
60
68
  }
61
69
  // Parse existing content to preserve other values
62
70
  const lines = existingContent.split('\n');
@@ -73,6 +81,7 @@ async function saveTokenToEnv(destination, savePath, config) {
73
81
  existingVars.set(key, value);
74
82
  }
75
83
  }
84
+ log?.debug(`Preserved ${existingVars.size} existing variables from env file`);
76
85
  // Update with new values
77
86
  if (config.sapUrl) {
78
87
  existingVars.set(constants_1.ABAP_CONNECTION_VARS.SERVICE_URL, config.sapUrl);
@@ -106,8 +115,10 @@ async function saveTokenToEnv(destination, savePath, config) {
106
115
  envLines.push(`${key}=${escapedValue}`);
107
116
  }
108
117
  const envContent = envLines.join('\n') + '\n';
118
+ log?.debug(`Writing ${envLines.length} variables to env file: ${Object.keys(config).join(', ')}`);
109
119
  // Write to temp file
110
120
  fs.writeFileSync(tempFilePath, envContent, 'utf8');
111
121
  // Atomic rename
112
122
  fs.renameSync(tempFilePath, envFilePath);
123
+ log?.info(`Token saved to ${envFilePath}: token(${config.jwtToken.length} chars), sapUrl(${config.sapUrl ? config.sapUrl.substring(0, 50) + '...' : 'none'}), variables(${envLines.length})`);
113
124
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * XSUAA Environment file loader - loads .env files with XSUAA_* variables for XSUAA
3
3
  */
4
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
4
5
  interface XsuaaSessionConfig {
5
6
  mcpUrl?: string;
6
7
  jwtToken: string;
@@ -14,7 +15,8 @@ interface XsuaaSessionConfig {
14
15
  * Reads XSUAA_* variables instead of SAP_* variables
15
16
  * @param destination Destination name
16
17
  * @param directory Directory where the file is located
18
+ * @param log Optional logger for logging operations
17
19
  * @returns XsuaaSessionConfig object or null if file not found
18
20
  */
19
- export declare function loadXsuaaEnvFile(destination: string, directory: string): Promise<XsuaaSessionConfig | null>;
21
+ export declare function loadXsuaaEnvFile(destination: string, directory: string, log?: ILogger): Promise<XsuaaSessionConfig | null>;
20
22
  export {};
@@ -46,21 +46,30 @@ const constants_1 = require("../../utils/constants");
46
46
  * Reads XSUAA_* variables instead of SAP_* variables
47
47
  * @param destination Destination name
48
48
  * @param directory Directory where the file is located
49
+ * @param log Optional logger for logging operations
49
50
  * @returns XsuaaSessionConfig object or null if file not found
50
51
  */
51
- async function loadXsuaaEnvFile(destination, directory) {
52
+ async function loadXsuaaEnvFile(destination, directory, log) {
52
53
  const fileName = `${destination}.env`;
53
54
  const envFilePath = path.join(directory, fileName);
55
+ log?.debug(`Reading XSUAA env file: ${envFilePath}`);
54
56
  if (!fs.existsSync(envFilePath)) {
57
+ log?.debug(`XSUAA env file not found: ${envFilePath}`);
55
58
  return null;
56
59
  }
57
60
  try {
58
61
  // Read and parse .env file
59
62
  const envContent = fs.readFileSync(envFilePath, 'utf8');
63
+ log?.debug(`XSUAA env file read successfully, size: ${envContent.length} bytes`);
60
64
  const parsed = dotenv.parse(envContent);
65
+ log?.debug(`Parsed XSUAA env variables: ${Object.keys(parsed).filter(k => k.startsWith('XSUAA_')).join(', ')}`);
61
66
  // Extract required fields (XSUAA_* variables)
62
67
  const jwtToken = parsed[constants_1.XSUAA_CONNECTION_VARS.AUTHORIZATION_TOKEN];
63
- if (!jwtToken) {
68
+ log?.debug(`Extracted fields: hasJwtToken(${jwtToken !== undefined && jwtToken !== null})`);
69
+ // Allow empty string for jwtToken (can be set later via setConnectionConfig)
70
+ // Only reject if jwtToken is undefined or null
71
+ if (jwtToken === undefined || jwtToken === null) {
72
+ log?.warn(`XSUAA env file missing required field: jwtToken`);
64
73
  return null;
65
74
  }
66
75
  const config = {
@@ -83,9 +92,11 @@ async function loadXsuaaEnvFile(destination, directory) {
83
92
  if (parsed[constants_1.XSUAA_AUTHORIZATION_VARS.UAA_CLIENT_SECRET]) {
84
93
  config.uaaClientSecret = parsed[constants_1.XSUAA_AUTHORIZATION_VARS.UAA_CLIENT_SECRET].trim();
85
94
  }
95
+ log?.info(`XSUAA env config loaded from ${envFilePath}: token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl}), mcpUrl(${config.mcpUrl ? config.mcpUrl.substring(0, 50) + '...' : 'none'})`);
86
96
  return config;
87
97
  }
88
98
  catch (error) {
99
+ log?.error(`Failed to load XSUAA env file from ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`);
89
100
  throw new Error(`Failed to load XSUAA environment file for destination "${destination}": ${error instanceof Error ? error.message : String(error)}`);
90
101
  }
91
102
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * XSUAA Token storage - saves tokens to .env files with XSUAA_* variables
3
3
  */
4
+ import type { ILogger } from '@mcp-abap-adt/interfaces';
4
5
  interface XsuaaSessionConfig {
5
6
  mcpUrl?: string;
6
7
  jwtToken: string;
@@ -14,6 +15,7 @@ interface XsuaaSessionConfig {
14
15
  * @param destination Destination name
15
16
  * @param savePath Path where to save the file
16
17
  * @param config XSUAA session configuration to save
18
+ * @param log Optional logger for logging operations
17
19
  */
18
- export declare function saveXsuaaTokenToEnv(destination: string, savePath: string, config: XsuaaSessionConfig): Promise<void>;
20
+ export declare function saveXsuaaTokenToEnv(destination: string, savePath: string, config: XsuaaSessionConfig, log?: ILogger): Promise<void>;
19
21
  export {};
@@ -45,18 +45,26 @@ const constants_1 = require("../../utils/constants");
45
45
  * @param destination Destination name
46
46
  * @param savePath Path where to save the file
47
47
  * @param config XSUAA session configuration to save
48
+ * @param log Optional logger for logging operations
48
49
  */
49
- async function saveXsuaaTokenToEnv(destination, savePath, config) {
50
+ async function saveXsuaaTokenToEnv(destination, savePath, config, log) {
51
+ const envFilePath = path.join(savePath, `${destination}.env`);
52
+ const tempFilePath = `${envFilePath}.tmp`;
53
+ log?.debug(`Saving XSUAA token to env file: ${envFilePath}`);
54
+ log?.debug(`Config to save: token(${config.jwtToken.length} chars), hasRefreshToken(${!!config.refreshToken}), hasUaaUrl(${!!config.uaaUrl}), mcpUrl(${config.mcpUrl ? config.mcpUrl.substring(0, 50) + '...' : 'none'})`);
50
55
  // Ensure directory exists
51
56
  if (!fs.existsSync(savePath)) {
57
+ log?.debug(`Creating directory: ${savePath}`);
52
58
  fs.mkdirSync(savePath, { recursive: true });
53
59
  }
54
- const envFilePath = path.join(savePath, `${destination}.env`);
55
- const tempFilePath = `${envFilePath}.tmp`;
56
60
  // Read existing .env file if it exists
57
61
  let existingContent = '';
58
62
  if (fs.existsSync(envFilePath)) {
59
63
  existingContent = fs.readFileSync(envFilePath, 'utf8');
64
+ log?.debug(`Reading existing XSUAA env file, size: ${existingContent.length} bytes`);
65
+ }
66
+ else {
67
+ log?.debug(`XSUAA env file does not exist, creating new one`);
60
68
  }
61
69
  // Parse existing content to preserve other values
62
70
  // Remove old SAP_* variables for XSUAA (use XSUAA_* instead)
@@ -79,6 +87,7 @@ async function saveXsuaaTokenToEnv(destination, savePath, config) {
79
87
  existingVars.set(key, value);
80
88
  }
81
89
  }
90
+ log?.debug(`Preserved ${existingVars.size} existing variables from XSUAA env file`);
82
91
  // Update with new values (XSUAA_* variables)
83
92
  // mcpUrl can be saved as additional variable (not part of CONNECTION_VARS, but can be stored for convenience)
84
93
  // URL comes from elsewhere (YAML config, parameter, or request header), but can be stored in .env
@@ -108,8 +117,10 @@ async function saveXsuaaTokenToEnv(destination, savePath, config) {
108
117
  envLines.push(`${key}=${escapedValue}`);
109
118
  }
110
119
  const envContent = envLines.join('\n') + '\n';
120
+ log?.debug(`Writing ${envLines.length} XSUAA variables to env file: ${Object.keys(config).join(', ')}`);
111
121
  // Write to temp file
112
122
  fs.writeFileSync(tempFilePath, envContent, 'utf8');
113
123
  // Atomic rename
114
124
  fs.renameSync(tempFilePath, envFilePath);
125
+ log?.info(`XSUAA token saved to ${envFilePath}: token(${config.jwtToken.length} chars), mcpUrl(${config.mcpUrl ? config.mcpUrl.substring(0, 50) + '...' : 'none'}), variables(${envLines.length})`);
115
126
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ABAP Service key store - reads ABAP service keys from {destination}.json files
3
3
  */
4
- import type { IServiceKeyStore, IAuthorizationConfig, IConnectionConfig, IConfig } from '@mcp-abap-adt/interfaces';
4
+ import type { IServiceKeyStore, IAuthorizationConfig, IConnectionConfig, IConfig, ILogger } from '@mcp-abap-adt/interfaces';
5
5
  /**
6
6
  * ABAP Service key store implementation
7
7
  *
@@ -10,11 +10,13 @@ import type { IServiceKeyStore, IAuthorizationConfig, IConnectionConfig, IConfig
10
10
  export declare class AbapServiceKeyStore implements IServiceKeyStore {
11
11
  private directory;
12
12
  private parser;
13
+ private log?;
13
14
  /**
14
15
  * Create a new AbapServiceKeyStore instance
15
16
  * @param directory Directory where service key .json files are located
17
+ * @param log Optional logger for logging operations
16
18
  */
17
- constructor(directory: string);
19
+ constructor(directory: string, log?: ILogger);
18
20
  /**
19
21
  * Get service key for destination
20
22
  * @param destination Destination name (e.g., "TRIAL")