arc-1 0.6.4 → 0.6.5
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/adt/crud.d.ts +1 -1
- package/dist/adt/crud.d.ts.map +1 -1
- package/dist/adt/crud.js +9 -2
- package/dist/adt/crud.js.map +1 -1
- package/dist/adt/ddic-xml.d.ts +17 -0
- package/dist/adt/ddic-xml.d.ts.map +1 -1
- package/dist/adt/ddic-xml.js +38 -3
- package/dist/adt/ddic-xml.js.map +1 -1
- package/dist/adt/devtools.d.ts +10 -2
- package/dist/adt/devtools.d.ts.map +1 -1
- package/dist/adt/devtools.js +169 -2
- package/dist/adt/devtools.js.map +1 -1
- package/dist/adt/errors.d.ts +19 -0
- package/dist/adt/errors.d.ts.map +1 -1
- package/dist/adt/errors.js +80 -0
- package/dist/adt/errors.js.map +1 -1
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +7 -0
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/types.d.ts +30 -0
- package/dist/adt/types.d.ts.map +1 -1
- package/dist/handlers/intent.d.ts +2 -0
- package/dist/handlers/intent.d.ts.map +1 -1
- package/dist/handlers/intent.js +253 -33
- package/dist/handlers/intent.js.map +1 -1
- package/dist/handlers/schemas.d.ts +25 -0
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +12 -1
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +65 -17
- package/dist/handlers/tools.js.map +1 -1
- package/dist/lint/config-builder.d.ts.map +1 -1
- package/dist/lint/config-builder.js +2 -0
- package/dist/lint/config-builder.js.map +1 -1
- package/dist/lint/lint.d.ts.map +1 -1
- package/dist/lint/lint.js +30 -9
- package/dist/lint/lint.js.map +1 -1
- package/dist/lint/presets/cloud.d.ts.map +1 -1
- package/dist/lint/presets/cloud.js +9 -0
- package/dist/lint/presets/cloud.js.map +1 -1
- package/dist/lint/presets/onprem.d.ts.map +1 -1
- package/dist/lint/presets/onprem.js +9 -0
- package/dist/lint/presets/onprem.js.map +1 -1
- package/dist/server/server.d.ts +1 -1
- package/dist/server/server.js +1 -1
- package/dist/server/xsuaa.d.ts +13 -8
- package/dist/server/xsuaa.d.ts.map +1 -1
- package/dist/server/xsuaa.js +51 -12
- package/dist/server/xsuaa.js.map +1 -1
- package/package.json +1 -1
package/dist/handlers/intent.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { findDefinition, findReferences, findWhereUsed, getCompletion, } from '../adt/codeintel.js';
|
|
13
13
|
import { createObject, deleteObject, lockObject, safeUpdateObject, safeUpdateSource, unlockObject, updateObject, } from '../adt/crud.js';
|
|
14
14
|
import { buildDataElementXml, buildDomainXml, buildMessageClassXml, buildPackageXml, buildServiceBindingXml, } from '../adt/ddic-xml.js';
|
|
15
|
-
import { activate, activateBatch, publishServiceBinding, runAtcCheck, runUnitTests, syntaxCheck, unpublishServiceBinding, } from '../adt/devtools.js';
|
|
15
|
+
import { activate, activateBatch, applyFixProposal, getFixProposals, publishServiceBinding, runAtcCheck, runUnitTests, syntaxCheck, unpublishServiceBinding, } from '../adt/devtools.js';
|
|
16
16
|
import { getDump, getTraceDbAccesses, getTraceHitlist, getTraceStatements, listDumps, listTraces, } from '../adt/diagnostics.js';
|
|
17
17
|
import { AdtApiError, AdtNetworkError, AdtSafetyError, isNotFoundError } from '../adt/errors.js';
|
|
18
18
|
import { classifyTextSearchError, mapSapReleaseToAbaplintVersion, probeFeatures } from '../adt/features.js';
|
|
@@ -77,6 +77,8 @@ function textResult(text) {
|
|
|
77
77
|
function errorResult(message) {
|
|
78
78
|
return { content: [{ type: 'text', text: message }], isError: true };
|
|
79
79
|
}
|
|
80
|
+
const DDIC_SAVE_HINT_TYPES = new Set(['TABL', 'DDLS', 'BDEF', 'SRVD', 'SRVB', 'DDLX', 'DOMA', 'DTEL']);
|
|
81
|
+
const DDIC_POST_SAVE_CHECK_TYPES = new Set(['TABL', 'DDLS', 'BDEF', 'SRVD', 'SRVB', 'DDLX']);
|
|
80
82
|
// ─── Search Helpers ─────────────────────────────────────────────────
|
|
81
83
|
/**
|
|
82
84
|
* Transliterate non-ASCII characters in search queries.
|
|
@@ -122,6 +124,7 @@ function formatErrorForLLM(err, message, _tool, args) {
|
|
|
122
124
|
if (err instanceof AdtApiError) {
|
|
123
125
|
// Append additional SAP messages (line numbers, secondary errors) if available
|
|
124
126
|
const enriched = enrichWithSapDetails(err, message);
|
|
127
|
+
const argType = String(args.type ?? '').toUpperCase();
|
|
125
128
|
if (err.isNotFound) {
|
|
126
129
|
const name = String(args.name ?? '');
|
|
127
130
|
const type = String(args.type ?? '');
|
|
@@ -135,6 +138,10 @@ function formatErrorForLLM(err, message, _tool, args) {
|
|
|
135
138
|
if (transportHint) {
|
|
136
139
|
return `${enriched}\n\nHint: ${transportHint}`;
|
|
137
140
|
}
|
|
141
|
+
if ((err.statusCode === 400 || err.statusCode === 409) && DDIC_SAVE_HINT_TYPES.has(argType)) {
|
|
142
|
+
return (`${enriched}\n\nHint: DDIC save failed. Check the diagnostic details above for specific field or annotation errors. ` +
|
|
143
|
+
'Common fixes: add missing @AbapCatalog annotations, fix field type names, check key field definitions.');
|
|
144
|
+
}
|
|
138
145
|
// Server errors (500, 502, 503, etc.)
|
|
139
146
|
if (err.isServerError) {
|
|
140
147
|
return `${enriched}\n\nHint: SAP application server error (${err.statusCode}). This is often transient — wait 10-30 seconds and retry. If the error persists, check SAPDiagnose(action="dumps") for short dumps, or verify the SAP system is responding via SAPRead(type="SYSTEM").`;
|
|
@@ -156,18 +163,49 @@ function enrichWithSapDetails(err, message) {
|
|
|
156
163
|
if (extraMessages.length > 0) {
|
|
157
164
|
parts.push(`\nAdditional detail:\n${extraMessages.map((m) => ` - ${m}`).join('\n')}`);
|
|
158
165
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
const ddicDiagnostics = AdtApiError.formatDdicDiagnostics(err.responseBody);
|
|
167
|
+
if (ddicDiagnostics) {
|
|
168
|
+
parts.push(ddicDiagnostics);
|
|
169
|
+
// Skip raw Properties dump — DDIC diagnostics already include the structured
|
|
170
|
+
// T100KEY details (message ID, number, variables, line). Showing both would
|
|
171
|
+
// triplicate the same information.
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Surface line/column info from properties if present (non-DDIC errors only)
|
|
175
|
+
const lineInfo = props.LINE || props['T100KEY-NO'];
|
|
176
|
+
if (lineInfo || Object.keys(props).length > 0) {
|
|
177
|
+
const propStr = Object.entries(props)
|
|
178
|
+
.slice(0, 5) // Limit to avoid overwhelming output
|
|
179
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
180
|
+
.join(', ');
|
|
181
|
+
if (propStr)
|
|
182
|
+
parts.push(`Properties: ${propStr}`);
|
|
183
|
+
}
|
|
168
184
|
}
|
|
169
185
|
return parts.join('\n');
|
|
170
186
|
}
|
|
187
|
+
async function tryPostSaveSyntaxCheck(client, type, name) {
|
|
188
|
+
if (!DDIC_POST_SAVE_CHECK_TYPES.has(type.toUpperCase()))
|
|
189
|
+
return '';
|
|
190
|
+
try {
|
|
191
|
+
const checkResult = await syntaxCheck(client.http, client.safety, objectUrlForType(type, name), {
|
|
192
|
+
version: 'inactive',
|
|
193
|
+
});
|
|
194
|
+
if (!checkResult.hasErrors)
|
|
195
|
+
return '';
|
|
196
|
+
const errors = checkResult.messages.filter((msg) => msg.severity === 'error');
|
|
197
|
+
if (errors.length === 0)
|
|
198
|
+
return '';
|
|
199
|
+
const lines = errors.map((msg) => {
|
|
200
|
+
const line = msg.line ? `Line ${msg.line}` : 'Line ?';
|
|
201
|
+
return ` - ${line}: ${msg.text}`;
|
|
202
|
+
});
|
|
203
|
+
return `\nServer syntax check (inactive):\n${lines.join('\n')}`;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return '';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
171
209
|
/** Detect transport/corrNr failure signatures and return a remediation hint, or undefined if not transport-related. */
|
|
172
210
|
function getTransportHint(err) {
|
|
173
211
|
const body = (err.responseBody ?? '').toLowerCase();
|
|
@@ -263,6 +301,7 @@ export async function handleToolCall(client, config, toolName, args, authInfo, _
|
|
|
263
301
|
// and returns a proper error message with the probe reason when source_code search is unavailable
|
|
264
302
|
const schema = getToolSchema(toolName, isBtp);
|
|
265
303
|
if (schema) {
|
|
304
|
+
args = normalizeTypeArgsForValidation(toolName, args);
|
|
266
305
|
const parsed = schema.safeParse(args);
|
|
267
306
|
if (!parsed.success) {
|
|
268
307
|
const validationError = formatZodError(parsed.error, toolName);
|
|
@@ -397,7 +436,7 @@ const BTP_HINTS = {
|
|
|
397
436
|
TRAN: 'Transaction codes (TRAN) are not available on BTP ABAP Environment. Use SAPSearch to find apps and services instead.',
|
|
398
437
|
};
|
|
399
438
|
async function handleSAPRead(client, args, cachingLayer) {
|
|
400
|
-
const type = String(args.type ?? '');
|
|
439
|
+
const type = normalizeObjectType(String(args.type ?? ''));
|
|
401
440
|
const name = String(args.name ?? '');
|
|
402
441
|
// BTP: return helpful error for unavailable types
|
|
403
442
|
if (isBtpSystem() && BTP_HINTS[type]) {
|
|
@@ -574,7 +613,7 @@ async function handleSAPRead(client, args, cachingLayer) {
|
|
|
574
613
|
}
|
|
575
614
|
case 'API_STATE': {
|
|
576
615
|
// Determine object type for URL construction — use explicit objectType, infer from name, or error
|
|
577
|
-
const explicitType = String(args.objectType ?? '')
|
|
616
|
+
const explicitType = normalizeObjectType(String(args.objectType ?? ''));
|
|
578
617
|
const inferredType = explicitType || inferObjectType(name);
|
|
579
618
|
if (!inferredType) {
|
|
580
619
|
return errorResult(`Cannot infer object type from name "${name}". Please specify objectType explicitly (e.g., objectType="CLAS", "INTF", "PROG", "TABL", "DDLS", "FUGR", "DOMA", "DTEL", "SRVD", "SRVB", "BDEF").`);
|
|
@@ -692,7 +731,7 @@ async function handleSAPRead(client, args, cachingLayer) {
|
|
|
692
731
|
}
|
|
693
732
|
default:
|
|
694
733
|
return errorResult(`Unknown SAPRead type: "${type}". Supported types: PROG, CLAS, INTF, FUNC, FUGR, INCL, DDLS, DDLX, BDEF, SRVD, SRVB, TABL, VIEW, STRU, DOMA, DTEL, TRAN, TABLE_CONTENTS, DEVC, SOBJ, SYSTEM, COMPONENTS, MESSAGES, TEXT_ELEMENTS, VARIANTS, BSP, BSP_DEPLOY, API_STATE, INACTIVE_OBJECTS. ` +
|
|
695
|
-
'Tip:
|
|
734
|
+
'Tip: Type aliases are auto-normalized (e.g., DDLS/DF → DDLS, CLAS/OC → CLAS, PROG/P → PROG). ' +
|
|
696
735
|
'Do not pass a URI — use the "type" and "name" parameters instead.');
|
|
697
736
|
}
|
|
698
737
|
}
|
|
@@ -706,7 +745,7 @@ async function handleSAPSearch(client, args) {
|
|
|
706
745
|
return errorResult(`Source code search is not available on this SAP system. ${cachedFeatures.textSearch.reason ?? ''}` +
|
|
707
746
|
`\nUse SAPSearch with searchType="object" to search by object name instead, or use SAPQuery to search metadata tables.`);
|
|
708
747
|
}
|
|
709
|
-
const objectType = args.objectType;
|
|
748
|
+
const objectType = args.objectType ? normalizeObjectType(String(args.objectType)) : undefined;
|
|
710
749
|
const packageName = args.packageName;
|
|
711
750
|
try {
|
|
712
751
|
const results = await client.searchSource(rawQuery, maxResults, objectType, packageName);
|
|
@@ -939,6 +978,7 @@ function getMetadataWriteProperties(input) {
|
|
|
939
978
|
bindingType: input.bindingType,
|
|
940
979
|
category: input.category,
|
|
941
980
|
version: input.version,
|
|
981
|
+
odataVersion: input.odataVersion,
|
|
942
982
|
};
|
|
943
983
|
return props;
|
|
944
984
|
}
|
|
@@ -1016,6 +1056,7 @@ async function mergeMetadataWriteProperties(client, type, name, provided) {
|
|
|
1016
1056
|
bindingType: provided.bindingType ?? existing.bindingType,
|
|
1017
1057
|
category: provided.category ?? normalizeSrvbCategory(existing.bindingCategory),
|
|
1018
1058
|
version: provided.version ?? existing.serviceVersion,
|
|
1059
|
+
odataVersion: provided.odataVersion ?? existing.odataVersion,
|
|
1019
1060
|
};
|
|
1020
1061
|
}
|
|
1021
1062
|
}
|
|
@@ -1233,7 +1274,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
|
|
|
1233
1274
|
throw new Error('SRVB create/update requires "serviceDefinition" (referenced SRVD name).');
|
|
1234
1275
|
}
|
|
1235
1276
|
const categoryRaw = properties?.category;
|
|
1236
|
-
const category = categoryRaw === '1' || categoryRaw === 1 ? '1' : '0';
|
|
1277
|
+
const category = categoryRaw === '1' || categoryRaw === 1 ? '1' : categoryRaw === '0' || categoryRaw === 0 ? '0' : undefined;
|
|
1237
1278
|
const params = {
|
|
1238
1279
|
name,
|
|
1239
1280
|
description,
|
|
@@ -1242,6 +1283,7 @@ export function buildCreateXml(type, name, pkg, description, properties) {
|
|
|
1242
1283
|
bindingType: properties?.bindingType ? String(properties.bindingType) : undefined,
|
|
1243
1284
|
category,
|
|
1244
1285
|
version: properties?.version ? String(properties.version) : undefined,
|
|
1286
|
+
odataVersion: properties?.odataVersion ? String(properties.odataVersion) : undefined,
|
|
1245
1287
|
};
|
|
1246
1288
|
return buildServiceBindingXml(params);
|
|
1247
1289
|
}
|
|
@@ -1341,6 +1383,97 @@ function escapeXml(s) {
|
|
|
1341
1383
|
.replace(/>/g, '>');
|
|
1342
1384
|
}
|
|
1343
1385
|
// ─── Object URL Mapping ──────────────────────────────────────────────
|
|
1386
|
+
const SLASH_TYPE_MAP = {
|
|
1387
|
+
'PROG/P': 'PROG',
|
|
1388
|
+
'PROG/I': 'INCL',
|
|
1389
|
+
'CLAS/OC': 'CLAS',
|
|
1390
|
+
'CLAS/LI': 'CLAS',
|
|
1391
|
+
'INTF/OI': 'INTF',
|
|
1392
|
+
'FUNC/FM': 'FUNC',
|
|
1393
|
+
'FUGR/F': 'FUGR',
|
|
1394
|
+
'FUGR/FF': 'FUGR',
|
|
1395
|
+
'DDLS/DF': 'DDLS',
|
|
1396
|
+
'BDEF/BDO': 'BDEF',
|
|
1397
|
+
'SRVD/SRV': 'SRVD',
|
|
1398
|
+
'SRVB/SVB': 'SRVB',
|
|
1399
|
+
'DDLX/EX': 'DDLX',
|
|
1400
|
+
'TABL/DT': 'TABL',
|
|
1401
|
+
'STRU/DS': 'STRU',
|
|
1402
|
+
'DOMA/DD': 'DOMA',
|
|
1403
|
+
'DTEL/DE': 'DTEL',
|
|
1404
|
+
'MSAG/N': 'MSAG',
|
|
1405
|
+
'DEVC/K': 'DEVC',
|
|
1406
|
+
'TRAN/O': 'TRAN',
|
|
1407
|
+
'VIEW/V': 'VIEW',
|
|
1408
|
+
};
|
|
1409
|
+
/** Normalize ADT type codes and aliases to ARC-1 canonical short types. */
|
|
1410
|
+
export function normalizeObjectType(type) {
|
|
1411
|
+
const normalized = String(type).trim().toUpperCase();
|
|
1412
|
+
if (!normalized)
|
|
1413
|
+
return '';
|
|
1414
|
+
return SLASH_TYPE_MAP[normalized] ?? normalized;
|
|
1415
|
+
}
|
|
1416
|
+
/** Normalize type fields before schema validation so slash/case aliases are accepted. */
|
|
1417
|
+
function normalizeTypeArgsForValidation(toolName, args) {
|
|
1418
|
+
switch (toolName) {
|
|
1419
|
+
case 'SAPRead':
|
|
1420
|
+
return {
|
|
1421
|
+
...args,
|
|
1422
|
+
type: normalizeObjectType(String(args.type ?? '')),
|
|
1423
|
+
objectType: args.objectType === undefined ? undefined : normalizeObjectType(String(args.objectType ?? '')),
|
|
1424
|
+
};
|
|
1425
|
+
case 'SAPWrite':
|
|
1426
|
+
return {
|
|
1427
|
+
...args,
|
|
1428
|
+
type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
|
|
1429
|
+
objects: Array.isArray(args.objects)
|
|
1430
|
+
? args.objects.map((obj) => typeof obj === 'object' && obj !== null
|
|
1431
|
+
? {
|
|
1432
|
+
...obj,
|
|
1433
|
+
type: normalizeObjectType(String(obj.type ?? '')),
|
|
1434
|
+
}
|
|
1435
|
+
: obj)
|
|
1436
|
+
: args.objects,
|
|
1437
|
+
};
|
|
1438
|
+
case 'SAPActivate':
|
|
1439
|
+
return {
|
|
1440
|
+
...args,
|
|
1441
|
+
type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
|
|
1442
|
+
objects: Array.isArray(args.objects)
|
|
1443
|
+
? args.objects.map((obj) => typeof obj === 'object' && obj !== null
|
|
1444
|
+
? {
|
|
1445
|
+
...obj,
|
|
1446
|
+
type: normalizeObjectType(String(obj.type ?? '')),
|
|
1447
|
+
}
|
|
1448
|
+
: obj)
|
|
1449
|
+
: args.objects,
|
|
1450
|
+
};
|
|
1451
|
+
case 'SAPSearch':
|
|
1452
|
+
return {
|
|
1453
|
+
...args,
|
|
1454
|
+
objectType: args.objectType === undefined ? undefined : normalizeObjectType(String(args.objectType ?? '')),
|
|
1455
|
+
};
|
|
1456
|
+
case 'SAPNavigate':
|
|
1457
|
+
// Only normalize `type` (for URL building). `objectType` is passed to SAP's
|
|
1458
|
+
// where-used scope API in slash format (e.g., CLAS/OC) — normalizing it would break the filter.
|
|
1459
|
+
return {
|
|
1460
|
+
...args,
|
|
1461
|
+
type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
|
|
1462
|
+
};
|
|
1463
|
+
case 'SAPDiagnose':
|
|
1464
|
+
return {
|
|
1465
|
+
...args,
|
|
1466
|
+
type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
|
|
1467
|
+
};
|
|
1468
|
+
case 'SAPContext':
|
|
1469
|
+
return {
|
|
1470
|
+
...args,
|
|
1471
|
+
type: args.type === undefined ? undefined : normalizeObjectType(String(args.type ?? '')),
|
|
1472
|
+
};
|
|
1473
|
+
default:
|
|
1474
|
+
return args;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1344
1477
|
/** Base path for an object type. Returns path prefix without trailing name segment. */
|
|
1345
1478
|
function objectBasePath(type) {
|
|
1346
1479
|
switch (type) {
|
|
@@ -1413,10 +1546,11 @@ function sourceUrlForType(type, name) {
|
|
|
1413
1546
|
// ─── SAPWrite Handler ────────────────────────────────────────────────
|
|
1414
1547
|
async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
1415
1548
|
const action = String(args.action ?? '');
|
|
1416
|
-
const type = String(args.type ?? '');
|
|
1549
|
+
const type = normalizeObjectType(String(args.type ?? ''));
|
|
1417
1550
|
const name = String(args.name ?? '');
|
|
1418
1551
|
const source = String(args.source ?? '');
|
|
1419
1552
|
const transport = args.transport;
|
|
1553
|
+
const lintOverride = args.lintBeforeWrite;
|
|
1420
1554
|
// type and name are required for all actions except batch_create
|
|
1421
1555
|
if (action !== 'batch_create' && (!type || !name)) {
|
|
1422
1556
|
return errorResult('"type" and "name" are required for this action.');
|
|
@@ -1454,7 +1588,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1454
1588
|
if (cdsGuardUpdate)
|
|
1455
1589
|
return cdsGuardUpdate;
|
|
1456
1590
|
// Pre-write lint validation
|
|
1457
|
-
const lintWarnings = runPreWriteLint(source, type, name, config);
|
|
1591
|
+
const lintWarnings = runPreWriteLint(source, type, name, config, lintOverride);
|
|
1458
1592
|
if (lintWarnings.blocked)
|
|
1459
1593
|
return lintWarnings.result;
|
|
1460
1594
|
await safeUpdateSource(client.http, client.safety, objectUrl, srcUrl, source, transport);
|
|
@@ -1520,7 +1654,20 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1520
1654
|
// 'application/*' — the wildcard lets the SAP server resolve the correct
|
|
1521
1655
|
// handler (matching how ADT Eclipse and abap-adt-api send requests).
|
|
1522
1656
|
const contentType = createContentTypeForType(type);
|
|
1523
|
-
const
|
|
1657
|
+
const needsPackageParam = type === 'BDEF' || type === 'TABL';
|
|
1658
|
+
let result;
|
|
1659
|
+
try {
|
|
1660
|
+
result = await createObject(client.http, client.safety, createUrl, body, contentType, effectiveTransport, needsPackageParam ? pkg : undefined);
|
|
1661
|
+
}
|
|
1662
|
+
catch (createErr) {
|
|
1663
|
+
if (createErr instanceof AdtApiError && (createErr.statusCode === 400 || createErr.statusCode === 409)) {
|
|
1664
|
+
const syntaxDetail = await tryPostSaveSyntaxCheck(client, type, name);
|
|
1665
|
+
if (syntaxDetail) {
|
|
1666
|
+
createErr.message += syntaxDetail;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
throw createErr;
|
|
1670
|
+
}
|
|
1524
1671
|
if (isMetadataWriteType(type)) {
|
|
1525
1672
|
// SAP's DTEL POST ignores labels, searchHelp, etc. — they require a follow-up PUT.
|
|
1526
1673
|
// Use withStatefulSession directly (not safeUpdateObject) to keep the lock cycle
|
|
@@ -1561,7 +1708,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1561
1708
|
// Step 2: Write source code if provided
|
|
1562
1709
|
if (source) {
|
|
1563
1710
|
// Pre-write lint validation
|
|
1564
|
-
const lintWarnings = runPreWriteLint(source, type, name, config);
|
|
1711
|
+
const lintWarnings = runPreWriteLint(source, type, name, config, lintOverride);
|
|
1565
1712
|
if (lintWarnings.blocked) {
|
|
1566
1713
|
return textResult(`Created ${type} ${name} in package ${pkg}, but source was rejected by lint:\n${lintWarnings.result.content[0].text}`);
|
|
1567
1714
|
}
|
|
@@ -1595,7 +1742,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1595
1742
|
return errorResult(spliced.error ?? `Failed to splice method "${method}" in ${name}.`);
|
|
1596
1743
|
}
|
|
1597
1744
|
// Pre-write lint validation on the full spliced source
|
|
1598
|
-
const lintWarnings = runPreWriteLint(spliced.newSource, type, name, config);
|
|
1745
|
+
const lintWarnings = runPreWriteLint(spliced.newSource, type, name, config, lintOverride);
|
|
1599
1746
|
if (lintWarnings.blocked)
|
|
1600
1747
|
return lintWarnings.result;
|
|
1601
1748
|
// Write the full source back (existing lock/modify/unlock flow)
|
|
@@ -1639,7 +1786,8 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1639
1786
|
try {
|
|
1640
1787
|
// Use first object's URL for the transport check
|
|
1641
1788
|
const firstObj = objects[0];
|
|
1642
|
-
const
|
|
1789
|
+
const firstType = normalizeObjectType(String(firstObj?.type ?? ''));
|
|
1790
|
+
const firstUrl = objectUrlForType(firstType, String(firstObj?.name ?? ''));
|
|
1643
1791
|
const transportInfo = await getTransportInfo(client.http, client.safety, firstUrl, pkg, 'I');
|
|
1644
1792
|
if (transportInfo.lockedTransport) {
|
|
1645
1793
|
batchTransport = transportInfo.lockedTransport;
|
|
@@ -1665,7 +1813,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1665
1813
|
}
|
|
1666
1814
|
const results = [];
|
|
1667
1815
|
for (const obj of objects) {
|
|
1668
|
-
const objType = String(obj.type ?? '');
|
|
1816
|
+
const objType = normalizeObjectType(String(obj.type ?? ''));
|
|
1669
1817
|
const objName = String(obj.name ?? '');
|
|
1670
1818
|
const metadataObject = isMetadataWriteType(objType);
|
|
1671
1819
|
const objSource = obj.source ? String(obj.source) : undefined;
|
|
@@ -1685,7 +1833,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1685
1833
|
// Pre-validate source with lint BEFORE creating the object to avoid orphaned objects.
|
|
1686
1834
|
// Metadata objects (DOMA/DTEL) are XML-only and intentionally skip source lint.
|
|
1687
1835
|
if (!metadataObject && objSource) {
|
|
1688
|
-
const lintWarnings = runPreWriteLint(objSource, objType, objName, config);
|
|
1836
|
+
const lintWarnings = runPreWriteLint(objSource, objType, objName, config, lintOverride);
|
|
1689
1837
|
if (lintWarnings.blocked) {
|
|
1690
1838
|
results.push({
|
|
1691
1839
|
type: objType,
|
|
@@ -1702,7 +1850,19 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1702
1850
|
const objMetadataProps = getMetadataWriteProperties(obj);
|
|
1703
1851
|
const body = buildCreateXml(objType, objName, pkg, objDescription, objMetadataProps);
|
|
1704
1852
|
const contentType = createContentTypeForType(objType);
|
|
1705
|
-
|
|
1853
|
+
const needsPackageParam = objType === 'BDEF' || objType === 'TABL';
|
|
1854
|
+
try {
|
|
1855
|
+
await createObject(client.http, client.safety, createUrl, body, contentType, batchTransport, needsPackageParam ? pkg : undefined);
|
|
1856
|
+
}
|
|
1857
|
+
catch (createErr) {
|
|
1858
|
+
if (createErr instanceof AdtApiError && (createErr.statusCode === 400 || createErr.statusCode === 409)) {
|
|
1859
|
+
const syntaxDetail = await tryPostSaveSyntaxCheck(client, objType, objName);
|
|
1860
|
+
if (syntaxDetail) {
|
|
1861
|
+
createErr.message += syntaxDetail;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
throw createErr;
|
|
1865
|
+
}
|
|
1706
1866
|
// Step 1b: DTEL POST ignores labels — follow up with PUT on main session
|
|
1707
1867
|
if (objType === 'DTEL' && dtelNeedsPostCreateUpdate(objMetadataProps)) {
|
|
1708
1868
|
await client.http.withStatefulSession(async (session) => {
|
|
@@ -1749,7 +1909,7 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1749
1909
|
for (let i = results.length; i < objects.length; i++) {
|
|
1750
1910
|
const skipped = objects[i];
|
|
1751
1911
|
results.push({
|
|
1752
|
-
type: String(skipped
|
|
1912
|
+
type: normalizeObjectType(String(skipped?.type ?? '')),
|
|
1753
1913
|
name: String(skipped.name ?? ''),
|
|
1754
1914
|
status: 'failed',
|
|
1755
1915
|
error: 'skipped — stopped after previous failure',
|
|
@@ -1786,8 +1946,20 @@ async function handleSAPWrite(client, args, config, cachingLayer) {
|
|
|
1786
1946
|
* If lint itself throws (e.g., abaplint bug on unusual syntax), we don't block
|
|
1787
1947
|
* the write — we let the SAP server-side syntax check handle it instead.
|
|
1788
1948
|
*/
|
|
1789
|
-
function runPreWriteLint(source, type, name, config) {
|
|
1790
|
-
|
|
1949
|
+
function runPreWriteLint(source, type, name, config, perCallOverride) {
|
|
1950
|
+
// Per-call override takes precedence over server config
|
|
1951
|
+
const enabled = perCallOverride ?? config.lintBeforeWrite;
|
|
1952
|
+
if (!enabled || !source) {
|
|
1953
|
+
return { blocked: false };
|
|
1954
|
+
}
|
|
1955
|
+
// abaplint supports ABAP source (PROG/CLAS/INTF/FUNC/INCL) and CDS views (DDLS) via
|
|
1956
|
+
// its CDS parser. DDLS lint catches syntax errors (cds_parser_error) like missing commas,
|
|
1957
|
+
// wrong keywords, and invalid DDL constructs. BDEF/SRVD/SRVB/DDLX are silently ignored
|
|
1958
|
+
// by abaplint (no parser for those types — garbage passes without errors). TABL (define
|
|
1959
|
+
// table syntax) is not supported by the CDS parser and produces false cds_parser_error.
|
|
1960
|
+
// For unsupported types, SAP server-side compilation handles validation.
|
|
1961
|
+
const LINTABLE_TYPES = new Set(['PROG', 'CLAS', 'INTF', 'FUNC', 'INCL', 'DDLS']);
|
|
1962
|
+
if (!LINTABLE_TYPES.has(type)) {
|
|
1791
1963
|
return { blocked: false };
|
|
1792
1964
|
}
|
|
1793
1965
|
try {
|
|
@@ -1894,12 +2066,12 @@ async function handleSAPActivate(client, args) {
|
|
|
1894
2066
|
return textResult(`Successfully unpublished service binding ${name}.${srvbInfo ? `\n\n${srvbInfo}` : ''}`);
|
|
1895
2067
|
}
|
|
1896
2068
|
// Batch activation: multiple objects at once (for RAP stacks etc.)
|
|
1897
|
-
const type = String(args.type ?? '');
|
|
2069
|
+
const type = normalizeObjectType(String(args.type ?? ''));
|
|
1898
2070
|
const preaudit = args.preaudit !== undefined ? Boolean(args.preaudit) : undefined;
|
|
1899
2071
|
const activateOpts = preaudit !== undefined ? { preaudit } : undefined;
|
|
1900
2072
|
if (args.objects && Array.isArray(args.objects)) {
|
|
1901
2073
|
const objects = args.objects.map((o) => {
|
|
1902
|
-
const objType = String(o.type ?? type);
|
|
2074
|
+
const objType = normalizeObjectType(String(o.type ?? type));
|
|
1903
2075
|
const objName = String(o.name ?? '');
|
|
1904
2076
|
return { url: objectUrlForType(objType, objName), name: objName };
|
|
1905
2077
|
});
|
|
@@ -1955,7 +2127,7 @@ async function handleSAPNavigate(client, args) {
|
|
|
1955
2127
|
const source = String(args.source ?? '');
|
|
1956
2128
|
// Allow symbolic type+name as alternative to uri for references
|
|
1957
2129
|
if (!uri && args.type && args.name) {
|
|
1958
|
-
const symType = String(args.type);
|
|
2130
|
+
const symType = normalizeObjectType(String(args.type));
|
|
1959
2131
|
const symName = String(args.name);
|
|
1960
2132
|
if (symType === 'FUNC') {
|
|
1961
2133
|
// FUNC needs group to build URL — auto-resolve it
|
|
@@ -1986,6 +2158,8 @@ async function handleSAPNavigate(client, args) {
|
|
|
1986
2158
|
if (!uri) {
|
|
1987
2159
|
return errorResult('Provide uri or type+name to find references.');
|
|
1988
2160
|
}
|
|
2161
|
+
// objectType is passed to SAP's where-used scope API which expects slash format (CLAS/OC, PROG/P).
|
|
2162
|
+
// Do NOT normalize it — the slash suffix is semantically meaningful for the SAP filter.
|
|
1989
2163
|
const objectType = args.objectType ? String(args.objectType) : undefined;
|
|
1990
2164
|
let results;
|
|
1991
2165
|
try {
|
|
@@ -2083,7 +2257,7 @@ async function handleSAPNavigate(client, args) {
|
|
|
2083
2257
|
async function handleSAPDiagnose(client, args) {
|
|
2084
2258
|
const action = String(args.action ?? '');
|
|
2085
2259
|
const name = String(args.name ?? '');
|
|
2086
|
-
const type = String(args.type ?? '');
|
|
2260
|
+
const type = normalizeObjectType(String(args.type ?? ''));
|
|
2087
2261
|
switch (action) {
|
|
2088
2262
|
case 'syntax': {
|
|
2089
2263
|
const objectUrl = objectUrlForType(type, name);
|
|
@@ -2101,6 +2275,52 @@ async function handleSAPDiagnose(client, args) {
|
|
|
2101
2275
|
const result = await runAtcCheck(client.http, client.safety, objectUrl, variant);
|
|
2102
2276
|
return textResult(JSON.stringify(result, null, 2));
|
|
2103
2277
|
}
|
|
2278
|
+
case 'quickfix': {
|
|
2279
|
+
const source = args.source;
|
|
2280
|
+
if (!name || !type)
|
|
2281
|
+
return errorResult('"name" and "type" are required for "quickfix" action.');
|
|
2282
|
+
if (!source)
|
|
2283
|
+
return errorResult('"source" is required for "quickfix" action.');
|
|
2284
|
+
if (args.line == null)
|
|
2285
|
+
return errorResult('"line" is required for "quickfix" action.');
|
|
2286
|
+
const line = Number(args.line);
|
|
2287
|
+
const column = Number(args.column ?? 0);
|
|
2288
|
+
if (!Number.isFinite(line))
|
|
2289
|
+
return errorResult('"line" must be a number for "quickfix" action.');
|
|
2290
|
+
if (!Number.isFinite(column))
|
|
2291
|
+
return errorResult('"column" must be a number for "quickfix" action.');
|
|
2292
|
+
const proposals = await getFixProposals(client.http, client.safety, sourceUrlForType(type, name), source, line, column);
|
|
2293
|
+
return textResult(JSON.stringify(proposals, null, 2));
|
|
2294
|
+
}
|
|
2295
|
+
case 'apply_quickfix': {
|
|
2296
|
+
const source = args.source;
|
|
2297
|
+
const proposalUri = args.proposalUri;
|
|
2298
|
+
const proposalUserContent = args.proposalUserContent;
|
|
2299
|
+
if (!name || !type)
|
|
2300
|
+
return errorResult('"name" and "type" are required for "apply_quickfix" action.');
|
|
2301
|
+
if (!source)
|
|
2302
|
+
return errorResult('"source" is required for "apply_quickfix" action.');
|
|
2303
|
+
if (args.line == null)
|
|
2304
|
+
return errorResult('"line" is required for "apply_quickfix" action.');
|
|
2305
|
+
if (!proposalUri)
|
|
2306
|
+
return errorResult('"proposalUri" is required for "apply_quickfix" action.');
|
|
2307
|
+
if (!proposalUserContent)
|
|
2308
|
+
return errorResult('"proposalUserContent" is required for "apply_quickfix" action.');
|
|
2309
|
+
const line = Number(args.line);
|
|
2310
|
+
const column = Number(args.column ?? 0);
|
|
2311
|
+
if (!Number.isFinite(line))
|
|
2312
|
+
return errorResult('"line" must be a number for "apply_quickfix" action.');
|
|
2313
|
+
if (!Number.isFinite(column))
|
|
2314
|
+
return errorResult('"column" must be a number for "apply_quickfix" action.');
|
|
2315
|
+
const deltas = await applyFixProposal(client.http, client.safety, {
|
|
2316
|
+
uri: proposalUri,
|
|
2317
|
+
type: 'quickfix/proposal',
|
|
2318
|
+
name: '',
|
|
2319
|
+
description: '',
|
|
2320
|
+
userContent: proposalUserContent,
|
|
2321
|
+
}, sourceUrlForType(type, name), source, line, column);
|
|
2322
|
+
return textResult(JSON.stringify(deltas, null, 2));
|
|
2323
|
+
}
|
|
2104
2324
|
case 'dumps': {
|
|
2105
2325
|
const id = args.id;
|
|
2106
2326
|
if (id) {
|
|
@@ -2141,7 +2361,7 @@ async function handleSAPDiagnose(client, args) {
|
|
|
2141
2361
|
return textResult(JSON.stringify(traces, null, 2));
|
|
2142
2362
|
}
|
|
2143
2363
|
default:
|
|
2144
|
-
return errorResult(`Unknown SAPDiagnose action: ${action}. Supported: syntax, unittest, atc, dumps, traces`);
|
|
2364
|
+
return errorResult(`Unknown SAPDiagnose action: ${action}. Supported: syntax, unittest, atc, quickfix, apply_quickfix, dumps, traces`);
|
|
2145
2365
|
}
|
|
2146
2366
|
}
|
|
2147
2367
|
// ─── SAPTransport Handler ────────────────────────────────────────────
|
|
@@ -2240,7 +2460,7 @@ async function handleSAPTransport(client, args) {
|
|
|
2240
2460
|
// ─── SAPContext Handler ───────────────────────────────────────────────
|
|
2241
2461
|
async function handleSAPContext(client, args, cachingLayer) {
|
|
2242
2462
|
const action = String(args.action ?? '');
|
|
2243
|
-
const type = String(args.type ?? '');
|
|
2463
|
+
const type = normalizeObjectType(String(args.type ?? ''));
|
|
2244
2464
|
const name = String(args.name ?? '');
|
|
2245
2465
|
const maxDeps = Number(args.maxDeps ?? 20);
|
|
2246
2466
|
const depth = Math.min(Math.max(Number(args.depth ?? 1), 1), 3);
|