@abapify/adt-plugin-abapgit 0.4.34 → 0.4.36

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/index.mjs CHANGED
@@ -68,7 +68,7 @@ function formatXmlAttributes(xml) {
68
68
  }
69
69
  const tagContent = xml.slice(openBracket + 1, closeBracket);
70
70
  const spaceIdx = tagContent.indexOf(" ");
71
- if (spaceIdx === -1 || tagContent[0] === "/" || tagContent[0] === "!" || tagContent[0] === "?") {
71
+ if (spaceIdx === -1 || tagContent.startsWith("/") || tagContent.startsWith("!") || tagContent.startsWith("?")) {
72
72
  result += xml.slice(openBracket, closeBracket + 1);
73
73
  pos = closeBracket + 1;
74
74
  continue;
@@ -257,7 +257,8 @@ function createHandler(adkClassOrType, definition) {
257
257
  const suffixBySourceKey = new Map([["main", void 0], ...Object.entries(definition.suffixToSourceKey ?? {}).map(([suffix, sourceKey]) => [sourceKey, suffix])]);
258
258
  const objectName = ctx.getObjectName(object);
259
259
  const files = [];
260
- for (const sourceKey of sourceKeys.sort((left, right) => left.localeCompare(right, "en-US"))) {
260
+ sourceKeys.sort((left, right) => left.localeCompare(right, "en-US"));
261
+ for (const sourceKey of sourceKeys) {
261
262
  const file = createExplicitSourceFile(sourceKey, sources[sourceKey], suffixBySourceKey, objectName);
262
263
  if (file) files.push(file);
263
264
  }
@@ -19478,10 +19479,11 @@ const programHandler = createHandler("PROG", {
19478
19479
  ...data,
19479
19480
  type: "PROG/I"
19480
19481
  };
19482
+ const baseProgramType = subc === "S" ? "subroutinePool" : "executableProgram";
19481
19483
  return {
19482
19484
  ...data,
19483
19485
  type: "PROG/P",
19484
- programType: subc === "M" ? "modulePool" : subc === "S" ? "subroutinePool" : "executableProgram"
19486
+ programType: subc === "M" ? "modulePool" : baseProgramType
19485
19487
  };
19486
19488
  },
19487
19489
  setSources: (obj, sources) => {
@@ -19567,7 +19569,7 @@ async function buildFunctionModuleFiles(obj, fmItems, suppliedSources, ctx) {
19567
19569
  const suppliedFmSource = suppliedSources?.[sourceKey];
19568
19570
  if (hasExplicitSources && suppliedFmSource === void 0) continue;
19569
19571
  try {
19570
- const source = suppliedFmSource !== void 0 ? suppliedFmSource : await obj.client.adt.functions.groups.fmodules.source.main.get(obj.name, fm.name);
19572
+ const source = suppliedFmSource ?? await obj.client.adt.functions.groups.fmodules.source.main.get(obj.name, fm.name);
19571
19573
  if (shouldIncludeSource(source, suppliedFmSource)) files.push(ctx.createFile(`${objectName}.${ctx.fileExtension}.${funcName}.abap`, source));
19572
19574
  } catch {}
19573
19575
  }
@@ -19664,7 +19666,10 @@ async function discoverFunctionModules(obj) {
19664
19666
  let match;
19665
19667
  while ((match = regex.exec(responseStr)) !== null) fmNames.push({ name: match[1] });
19666
19668
  const regex2 = /adtcore:name="([^"]+)"[^>]*type="FUGR\/FF"/g;
19667
- while ((match = regex2.exec(responseStr)) !== null) if (match?.[1] && !fmNames.some((f) => f.name === match[1])) fmNames.push({ name: match[1] });
19669
+ while ((match = regex2.exec(responseStr)) !== null) {
19670
+ const name = match[1];
19671
+ if (name && !fmNames.some((f) => f.name === name)) fmNames.push({ name });
19672
+ }
19668
19673
  return fmNames;
19669
19674
  } catch {
19670
19675
  return [];
@@ -19694,7 +19699,13 @@ async function serializeFunctions(obj, fmItems) {
19694
19699
  }
19695
19700
  function extractFunctionDescriptors(functions) {
19696
19701
  if (!functions) return [];
19697
- return (Array.isArray(functions) ? functions : Array.isArray(functions?.item) ? functions.item : functions?.item ? [functions.item] : []).map((fm) => ({
19702
+ const functionsRecord = functions;
19703
+ let items;
19704
+ if (Array.isArray(functions)) items = functions;
19705
+ else if (Array.isArray(functionsRecord?.item)) items = functionsRecord.item;
19706
+ else if (functionsRecord?.item) items = [functionsRecord.item];
19707
+ else items = [];
19708
+ return items.map((fm) => ({
19698
19709
  funcName: String(fm.FUNCNAME ?? ""),
19699
19710
  shortText: fm.SHORT_TEXT ? String(fm.SHORT_TEXT) : void 0,
19700
19711
  processingType: remoteCallToProcessingType(fm.REMOTE_CALL, fm.UPDATE_TASK),
@@ -19847,6 +19858,18 @@ const TYPEKIND_TO_REFTYPE = {
19847
19858
  refToPredefinedAbapType: "E"
19848
19859
  };
19849
19860
  /**
19861
+ * Map abapGit REFKIND/REFTYPE back to the ADT `typeKind` enum.
19862
+ */
19863
+ function typeKindFromRef(refKind, refType, dataType) {
19864
+ if (refKind === "D") return "domain";
19865
+ if (refKind === "R") return refType === "C" ? "refToClifType" : "refToDictionaryType";
19866
+ return dataType ? "predefinedAbapType" : "domain";
19867
+ }
19868
+ /** Numeric DD04V field → number (empty/absent → 0). */
19869
+ function numOr0(value) {
19870
+ return value ? Number(value) : 0;
19871
+ }
19872
+ /**
19850
19873
  * Derive OUTPUTLEN from ABAP data type + length + decimals.
19851
19874
  *
19852
19875
  * The ADT REST API for data elements does not return OUTPUTLEN — SAP derives
@@ -19929,12 +19952,7 @@ const dataElementHandler = createHandler(AdkDataElement, {
19929
19952
  } };
19930
19953
  },
19931
19954
  fromAbapGit: ({ DD04V }) => {
19932
- const refKind = DD04V?.REFKIND ?? "";
19933
- const refType = DD04V?.REFTYPE ?? "";
19934
- let typeKind;
19935
- if (refKind === "D") typeKind = "domain";
19936
- else if (refKind === "R") typeKind = refType === "C" ? "refToClifType" : "refToDictionaryType";
19937
- else typeKind = DD04V?.DATATYPE ? "predefinedAbapType" : "domain";
19955
+ const typeKind = typeKindFromRef(DD04V?.REFKIND ?? "", DD04V?.REFTYPE ?? "", DD04V?.DATATYPE);
19938
19956
  return {
19939
19957
  name: (DD04V?.ROLLNAME ?? "").toUpperCase(),
19940
19958
  type: "DTEL/DE",
@@ -19946,22 +19964,22 @@ const dataElementHandler = createHandler(AdkDataElement, {
19946
19964
  typeKind,
19947
19965
  typeName: DD04V?.DOMNAME || "",
19948
19966
  dataType: DD04V?.DATATYPE || "",
19949
- dataTypeLength: DD04V?.LENG ? Number(DD04V.LENG) : 0,
19967
+ dataTypeLength: numOr0(DD04V?.LENG),
19950
19968
  dataTypeLengthEnabled: false,
19951
- dataTypeDecimals: DD04V?.DECIMALS ? Number(DD04V.DECIMALS) : 0,
19969
+ dataTypeDecimals: numOr0(DD04V?.DECIMALS),
19952
19970
  dataTypeDecimalsEnabled: false,
19953
19971
  shortFieldLabel: DD04V?.SCRTEXT_S || "",
19954
- shortFieldLength: DD04V?.SCRLEN1 ? Number(DD04V.SCRLEN1) : 0,
19955
- shortFieldMaxLength: DD04V?.SCRLEN1 ? Number(DD04V.SCRLEN1) : 0,
19972
+ shortFieldLength: numOr0(DD04V?.SCRLEN1),
19973
+ shortFieldMaxLength: numOr0(DD04V?.SCRLEN1),
19956
19974
  mediumFieldLabel: DD04V?.SCRTEXT_M || "",
19957
- mediumFieldLength: DD04V?.SCRLEN2 ? Number(DD04V.SCRLEN2) : 0,
19958
- mediumFieldMaxLength: DD04V?.SCRLEN2 ? Number(DD04V.SCRLEN2) : 0,
19975
+ mediumFieldLength: numOr0(DD04V?.SCRLEN2),
19976
+ mediumFieldMaxLength: numOr0(DD04V?.SCRLEN2),
19959
19977
  longFieldLabel: DD04V?.SCRTEXT_L || "",
19960
- longFieldLength: DD04V?.SCRLEN3 ? Number(DD04V.SCRLEN3) : 0,
19961
- longFieldMaxLength: DD04V?.SCRLEN3 ? Number(DD04V.SCRLEN3) : 0,
19978
+ longFieldLength: numOr0(DD04V?.SCRLEN3),
19979
+ longFieldMaxLength: numOr0(DD04V?.SCRLEN3),
19962
19980
  headingFieldLabel: DD04V?.REPTEXT || "",
19963
- headingFieldLength: DD04V?.HEADLEN ? Number(DD04V.HEADLEN) : 0,
19964
- headingFieldMaxLength: DD04V?.HEADLEN ? Number(DD04V.HEADLEN) : 0,
19981
+ headingFieldLength: numOr0(DD04V?.HEADLEN),
19982
+ headingFieldMaxLength: numOr0(DD04V?.HEADLEN),
19965
19983
  searchHelp: "",
19966
19984
  searchHelpParameter: "",
19967
19985
  setGetParameter: "",
@@ -20200,109 +20218,98 @@ function computeIntlen(builtin, length) {
20200
20218
  if (builtin.length !== void 0) return builtin.length * 2;
20201
20219
  return 0;
20202
20220
  }
20203
- /** Build a DD03P entry from a single field definition */
20204
- async function buildFieldDD03P(field, tableName, resolver) {
20205
- const isKey = field.isKey;
20206
- const notNull = field.notNull;
20207
- let rollname;
20208
- let comptype;
20209
- let inttype;
20210
- let intlen;
20211
- let datatype;
20212
- let leng;
20213
- let decimals;
20214
- let mask;
20215
- let shlporigin;
20216
- let reftable;
20217
- let reffield;
20218
- if (field.type.kind === "builtin") {
20219
- const bt = field.type;
20220
- const typeInfo = BUILTIN_TYPES[bt.name];
20221
- if (typeInfo) {
20222
- const length = bt.length ?? typeInfo.length;
20223
- inttype = typeInfo.inttype;
20224
- intlen = zeroPad(computeIntlen(typeInfo, length), 6);
20225
- datatype = typeInfo.datatype;
20226
- if (!typeInfo.noLeng && length !== void 0) leng = zeroPad(length, 6);
20227
- const dec = bt.decimals ?? typeInfo.decimals;
20228
- if (dec !== void 0) decimals = zeroPad(dec, 6);
20229
- mask = ` ${typeInfo.datatype}`;
20230
- if (typeInfo.shlporigin) shlporigin = typeInfo.shlporigin;
20231
- if (bt.name === "curr" || bt.name === "quan") {
20232
- const refAnnotation = getAnnotation(field.annotations, bt.name === "curr" ? "Semantics.amount.currencyCode" : "Semantics.quantity.unitOfMeasure");
20233
- if (refAnnotation) {
20234
- reftable = tableName;
20235
- const parts = refAnnotation.split(".");
20236
- reffield = parts[parts.length - 1].toUpperCase();
20237
- }
20238
- }
20221
+ /** Type columns derived from a builtin CDS type reference. */
20222
+ function builtinFieldColumns(bt, field, tableName) {
20223
+ const typeInfo = BUILTIN_TYPES[bt.name];
20224
+ if (!typeInfo) return {};
20225
+ const out = {};
20226
+ const length = bt.length ?? typeInfo.length;
20227
+ out.inttype = typeInfo.inttype;
20228
+ out.intlen = zeroPad(computeIntlen(typeInfo, length), 6);
20229
+ out.datatype = typeInfo.datatype;
20230
+ if (!typeInfo.noLeng && length !== void 0) out.leng = zeroPad(length, 6);
20231
+ const dec = bt.decimals ?? typeInfo.decimals;
20232
+ if (dec !== void 0) out.decimals = zeroPad(dec, 6);
20233
+ out.mask = ` ${typeInfo.datatype}`;
20234
+ if (typeInfo.shlporigin) out.shlporigin = typeInfo.shlporigin;
20235
+ if (bt.name === "curr" || bt.name === "quan") {
20236
+ const refAnnotation = getAnnotation(field.annotations, bt.name === "curr" ? "Semantics.amount.currencyCode" : "Semantics.quantity.unitOfMeasure");
20237
+ if (refAnnotation) {
20238
+ out.reftable = tableName;
20239
+ out.reffield = refAnnotation.split(".").at(-1).toUpperCase();
20239
20240
  }
20240
- } else {
20241
- rollname = field.type.name.toUpperCase();
20242
- comptype = "E";
20243
- if (resolver) {
20244
- const resolved = await resolver.resolve(field.type.name);
20245
- comptype = resolved.comptype;
20246
- if (resolved.shlporigin) shlporigin = resolved.shlporigin;
20247
- if (resolved.comptype === "S") {
20248
- datatype = "STRU";
20249
- mask = " STRUS";
20250
- }
20241
+ }
20242
+ return out;
20243
+ }
20244
+ /** Type columns derived from a named type reference (resolved via ADT). */
20245
+ async function namedFieldColumns(field, resolver) {
20246
+ const out = {
20247
+ rollname: field.type.name.toUpperCase(),
20248
+ comptype: "E"
20249
+ };
20250
+ if (resolver) {
20251
+ const resolved = await resolver.resolve(field.type.name);
20252
+ out.comptype = resolved.comptype;
20253
+ if (resolved.shlporigin) out.shlporigin = resolved.shlporigin;
20254
+ if (resolved.comptype === "S") {
20255
+ out.datatype = "STRU";
20256
+ out.mask = " STRUS";
20251
20257
  }
20252
20258
  }
20259
+ return out;
20260
+ }
20261
+ /**
20262
+ * Assemble the DD03P entry in standard abapGit DD03P field order.
20263
+ *
20264
+ * NOTE: POSITION is a real DD03P database value. abapGit emits it
20265
+ * inconsistently (only for some transparent tables). We omit it to
20266
+ * avoid roundtrip mismatches — SAP auto-computes it on import.
20267
+ */
20268
+ function assembleDD03P(field, col) {
20253
20269
  const result = {};
20254
20270
  result.FIELDNAME = field.name.toUpperCase();
20255
- if (isKey) result.KEYFLAG = "X";
20256
- if (rollname) result.ROLLNAME = rollname;
20271
+ if (field.isKey) result.KEYFLAG = "X";
20272
+ if (col.rollname) result.ROLLNAME = col.rollname;
20257
20273
  result.ADMINFIELD = "0";
20258
- if (inttype) result.INTTYPE = inttype;
20259
- if (intlen) result.INTLEN = intlen;
20260
- if (notNull) result.NOTNULL = "X";
20261
- if (datatype) result.DATATYPE = datatype;
20262
- if (leng) result.LENG = leng;
20263
- if (decimals) result.DECIMALS = decimals;
20264
- if (shlporigin) result.SHLPORIGIN = shlporigin;
20265
- if (reftable) result.REFTABLE = reftable;
20266
- if (reffield) result.REFFIELD = reffield;
20267
- if (mask) result.MASK = mask;
20268
- if (comptype) result.COMPTYPE = comptype;
20274
+ if (col.inttype) result.INTTYPE = col.inttype;
20275
+ if (col.intlen) result.INTLEN = col.intlen;
20276
+ if (field.notNull) result.NOTNULL = "X";
20277
+ if (col.datatype) result.DATATYPE = col.datatype;
20278
+ if (col.leng) result.LENG = col.leng;
20279
+ if (col.decimals) result.DECIMALS = col.decimals;
20280
+ if (col.shlporigin) result.SHLPORIGIN = col.shlporigin;
20281
+ if (col.reftable) result.REFTABLE = col.reftable;
20282
+ if (col.reffield) result.REFFIELD = col.reffield;
20283
+ if (col.mask) result.MASK = col.mask;
20284
+ if (col.comptype) result.COMPTYPE = col.comptype;
20269
20285
  return result;
20270
20286
  }
20287
+ /** Build a DD03P entry from a single field definition */
20288
+ async function buildFieldDD03P(field, tableName, resolver) {
20289
+ return assembleDD03P(field, field.type.kind === "builtin" ? builtinFieldColumns(field.type, field, tableName) : await namedFieldColumns(field, resolver));
20290
+ }
20291
+ /** Build the DD03P entry for an INCLUDE directive (with or without suffix). */
20292
+ async function buildIncludeDD03P(inc, resolver) {
20293
+ const includeName = inc.name.toUpperCase();
20294
+ let ddtext;
20295
+ if (resolver) ddtext = (await resolver.resolve(inc.name)).description;
20296
+ const entry = {
20297
+ FIELDNAME: inc.suffix ? `.INCLU-${inc.suffix.toUpperCase()}` : ".INCLUDE",
20298
+ ADMINFIELD: "0",
20299
+ PRECFIELD: includeName,
20300
+ MASK: " S",
20301
+ COMPTYPE: "S"
20302
+ };
20303
+ if (ddtext) entry.DDTEXT = ddtext;
20304
+ return entry;
20305
+ }
20271
20306
  /**
20272
20307
  * Build DD03P entries from table/structure members
20273
20308
  */
20274
20309
  async function buildDD03P(members, tableName = "", resolver) {
20275
20310
  const entries = [];
20276
- for (const member of members) if ("kind" in member && member.kind === "include") {
20277
- const inc = member;
20278
- const includeName = inc.name.toUpperCase();
20279
- let ddtext;
20280
- if (resolver) ddtext = (await resolver.resolve(inc.name)).description;
20281
- if (inc.suffix) {
20282
- const entry = {
20283
- FIELDNAME: `.INCLU-${inc.suffix.toUpperCase()}`,
20284
- ADMINFIELD: "0",
20285
- PRECFIELD: includeName,
20286
- MASK: " S",
20287
- COMPTYPE: "S"
20288
- };
20289
- if (ddtext) entry.DDTEXT = ddtext;
20290
- entries.push(entry);
20291
- } else {
20292
- const entry = {
20293
- FIELDNAME: ".INCLUDE",
20294
- ADMINFIELD: "0",
20295
- PRECFIELD: includeName,
20296
- MASK: " S",
20297
- COMPTYPE: "S"
20298
- };
20299
- if (ddtext) entry.DDTEXT = ddtext;
20300
- entries.push(entry);
20301
- }
20302
- } else {
20303
- const field = member;
20304
- entries.push(await buildFieldDD03P(field, tableName, resolver));
20305
- }
20311
+ for (const member of members) if ("kind" in member && member.kind === "include") entries.push(await buildIncludeDD03P(member, resolver));
20312
+ else entries.push(await buildFieldDD03P(member, tableName, resolver));
20306
20313
  return entries;
20307
20314
  }
20308
20315
 
@@ -20323,8 +20330,8 @@ const XML_NAMED_ENTITIES = {
20323
20330
  };
20324
20331
  function decodeNumericCode(decimal, hex) {
20325
20332
  const base = decimal !== void 0 ? 10 : 16;
20326
- const value = decimal !== void 0 ? decimal : hex ?? "";
20327
- return String.fromCharCode(parseInt(value, base));
20333
+ const value = decimal ?? hex ?? "";
20334
+ return String.fromCodePoint(Number.parseInt(value, base));
20328
20335
  }
20329
20336
  function decodeXmlEntities(text) {
20330
20337
  return text.replace(/&(?:#(\d+)|#x([0-9a-fA-F]+)|([a-zA-Z][a-zA-Z0-9]*));/g, (match, decimal, hex, name) => decimal !== void 0 || hex !== void 0 ? decodeNumericCode(decimal, hex) : XML_NAMED_ENTITIES[name] ?? match);
@@ -20367,7 +20374,7 @@ function parseSettingsToDD09L(settingsXml, def) {
20367
20374
  function resolveReferencedType(xml, comptype) {
20368
20375
  const result = { comptype };
20369
20376
  if (comptype === "E" && /<dtel:searchHelp>[^<]+<\/dtel:searchHelp>/.test(xml)) result.shlporigin = "D";
20370
- const descMatch = xml.match(/adtcore:description="([^"]+)"/);
20377
+ const descMatch = /adtcore:description="([^"]+)"/.exec(xml);
20371
20378
  if (descMatch) result.description = decodeXmlEntities(descMatch[1]);
20372
20379
  return result;
20373
20380
  }
@@ -20891,7 +20898,7 @@ const serviceBindingHandler = createHandler("SRVB", {
20891
20898
  services: binding?.services
20892
20899
  };
20893
20900
  },
20894
- async serialize(object, ctx, options) {
20901
+ serialize(object, ctx, options) {
20895
20902
  const objectName = ctx.getObjectName(object);
20896
20903
  if (options?.format === "aff") {
20897
20904
  const bindingType = String(object?.bindingType ?? object?.data?.bindingType ?? "odataV4");
@@ -20902,10 +20909,10 @@ const serviceBindingHandler = createHandler("SRVB", {
20902
20909
  bindingTypeCategory,
20903
20910
  ...Array.isArray(services) && services.length > 0 ? { services } : {}
20904
20911
  } });
20905
- return [ctx.createFile(`${objectName}.${ctx.fileExtension}.json`, jsonContent)];
20912
+ return Promise.resolve([ctx.createFile(`${objectName}.${ctx.fileExtension}.json`, jsonContent)]);
20906
20913
  }
20907
20914
  const xmlContent = ctx.toAbapGitXml(object);
20908
- return [ctx.createFile(`${objectName}.${ctx.fileExtension}.xml`, xmlContent)];
20915
+ return Promise.resolve([ctx.createFile(`${objectName}.${ctx.fileExtension}.xml`, xmlContent)]);
20909
20916
  }
20910
20917
  });
20911
20918
 
@@ -21713,15 +21720,15 @@ const externalSchemaHandler = createHandler("DESD", {
21713
21720
  } };
21714
21721
  },
21715
21722
  fromAffJson: (json) => affFromAffJson(json, ""),
21716
- async serialize(object, ctx) {
21723
+ serialize(object, ctx) {
21717
21724
  const header = {
21718
21725
  description: object.description || String(object.name ?? ""),
21719
21726
  originalLanguage: (object.originalLanguage ?? "en").toLowerCase()
21720
21727
  };
21721
- return [ctx.createFile(`${ctx.getObjectName(object)}.desd.json`, `${JSON.stringify({
21728
+ return Promise.resolve([ctx.createFile(`${ctx.getObjectName(object)}.desd.json`, `${JSON.stringify({
21722
21729
  formatVersion: "1",
21723
21730
  header
21724
- }, null, 2)}\n`)];
21731
+ }, null, 2)}\n`)]);
21725
21732
  }
21726
21733
  });
21727
21734
 
@@ -21751,15 +21758,15 @@ function createAffFirstHandler(type, ext) {
21751
21758
  NAME: String(obj?.name ?? "").toUpperCase()
21752
21759
  } }),
21753
21760
  fromAffJson: (json) => affFromAffJson(json, ""),
21754
- async serialize(object, ctx) {
21761
+ serialize(object, ctx) {
21755
21762
  const header = {
21756
21763
  description: object.description || String(object.name ?? ""),
21757
21764
  originalLanguage: (object.originalLanguage ?? "en").toLowerCase()
21758
21765
  };
21759
- return [ctx.createFile(`${ctx.getObjectName(object)}.${ext}.json`, `${JSON.stringify({
21766
+ return Promise.resolve([ctx.createFile(`${ctx.getObjectName(object)}.${ext}.json`, `${JSON.stringify({
21760
21767
  formatVersion: "1",
21761
21768
  header
21762
- }, null, 2)}\n`)];
21769
+ }, null, 2)}\n`)]);
21763
21770
  }
21764
21771
  });
21765
21772
  }
@@ -21837,12 +21844,19 @@ const httpServiceHandler = createHandler("HTTP", {
21837
21844
  toAbapGit: (raw) => {
21838
21845
  const obj = unwrapData(raw);
21839
21846
  const name = String(obj.name ?? "").toUpperCase();
21847
+ const lang = isoToSapLang(obj.masterLanguage || obj.language);
21848
+ const handlerClassFallback = obj.handlerClass ? { UCONSERVHANDLER: {
21849
+ ID: name,
21850
+ VERSION: "A",
21851
+ SERVICEORDER: obj.serviceOrder ?? "01",
21852
+ SERVICEHANDLER: obj.handlerClass
21853
+ } } : void 0;
21840
21854
  return {
21841
21855
  HTTPID: name,
21842
21856
  HTTPTEXT: {
21843
21857
  ID: name,
21844
21858
  VERSION: "A",
21845
- LANG: isoToSapLang(obj.masterLanguage || obj.language),
21859
+ LANG: lang,
21846
21860
  SHORTTEXT: obj.description ?? ""
21847
21861
  },
21848
21862
  HTTPHDL: obj.handlers?.length ? { UCONSERVHANDLER: obj.handlers.map((h, i) => ({
@@ -21850,12 +21864,7 @@ const httpServiceHandler = createHandler("HTTP", {
21850
21864
  VERSION: "A",
21851
21865
  SERVICEORDER: h.serviceOrder ?? String(i + 1).padStart(2, "0"),
21852
21866
  SERVICEHANDLER: h.handler
21853
- })) } : obj.handlerClass ? { UCONSERVHANDLER: {
21854
- ID: name,
21855
- VERSION: "A",
21856
- SERVICEORDER: obj.serviceOrder ?? "01",
21857
- SERVICEHANDLER: obj.handlerClass
21858
- } } : void 0
21867
+ })) } : handlerClassFallback
21859
21868
  };
21860
21869
  },
21861
21870
  fromAbapGit: ({ HTTPID, HTTPTEXT, HTTPHDL }) => {
@@ -21903,9 +21912,7 @@ const numberRangeObjectHandler = createHandler("NROB", {
21903
21912
  CODE: obj.code,
21904
21913
  GAP: obj.gap,
21905
21914
  ROLLNR: obj.rollNr,
21906
- YEARLY: obj.yearly,
21907
- NRLVL: obj.nrLvl,
21908
- PROCIND: obj.procInd
21915
+ YEARLY: obj.yearly
21909
21916
  },
21910
21917
  TEXT: {
21911
21918
  LANGU: isoToSapLang(obj.masterLanguage || obj.language),
@@ -21929,9 +21936,7 @@ const numberRangeObjectHandler = createHandler("NROB", {
21929
21936
  code: ATTRIBUTES?.CODE,
21930
21937
  gap: ATTRIBUTES?.GAP,
21931
21938
  rollNr: ATTRIBUTES?.ROLLNR,
21932
- yearly: ATTRIBUTES?.YEARLY,
21933
- nrLvl: ATTRIBUTES?.NRLVL,
21934
- procInd: ATTRIBUTES?.PROCIND
21939
+ yearly: ATTRIBUTES?.YEARLY
21935
21940
  })
21936
21941
  });
21937
21942
 
@@ -21949,6 +21954,11 @@ const changeDocumentObjectHandler = createHandler("CHDO", {
21949
21954
  serializer_version: "v1.0.0",
21950
21955
  toAbapGit: (obj) => {
21951
21956
  const name = String(obj.name ?? "").toUpperCase();
21957
+ const objectTextFallback = obj.description ? { TCDOBTS: [{
21958
+ SPRAS: isoToSapLang(obj.masterLanguage || obj.language),
21959
+ OBJECT: name,
21960
+ OBTEXT: obj.description
21961
+ }] } : void 0;
21952
21962
  return { CHDO: {
21953
21963
  REPORTS_GENERATED: obj.generatedReports?.length ? { TCDRPS: obj.generatedReports.map((r) => ({
21954
21964
  OBJECT: r.object ?? name,
@@ -21967,11 +21977,7 @@ const changeDocumentObjectHandler = createHandler("CHDO", {
21967
21977
  SPRAS: isoToSapLang(t.language || obj.masterLanguage || obj.language),
21968
21978
  OBJECT: t.object ?? name,
21969
21979
  OBTEXT: t.text
21970
- })) } : obj.description ? { TCDOBTS: [{
21971
- SPRAS: isoToSapLang(obj.masterLanguage || obj.language),
21972
- OBJECT: name,
21973
- OBTEXT: obj.description
21974
- }] } : void 0
21980
+ })) } : objectTextFallback
21975
21981
  } };
21976
21982
  },
21977
21983
  fromAbapGit: ({ CHDO }) => {
@@ -22208,7 +22214,8 @@ const icfServiceHandler = createHandler("SICF", {
22208
22214
  const obj = unwrapData(raw);
22209
22215
  const name = String(obj.name ?? "").toUpperCase();
22210
22216
  const lang = isoToSapLang(obj.language);
22211
- const handlers = obj.handlers?.length ? obj.handlers : obj.handlerClass ? [{ handler: obj.handlerClass }] : [];
22217
+ const fallbackHandlers = obj.handlerClass ? [{ handler: obj.handlerClass }] : [];
22218
+ const handlers = obj.handlers?.length ? obj.handlers : fallbackHandlers;
22212
22219
  return {
22213
22220
  URL: obj.url ?? "",
22214
22221
  ICFSERVICE: {
@@ -22811,11 +22818,11 @@ const mimeObjectHandler = createHandler("SMIM", {
22811
22818
  PARENT_FOLDER_ID: obj.parentFolderId
22812
22819
  }
22813
22820
  }),
22814
- serialize: async (obj, ctx) => {
22821
+ serialize: (obj, ctx) => {
22815
22822
  const objectName = ctx.getObjectName(obj);
22816
22823
  const files = [ctx.createFile(`${objectName}.smim.xml`, formatAbapGitXml(ctx.toAbapGitXml(obj)))];
22817
22824
  if (obj.content && obj.fileName) files.push(ctx.createFile(`${objectName}.smim.${obj.fileName.toLowerCase()}`, obj.content, "base64"));
22818
- return files;
22825
+ return Promise.resolve(files);
22819
22826
  },
22820
22827
  setSources: (obj, sources) => {
22821
22828
  const data = unwrapData(obj);
@@ -23146,7 +23153,7 @@ const formHandler = createHandler("FORM", {
23146
23153
  })) } : void 0
23147
23154
  }] } };
23148
23155
  },
23149
- serialize: async (obj, ctx) => {
23156
+ serialize: (obj, ctx) => {
23150
23157
  const objectName = ctx.getObjectName(obj);
23151
23158
  const files = [ctx.createFile(`${objectName}.form.xml`, formatAbapGitXml(ctx.toAbapGitXml(obj)))];
23152
23159
  for (const [lang, lines] of Object.entries(obj.tdlines ?? {})) {
@@ -23165,13 +23172,13 @@ const formHandler = createHandler("FORM", {
23165
23172
  }, { pretty: true });
23166
23173
  files.push(ctx.createFile(`${objectName}.form.tdlines_${isoToSapLang(lang).toLowerCase()}.xml`, formatAbapGitXml(xml)));
23167
23174
  }
23168
- return files;
23175
+ return Promise.resolve(files);
23169
23176
  },
23170
23177
  setSources: (obj, sources) => {
23171
23178
  const data = unwrapData(obj);
23172
23179
  const tdlinesData = data.tdlines ?? {};
23173
23180
  for (const [key, content] of Object.entries(sources)) {
23174
- const match = key.match(/^tdlines_([a-zA-Z0-9]+)$/);
23181
+ const match = /^tdlines_([a-zA-Z0-9]+)$/.exec(key);
23175
23182
  if (!match) continue;
23176
23183
  const parsed = tdlines.parse(content);
23177
23184
  const items = normalizeItems(parsed.abapGit?.abap?.values?.TDLINES?.item);
@@ -24954,7 +24961,7 @@ const compositeTemplateHandler = createHandler("CMPT", {
24954
24961
  NAME: String(obj.name ?? "").toUpperCase(),
24955
24962
  DESCRIPTION: obj.description
24956
24963
  },
24957
- ...obj.extra ?? {}
24964
+ ...obj.extra
24958
24965
  } };
24959
24966
  },
24960
24967
  fromAbapGit: ({ CMPT }) => {
@@ -25304,12 +25311,12 @@ const iatuHandler = createHandler("IATU", {
25304
25311
  DEVCLASS: obj.packageName
25305
25312
  } };
25306
25313
  },
25307
- serialize: async (raw, ctx) => {
25314
+ serialize: (raw, ctx) => {
25308
25315
  const obj = unwrapData(raw);
25309
25316
  const objectName = ctx.getObjectName(obj);
25310
25317
  const files = [ctx.createFile(`${objectName}.iatu.xml`, formatAbapGitXml(ctx.toAbapGitXml(obj)))];
25311
25318
  if (obj.html) files.push(ctx.createFile(`${objectName}.iatu.html`, obj.html));
25312
- return files;
25319
+ return Promise.resolve(files);
25313
25320
  },
25314
25321
  setSources: (obj, sources) => {
25315
25322
  const data = unwrapData(obj);
@@ -25390,7 +25397,7 @@ function createGenericObjslHandler(type, schema) {
25390
25397
  serializer: `LCL_OBJECT_${type}`,
25391
25398
  serializer_version: "v1.0.0",
25392
25399
  toAbapGit: (raw) => {
25393
- return { ...unwrapData(raw).tables ?? {} };
25400
+ return { ...unwrapData(raw).tables };
25394
25401
  },
25395
25402
  fromAbapGit: (values) => ({
25396
25403
  name: "",
@@ -25426,20 +25433,20 @@ function createRawXmlHandler(type, schema) {
25426
25433
  serializer: `LCL_OBJECT_${type}`,
25427
25434
  serializer_version: "v1.0.0",
25428
25435
  toAbapGit: (raw) => {
25429
- return { ...unwrapData(raw).raw ?? {} };
25436
+ return { ...unwrapData(raw).raw };
25430
25437
  },
25431
25438
  fromAbapGit: (values) => ({
25432
25439
  name: "",
25433
25440
  raw: { ...values }
25434
25441
  }),
25435
- serialize: async (obj, ctx) => {
25442
+ serialize: (obj, ctx) => {
25436
25443
  const xml = schema.build({
25437
- ...obj.raw ?? {},
25444
+ ...obj.raw,
25438
25445
  version: "v1.0.0",
25439
25446
  serializer: `LCL_OBJECT_${type}`,
25440
25447
  serializer_version: "v1.0.0"
25441
25448
  });
25442
- return [ctx.createFile(`${ctx.getObjectName(obj)}.${ctx.fileExtension}.xml`, formatAbapGitXml(xml))];
25449
+ return Promise.resolve([ctx.createFile(`${ctx.getObjectName(obj)}.${ctx.fileExtension}.xml`, formatAbapGitXml(xml))]);
25443
25450
  }
25444
25451
  });
25445
25452
  }
@@ -25620,7 +25627,7 @@ function generateAbapGitXml(folderLogic) {
25620
25627
  * plus source file extensions (.abap, .abdl, .acds, .asrvd).
25621
25628
  */
25622
25629
  function parseAbapGitFilename(filename) {
25623
- const match = filename.match(/^([^.]+)\.([^.]+)(?:\.([^.]+))?\.(xml|json|abap|abdl|acds|asrvd)$/);
25630
+ const match = /^([^.]+)\.([^.]+)(?:\.([^.]+))?\.(xml|json|abap|abdl|acds|asrvd)$/.exec(filename);
25624
25631
  if (!match) return null;
25625
25632
  const [, name, type, suffixOrExt, extension] = match;
25626
25633
  return {
@@ -25729,7 +25736,7 @@ function collectXmlCompanions(xmlFiles, objectMap) {
25729
25736
  function collectBinaryCompanions(allFiles, objectMap) {
25730
25737
  for (const sourcePath of allFiles) {
25731
25738
  const filename = sourcePath.slice(sourcePath.lastIndexOf("/") + 1);
25732
- const match = filename.match(/^([^.]+)\.([^.]+)\..+$/);
25739
+ const match = /^([^.]+)\.([^.]+)\..+$/.exec(filename);
25733
25740
  if (!match) continue;
25734
25741
  const key = `${match[1].toUpperCase()}:${match[2].toUpperCase()}`;
25735
25742
  const obj = objectMap.get(key);
@@ -25817,6 +25824,58 @@ function setAbapLanguageVersion(adkObject, options) {
25817
25824
  if (data && !data.abapLanguageVersion) data.abapLanguageVersion = abapLangVerToAdt(options.abapLanguageVersion) ?? options.abapLanguageVersion;
25818
25825
  }
25819
25826
  /**
25827
+ * Read all source files of an object into a source-key → content map.
25828
+ * Binary companions travel base64-encoded.
25829
+ */
25830
+ async function loadObjectSources(fileTree, objFiles, handler) {
25831
+ const contents = await Promise.all(objFiles.sourceFiles.map(({ path, binary }) => binary ? fileTree.readBuffer(path).then((buffer) => buffer.toString("base64")) : fileTree.read(path)));
25832
+ const sources = {};
25833
+ for (const [index, { suffix }] of objFiles.sourceFiles.entries()) {
25834
+ const sourceKey = suffix ? handler.suffixToSourceKey?.[suffix] ?? suffix : "main";
25835
+ sources[sourceKey] = contents[index];
25836
+ }
25837
+ return sources;
25838
+ }
25839
+ /**
25840
+ * Compute the relative directory for package resolution and attach the
25841
+ * folder-logic hints to the ADK object.
25842
+ */
25843
+ function applyFolderInfo(adkObject, objFiles, folderConfig) {
25844
+ if (!folderConfig.hasAbapGitXml) return;
25845
+ const sourceDir = (objFiles.xmlFile ?? objFiles.jsonFile ?? "").split("/").slice(0, -1).join("/");
25846
+ adkObject._relDir = sourceDir.startsWith(folderConfig.startDir) ? sourceDir.slice(folderConfig.startDir.length).replace(/^\/+/, "") : sourceDir;
25847
+ adkObject._folderLogic = folderConfig.folderLogic;
25848
+ }
25849
+ /**
25850
+ * Build one object (plus its FUGR children) from a metadata/source file
25851
+ * group. A failure inside one object is logged and swallowed, mirroring
25852
+ * the abapGit log-and-continue rule.
25853
+ */
25854
+ async function* buildObject(fileTree, adk, objFiles, handler, options, folderConfig) {
25855
+ try {
25856
+ const meta = await parseMetadata(fileTree, objFiles, handler);
25857
+ if (!meta) return;
25858
+ const { values, isAffJson } = meta;
25859
+ const sources = await loadObjectSources(fileTree, objFiles, handler);
25860
+ const payload = buildPayload(handler, values, isAffJson, objFiles);
25861
+ const objectName = payload.name || objFiles.name;
25862
+ const adkType = typeof payload.type === "string" ? payload.type : objFiles.type;
25863
+ const adkObject = adk.getWithData({
25864
+ ...payload,
25865
+ name: objectName
25866
+ }, adkType);
25867
+ setObjectSources(handler, adkObject, sources);
25868
+ if (payload.description) adkObject._pendingDescription = payload.description;
25869
+ applyFolderInfo(adkObject, objFiles, folderConfig);
25870
+ resolvePackageRef(adkObject, options, folderConfig.hasAbapGitXml, folderConfig.folderLogic);
25871
+ setAbapLanguageVersion(adkObject, options);
25872
+ yield adkObject;
25873
+ if (objFiles.type === "FUGR" && payload._functions) yield* yieldFugrChildren(adk, payload, objectName, options, adkObject);
25874
+ } catch (error) {
25875
+ console.error(`Failed to deserialize ${objFiles.type} ${objFiles.name}:`, error);
25876
+ }
25877
+ }
25878
+ /**
25820
25879
  * Deserialize abapGit files to ADK objects
25821
25880
  *
25822
25881
  * True generator - yields objects one at a time as they're discovered.
@@ -25843,37 +25902,11 @@ async function* deserialize(fileTree, client, options) {
25843
25902
  if (!objFiles.xmlFile && !objFiles.jsonFile) continue;
25844
25903
  const handler = getHandler(objFiles.type);
25845
25904
  if (!handler) continue;
25846
- try {
25847
- const meta = await parseMetadata(fileTree, objFiles, handler);
25848
- if (!meta) continue;
25849
- const { values, isAffJson } = meta;
25850
- const sources = {};
25851
- for (const { path, suffix, binary } of objFiles.sourceFiles) {
25852
- const content = binary ? (await fileTree.readBuffer(path)).toString("base64") : await fileTree.read(path);
25853
- const sourceKey = suffix ? handler.suffixToSourceKey?.[suffix] ?? suffix : "main";
25854
- sources[sourceKey] = content;
25855
- }
25856
- const payload = buildPayload(handler, values, isAffJson, objFiles);
25857
- const objectName = payload.name || objFiles.name;
25858
- const adkType = typeof payload.type === "string" ? payload.type : objFiles.type;
25859
- const adkObject = adk.getWithData({
25860
- ...payload,
25861
- name: objectName
25862
- }, adkType);
25863
- setObjectSources(handler, adkObject, sources);
25864
- if (payload.description) adkObject._pendingDescription = payload.description;
25865
- if (hasAbapGitXml) {
25866
- const sourceDir = (objFiles.xmlFile ?? objFiles.jsonFile ?? "").split("/").slice(0, -1).join("/");
25867
- adkObject._relDir = sourceDir.startsWith(startDir) ? sourceDir.slice(startDir.length).replace(/^\/+/, "") : sourceDir;
25868
- adkObject._folderLogic = folderLogic;
25869
- }
25870
- resolvePackageRef(adkObject, options, hasAbapGitXml, folderLogic);
25871
- setAbapLanguageVersion(adkObject, options);
25872
- yield adkObject;
25873
- if (objFiles.type === "FUGR" && payload._functions) yield* yieldFugrChildren(adk, payload, objectName, options, adkObject);
25874
- } catch (error) {
25875
- console.error(`Failed to deserialize ${objFiles.type} ${objFiles.name}:`, error);
25876
- }
25905
+ yield* buildObject(fileTree, adk, objFiles, handler, options, {
25906
+ hasAbapGitXml,
25907
+ startDir,
25908
+ folderLogic
25909
+ });
25877
25910
  }
25878
25911
  }
25879
25912
  /**
@@ -25950,7 +25983,10 @@ async function buildMaterializedFiles(handler, object, packageDir, sources, form
25950
25983
  path,
25951
25984
  ...classification
25952
25985
  };
25953
- }).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
25986
+ }).sort((left, right) => {
25987
+ if (left.path < right.path) return -1;
25988
+ return left.path > right.path ? 1 : 0;
25989
+ });
25954
25990
  }
25955
25991
  const abapgitFormatPlugin = {
25956
25992
  id: "abapgit",
@@ -26175,7 +26211,7 @@ const abapGitPlugin = createPlugin({
26175
26211
  * PROG deletions without obj_func=D), no files are present and nothing
26176
26212
  * happens. This is expected behavior.
26177
26213
  */
26178
- async delete(objectRef, targetPath, _context) {
26214
+ delete(objectRef, targetPath, _context) {
26179
26215
  try {
26180
26216
  const srcDir = resolve(targetPath, "src");
26181
26217
  const matchedFiles = findObjectFiles(srcDir, objectRef.name, objectRef.type);
@@ -26192,21 +26228,21 @@ const abapGitPlugin = createPlugin({
26192
26228
  errors.push(error instanceof Error ? `Failed to delete ${relative(targetPath, filePath)}: ${error.message}` : `Failed to delete ${relative(targetPath, filePath)}: ${String(error)}`);
26193
26229
  }
26194
26230
  srcDirIndexCache.get(srcDir)?.set(`${objectRef.name.toLowerCase()}.${objectRef.type.toLowerCase()}.`, matchedFiles.filter((filePath) => existsSync(filePath)));
26195
- return {
26231
+ return Promise.resolve({
26196
26232
  success: errors.length === 0,
26197
26233
  filesRemoved,
26198
26234
  ...errors.length > 0 ? { errors } : {}
26199
- };
26235
+ });
26200
26236
  } catch (error) {
26201
- return {
26237
+ return Promise.resolve({
26202
26238
  success: false,
26203
26239
  filesRemoved: [],
26204
26240
  errors: [error instanceof Error ? error.message : String(error)]
26205
- };
26241
+ });
26206
26242
  }
26207
26243
  }
26208
26244
  },
26209
- hooks: { async afterImport(targetPath) {
26245
+ hooks: { afterImport(targetPath) {
26210
26246
  const target = resolve(targetPath);
26211
26247
  const folderLogic = folderLogicByTarget.get(target) ?? readFolderLogicFromExistingRepo(targetPath) ?? "prefix";
26212
26248
  try {
@@ -26217,6 +26253,7 @@ const abapGitPlugin = createPlugin({
26217
26253
  folderLogicByTarget.delete(target);
26218
26254
  srcDirIndexCache.delete(resolve(targetPath, "src"));
26219
26255
  }
26256
+ return Promise.resolve();
26220
26257
  } }
26221
26258
  });
26222
26259