arc-1 0.9.8 → 0.9.9

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 (53) hide show
  1. package/dist/adt/client.d.ts +28 -1
  2. package/dist/adt/client.d.ts.map +1 -1
  3. package/dist/adt/client.js +75 -2
  4. package/dist/adt/client.js.map +1 -1
  5. package/dist/adt/ddic-xml.d.ts +18 -0
  6. package/dist/adt/ddic-xml.d.ts.map +1 -1
  7. package/dist/adt/ddic-xml.js +21 -4
  8. package/dist/adt/ddic-xml.js.map +1 -1
  9. package/dist/adt/devtools.d.ts.map +1 -1
  10. package/dist/adt/devtools.js +29 -13
  11. package/dist/adt/devtools.js.map +1 -1
  12. package/dist/adt/http.d.ts +4 -0
  13. package/dist/adt/http.d.ts.map +1 -1
  14. package/dist/adt/http.js +8 -0
  15. package/dist/adt/http.js.map +1 -1
  16. package/dist/adt/transport.d.ts +75 -2
  17. package/dist/adt/transport.d.ts.map +1 -1
  18. package/dist/adt/transport.js +148 -2
  19. package/dist/adt/transport.js.map +1 -1
  20. package/dist/adt/types.d.ts +20 -0
  21. package/dist/adt/types.d.ts.map +1 -1
  22. package/dist/adt/xml-parser.d.ts +11 -5
  23. package/dist/adt/xml-parser.d.ts.map +1 -1
  24. package/dist/adt/xml-parser.js +25 -10
  25. package/dist/adt/xml-parser.js.map +1 -1
  26. package/dist/authz/policy.d.ts.map +1 -1
  27. package/dist/authz/policy.js +2 -0
  28. package/dist/authz/policy.js.map +1 -1
  29. package/dist/handlers/intent.d.ts +1 -1
  30. package/dist/handlers/intent.d.ts.map +1 -1
  31. package/dist/handlers/intent.js +166 -36
  32. package/dist/handlers/intent.js.map +1 -1
  33. package/dist/handlers/schemas.d.ts +4 -0
  34. package/dist/handlers/schemas.d.ts.map +1 -1
  35. package/dist/handlers/schemas.js +15 -1
  36. package/dist/handlers/schemas.js.map +1 -1
  37. package/dist/handlers/tools.d.ts.map +1 -1
  38. package/dist/handlers/tools.js +14 -4
  39. package/dist/handlers/tools.js.map +1 -1
  40. package/dist/server/auth-rate-limit.d.ts +2 -0
  41. package/dist/server/auth-rate-limit.d.ts.map +1 -1
  42. package/dist/server/auth-rate-limit.js +9 -2
  43. package/dist/server/auth-rate-limit.js.map +1 -1
  44. package/dist/server/http.d.ts.map +1 -1
  45. package/dist/server/http.js +86 -11
  46. package/dist/server/http.js.map +1 -1
  47. package/dist/server/server.d.ts +1 -1
  48. package/dist/server/server.js +1 -1
  49. package/dist/server/xsuaa.d.ts +14 -0
  50. package/dist/server/xsuaa.d.ts.map +1 -1
  51. package/dist/server/xsuaa.js +21 -6
  52. package/dist/server/xsuaa.js.map +1 -1
  53. package/package.json +10 -10
