arc-1 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/dist/adt/atc-batch.d.ts +71 -0
- package/dist/adt/atc-batch.d.ts.map +1 -0
- package/dist/adt/atc-batch.js +182 -0
- package/dist/adt/atc-batch.js.map +1 -0
- package/dist/adt/atc.d.ts +34 -0
- package/dist/adt/atc.d.ts.map +1 -1
- package/dist/adt/atc.js +58 -12
- package/dist/adt/atc.js.map +1 -1
- package/dist/adt/aunit.d.ts.map +1 -1
- package/dist/adt/aunit.js +50 -7
- package/dist/adt/aunit.js.map +1 -1
- package/dist/adt/bounded-response.d.ts +4 -1
- package/dist/adt/bounded-response.d.ts.map +1 -1
- package/dist/adt/bounded-response.js +33 -7
- package/dist/adt/bounded-response.js.map +1 -1
- package/dist/adt/ci-quality-xml.d.ts +44 -0
- package/dist/adt/ci-quality-xml.d.ts.map +1 -0
- package/dist/adt/ci-quality-xml.js +98 -0
- package/dist/adt/ci-quality-xml.js.map +1 -0
- package/dist/adt/ci-quality.d.ts +64 -0
- package/dist/adt/ci-quality.d.ts.map +1 -0
- package/dist/adt/ci-quality.js +132 -0
- package/dist/adt/ci-quality.js.map +1 -0
- package/dist/adt/client.d.ts +20 -16
- package/dist/adt/client.d.ts.map +1 -1
- package/dist/adt/client.js +35 -70
- package/dist/adt/client.js.map +1 -1
- package/dist/adt/data-source-policy.d.ts +6 -15
- package/dist/adt/data-source-policy.d.ts.map +1 -1
- package/dist/adt/data-source-policy.js +18 -11
- package/dist/adt/data-source-policy.js.map +1 -1
- package/dist/adt/ddic-xml.d.ts +78 -4
- package/dist/adt/ddic-xml.d.ts.map +1 -1
- package/dist/adt/ddic-xml.js +647 -54
- package/dist/adt/ddic-xml.js.map +1 -1
- package/dist/adt/devtools.d.ts +1 -0
- package/dist/adt/devtools.d.ts.map +1 -1
- package/dist/adt/devtools.js +24 -4
- package/dist/adt/devtools.js.map +1 -1
- package/dist/adt/http-deadline.d.ts +5 -0
- package/dist/adt/http-deadline.d.ts.map +1 -1
- package/dist/adt/http-deadline.js +2 -2
- package/dist/adt/http-deadline.js.map +1 -1
- package/dist/adt/http.d.ts +6 -3
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +57 -15
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/internal-data-operations.d.ts +9 -4
- package/dist/adt/internal-data-operations.d.ts.map +1 -1
- package/dist/adt/internal-data-operations.js +11 -7
- package/dist/adt/internal-data-operations.js.map +1 -1
- package/dist/adt/package-contents.d.ts +12 -0
- package/dist/adt/package-contents.d.ts.map +1 -0
- package/dist/adt/package-contents.js +19 -0
- package/dist/adt/package-contents.js.map +1 -0
- package/dist/adt/relation-objects.d.ts +385 -0
- package/dist/adt/relation-objects.d.ts.map +1 -0
- package/dist/adt/relation-objects.js +262 -0
- package/dist/adt/relation-objects.js.map +1 -0
- package/dist/adt/repository-relations.d.ts +41 -0
- package/dist/adt/repository-relations.d.ts.map +1 -0
- package/dist/adt/repository-relations.js +246 -0
- package/dist/adt/repository-relations.js.map +1 -0
- package/dist/adt/request-attempt-budget.d.ts +26 -0
- package/dist/adt/request-attempt-budget.d.ts.map +1 -0
- package/dist/adt/request-attempt-budget.js +63 -0
- package/dist/adt/request-attempt-budget.js.map +1 -0
- package/dist/adt/result-limits.d.ts +3 -0
- package/dist/adt/result-limits.d.ts.map +1 -0
- package/dist/adt/result-limits.js +7 -0
- package/dist/adt/result-limits.js.map +1 -0
- package/dist/adt/server-driven.d.ts +6 -1
- package/dist/adt/server-driven.d.ts.map +1 -1
- package/dist/adt/server-driven.js +24 -20
- package/dist/adt/server-driven.js.map +1 -1
- package/dist/adt/table-query.d.ts +2 -0
- package/dist/adt/table-query.d.ts.map +1 -1
- package/dist/adt/table-query.js +61 -0
- package/dist/adt/table-query.js.map +1 -1
- package/dist/adt/text-elements.d.ts +48 -0
- package/dist/adt/text-elements.d.ts.map +1 -0
- package/dist/adt/text-elements.js +116 -0
- package/dist/adt/text-elements.js.map +1 -0
- package/dist/adt/types.d.ts +16 -0
- package/dist/adt/types.d.ts.map +1 -1
- package/dist/adt/uiad.d.ts +19 -0
- package/dist/adt/uiad.d.ts.map +1 -0
- package/dist/adt/uiad.js +173 -0
- package/dist/adt/uiad.js.map +1 -0
- package/dist/adt/xml-parser.d.ts +2 -0
- package/dist/adt/xml-parser.d.ts.map +1 -1
- package/dist/adt/xml-parser.js +20 -1
- package/dist/adt/xml-parser.js.map +1 -1
- package/dist/authz/policy.d.ts.map +1 -1
- package/dist/authz/policy.js +4 -1
- package/dist/authz/policy.js.map +1 -1
- package/dist/cache/cache.d.ts +3 -23
- package/dist/cache/cache.d.ts.map +1 -1
- package/dist/cache/cache.js +2 -3
- package/dist/cache/cache.js.map +1 -1
- package/dist/cache/caching-layer.d.ts +6 -25
- package/dist/cache/caching-layer.d.ts.map +1 -1
- package/dist/cache/caching-layer.js +3 -45
- package/dist/cache/caching-layer.js.map +1 -1
- package/dist/cache/memory.d.ts +1 -4
- package/dist/cache/memory.d.ts.map +1 -1
- package/dist/cache/memory.js +1 -10
- package/dist/cache/memory.js.map +1 -1
- package/dist/cache/sqlite.d.ts +1 -3
- package/dist/cache/sqlite.d.ts.map +1 -1
- package/dist/cache/sqlite.js +2 -17
- package/dist/cache/sqlite.js.map +1 -1
- package/dist/cli-checks.d.ts +3 -0
- package/dist/cli-checks.d.ts.map +1 -1
- package/dist/cli-checks.js +16 -0
- package/dist/cli-checks.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -2
- package/dist/cli.js.map +1 -1
- package/dist/context/compressor.d.ts +2 -3
- package/dist/context/compressor.d.ts.map +1 -1
- package/dist/context/compressor.js +30 -49
- package/dist/context/compressor.js.map +1 -1
- package/dist/context/contract.d.ts +5 -0
- package/dist/context/contract.d.ts.map +1 -1
- package/dist/context/contract.js +2 -2
- package/dist/context/contract.js.map +1 -1
- package/dist/context/deps.d.ts +11 -0
- package/dist/context/deps.d.ts.map +1 -1
- package/dist/context/deps.js +2 -2
- package/dist/context/deps.js.map +1 -1
- package/dist/context/parse-cache.d.ts +23 -0
- package/dist/context/parse-cache.d.ts.map +1 -0
- package/dist/context/parse-cache.js +71 -0
- package/dist/context/parse-cache.js.map +1 -0
- package/dist/context/relation-walk.d.ts +48 -0
- package/dist/context/relation-walk.d.ts.map +1 -0
- package/dist/context/relation-walk.js +137 -0
- package/dist/context/relation-walk.js.map +1 -0
- package/dist/context/types.d.ts +4 -4
- package/dist/context/types.d.ts.map +1 -1
- package/dist/handlers/activate.d.ts +1 -1
- package/dist/handlers/activate.d.ts.map +1 -1
- package/dist/handlers/activate.js +33 -56
- package/dist/handlers/activate.js.map +1 -1
- package/dist/handlers/activation-results.d.ts +3 -0
- package/dist/handlers/activation-results.d.ts.map +1 -0
- package/dist/handlers/activation-results.js +8 -0
- package/dist/handlers/activation-results.js.map +1 -0
- package/dist/handlers/context.d.ts.map +1 -1
- package/dist/handlers/context.js +8 -32
- package/dist/handlers/context.js.map +1 -1
- package/dist/handlers/data-element-fields.d.ts +26 -0
- package/dist/handlers/data-element-fields.d.ts.map +1 -0
- package/dist/handlers/data-element-fields.js +27 -0
- package/dist/handlers/data-element-fields.js.map +1 -0
- package/dist/handlers/diagnose-ci.d.ts +9 -0
- package/dist/handlers/diagnose-ci.d.ts.map +1 -0
- package/dist/handlers/diagnose-ci.js +120 -0
- package/dist/handlers/diagnose-ci.js.map +1 -0
- package/dist/handlers/diagnose-fields.d.ts +51 -0
- package/dist/handlers/diagnose-fields.d.ts.map +1 -0
- package/dist/handlers/diagnose-fields.js +48 -0
- package/dist/handlers/diagnose-fields.js.map +1 -0
- package/dist/handlers/diagnose.d.ts +4 -1
- package/dist/handlers/diagnose.d.ts.map +1 -1
- package/dist/handlers/diagnose.js +28 -6
- package/dist/handlers/diagnose.js.map +1 -1
- package/dist/handlers/dispatch.d.ts.map +1 -1
- package/dist/handlers/dispatch.js +22 -5
- package/dist/handlers/dispatch.js.map +1 -1
- package/dist/handlers/feature-cache.d.ts +4 -3
- package/dist/handlers/feature-cache.d.ts.map +1 -1
- package/dist/handlers/feature-cache.js +4 -3
- package/dist/handlers/feature-cache.js.map +1 -1
- package/dist/handlers/hyperfocused.js +1 -1
- package/dist/handlers/live-relations.d.ts +5 -0
- package/dist/handlers/live-relations.d.ts.map +1 -0
- package/dist/handlers/live-relations.js +89 -0
- package/dist/handlers/live-relations.js.map +1 -0
- package/dist/handlers/manage.d.ts.map +1 -1
- package/dist/handlers/manage.js +7 -4
- package/dist/handlers/manage.js.map +1 -1
- package/dist/handlers/navigate.d.ts +1 -1
- package/dist/handlers/navigate.d.ts.map +1 -1
- package/dist/handlers/navigate.js +11 -5
- package/dist/handlers/navigate.js.map +1 -1
- package/dist/handlers/object-types.d.ts +2 -0
- package/dist/handlers/object-types.d.ts.map +1 -1
- package/dist/handlers/object-types.js +41 -6
- package/dist/handlers/object-types.js.map +1 -1
- package/dist/handlers/query-errors.d.ts +9 -1
- package/dist/handlers/query-errors.d.ts.map +1 -1
- package/dist/handlers/query-errors.js +40 -28
- package/dist/handlers/query-errors.js.map +1 -1
- package/dist/handlers/query.d.ts +1 -1
- package/dist/handlers/query.d.ts.map +1 -1
- package/dist/handlers/query.js +2 -2
- package/dist/handlers/query.js.map +1 -1
- package/dist/handlers/read.d.ts +1 -1
- package/dist/handlers/read.d.ts.map +1 -1
- package/dist/handlers/read.js +48 -15
- package/dist/handlers/read.js.map +1 -1
- package/dist/handlers/relation-input.d.ts +42 -0
- package/dist/handlers/relation-input.d.ts.map +1 -0
- package/dist/handlers/relation-input.js +22 -0
- package/dist/handlers/relation-input.js.map +1 -0
- package/dist/handlers/relation-tool.d.ts +5 -0
- package/dist/handlers/relation-tool.d.ts.map +1 -0
- package/dist/handlers/relation-tool.js +36 -0
- package/dist/handlers/relation-tool.js.map +1 -0
- package/dist/handlers/schemas.d.ts +119 -62
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +150 -13
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/search.d.ts +1 -1
- package/dist/handlers/search.d.ts.map +1 -1
- package/dist/handlers/search.js +18 -5
- package/dist/handlers/search.js.map +1 -1
- package/dist/handlers/tool-descriptions.d.ts.map +1 -1
- package/dist/handlers/tool-descriptions.js +12 -13
- package/dist/handlers/tool-descriptions.js.map +1 -1
- package/dist/handlers/tool-registry.d.ts +1 -0
- package/dist/handlers/tool-registry.d.ts.map +1 -1
- package/dist/handlers/tool-registry.js +4 -0
- package/dist/handlers/tool-registry.js.map +1 -1
- package/dist/handlers/tools.d.ts +2 -3
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +127 -72
- package/dist/handlers/tools.js.map +1 -1
- package/dist/handlers/write/batch-results.d.ts +28 -0
- package/dist/handlers/write/batch-results.d.ts.map +1 -0
- package/dist/handlers/write/batch-results.js +106 -0
- package/dist/handlers/write/batch-results.js.map +1 -0
- package/dist/handlers/write/create-transport.d.ts +7 -0
- package/dist/handlers/write/create-transport.d.ts.map +1 -0
- package/dist/handlers/write/create-transport.js +38 -0
- package/dist/handlers/write/create-transport.js.map +1 -0
- package/dist/handlers/write/create.d.ts.map +1 -1
- package/dist/handlers/write/create.js +276 -413
- package/dist/handlers/write/create.js.map +1 -1
- package/dist/handlers/write/uiad.d.ts +8 -0
- package/dist/handlers/write/uiad.d.ts.map +1 -0
- package/dist/handlers/write/uiad.js +115 -0
- package/dist/handlers/write/uiad.js.map +1 -0
- package/dist/handlers/write/update-delete.d.ts +6 -6
- package/dist/handlers/write/update-delete.d.ts.map +1 -1
- package/dist/handlers/write/update-delete.js +46 -15
- package/dist/handlers/write/update-delete.js.map +1 -1
- package/dist/handlers/write-helpers.d.ts +0 -6
- package/dist/handlers/write-helpers.d.ts.map +1 -1
- package/dist/handlers/write-helpers.js +88 -80
- package/dist/handlers/write-helpers.js.map +1 -1
- package/dist/handlers/write.d.ts.map +1 -1
- package/dist/handlers/write.js +5 -1
- package/dist/handlers/write.js.map +1 -1
- package/dist/public/ui/app.js +0 -2
- package/dist/server/audit.d.ts +1 -1
- package/dist/server/audit.d.ts.map +1 -1
- package/dist/server/http.d.ts +1 -1
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +1 -0
- package/dist/server/http.js.map +1 -1
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/logger.js +4 -1
- package/dist/server/logger.js.map +1 -1
- package/dist/server/multi-target-tools.d.ts.map +1 -1
- package/dist/server/multi-target-tools.js +3 -1
- package/dist/server/multi-target-tools.js.map +1 -1
- package/dist/server/server-instructions.d.ts.map +1 -1
- package/dist/server/server-instructions.js +11 -5
- package/dist/server/server-instructions.js.map +1 -1
- package/dist/server/server.d.ts +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +18 -44
- package/dist/server/server.js.map +1 -1
- package/dist/server/shutdown.d.ts +6 -0
- package/dist/server/shutdown.d.ts.map +1 -0
- package/dist/server/shutdown.js +52 -0
- package/dist/server/shutdown.js.map +1 -0
- package/dist/server/sinks/btp-auditlog.d.ts +10 -8
- package/dist/server/sinks/btp-auditlog.d.ts.map +1 -1
- package/dist/server/sinks/btp-auditlog.js +76 -68
- package/dist/server/sinks/btp-auditlog.js.map +1 -1
- package/dist/server/sinks/file.d.ts +1 -1
- package/dist/server/sinks/file.d.ts.map +1 -1
- package/dist/server/sinks/file.js +12 -21
- package/dist/server/sinks/file.js.map +1 -1
- package/dist/server/ui.js +0 -2
- package/dist/server/ui.js.map +1 -1
- package/package.json +15 -13
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
* SAPWrite actions — create + batch_create.
|
|
3
3
|
*/
|
|
4
4
|
import { createObject, lockObject, safeUpdateObject, safeUpdateSource, unlockObject, updateObject, } from '../../adt/crud.js';
|
|
5
|
-
import { normalizeAdtLanguage,
|
|
5
|
+
import { formatKtdWriteReport, normalizeAdtLanguage, rewriteKtdDocument, } from '../../adt/ddic-xml.js';
|
|
6
6
|
import { activate, activateBatch } from '../../adt/devtools.js';
|
|
7
|
-
import { AdtApiError } from '../../adt/errors.js';
|
|
7
|
+
import { AdtApiError, AdtSafetyError } from '../../adt/errors.js';
|
|
8
8
|
import { spliceFmSignature } from '../../adt/fm-signature.js';
|
|
9
|
-
import { checkPackage } from '../../adt/safety.js';
|
|
9
|
+
import { checkOperation, checkPackage, OperationType } from '../../adt/safety.js';
|
|
10
10
|
import { isServerDrivenObjectType } from '../../adt/server-driven.js';
|
|
11
|
-
import { getTransport
|
|
11
|
+
import { getTransport } from '../../adt/transport.js';
|
|
12
12
|
import { escapeXmlAttr, parseFunctionModuleProperties } from '../../adt/xml-parser.js';
|
|
13
13
|
import { validateAffHeader } from '../../aff/validator.js';
|
|
14
|
-
import {
|
|
15
|
-
import { buildBatchActivationStatuses, formatBatchActivationStatuses, } from '../activate.js';
|
|
16
|
-
import { invalidateInactiveList } from '../cache-security.js';
|
|
14
|
+
import { activationDetailMatchesObject } from '../activation-results.js';
|
|
17
15
|
import { guardCdsSyntax } from '../cds-hints.js';
|
|
18
16
|
import { getCachedFeatures, isDomainsEndpointAvailable, isTablesEndpointAvailable, isTableTypesEndpointAvailable, } from '../feature-cache.js';
|
|
19
|
-
import { functionGroupObjectUrl, functionModuleObjectUrl,
|
|
17
|
+
import { canonicalTablType, functionGroupObjectUrl, functionModuleObjectUrl, normalizeWriteObjectType, objectBasePath, objectUrlForType, sourceUrlForType, } from '../object-types.js';
|
|
20
18
|
import { errorResult, textResult } from '../shared.js';
|
|
21
|
-
import { buildCreateXml, createContentTypeForType, DOMA_WRITE_UNAVAILABLE_HINT,
|
|
19
|
+
import { buildCreateXml, createContentTypeForType, DOMA_WRITE_UNAVAILABLE_HINT, getMetadataWriteProperties, isMetadataWriteType, mergePreWriteWarnings, resolveWriteSystemType, runPreWriteLint, runRapPreflightValidation, SKTD_V2_CONTENT_TYPE, stripFmParamCommentBlock, TABL_DT_WRITE_UNAVAILABLE_HINT, TTYP_WRITE_UNAVAILABLE_HINT, tryPostSaveSyntaxCheck, vendorContentTypeForType, } from '../write-helpers.js';
|
|
20
|
+
import { batchCreateResult, batchEntryResult, batchFailureMessage, failBatchEntry, } from './batch-results.js';
|
|
21
|
+
import { resolveCreateTransport } from './create-transport.js';
|
|
22
22
|
const FUNCTION_MODULE_MEDIA_TYPE = /^application\/vnd\.sap\.adt\.functions\.fmodules(?:\.v\d+)?\+xml\b/i;
|
|
23
23
|
function normalizePackageOverride(rawPackage, fallback) {
|
|
24
24
|
if (rawPackage === undefined || rawPackage === null) {
|
|
@@ -300,7 +300,7 @@ function isKtdCreateEndpointUnavailable(err) {
|
|
|
300
300
|
return combined.includes('resource') && combined.includes('/sap/bc/adt/documentation/ktd/documents');
|
|
301
301
|
}
|
|
302
302
|
export async function writeActionCreate(ctx) {
|
|
303
|
-
const { client, args, config, type, name, source, transport, lintOverride, preflightOverride, objectUrl, srcUrl, invalidateWrittenObject, } = ctx;
|
|
303
|
+
const { client, args, config, type, name, source, hasSource, transport, lintOverride, preflightOverride, objectUrl, srcUrl, invalidateWrittenObject, } = ctx;
|
|
304
304
|
// FUNC and FUGR structural includes both INHERIT the parent group's package — SAP ignores
|
|
305
305
|
// _package for them — so the allowlist must be checked against the group's real package.
|
|
306
306
|
// Gating on args.package here would let a caller write into a disallowed package by claiming $TMP.
|
|
@@ -311,39 +311,12 @@ export async function writeActionCreate(ctx) {
|
|
|
311
311
|
: String(args.package ?? '$TMP');
|
|
312
312
|
await checkPackage(client.safety, pkg, client.getPackageHierarchyResolver());
|
|
313
313
|
const description = String(args.description ?? name);
|
|
314
|
-
// Pre-flight: check transport requirements for non-$TMP packages when no transport provided.
|
|
315
|
-
// SAP requires a transport number for objects in transportable packages.
|
|
316
|
-
// Instead of letting SAP return a cryptic error, we detect this early and return
|
|
317
|
-
// an actionable error message guiding the LLM to use SAPTransport first.
|
|
318
314
|
let effectiveTransport = transport;
|
|
319
|
-
if (!transport
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
effectiveTransport = transportInfo.lockedTransport;
|
|
325
|
-
}
|
|
326
|
-
else if (!transportInfo.isLocal && transportInfo.recording) {
|
|
327
|
-
// Transport IS required but none provided — return guidance
|
|
328
|
-
const existingList = transportInfo.existingTransports.length > 0
|
|
329
|
-
? `\n\nExisting transports for this package:\n${transportInfo.existingTransports
|
|
330
|
-
.slice(0, 10)
|
|
331
|
-
.map((t) => ` - ${t.id}: ${t.description} (${t.owner})`)
|
|
332
|
-
.join('\n')}`
|
|
333
|
-
: '';
|
|
334
|
-
return errorResult(`Package "${pkg}" requires a transport number for object creation, but none was provided.\n\n` +
|
|
335
|
-
`To fix this, either:\n` +
|
|
336
|
-
`1. Use SAPTransport(action="list") to find an existing modifiable transport\n` +
|
|
337
|
-
`2. Use SAPTransport(action="create", description="...") to create a new one\n` +
|
|
338
|
-
`3. Then retry SAPWrite(action="create", ..., transport="<transport_id>")` +
|
|
339
|
-
existingList);
|
|
340
|
-
}
|
|
341
|
-
// isLocal=true or recording=false → no transport needed, proceed without one
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
// If transportInfo check fails (older system, permissions, etc.), proceed without it.
|
|
345
|
-
// SAP will return its own error if a transport is actually needed.
|
|
346
|
-
}
|
|
315
|
+
if (!transport) {
|
|
316
|
+
const resolved = await resolveCreateTransport(client, objectUrl, pkg, config.minimalErrors);
|
|
317
|
+
if (resolved.error)
|
|
318
|
+
return errorResult(resolved.error);
|
|
319
|
+
effectiveTransport = resolved.transport;
|
|
347
320
|
}
|
|
348
321
|
// MSAG transport-vs-task guard. Some SAP releases silently drop message inserts when
|
|
349
322
|
// given a task number as corrNr — CL_ADT_MESSAGE_CLASS_API=>create() passes corrNr to
|
|
@@ -412,19 +385,36 @@ export async function writeActionCreate(ctx) {
|
|
|
412
385
|
}
|
|
413
386
|
throw err;
|
|
414
387
|
}
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
388
|
+
// Bodies and/or short texts require a follow-up PUT of the server-provided envelope.
|
|
389
|
+
// The POST already succeeded, so every later failure is reported as partial success.
|
|
390
|
+
const shortTexts = args.shortTexts;
|
|
391
|
+
if (hasSource || shortTexts?.length) {
|
|
392
|
+
let summary;
|
|
393
|
+
try {
|
|
394
|
+
const { source: currentEnvelope } = await client.getKtd(name);
|
|
395
|
+
const report = { proseHeadings: [] };
|
|
396
|
+
const body = rewriteKtdDocument(currentEnvelope, hasSource ? source : undefined, shortTexts, report);
|
|
397
|
+
summary = formatKtdWriteReport(currentEnvelope, body, report);
|
|
398
|
+
await safeUpdateObject(client.http, client.safety, objectUrl, body, SKTD_V2_CONTENT_TYPE, effectiveTransport, getCachedFeatures()?.abapRelease);
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
// The POST above already succeeded, so the KTD exists. Say so — a generic
|
|
402
|
+
// read/rewrite/PUT failure invites the same create again, which 409s.
|
|
403
|
+
invalidateWrittenObject(type, name);
|
|
404
|
+
const documentationPart = shortTexts?.length ? 'documentation' : 'documentation body';
|
|
405
|
+
return errorResult(`Created SKTD ${name} in package ${pkg}, but the ${documentationPart} was NOT written: ` +
|
|
406
|
+
`${err instanceof Error ? err.message : String(err)}\n` +
|
|
407
|
+
`The object exists — verify it with SAPRead. Inspect the reported cause: correct or remove invalid ` +
|
|
408
|
+
`${[hasSource && 'source', shortTexts?.length && 'shortTexts'].filter(Boolean).join(' and ')} input, ` +
|
|
409
|
+
`or wait for a transient read/PUT failure to recover. Then use ` +
|
|
410
|
+
`SAPWrite(action="update", type="SKTD", name="${name}", …); do not retry create.`);
|
|
411
|
+
}
|
|
423
412
|
invalidateWrittenObject(type, name);
|
|
424
|
-
|
|
413
|
+
const writtenPart = shortTexts?.length ? 'its documentation' : 'Markdown content';
|
|
414
|
+
return textResult(`Created SKTD ${name} in package ${pkg} and wrote ${writtenPart}.\n${summary}\nNext step: SAPActivate(type="SKTD", name="${name}").\n${ktdResult}`);
|
|
425
415
|
}
|
|
426
416
|
invalidateWrittenObject();
|
|
427
|
-
return textResult(`Created SKTD ${name} in package ${pkg} (no Markdown content written — pass "source"
|
|
417
|
+
return textResult(`Created SKTD ${name} in package ${pkg} (no Markdown content written — pass "source" for node bodies, and optionally "shortTexts" for per-node short texts).\nNext step: SAPActivate(type="SKTD", name="${name}").\n${ktdResult}`);
|
|
428
418
|
}
|
|
429
419
|
// Build type-specific creation XML body.
|
|
430
420
|
// SAP ADT requires the root element to match the object type —
|
|
@@ -472,10 +462,11 @@ export async function writeActionCreate(ctx) {
|
|
|
472
462
|
await persistFunctionModuleProcessingMetadata(client, objectUrl, name, metadataProperties.processingType, metadataProperties.updateTaskKind, effectiveTransport);
|
|
473
463
|
}
|
|
474
464
|
if (isMetadataWriteType(type)) {
|
|
475
|
-
// SAP's DTEL POST
|
|
465
|
+
// SAP's DTEL POST stores only a shell without the description, labels or custom lengths, so
|
|
466
|
+
// every DTEL create needs this follow-up PUT.
|
|
476
467
|
// Use withStatefulSession directly (not safeUpdateObject) to keep the lock cycle
|
|
477
468
|
// on the main client's session, avoiding lock contention with subsequent operations.
|
|
478
|
-
if (type === 'DTEL'
|
|
469
|
+
if (type === 'DTEL') {
|
|
479
470
|
const ct = vendorContentTypeForType(type);
|
|
480
471
|
await client.http.withStatefulSession(async (session) => {
|
|
481
472
|
const lock = await lockObject(session, client.safety, objectUrl, 'MODIFY', getCachedFeatures()?.abapRelease, systemType);
|
|
@@ -543,420 +534,292 @@ export async function writeActionCreate(ctx) {
|
|
|
543
534
|
return textResult(`Created ${type} ${name} in package ${pkg}.\n${result}`);
|
|
544
535
|
}
|
|
545
536
|
export async function writeActionBatchCreate(ctx) {
|
|
546
|
-
const { client, args, config,
|
|
537
|
+
const { client, args, config, transport, lintOverride, preflightOverride, invalidateWrittenObject } = ctx;
|
|
547
538
|
const objects = args.objects;
|
|
548
539
|
if (!objects || !Array.isArray(objects) || objects.length === 0) {
|
|
549
540
|
return errorResult('"objects" array is required and must be non-empty for batch_create action.');
|
|
550
541
|
}
|
|
551
|
-
|
|
552
|
-
// then issues a single terminal activateBatch over the written subset. Use case:
|
|
553
|
-
// composition-linked DDLS / interdependent RAP graphs where per-object inline
|
|
554
|
-
// activate() can't resolve cross-references to not-yet-active siblings.
|
|
542
|
+
checkOperation(client.safety, OperationType.Create, 'CreateObject');
|
|
555
543
|
const activateAtEnd = args.activateAtEnd === true || String(args.activateAtEnd) === 'true';
|
|
556
544
|
const defaultPackage = normalizePackageOverride(args.package, '$TMP');
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
const group =
|
|
563
|
-
const objectUrl =
|
|
564
|
-
|
|
545
|
+
const warnings = [];
|
|
546
|
+
const batchPlan = objects.map((obj, index) => {
|
|
547
|
+
const type = normalizeWriteObjectType(String(obj.type ?? ''));
|
|
548
|
+
const name = String(obj.name ?? '');
|
|
549
|
+
const packageName = normalizePackageOverride(obj.package, defaultPackage);
|
|
550
|
+
const group = type === 'FUNC' ? String(obj.group ?? args.group ?? '').trim() : undefined;
|
|
551
|
+
const objectUrl = type === 'FUNC' ? (group ? functionModuleObjectUrl(group, name) : '') : objectUrlForType(type, name);
|
|
552
|
+
const metadata = getMetadataWriteProperties(obj);
|
|
553
|
+
if (type === 'FUNC')
|
|
554
|
+
metadata.group = group;
|
|
555
|
+
const source = obj.source ? String(obj.source) : undefined;
|
|
556
|
+
return {
|
|
557
|
+
obj,
|
|
558
|
+
type,
|
|
559
|
+
name,
|
|
560
|
+
packageName,
|
|
561
|
+
group,
|
|
562
|
+
objectUrl,
|
|
563
|
+
metadata,
|
|
564
|
+
source,
|
|
565
|
+
transport: normalizeTransportOverride(obj.transport) ?? transport,
|
|
566
|
+
description: String(obj.description ?? name),
|
|
567
|
+
metadataObject: isMetadataWriteType(type),
|
|
568
|
+
body: '',
|
|
569
|
+
contentType: '',
|
|
570
|
+
sourceUrl: '',
|
|
571
|
+
result: batchEntryResult(index, type, name, packageName),
|
|
572
|
+
};
|
|
565
573
|
});
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
//
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
574
|
+
const results = batchPlan.map((plan) => plan.result);
|
|
575
|
+
const activationMessages = [];
|
|
576
|
+
const report = (preflight = false) => batchCreateResult(results, { preflight, activateAtEnd, warnings, activationMessages });
|
|
577
|
+
const seen = new Map();
|
|
578
|
+
// Validate the complete input before package/transport reads or the first create.
|
|
579
|
+
for (const plan of batchPlan) {
|
|
580
|
+
const errors = [];
|
|
581
|
+
try {
|
|
582
|
+
if (!plan.name.trim() || plan.name !== plan.name.trim() || plan.name !== plan.name.toUpperCase()) {
|
|
583
|
+
errors.push(`Object name "${plan.name}" must be non-empty, uppercase, and have no surrounding whitespace (e.g. "${plan.name.trim().toUpperCase()}"); source may stay mixed case.`);
|
|
584
|
+
}
|
|
585
|
+
const keyType = plan.type === 'INCL' ? 'PROG' : ['CLAS', 'INTF'].includes(plan.type) ? 'OO' : canonicalTablType(plan.type);
|
|
586
|
+
const key = `${keyType}\0${plan.name.toUpperCase()}`;
|
|
587
|
+
const previous = seen.get(key);
|
|
588
|
+
if (previous !== undefined)
|
|
589
|
+
errors.push(`Duplicate object: entry ${plan.result.index + 1} repeats entry ${previous + 1}.`);
|
|
590
|
+
else
|
|
591
|
+
seen.set(key, plan.result.index);
|
|
592
|
+
if (plan.type === 'FUNC' && !plan.group) {
|
|
593
|
+
errors.push(`FUNC ${plan.name} in batch_create requires "group" on the object entry or top-level request.`);
|
|
594
|
+
}
|
|
595
|
+
if (isServerDrivenObjectType(plan.type)) {
|
|
596
|
+
errors.push(`batch_create does not support server-driven object type ${plan.type}. Create it with a single SAPWrite(action="create", type="${plan.type}", name="${plan.name}") call.`);
|
|
597
|
+
}
|
|
598
|
+
if ((plan.type === 'TABL' || plan.type === 'TABL/DT') && isTablesEndpointAvailable() === false)
|
|
599
|
+
errors.push(TABL_DT_WRITE_UNAVAILABLE_HINT);
|
|
600
|
+
if (plan.type === 'DOMA' && isDomainsEndpointAvailable() === false)
|
|
601
|
+
errors.push(DOMA_WRITE_UNAVAILABLE_HINT);
|
|
602
|
+
if (plan.type === 'TTYP' && isTableTypesEndpointAvailable() === false)
|
|
603
|
+
errors.push(TTYP_WRITE_UNAVAILABLE_HINT);
|
|
604
|
+
if (plan.type === 'INCL' && plan.name.startsWith('L')) {
|
|
605
|
+
errors.push('Function-group structural includes require a single SAPWrite create with group; batch_create does not support them.');
|
|
606
|
+
}
|
|
607
|
+
const aff = validateAffHeader(plan.type, { description: plan.description, originalLanguage: 'en' });
|
|
608
|
+
if (!aff.valid)
|
|
609
|
+
errors.push(`AFF metadata validation failed:\n- ${(aff.errors ?? []).join('\n- ')}`);
|
|
575
610
|
if (plan.type === 'FUNC') {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
611
|
+
const prepared = prepareFunctionModuleCreateSource(plan.name, plan.source, plan.obj.parameters);
|
|
612
|
+
plan.source = prepared.shouldWrite ? prepared.source : undefined;
|
|
613
|
+
warnings.push(...prepared.warnings.map((warning) => `${plan.type} ${plan.name}: ${warning}`));
|
|
614
|
+
}
|
|
615
|
+
if (!plan.metadataObject && plan.source && !isServerDrivenObjectType(plan.type)) {
|
|
616
|
+
const cds = guardCdsSyntax(plan.type, plan.source, getCachedFeatures());
|
|
617
|
+
if (cds)
|
|
618
|
+
errors.push(cds.content[0].text);
|
|
619
|
+
const rap = runRapPreflightValidation(plan.source, plan.type, plan.name, getCachedFeatures(), config.systemType, preflightOverride);
|
|
620
|
+
if (rap.blocked)
|
|
621
|
+
errors.push(rap.result.content[0].text);
|
|
622
|
+
if (rap.warnings)
|
|
623
|
+
warnings.push(`${plan.type} ${plan.name}: ${rap.warnings}`);
|
|
624
|
+
const lint = runPreWriteLint(plan.source, plan.type, plan.name, config, lintOverride);
|
|
625
|
+
if (lint.blocked)
|
|
626
|
+
errors.push(`source rejected by lint: ${lint.result.content[0].text}`);
|
|
627
|
+
if (lint.warnings)
|
|
628
|
+
warnings.push(`${plan.type} ${plan.name}: ${lint.warnings}`);
|
|
629
|
+
}
|
|
630
|
+
if (!isServerDrivenObjectType(plan.type)) {
|
|
631
|
+
plan.sourceUrl =
|
|
632
|
+
plan.type === 'FUNC' ? `${plan.objectUrl}/source/main` : sourceUrlForType(plan.type, plan.name);
|
|
633
|
+
}
|
|
634
|
+
if (plan.type === 'DTEL' ||
|
|
635
|
+
plan.type === 'TTYP' ||
|
|
636
|
+
(plan.type === 'FUNC' && plan.metadata.processingType !== undefined) ||
|
|
637
|
+
(!plan.metadataObject && plan.source)) {
|
|
638
|
+
plan.result.write = 'not_attempted';
|
|
581
639
|
}
|
|
582
640
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
return errorResult(err instanceof Error ? err.message : String(err));
|
|
586
|
-
}
|
|
587
|
-
// Check every target package before starting any creates.
|
|
588
|
-
// Resolver is shared across the loop so subtree BFS happens once even when
|
|
589
|
-
// many objects target descendants of the same `ZFOO/**` root.
|
|
590
|
-
{
|
|
591
|
-
const resolver = client.getPackageHierarchyResolver();
|
|
592
|
-
for (const pkg of new Set(batchPlan.map((item) => item.packageName))) {
|
|
593
|
-
await checkPackage(client.safety, pkg, resolver);
|
|
641
|
+
catch (err) {
|
|
642
|
+
errors.push(batchFailureMessage(err, false));
|
|
594
643
|
}
|
|
644
|
+
if (errors.length)
|
|
645
|
+
failBatchEntry(plan.result, 'preflight', errors.join('\n'));
|
|
595
646
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const firstPlanNeedingTransportByPackage = new Map();
|
|
647
|
+
if (results.some((entry) => entry.status === 'failed'))
|
|
648
|
+
return report(true);
|
|
649
|
+
// Resolve FUNC's actual inherited package and enforce every package before mutations.
|
|
600
650
|
for (const plan of batchPlan) {
|
|
601
|
-
if (!plan.explicitTransport &&
|
|
602
|
-
plan.packageName.toUpperCase() !== '$TMP' &&
|
|
603
|
-
!firstPlanNeedingTransportByPackage.has(plan.packageName)) {
|
|
604
|
-
firstPlanNeedingTransportByPackage.set(plan.packageName, plan);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
for (const [pkg, plan] of firstPlanNeedingTransportByPackage) {
|
|
608
651
|
try {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
else if (!transportInfo.isLocal && transportInfo.recording) {
|
|
614
|
-
const existingList = transportInfo.existingTransports.length > 0
|
|
615
|
-
? `\n\nExisting transports for this package:\n${transportInfo.existingTransports
|
|
616
|
-
.slice(0, 10)
|
|
617
|
-
.map((t) => ` - ${t.id}: ${t.description} (${t.owner})`)
|
|
618
|
-
.join('\n')}`
|
|
619
|
-
: '';
|
|
620
|
-
return errorResult(`Package "${pkg}" requires a transport number for object creation, but none was provided.\n\n` +
|
|
621
|
-
`To fix this, either:\n` +
|
|
622
|
-
`1. Use SAPTransport(action="list") to find an existing modifiable transport\n` +
|
|
623
|
-
`2. Use SAPTransport(action="create", description="...") to create a new one\n` +
|
|
624
|
-
`3. Then retry SAPWrite(action="batch_create", ..., transport="<transport_id>")` +
|
|
625
|
-
existingList);
|
|
652
|
+
if (plan.type === 'FUNC') {
|
|
653
|
+
plan.packageName = await resolveFunctionGroupCreatePackage(client, plan.group, plan.obj.package);
|
|
654
|
+
plan.result.packageName = plan.packageName;
|
|
626
655
|
}
|
|
656
|
+
await checkPackage(client.safety, plan.packageName, client.getPackageHierarchyResolver());
|
|
627
657
|
}
|
|
628
658
|
catch (err) {
|
|
629
|
-
|
|
630
|
-
package: pkg,
|
|
631
|
-
type: plan.type,
|
|
632
|
-
name: plan.name,
|
|
633
|
-
error: err instanceof Error ? err.message : String(err),
|
|
634
|
-
});
|
|
635
|
-
// If transportInfo check fails, proceed — SAP will return its own error if needed.
|
|
659
|
+
failBatchEntry(plan.result, 'preflight', batchFailureMessage(err, config.minimalErrors));
|
|
636
660
|
}
|
|
637
661
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
662
|
+
if (results.some((entry) => entry.status === 'failed'))
|
|
663
|
+
return report(true);
|
|
664
|
+
const autoTransportByPackage = new Map();
|
|
665
|
+
for (const plan of batchPlan) {
|
|
666
|
+
if (plan.transport || autoTransportByPackage.has(plan.packageName))
|
|
667
|
+
continue;
|
|
668
|
+
autoTransportByPackage.set(plan.packageName, undefined);
|
|
669
|
+
try {
|
|
670
|
+
const resolved = await resolveCreateTransport(client, plan.objectUrl, plan.packageName, config.minimalErrors);
|
|
671
|
+
if (resolved.error)
|
|
672
|
+
failBatchEntry(plan.result, 'preflight', resolved.error);
|
|
673
|
+
else
|
|
674
|
+
autoTransportByPackage.set(plan.packageName, resolved.transport);
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
failBatchEntry(plan.result, 'preflight', batchFailureMessage(error, config.minimalErrors));
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (results.some((entry) => entry.status === 'failed'))
|
|
681
|
+
return report(true);
|
|
649
682
|
const systemType = resolveWriteSystemType(config, client);
|
|
650
683
|
const cloud = systemType === 'btp';
|
|
651
684
|
const responsible = config.username || (await client.getEffectiveUser());
|
|
685
|
+
const transportLookupCache = new Map();
|
|
652
686
|
for (const plan of batchPlan) {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
// Mixed-case object name rejection (matches the create-path check above).
|
|
664
|
-
// Universal SAP convention — TADIR is uppercase on every release.
|
|
665
|
-
// Cheap check first: no HTTP call, fail fast on bad names.
|
|
666
|
-
if (objName && objName !== objName.toUpperCase()) {
|
|
667
|
-
results.push({
|
|
668
|
-
type: objType,
|
|
669
|
-
name: objName,
|
|
670
|
-
packageName: objPackage,
|
|
671
|
-
status: 'failed',
|
|
672
|
-
error: `Object name "${objName}" contains lowercase characters. SAP object names must be uppercase (e.g. "${objName.toUpperCase()}"). Source code inside the object can use mixed case.`,
|
|
673
|
-
});
|
|
674
|
-
break;
|
|
675
|
-
}
|
|
676
|
-
// MSAG transport-vs-task guard (per-batch cache to avoid per-object roundtrip).
|
|
677
|
-
if (objType === 'MSAG' && objTransport) {
|
|
678
|
-
let tr = transportLookupCache.get(objTransport);
|
|
679
|
-
if (tr === undefined) {
|
|
680
|
-
tr = await getTransport(client.http, client.safety, objTransport);
|
|
681
|
-
transportLookupCache.set(objTransport, tr);
|
|
682
|
-
}
|
|
683
|
-
if (!tr) {
|
|
684
|
-
results.push({
|
|
685
|
-
type: objType,
|
|
686
|
-
name: objName,
|
|
687
|
-
packageName: objPackage,
|
|
688
|
-
status: 'failed',
|
|
689
|
-
error: `Transport "${objTransport}" is not a valid transport request. MSAG creation requires a transport request number, not a task number.`,
|
|
690
|
-
});
|
|
691
|
-
break;
|
|
687
|
+
try {
|
|
688
|
+
plan.transport ??= autoTransportByPackage.get(plan.packageName);
|
|
689
|
+
if (plan.type === 'MSAG' && plan.transport) {
|
|
690
|
+
if (!transportLookupCache.has(plan.transport))
|
|
691
|
+
transportLookupCache.set(plan.transport, await getTransport(client.http, client.safety, plan.transport));
|
|
692
|
+
if (!transportLookupCache.get(plan.transport)) {
|
|
693
|
+
failBatchEntry(plan.result, 'preflight', `Transport "${plan.transport}" is not a valid transport request. MSAG creation requires a transport request number, not a task number.`);
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
692
696
|
}
|
|
693
697
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
results.push({
|
|
698
|
-
type: objType,
|
|
699
|
-
name: objName,
|
|
700
|
-
packageName: objPackage,
|
|
701
|
-
status: 'failed',
|
|
702
|
-
error: `AFF metadata validation failed:\n- ${(affResult.errors ?? []).join('\n- ')}`,
|
|
703
|
-
});
|
|
704
|
-
break;
|
|
698
|
+
catch (err) {
|
|
699
|
+
failBatchEntry(plan.result, 'preflight', batchFailureMessage(err, config.minimalErrors));
|
|
700
|
+
continue;
|
|
705
701
|
}
|
|
706
702
|
try {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
name: objName,
|
|
726
|
-
packageName: objPackage,
|
|
727
|
-
status: 'failed',
|
|
728
|
-
error: preflightWarnings.result.content[0].text,
|
|
729
|
-
});
|
|
730
|
-
break;
|
|
731
|
-
}
|
|
732
|
-
if (preflightWarnings.warnings) {
|
|
733
|
-
batchWarnings.push(`${objType} ${objName}: ${preflightWarnings.warnings}`);
|
|
734
|
-
}
|
|
735
|
-
const lintWarnings = runPreWriteLint(objSource, objType, objName, config, lintOverride);
|
|
736
|
-
if (lintWarnings.blocked) {
|
|
737
|
-
results.push({
|
|
738
|
-
type: objType,
|
|
739
|
-
name: objName,
|
|
740
|
-
packageName: objPackage,
|
|
741
|
-
status: 'failed',
|
|
742
|
-
error: `source rejected by lint: ${lintWarnings.result.content[0].text}`,
|
|
743
|
-
});
|
|
744
|
-
break;
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
// Step 1: Create the object (per-entry transparent-table discovery gate;
|
|
748
|
-
// mirrors the single-create site above. TABL/DS skips it — /structures/ always exists.)
|
|
749
|
-
if ((objType === 'TABL' || objType === 'TABL/DT') && isTablesEndpointAvailable() === false) {
|
|
750
|
-
results.push({
|
|
751
|
-
type: objType,
|
|
752
|
-
name: objName,
|
|
753
|
-
packageName: objPackage,
|
|
754
|
-
status: 'failed',
|
|
755
|
-
error: TABL_DT_WRITE_UNAVAILABLE_HINT,
|
|
756
|
-
});
|
|
757
|
-
break;
|
|
758
|
-
}
|
|
759
|
-
// DOMA create needs /ddic/domains/ (absent wholesale on NW 7.50/7.51) — mirror the gate above.
|
|
760
|
-
if (objType === 'DOMA' && isDomainsEndpointAvailable() === false) {
|
|
761
|
-
results.push({
|
|
762
|
-
type: objType,
|
|
763
|
-
name: objName,
|
|
764
|
-
packageName: objPackage,
|
|
765
|
-
status: 'failed',
|
|
766
|
-
error: DOMA_WRITE_UNAVAILABLE_HINT,
|
|
767
|
-
});
|
|
768
|
-
break;
|
|
769
|
-
}
|
|
770
|
-
// TTYP create needs /ddic/tabletypes/ (absent on NW 7.50) — mirror the TABL/DT gate above.
|
|
771
|
-
if (objType === 'TTYP' && isTableTypesEndpointAvailable() === false) {
|
|
772
|
-
results.push({
|
|
773
|
-
type: objType,
|
|
774
|
-
name: objName,
|
|
775
|
-
packageName: objPackage,
|
|
776
|
-
status: 'failed',
|
|
777
|
-
error: TTYP_WRITE_UNAVAILABLE_HINT,
|
|
778
|
-
});
|
|
779
|
-
break;
|
|
780
|
-
}
|
|
781
|
-
// Server-driven objects need the AFF engine (handleServerDrivenObjectWrite), which only runs on
|
|
782
|
-
// the single-object path. objectBasePath() has no SDO case and silently falls through to the
|
|
783
|
-
// PROG path, so without this guard a batched SDO would POST a program create body.
|
|
784
|
-
if (isServerDrivenObjectType(objType)) {
|
|
785
|
-
results.push({
|
|
786
|
-
type: objType,
|
|
787
|
-
name: objName,
|
|
788
|
-
packageName: objPackage,
|
|
789
|
-
status: 'failed',
|
|
790
|
-
error: `batch_create does not support server-driven object type ${objType}. Create it with a single SAPWrite(action="create", type="${objType}", name="${objName}") call.`,
|
|
791
|
-
});
|
|
792
|
-
break;
|
|
793
|
-
}
|
|
794
|
-
const createUrl = objUrl.replace(/\/[^/]+$/, '');
|
|
795
|
-
const objMetadataProps = getMetadataWriteProperties(obj);
|
|
796
|
-
if (objType === 'FUNC') {
|
|
797
|
-
objMetadataProps.group = plan.group;
|
|
798
|
-
}
|
|
799
|
-
// Sets behaviorExtension + baseBdef on the metadata so buildCreateXml emits the adtTemplate;
|
|
800
|
-
// the return value (used for the optional read-back warning) isn't needed in the batch path.
|
|
801
|
-
applyBdefBehaviorExtensionMetadata(objType, objSource, objMetadataProps);
|
|
802
|
-
const body = buildCreateXml(objType, objName, objPackage, objDescription, objMetadataProps, config.language, responsible, cloud);
|
|
803
|
-
const contentType = createContentTypeForType(objType, cloud);
|
|
804
|
-
const needsPackageParam = objType === 'BDEF' || objType === 'TABL' || objType === 'TABL/DT' || objType === 'TABL/DS';
|
|
703
|
+
applyBdefBehaviorExtensionMetadata(plan.type, plan.source, plan.metadata);
|
|
704
|
+
plan.body = buildCreateXml(plan.type, plan.name, plan.packageName, plan.description, plan.metadata, config.language, responsible, cloud);
|
|
705
|
+
plan.contentType = createContentTypeForType(plan.type, cloud);
|
|
706
|
+
}
|
|
707
|
+
catch (err) {
|
|
708
|
+
// Construction errors are local input diagnostics, not SAP response details.
|
|
709
|
+
failBatchEntry(plan.result, 'preflight', batchFailureMessage(err, false));
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (results.some((entry) => entry.status === 'failed'))
|
|
713
|
+
return report(true);
|
|
714
|
+
const writtenPlans = [];
|
|
715
|
+
for (const plan of batchPlan) {
|
|
716
|
+
const entry = plan.result;
|
|
717
|
+
let phase = 'create';
|
|
718
|
+
try {
|
|
719
|
+
const needsPackage = plan.type === 'BDEF' || canonicalTablType(plan.type) === 'TABL';
|
|
720
|
+
entry.creation = 'unknown';
|
|
805
721
|
try {
|
|
806
|
-
await createObject(client.http, client.safety,
|
|
722
|
+
await createObject(client.http, client.safety, plan.objectUrl.replace(/\/[^/]+$/, ''), plan.body, plan.contentType, plan.transport, needsPackage ? plan.packageName : undefined, getCachedFeatures()?.abapRelease, systemType, plan.name);
|
|
807
723
|
}
|
|
808
|
-
catch (
|
|
809
|
-
if (
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
724
|
+
catch (err) {
|
|
725
|
+
if (err instanceof AdtSafetyError)
|
|
726
|
+
entry.creation = 'not_attempted';
|
|
727
|
+
// Keep the original create failure even if the optional diagnostic cannot run.
|
|
728
|
+
if (!config.minimalErrors && err instanceof AdtApiError && [400, 409].includes(err.statusCode)) {
|
|
729
|
+
const detail = await tryPostSaveSyntaxCheck(client, plan.type, plan.name).catch(() => undefined);
|
|
730
|
+
if (detail)
|
|
731
|
+
err.message += detail;
|
|
814
732
|
}
|
|
815
|
-
throw
|
|
733
|
+
throw err;
|
|
816
734
|
}
|
|
817
|
-
|
|
818
|
-
|
|
735
|
+
entry.creation = 'confirmed';
|
|
736
|
+
phase = 'write';
|
|
737
|
+
if (entry.write !== 'not_required')
|
|
738
|
+
entry.write = 'unknown';
|
|
739
|
+
if (plan.type === 'FUNC' && plan.metadata.processingType !== undefined) {
|
|
740
|
+
await persistFunctionModuleProcessingMetadata(client, plan.objectUrl, plan.name, plan.metadata.processingType, plan.metadata.updateTaskKind, plan.transport);
|
|
819
741
|
}
|
|
820
|
-
|
|
821
|
-
if (objType === 'DTEL' && dtelNeedsPostCreateUpdate(objMetadataProps)) {
|
|
742
|
+
if (plan.type === 'DTEL') {
|
|
822
743
|
await client.http.withStatefulSession(async (session) => {
|
|
823
|
-
const lock = await lockObject(session, client.safety,
|
|
824
|
-
const lockTransport = objTransport ?? (lock.corrNr || undefined);
|
|
744
|
+
const lock = await lockObject(session, client.safety, plan.objectUrl, 'MODIFY', getCachedFeatures()?.abapRelease);
|
|
825
745
|
try {
|
|
826
|
-
await updateObject(session, client.safety,
|
|
746
|
+
await updateObject(session, client.safety, plan.objectUrl, plan.body, lock.lockHandle, plan.contentType, plan.transport ?? (lock.corrNr || undefined));
|
|
827
747
|
}
|
|
828
748
|
finally {
|
|
829
|
-
await unlockObject(session,
|
|
749
|
+
await unlockObject(session, plan.objectUrl, lock.lockHandle);
|
|
830
750
|
}
|
|
831
751
|
});
|
|
832
752
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
// Step 2: Write source if provided
|
|
838
|
-
if (!metadataObject && objSource) {
|
|
839
|
-
const srcUrl = objType === 'FUNC' ? `${objUrl}/source/main` : sourceUrlForType(objType, objName);
|
|
840
|
-
await safeUpdateSource(client.http, client.safety, objUrl, srcUrl, objSource, objTransport, getCachedFeatures()?.abapRelease);
|
|
753
|
+
if (plan.type === 'TTYP')
|
|
754
|
+
await putTtypMetadataAfterCreate(client, plan.objectUrl, plan.name, plan.body, plan.contentType, plan.transport);
|
|
755
|
+
if (!plan.metadataObject && plan.source) {
|
|
756
|
+
await safeUpdateSource(client.http, client.safety, plan.objectUrl, plan.sourceUrl, plan.source, plan.transport, getCachedFeatures()?.abapRelease);
|
|
841
757
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const activationUrl = objUrl;
|
|
758
|
+
if (entry.write !== 'not_required')
|
|
759
|
+
entry.write = 'confirmed';
|
|
845
760
|
if (activateAtEnd) {
|
|
846
|
-
|
|
847
|
-
// Cache invalidation also moves to AFTER the terminal activate succeeds —
|
|
848
|
-
// invalidating now would let the next read see a draft we couldn't activate.
|
|
849
|
-
writtenObjects.push({ type: objType, name: objName, url: activationUrl });
|
|
850
|
-
results.push({ type: objType, name: objName, packageName: objPackage, status: 'success' });
|
|
761
|
+
writtenPlans.push(plan);
|
|
851
762
|
}
|
|
852
763
|
else {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
packageName: objPackage,
|
|
860
|
-
status: 'failed',
|
|
861
|
-
error: `activation failed: ${activationResult.messages.join('; ')}`,
|
|
862
|
-
});
|
|
764
|
+
phase = 'activate';
|
|
765
|
+
entry.activation = 'unknown';
|
|
766
|
+
const outcome = await activate(client.http, client.safety, plan.objectUrl);
|
|
767
|
+
if (!outcome.success) {
|
|
768
|
+
entry.activation = 'failed';
|
|
769
|
+
failBatchEntry(entry, phase, `activation failed: ${batchFailureMessage(outcome.messages.join('; '), config.minimalErrors)}`);
|
|
863
770
|
break;
|
|
864
771
|
}
|
|
865
|
-
|
|
866
|
-
results.push({ type: objType, name: objName, packageName: objPackage, status: 'success' });
|
|
772
|
+
entry.activation = 'confirmed';
|
|
867
773
|
}
|
|
774
|
+
entry.status = 'success';
|
|
868
775
|
}
|
|
869
776
|
catch (err) {
|
|
870
|
-
|
|
871
|
-
type: objType,
|
|
872
|
-
name: objName,
|
|
873
|
-
packageName: objPackage,
|
|
874
|
-
status: 'failed',
|
|
875
|
-
error: err instanceof Error ? err.message : String(err),
|
|
876
|
-
});
|
|
777
|
+
failBatchEntry(entry, phase, batchFailureMessage(err, config.minimalErrors));
|
|
877
778
|
break;
|
|
878
779
|
}
|
|
780
|
+
finally {
|
|
781
|
+
if (entry.creation !== 'not_attempted')
|
|
782
|
+
invalidateWrittenObject(plan.type, plan.name);
|
|
783
|
+
}
|
|
879
784
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
// for composition-linked DDLS and RAP behavior stacks — SAP's activator
|
|
896
|
-
// sees the whole graph in a single POST and resolves cross-references
|
|
897
|
-
// internally, so parent → child siblings activate cleanly.
|
|
898
|
-
let terminalActivationFailure;
|
|
899
|
-
if (activateAtEnd && writtenObjects.length > 0) {
|
|
900
|
-
const activationOutcome = await activateBatch(client.http, client.safety, writtenObjects);
|
|
901
|
-
if (activationOutcome.success) {
|
|
902
|
-
// Defensive: per-object status was already 'success' from the write phase.
|
|
903
|
-
// Cache invalidation moves here so a failed terminal activate doesn't strand
|
|
904
|
-
// a stale 'active' cache entry. Invalidate inactive-lists once for the user.
|
|
905
|
-
for (const o of writtenObjects) {
|
|
906
|
-
cachingLayer?.invalidate(o.type, o.name, 'all');
|
|
785
|
+
if (activateAtEnd && writtenPlans.length > 0) {
|
|
786
|
+
for (const plan of writtenPlans)
|
|
787
|
+
plan.result.activation = 'unknown';
|
|
788
|
+
try {
|
|
789
|
+
const outcome = await activateBatch(client.http, client.safety, writtenPlans.map((plan) => ({ type: plan.type, name: plan.name, url: plan.objectUrl })));
|
|
790
|
+
if (!outcome.success) {
|
|
791
|
+
activationMessages.push(`${writtenPlans.length}/${writtenPlans.length} written, batch activation failed.`);
|
|
792
|
+
const unassigned = outcome.details.filter((detail) => !writtenPlans.some((plan) => activationDetailMatchesObject(detail.uri, plan.objectUrl)));
|
|
793
|
+
// Flat messages duplicate structured details; retain only otherwise-unrepresented messages.
|
|
794
|
+
const messages = [
|
|
795
|
+
...unassigned.map((detail) => detail.text),
|
|
796
|
+
...outcome.messages.filter((message) => !outcome.details.some((detail) => detail.text === message)),
|
|
797
|
+
];
|
|
798
|
+
if (messages.length)
|
|
799
|
+
activationMessages.push(batchFailureMessage([...new Set(messages)].join('; '), config.minimalErrors));
|
|
907
800
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
// Flip every written-but-not-yet-activated entry to 'failed', preserving the
|
|
912
|
-
// "create + source-write succeeded" context. Reuse the existing per-object
|
|
913
|
-
// diagnostic mapper so callers see the activation messages keyed by object name.
|
|
914
|
-
const batchStatuses = buildBatchActivationStatuses(writtenObjects, activationOutcome);
|
|
915
|
-
const statusDetails = formatBatchActivationStatuses(batchStatuses);
|
|
916
|
-
terminalActivationFailure = statusDetails;
|
|
917
|
-
const statusByName = new Map(batchStatuses.map((s) => [`${s.type}\x00${s.name}`, s]));
|
|
918
|
-
for (const result of results) {
|
|
919
|
-
if (result.status !== 'success')
|
|
801
|
+
for (const plan of writtenPlans) {
|
|
802
|
+
if (outcome.success) {
|
|
803
|
+
plan.result.activation = 'confirmed';
|
|
920
804
|
continue;
|
|
921
|
-
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
continue;
|
|
929
|
-
result.status = 'failed';
|
|
930
|
-
const detail = matched.messages.length > 0 ? ` — ${matched.messages.join('; ')}` : '';
|
|
931
|
-
// Preserve the "create + source-write succeeded" context so the user sees that
|
|
932
|
-
// the failure was specifically the activation step, not the write step.
|
|
933
|
-
result.error = `${writtenObjects.length}/${writtenObjects.length} written, batch activation failed${detail}`;
|
|
805
|
+
}
|
|
806
|
+
const errors = outcome.details.filter((detail) => detail.severity === 'error' && activationDetailMatchesObject(detail.uri, plan.objectUrl));
|
|
807
|
+
plan.result.activation = errors.length ? 'failed' : 'unknown';
|
|
808
|
+
const message = errors.length
|
|
809
|
+
? batchFailureMessage(errors.map((detail) => detail.text).join('; '), config.minimalErrors)
|
|
810
|
+
: 'Activation remains unknown: batch activation failed without an object-specific error.';
|
|
811
|
+
failBatchEntry(plan.result, 'activate', message);
|
|
934
812
|
}
|
|
935
813
|
}
|
|
814
|
+
catch (err) {
|
|
815
|
+
for (const plan of writtenPlans)
|
|
816
|
+
failBatchEntry(plan.result, 'activate', batchFailureMessage(err, config.minimalErrors));
|
|
817
|
+
}
|
|
818
|
+
finally {
|
|
819
|
+
for (const plan of writtenPlans)
|
|
820
|
+
invalidateWrittenObject(plan.type, plan.name);
|
|
821
|
+
}
|
|
936
822
|
}
|
|
937
|
-
|
|
938
|
-
const summary = results
|
|
939
|
-
.map((r) => r.status === 'success'
|
|
940
|
-
? `${r.name} (${r.type}) ✓ [${r.packageName}]`
|
|
941
|
-
: `${r.name} (${r.type}) ✗ [${r.packageName}] — ${r.error}`)
|
|
942
|
-
.join(', ');
|
|
943
|
-
const successCount = results.filter((r) => r.status === 'success').length;
|
|
944
|
-
const hasFailure = results.some((r) => r.status === 'failed');
|
|
945
|
-
const warningSuffix = batchWarnings.length > 0 ? `\n\nRAP preflight warnings:\n- ${batchWarnings.join('\n- ')}` : '';
|
|
946
|
-
const activateAtEndSuffix = terminalActivationFailure !== undefined ? `\n\nBatch activation diagnostics:${terminalActivationFailure}` : '';
|
|
947
|
-
const packageNames = [...new Set(batchPlan.map((item) => item.packageName))];
|
|
948
|
-
const packageSummary = packageNames.length === 1
|
|
949
|
-
? `in package ${packageNames[0]}`
|
|
950
|
-
: packageNames.length <= 3
|
|
951
|
-
? `across packages [${packageNames.join(', ')}]`
|
|
952
|
-
: `across ${packageNames.length} packages`;
|
|
953
|
-
const activateAtEndPrefix = activateAtEnd ? '; activated as a single batch' : '';
|
|
954
|
-
if (hasFailure) {
|
|
955
|
-
const cleanupHint = successCount > 0
|
|
956
|
-
? ` Note: ${successCount} already-created object(s) remain on the SAP system and may need manual cleanup.`
|
|
957
|
-
: '';
|
|
958
|
-
return errorResult(`Batch created ${successCount}/${objects.length} objects ${packageSummary}${activateAtEndPrefix}: ${summary}${cleanupHint}${warningSuffix}${activateAtEndSuffix}`);
|
|
959
|
-
}
|
|
960
|
-
return textResult(`Batch created ${successCount} objects ${packageSummary}${activateAtEndPrefix}: ${summary}${warningSuffix}${activateAtEndSuffix}`);
|
|
823
|
+
return report();
|
|
961
824
|
}
|
|
962
825
|
//# sourceMappingURL=create.js.map
|