@@ -14,7 +14,7 @@ import { buildSiblingExtensionFinding, classifyCdsImpact, deriveSiblingStem, isS
14
14
  import { diffMethodSets, extractMethodNameFromClause, findSectionAnchor, insertMethodPair, moveMethodDefinition, removeMethodPair, spliceClassDefinition, spliceMethodSignature, } from '../adt/class-structure.js';
15
15
  import { findDefinition, findInterfaceImplementersViaSeoMetaRel, findReferences, findWhereUsed, getCompletion, getWhereUsedScope, } from '../adt/codeintel.js';
16
16
  import { createObject, deleteObject, lockObject, safeUpdateClassInclude, safeUpdateObject, safeUpdateSource, unlockObject, updateObject, updateSource, } from '../adt/crud.js';
17
- import { buildDataElementXml, buildDomainXml, buildMessageClassXml, buildPackageXml, buildServiceBindingXml, decodeKtdText, rewriteKtdText, } from '../adt/ddic-xml.js';
17
+ import { buildDataElementXml, buildDomainXml, buildMessageClassXml, buildPackageXml, buildServiceBindingXml, decodeKtdText, normalizeAdtLanguage, rewriteKtdText, } from '../adt/ddic-xml.js';
18
18
  import { activate, activateBatch, applyFixProposal, getFixProposals, getPrettyPrinterSettings, prettyPrint, publishServiceBinding, runAtcCheck, runUnitTests, setPrettyPrinterSettings, syntaxCheck, unpublishServiceBinding, } from '../adt/devtools.js';
19
19
  import { getDump, getGatewayErrorDetail, getObjectState, getTraceDbAccesses, getTraceHitlist, getTraceStatements, listDumps, listGatewayErrors, listSystemMessages, listTraces, } from '../adt/diagnostics.js';
20
20
  import { AdtApiError, AdtNetworkError, AdtSafetyError, classifySapDomainError, isNotFoundError, } from '../adt/errors.js';
@@ -27,7 +27,7 @@ import { applyRapHandlerScaffold, extractRapHandlerRequirements, findMissingRapH
27
27
  import { formatRapPreflightFindings, validateRapSource } from '../adt/rap-preflight.js';
28
28
  import { changePackage } from '../adt/refactoring.js';
29
29
  import { checkOperation, checkPackage, isOperationAllowed, OperationType } from '../adt/safety.js';
30
- import { createTransport, deleteTransport, getObjectTransports, getTransport, getTransportInfo, listTransports, reassignTransport, releaseTransport, releaseTransportRecursive, } from '../adt/transport.js';
30
+ import { createTransport, createTransportWithTarget, deleteTransport, getObjectTransports, getTransport, getTransportInfo, listTransportLayers, listTransports, listTransportTargets, reassignTransport, releaseTransport, releaseTransportRecursive, supportsExplicitTransportTarget, } from '../adt/transport.js';
31
31
  import { getAppInfo } from '../adt/ui5-repository.js';
32
32
  import { validateAffHeader } from '../aff/validator.js';
33
33
  import { extractCdsDependencies, extractCdsElements } from '../context/cds-deps.js';
@@ -1248,22 +1248,18 @@ async function handleSAPRead(client, args, cachingLayer) {
1248
1248
  case 'FUGR': {
1249
1249
  const expand = Boolean(args.expand_includes);
1250
1250
  if (expand) {
1251
- const { source } = await client.getFunctionGroupSource(name, { version: effectiveVersion });
1252
- // Match INCLUDE statements but skip ABAP comment lines (starting with *)
1253
- const includePattern = /^[^*\n]*\bINCLUDE\s+(\S+)\s*\./gim;
1254
- const parts = [`=== FUGR ${name} (main) ===\n${source}`];
1255
- let m;
1256
- while ((m = includePattern.exec(source)) !== null) {
1257
- const inclName = m[1];
1258
- try {
1259
- const { source: inclSource } = await client.getInclude(inclName, { version: effectiveVersion });
1260
- parts.push(`\n=== ${inclName} ===\n${inclSource}`);
1261
- }
1262
- catch {
1263
- parts.push(`\n=== ${inclName} ===\n[Could not read include "${inclName}"]`);
1264
- }
1251
+ // Recursive expansion: the function module bodies (FUNCTION…ENDFUNCTION) and
1252
+ // PBO/PAI modules live in nested includes (LZ<grp>U01, …O…, …I…) pulled in from
1253
+ // the UXX include — a one-level walk misses them. getFunctionGroupExpanded BFS-es
1254
+ // the include graph (depth/count-capped, cycle-guarded). Dynpros + GUI status are
1255
+ // not included: ADT doesn't expose them over REST (SAPGUI-only).
1256
+ const { blocks, truncated } = await client.getFunctionGroupExpanded(name, { version: effectiveVersion });
1257
+ const parts = blocks.map((b) => `=== ${b.name} ===\n${b.source}`);
1258
+ if (truncated) {
1259
+ parts.push('=== [truncated] ===\nInclude cap reached; some nested includes were not expanded. ' +
1260
+ 'Read remaining includes individually with SAPRead(type="INCL", name="...").');
1265
1261
  }
1266
- return textResult(parts.join('\n'));
1262
+ return textResult(parts.join('\n\n'));
1267
1263
  }
1268
1264
  const fg = await client.getFunctionGroup(name);
1269
1265
  return textResult(JSON.stringify(fg, null, 2));
@@ -2326,7 +2322,12 @@ export function warnCdsReservedKeywords(source) {
2326
2322
  return (`Warning: field name(s) ${fieldNames.map((f) => `'${f}'`).join(', ')} may be CDS reserved keywords. ` +
2327
2323
  `If the DDL save fails with a generic syntax error, rename them (e.g., 'position' → 'playing_position', 'type' → 'obj_type').`);
2328
2324
  }
2329
- export function buildCreateXml(type, name, pkg, description, properties) {
2325
+ export function buildCreateXml(type, name, pkg, description, properties, language) {
2326
+ // Master/original language for the created object. Derived from the configured
2327
+ // SAP_LANGUAGE (passed by callers as config.language) so the create-XML body
2328
+ // matches the sap-language URL param ARC-1 already sends. Defaults to "EN" when
2329
+ // unset, preserving legacy output. See issue #343.
2330
+ const masterLanguage = normalizeAdtLanguage(language);
2330
2331
  switch (type) {
2331
2332
  case 'PROG':
2332
2333
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -2335,7 +2336,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2335
2336
  adtcore:description="${escapeXml(description)}"
2336
2337
  adtcore:name="${escapeXml(name)}"
2337
2338
  adtcore:type="PROG/P"
2338
- adtcore:masterLanguage="EN"
2339
+ adtcore:masterLanguage="${masterLanguage}"
2339
2340
  adtcore:masterSystem="H00"
2340
2341
  adtcore:responsible="DEVELOPER">
2341
2342
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
@@ -2347,7 +2348,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2347
2348
  adtcore:description="${escapeXml(description)}"
2348
2349
  adtcore:name="${escapeXml(name)}"
2349
2350
  adtcore:type="CLAS/OC"
2350
- adtcore:masterLanguage="EN"
2351
+ adtcore:masterLanguage="${masterLanguage}"
2351
2352
  adtcore:masterSystem="H00"
2352
2353
  adtcore:responsible="DEVELOPER">
2353
2354
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
@@ -2359,7 +2360,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2359
2360
  adtcore:description="${escapeXml(description)}"
2360
2361
  adtcore:name="${escapeXml(name)}"
2361
2362
  adtcore:type="INTF/OI"
2362
- adtcore:masterLanguage="EN"
2363
+ adtcore:masterLanguage="${masterLanguage}"
2363
2364
  adtcore:masterSystem="H00"
2364
2365
  adtcore:responsible="DEVELOPER">
2365
2366
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
@@ -2371,7 +2372,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2371
2372
  adtcore:description="${escapeXml(description)}"
2372
2373
  adtcore:name="${escapeXml(name)}"
2373
2374
  adtcore:type="PROG/I"
2374
- adtcore:masterLanguage="EN"
2375
+ adtcore:masterLanguage="${masterLanguage}"
2375
2376
  adtcore:masterSystem="H00"
2376
2377
  adtcore:responsible="DEVELOPER">
2377
2378
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
@@ -2383,7 +2384,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2383
2384
  adtcore:description="${escapeXml(description)}"
2384
2385
  adtcore:name="${escapeXml(name)}"
2385
2386
  adtcore:type="DDLS/DF"
2386
- adtcore:masterLanguage="EN"
2387
+ adtcore:masterLanguage="${masterLanguage}"
2387
2388
  adtcore:masterSystem="H00"
2388
2389
  adtcore:responsible="DEVELOPER">
2389
2390
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
@@ -2395,7 +2396,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2395
2396
  adtcore:description="${escapeXml(description)}"
2396
2397
  adtcore:name="${escapeXml(name)}"
2397
2398
  adtcore:type="DCLS/DL"
2398
- adtcore:masterLanguage="EN"
2399
+ adtcore:masterLanguage="${masterLanguage}"
2399
2400
  adtcore:masterSystem="H00"
2400
2401
  adtcore:responsible="DEVELOPER">
2401
2402
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
@@ -2413,7 +2414,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2413
2414
  adtcore:description="${escapeXml(description)}"
2414
2415
  adtcore:name="${escapeXml(name)}"
2415
2416
  adtcore:type="${adtType}"
2416
- adtcore:masterLanguage="EN"
2417
+ adtcore:masterLanguage="${masterLanguage}"
2417
2418
  adtcore:masterSystem="H00"
2418
2419
  adtcore:responsible="DEVELOPER">
2419
2420
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
@@ -2428,7 +2429,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2428
2429
  adtcore:description="${escapeXml(description)}"
2429
2430
  adtcore:name="${escapeXml(name)}"
2430
2431
  adtcore:type="BDEF/BDO"
2431
- adtcore:masterLanguage="EN"
2432
+ adtcore:masterLanguage="${masterLanguage}"
2432
2433
  adtcore:masterSystem="H00"
2433
2434
  adtcore:responsible="DEVELOPER">
2434
2435
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
@@ -2440,7 +2441,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2440
2441
  adtcore:description="${escapeXml(description)}"
2441
2442
  adtcore:name="${escapeXml(name)}"
2442
2443
  adtcore:type="SRVD/SRV"
2443
- adtcore:masterLanguage="EN"
2444
+ adtcore:masterLanguage="${masterLanguage}"
2444
2445
  adtcore:masterSystem="H00"
2445
2446
  adtcore:responsible="DEVELOPER"
2446
2447
  srvd:srvdSourceType="S">
@@ -2462,6 +2463,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2462
2463
  category,
2463
2464
  version: properties?.version ? String(properties.version) : undefined,
2464
2465
  odataVersion: properties?.odataVersion ? String(properties.odataVersion) : undefined,
2466
+ language: masterLanguage,
2465
2467
  };
2466
2468
  return buildServiceBindingXml(params);
2467
2469
  }
@@ -2472,7 +2474,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2472
2474
  adtcore:description="${escapeXml(description)}"
2473
2475
  adtcore:name="${escapeXml(name)}"
2474
2476
  adtcore:type="DDLX/EX"
2475
- adtcore:masterLanguage="EN"
2477
+ adtcore:masterLanguage="${masterLanguage}"
2476
2478
  adtcore:masterSystem="H00"
2477
2479
  adtcore:responsible="DEVELOPER">
2478
2480
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
@@ -2499,6 +2501,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2499
2501
  lowercase: toBoolean(properties?.lowercase),
2500
2502
  fixedValues,
2501
2503
  valueTable: properties?.valueTable ? String(properties.valueTable) : undefined,
2504
+ language: masterLanguage,
2502
2505
  };
2503
2506
  return buildDomainXml(params);
2504
2507
  }
@@ -2524,6 +2527,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2524
2527
  setGetParameter: properties?.setGetParameter ? String(properties.setGetParameter) : undefined,
2525
2528
  defaultComponentName: properties?.defaultComponentName ? String(properties.defaultComponentName) : undefined,
2526
2529
  changeDocument: toBoolean(properties?.changeDocument),
2530
+ language: masterLanguage,
2527
2531
  };
2528
2532
  return buildDataElementXml(params);
2529
2533
  }
@@ -2548,7 +2552,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
2548
2552
  // with Content-Type: application/vnd.sap.adt.functions.groups.v3+xml.
2549
2553
  // Verified live on a4h S/4HANA 2023 (issue #250).
2550
2554
  return `<?xml version="1.0" encoding="UTF-8"?>
2551
- <group:abapFunctionGroup xmlns:group="http://www.sap.com/adt/functions/groups" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:description="${escapeXml(description)}" adtcore:language="EN" adtcore:name="${escapeXml(name)}" adtcore:type="FUGR/F" adtcore:masterLanguage="EN">
2555
+ <group:abapFunctionGroup xmlns:group="http://www.sap.com/adt/functions/groups" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:description="${escapeXml(description)}" adtcore:language="${masterLanguage}" adtcore:name="${escapeXml(name)}" adtcore:type="FUGR/F" adtcore:masterLanguage="${masterLanguage}">
2552
2556
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
2553
2557
  </group:abapFunctionGroup>`;
2554
2558
  case 'FUNC': {
@@ -3196,7 +3200,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
3196
3200
  const mergedProps = await mergeMetadataWriteProperties(client, type, name, metadataProps);
3197
3201
  const description = String(args.description ?? mergedProps._description ?? name);
3198
3202
  const pkg = String(args.package ?? existingPackage ?? mergedProps._package ?? '$TMP');
3199
- const body = buildCreateXml(type, name, pkg, description, mergedProps);
3203
+ const body = buildCreateXml(type, name, pkg, description, mergedProps, config.language);
3200
3204
  await safeUpdateObject(client.http, client.safety, objectUrl, body, vendorContentTypeForType(type), transport, cachedFeatures?.abapRelease);
3201
3205
  invalidateWrittenObject(type, name);
3202
3206
  return textResult(`Successfully updated ${type} ${name}.`);
@@ -3355,8 +3359,9 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
3355
3359
  // Build the parent URI. ADT URIs use lowercase names by convention (matches the Eclipse trace).
3356
3360
  const refParentType = refType.split('/')[0] ?? '';
3357
3361
  const refUri = `${objectBasePath(refParentType)}${encodeURIComponent(refName.toLowerCase())}`;
3362
+ const ktdLang = normalizeAdtLanguage(config.language);
3358
3363
  const ktdBody = `<?xml version="1.0" encoding="UTF-8"?>
3359
- <sktd:docu xmlns:sktd="http://www.sap.com/wbobj/texts/sktd" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:language="EN" adtcore:name="${escapeXml(name)}" adtcore:type="SKTD/TYP" adtcore:masterLanguage="EN">
3364
+ <sktd:docu xmlns:sktd="http://www.sap.com/wbobj/texts/sktd" xmlns:adtcore="http://www.sap.com/adt/core" adtcore:language="${ktdLang}" adtcore:name="${escapeXml(name)}" adtcore:type="SKTD/TYP" adtcore:masterLanguage="${ktdLang}">
3360
3365
  <adtcore:packageRef adtcore:name="${escapeXml(pkg)}"/>
3361
3366
  <sktd:refObject adtcore:description="${escapeXml(refDescription)}" adtcore:name="${escapeXml(refName)}" adtcore:type="${escapeXml(refType)}" adtcore:uri="${escapeXml(refUri)}"/>
3362
3367
  </sktd:docu>`;
@@ -3380,7 +3385,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
3380
3385
  // SAP ADT requires the root element to match the object type —
3381
3386
  // a generic objectReferences body returns 400 "System expected the element ...".
3382
3387
  const metadataProperties = getMetadataWriteProperties(args);
3383
- const body = buildCreateXml(type, name, pkg, description, metadataProperties);
3388
+ const body = buildCreateXml(type, name, pkg, description, metadataProperties, config.language);
3384
3389
  // Step 1: Create the object (metadata only)
3385
3390
  const createUrl = objectUrl.replace(/\/[^/]+$/, ''); // parent collection URL
3386
3391
  // DOMA/DTEL/BDEF require vendor-specific content types; all other types use
@@ -4278,7 +4283,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
4278
4283
  const objUrl = objectUrlForType(objType, objName);
4279
4284
  const createUrl = objUrl.replace(/\/[^/]+$/, '');
4280
4285
  const objMetadataProps = getMetadataWriteProperties(obj);
4281
- const body = buildCreateXml(objType, objName, objPackage, objDescription, objMetadataProps);
4286
+ const body = buildCreateXml(objType, objName, objPackage, objDescription, objMetadataProps, config.language);
4282
4287
  const contentType = createContentTypeForType(objType);
4283
4288
  const needsPackageParam = objType === 'BDEF' || objType === 'TABL' || objType === 'TABL/DT' || objType === 'TABL/DS';
4284
4289
  try {
@@ -5565,11 +5570,136 @@ async function handleSAPTransport(client, args) {
5565
5570
  const description = String(args.description ?? '');
5566
5571
  if (!description)
5567
5572
  return errorResult('Description is required for "create" action.');
5568
- const targetPackage = args.package ? String(args.package) : undefined;
5569
- const id = await createTransport(client.http, client.safety, description, targetPackage);
5573
+ // Distinguish "target omitted" from "target present but empty": an explicit but
5574
+ // blank target is a caller mistake, not a request to use the package/layer path.
5575
+ const targetProvided = args.target !== undefined && args.target !== null;
5576
+ const explicitTarget = targetProvided ? String(args.target).trim() : undefined;
5577
+ if (targetProvided && !explicitTarget) {
5578
+ return errorResult('"target" was provided but is empty. Pass a transport target — a system (C11), ' +
5579
+ 'system.client (C11.021), or target group (/GROUP/) — or omit target to let SAP resolve it from the package.');
5580
+ }
5581
+ // Shared guidance when this release's ADT stack can't set an explicit target.
5582
+ const targetUnsupportedMsg = "This system's ADT stack does not support setting an explicit transport target — the tm:root/newrequest " +
5583
+ 'action is not implemented (an ADT-framework limitation on SAP_BASIS 7.50, verified on SP02 and SP32, ' +
5584
+ 'independent of STMS/CTC config). It works on newer ABAP Platform / S/4HANA releases. Workaround here: ' +
5585
+ 'create the request without "target", then set the Transportziel manually in SE09/SE10 (SAP GUI), which ' +
5586
+ 'works when CTC and the target group are configured.';
5587
+ let id;
5588
+ if (explicitTarget) {
5589
+ // Discovery-gate first — the same capability SAP's own Eclipse client checks: the TM
5590
+ // resource that sets an explicit target is advertised in ADT discovery only on releases
5591
+ // that implement it (the transportorganizer Accept type on cts/transportrequests). When
5592
+ // discovery is loaded and the capability is absent (NW 7.50/7.51), fail fast with guidance
5593
+ // rather than POST a request the backend rejects with "user action is not supported".
5594
+ if (supportsExplicitTransportTarget(client.http) === false) {
5595
+ return errorResult(targetUnsupportedMsg);
5596
+ }
5597
+ // Explicit transport target (Transportziel / TR_TARGET): a system (C11),
5598
+ // system.client (C11.021), or target group (/TRG/). Routed via the tm:root/
5599
+ // newrequest endpoint — the only ADT path that sets the target directly. The
5600
+ // group and system.client forms require extended transport control (CTC).
5601
+ try {
5602
+ id = await createTransportWithTarget(client.http, client.safety, description, explicitTarget, client.username);
5603
+ }
5604
+ catch (err) {
5605
+ if (err instanceof AdtApiError && (err.statusCode === 400 || err.statusCode === 404)) {
5606
+ // SAP validates the target server-side: a4h (7.58) returns 400 "Target 'X'
5607
+ // does not exist"; other releases may use 404.
5608
+ if (/does not exist/i.test(err.message)) {
5609
+ return errorResult(`Transport target "${explicitTarget}" does not exist on this system. Valid targets are a ` +
5610
+ 'system (e.g. C11), system.client (C11.021), or a target group (/GROUP/) — the group and ' +
5611
+ 'system.client forms require extended transport control (CTC) to be active. Use ' +
5612
+ 'SAPTransport(action="targets") to list the targets this system actually offers.');
5613
+ }
5614
+ // Fallback for when discovery was not loaded: NW 7.50/7.51 reject tm:root/newrequest
5615
+ // with "user action is not supported" (the gate above pre-empts this when discovery is known).
5616
+ if (/user action/i.test(err.message) && /not supported/i.test(err.message)) {
5617
+ return errorResult(targetUnsupportedMsg);
5618
+ }
5619
+ }
5620
+ throw err;
5621
+ }
5622
+ }
5623
+ else {
5624
+ const targetPackage = args.package ? String(args.package) : undefined;
5625
+ const transportLayer = args.transportLayer ? String(args.transportLayer) : undefined;
5626
+ id = await createTransport(client.http, client.safety, description, targetPackage, undefined, transportLayer);
5627
+ }
5570
5628
  if (!id)
5571
5629
  return errorResult('Transport creation succeeded but no transport ID was returned. Check the SAP system manually.');
5572
- return textResult(`Created transport request: ${id}`);
5630
+ // Read the new request back (best-effort) to report its actual transport target.
5631
+ // An empty target means the request is local ("Local Change Requests") — the #1
5632
+ // source of "why does it always create a local transport?" confusion — so we
5633
+ // surface it explicitly instead of just echoing the ID.
5634
+ const created = await getTransport(client.http, client.safety, id).catch(() => null);
5635
+ const target = created?.target?.trim() ?? '';
5636
+ const targetDesc = created?.targetDesc?.trim() ?? '';
5637
+ if (!created)
5638
+ return textResult(`Created transport request: ${id}`);
5639
+ if (target) {
5640
+ return textResult(`Created transport request: ${id}\nTransport target: ${target}${targetDesc ? ` (${targetDesc})` : ''}`);
5641
+ }
5642
+ return textResult(`Created transport request: ${id}\n` +
5643
+ `Transport target: <none>${targetDesc ? ` — "${targetDesc}"` : ''}. This is a LOCAL request — it cannot be transported onward.\n\n` +
5644
+ 'To create a request that targets another system, either:\n' +
5645
+ ' • set an explicit target — pass target=<system | system.client | /group/> (e.g. target="/TRG/" or "C11"); ' +
5646
+ 'the group and system.client forms require extended transport control (CTC) to be active; or\n' +
5647
+ ' • let SAP resolve it from the package transport layer + STMS consolidation route (pass transportLayer=<layer> to override the layer).\n' +
5648
+ 'Both require the SAP system to actually have transport routes/targets configured (a Basis task). ' +
5649
+ 'On a standalone system with no routes, every request is local — expected, and no ADT/Eclipse/SE10 client can change it.');
5650
+ }
5651
+ case 'layers': {
5652
+ // Discovery: list valid values for create's `transportLayer`. Lets a client pick a
5653
+ // real layer instead of guessing. Read-only (no allowTransportWrites required).
5654
+ let layers;
5655
+ try {
5656
+ layers = await listTransportLayers(client.http, client.safety);
5657
+ }
5658
+ catch (err) {
5659
+ // The package transport-layer value help is 7.52+; NW 7.50/7.51 return 404
5660
+ // "No suitable resource found" (verified live on npl 7.50). Surface that clearly
5661
+ // instead of a raw 404, so the caller knows discovery is unavailable on this release.
5662
+ if (err instanceof AdtApiError && err.isNotFound) {
5663
+ return errorResult('Transport-layer discovery is not available on this SAP release — the value help ' +
5664
+ '(/sap/bc/adt/packages/valuehelps/transportlayers) returned 404 (typically NW < 7.52). ' +
5665
+ 'Create requests without transportLayer; the route/target is governed by the package + STMS on these releases.');
5666
+ }
5667
+ throw err;
5668
+ }
5669
+ const routed = layers.filter((l) => l.target);
5670
+ const summary = layers.length
5671
+ ? routed.length
5672
+ ? `${layers.length} transport layer(s); ${routed.length} carry a target. Pass one as transportLayer= on create.`
5673
+ : `${layers.length} transport layer(s), but none expose a consolidation target — created requests will be local on this system.`
5674
+ : 'No transport layers are defined on this system — every request will be local.';
5675
+ return textResult(JSON.stringify({ transportLayers: layers, summary }, null, 2));
5676
+ }
5677
+ case 'targets': {
5678
+ // Discovery: list valid values for create's `target` (Transportziel / TR_TARGET) via the
5679
+ // official ADT target value help. Read-only (no allowTransportWrites required).
5680
+ // Gate on the same capability as create's target path: NW 7.50 returns an empty list
5681
+ // (HTTP 200) from the value help rather than 404, so the discovery accept type — not the
5682
+ // HTTP status — is the reliable "is target discovery meaningful here?" signal.
5683
+ if (supportsExplicitTransportTarget(client.http) === false) {
5684
+ return errorResult("Transport-target discovery is not available on this SAP release — its ADT stack doesn't support " +
5685
+ 'explicit transport targets (verified on NW 7.50). Set the target in SE09/SE10 instead.');
5686
+ }
5687
+ let targets;
5688
+ try {
5689
+ targets = await listTransportTargets(client.http, client.safety);
5690
+ }
5691
+ catch (err) {
5692
+ // Fallback when discovery wasn't loaded: some releases 404 the value help.
5693
+ if (err instanceof AdtApiError && err.isNotFound) {
5694
+ return errorResult('Transport-target discovery is not available on this SAP release — the value help ' +
5695
+ '(/sap/bc/adt/cts/transportrequests/valuehelp/target) returned 404. Set the target in SE09/SE10 instead.');
5696
+ }
5697
+ throw err;
5698
+ }
5699
+ const summary = targets.length
5700
+ ? `${targets.length} valid transport target(s). Pass one as target= on create.`
5701
+ : 'No transport targets are configured on this system (so created requests are local).';
5702
+ return textResult(JSON.stringify({ transportTargets: targets, summary }, null, 2));
5573
5703
  }
5574
5704
  case 'release': {
5575
5705
  const id = String(args.id ?? '');