@abapify/adt-plugin-gcts 0.4.1 → 0.4.3

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
@@ -1,7 +1,7 @@
1
1
  import { createPlugin, registerFormatPlugin } from "@abapify/adt-plugin";
2
2
  import { AdkClass, AdkDataElement, AdkDomain, AdkFunctionGroup, AdkInterface, AdkPackage, AdkProgram, AdkTable, AdkTableType, createAdk, getMainType, getTypeForKind } from "@abapify/adk";
3
- import { mkdirSync, writeFileSync } from "fs";
4
- import { dirname, join } from "path";
3
+ import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
5
 
6
6
  //#region src/lib/format/filename.ts
7
7
  /**
@@ -15,8 +15,11 @@ const TYPE_TO_SOURCE_EXT = {
15
15
  INTF: "abap",
16
16
  PROG: "abap",
17
17
  FUGR: "abap",
18
+ FUNC: "abap",
18
19
  DDLS: "asddls",
19
- DCLS: "asdcls"
20
+ DCLS: "asdcls",
21
+ BDEF: "abdl",
22
+ SRVD: "acds"
20
23
  };
21
24
  /** Metadata extension for all AFF/gCTS objects. */
22
25
  const METADATA_EXTENSION = "json";
@@ -59,11 +62,14 @@ function parseGctsFilename(filename) {
59
62
  type: parts.at(-2).toUpperCase(),
60
63
  extension
61
64
  };
62
- if (!new Set([
65
+ if (!(/* @__PURE__ */ new Set([
63
66
  "abap",
64
67
  "asddls",
65
- "asdcls"
66
- ]).has(extension)) return;
68
+ "asdcls",
69
+ "abdl",
70
+ "acds",
71
+ "properties"
72
+ ])).has(extension)) return;
67
73
  if (parts.length >= 4) return {
68
74
  name: parts.slice(0, -3).join("."),
69
75
  type: parts.at(-3).toUpperCase(),
@@ -179,6 +185,17 @@ function createHandler(adkClassOrType, definition) {
179
185
  path: metadataFile,
180
186
  content: buildMetadataFile(metadata)
181
187
  });
188
+ if (definition.getExtraFiles) {
189
+ const extras = await definition.getExtraFiles(object);
190
+ for (const extra of extras) {
191
+ const content = await resolveSource(extra);
192
+ if (!content) continue;
193
+ files.push({
194
+ path: extra.path,
195
+ content
196
+ });
197
+ }
198
+ }
182
199
  return files;
183
200
  },
184
201
  fromAbapGit: definition.fromMetadata ? (values) => definition.fromMetadata(values) : void 0,
@@ -193,11 +210,15 @@ function createHandler(adkClassOrType, definition) {
193
210
  /**
194
211
  * CLAS handler for gCTS / AFF format.
195
212
  *
196
- * Projects ADK class data to the AFF class-metadata shape:
213
+ * Projects ADK class data to the AFF clas-v1.json schema shape:
197
214
  *
198
215
  * {
199
- * header: { formatVersion, description, originalLanguage, ... },
200
- * class: { category, visibility, final, abstract, ... }
216
+ * formatVersion: "1",
217
+ * header: { description, originalLanguage, abapLanguageVersion? },
218
+ * category?,
219
+ * fixPointArithmetic?,
220
+ * messageClass?,
221
+ * descriptions?: { types?, attributes?, events?, methods? }
201
222
  * }
202
223
  *
203
224
  * Sources are emitted per include (main, definitions, implementations, ...).
@@ -215,27 +236,39 @@ const SUFFIX = {
215
236
  testclasses: "testclasses"
216
237
  };
217
238
  const SUFFIX_TO_SOURCE_KEY = Object.fromEntries(Object.entries(SUFFIX).filter(([, v]) => v !== void 0).map(([k, v]) => [v, k]));
239
+ /**
240
+ * ADK category enum → AFF category enum.
241
+ * AFF uses a different, finer-grained set of category names.
242
+ */
243
+ const CATEGORY_TO_AFF = {
244
+ generalObjectType: "generalObjectType",
245
+ exitClass: "exitClass",
246
+ testClass: "testclassAbapUnit",
247
+ behaviorPool: "behaviorClass",
248
+ entityEventHandler: "entityEventHandler",
249
+ persistentClass: "persistentClass",
250
+ factoryClass: "factoryForPersistentClass",
251
+ rfcProxyClass: "rfcProxyClass",
252
+ communicationConnectionClass: "communicationConnectionClass",
253
+ exceptionClass: "exceptionClass",
254
+ areaClass: "areaClassSharedObjects",
255
+ bspClass: "bspApplicationClass"
256
+ };
257
+ const CATEGORY_FROM_AFF = Object.fromEntries(Object.entries(CATEGORY_TO_AFF).map(([k, v]) => [v, k]));
218
258
  const classHandler = createHandler(AdkClass, {
219
259
  suffixToSourceKey: SUFFIX_TO_SOURCE_KEY,
220
260
  toMetadata(cls) {
221
261
  const data = cls.dataSync;
262
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
222
263
  return {
264
+ formatVersion: "1",
223
265
  header: {
224
- formatVersion: "1.0",
225
266
  description: cls.description ?? data.description ?? "",
226
- originalLanguage: data.language ?? data.masterLanguage,
227
- abapLanguageVersion: data.abapLanguageVersion
267
+ originalLanguage: lang,
268
+ ...data.abapLanguageVersion && data.abapLanguageVersion !== "standard" ? { abapLanguageVersion: data.abapLanguageVersion } : {}
228
269
  },
229
- class: {
230
- category: data.category,
231
- visibility: data.visibility ?? "public",
232
- final: data.final === true,
233
- abstract: data.abstract === true,
234
- fixedPointArithmetic: data.fixPointArithmetic === true,
235
- unicodeChecksActive: data.activeUnicodeCheck !== false,
236
- sharedMemoryEnabled: data.sharedMemoryEnabled === true,
237
- superClass: data.superClassRef?.name
238
- }
270
+ ...data.category ? { category: CATEGORY_TO_AFF[data.category] ?? "generalObjectType" } : {},
271
+ ...typeof data.fixPointArithmetic === "boolean" ? { fixPointArithmetic: data.fixPointArithmetic } : {}
239
272
  };
240
273
  },
241
274
  getSources: (cls) => {
@@ -245,16 +278,14 @@ const classHandler = createHandler(AdkClass, {
245
278
  }));
246
279
  },
247
280
  fromMetadata: (meta) => ({
248
- name: (meta?.class?.name ?? "").toUpperCase(),
281
+ name: "",
249
282
  type: "CLAS/OC",
250
- description: meta?.header?.description,
251
- language: meta?.header?.originalLanguage,
252
- masterLanguage: meta?.header?.originalLanguage,
253
- category: meta?.class?.category,
254
- visibility: meta?.class?.visibility,
255
- final: meta?.class?.final === true,
256
- abstract: meta?.class?.abstract === true,
257
- abapLanguageVersion: meta?.header?.abapLanguageVersion
283
+ description: meta.header.description,
284
+ language: meta.header.originalLanguage?.toUpperCase(),
285
+ masterLanguage: meta.header.originalLanguage?.toUpperCase(),
286
+ category: meta.category ? CATEGORY_FROM_AFF[meta.category] ?? meta.category : void 0,
287
+ abapLanguageVersion: meta.header.abapLanguageVersion,
288
+ fixPointArithmetic: meta.fixPointArithmetic
258
289
  }),
259
290
  setSources: (cls, sources) => {
260
291
  cls._pendingSources = sources;
@@ -266,28 +297,38 @@ const classHandler = createHandler(AdkClass, {
266
297
  //#region src/lib/handlers/objects/intf.ts
267
298
  /**
268
299
  * INTF handler for gCTS / AFF format.
300
+ *
301
+ * Projects ADK interface data to the AFF intf-v1.json schema shape:
302
+ *
303
+ * {
304
+ * formatVersion: "1",
305
+ * header: { description, originalLanguage, abapLanguageVersion? },
306
+ * category?,
307
+ * proxy?,
308
+ * descriptions?
309
+ * }
269
310
  */
270
311
  const interfaceHandler = createHandler(AdkInterface, {
271
312
  toMetadata(intf) {
272
313
  const data = intf.dataSync;
314
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
273
315
  return {
316
+ formatVersion: "1",
274
317
  header: {
275
- formatVersion: "1.0",
276
318
  description: intf.description ?? data.description ?? "",
277
- originalLanguage: data.language ?? data.masterLanguage,
278
- abapLanguageVersion: data.abapLanguageVersion
279
- },
280
- interface: { unicodeChecksActive: data.activeUnicodeCheck !== false }
319
+ originalLanguage: lang,
320
+ ...data.abapLanguageVersion && data.abapLanguageVersion !== "standard" ? { abapLanguageVersion: data.abapLanguageVersion } : {}
321
+ }
281
322
  };
282
323
  },
283
324
  getSource: (obj) => obj.getSource(),
284
325
  fromMetadata: (meta) => ({
285
- name: (meta?.interface?.name ?? "").toUpperCase(),
326
+ name: "",
286
327
  type: "INTF/OI",
287
- description: meta?.header?.description,
288
- language: meta?.header?.originalLanguage,
289
- masterLanguage: meta?.header?.originalLanguage,
290
- abapLanguageVersion: meta?.header?.abapLanguageVersion
328
+ description: meta.header.description,
329
+ language: meta.header.originalLanguage?.toUpperCase(),
330
+ masterLanguage: meta.header.originalLanguage?.toUpperCase(),
331
+ abapLanguageVersion: meta.header.abapLanguageVersion
291
332
  }),
292
333
  setSources: (intf, sources) => {
293
334
  if (sources.main) intf._pendingSource = sources.main;
@@ -298,31 +339,53 @@ const interfaceHandler = createHandler(AdkInterface, {
298
339
  //#region src/lib/handlers/objects/prog.ts
299
340
  /**
300
341
  * PROG handler for gCTS / AFF format.
342
+ *
343
+ * Projects ADK program data to the AFF prog-v1.json schema shape:
344
+ *
345
+ * {
346
+ * formatVersion: "1",
347
+ * header: { description, originalLanguage, abapLanguageVersion? },
348
+ * generalInformation: { programType, programStatus, fixPointArithmetic?, ... }
349
+ * }
301
350
  */
351
+ /** ADK sourceObjectStatus → AFF programStatus */
352
+ const STATUS_TO_AFF = {
353
+ SAPStandardProduction: "sapProductionProgram",
354
+ customerProduction: "customerProductionProgram",
355
+ system: "systemProgram",
356
+ test: "testProgram"
357
+ };
358
+ const STATUS_FROM_AFF = Object.fromEntries(Object.entries(STATUS_TO_AFF).map(([k, v]) => [v, k]));
302
359
  const programHandler = createHandler(AdkProgram, {
303
360
  toMetadata(prog) {
304
361
  const data = prog.dataSync;
362
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
363
+ const generalInformation = {
364
+ programType: data.programType ?? "executableProgram",
365
+ programStatus: STATUS_TO_AFF[data.sourceObjectStatus ?? ""] ?? "systemProgram"
366
+ };
367
+ if (typeof data.fixPointArithmetic === "boolean") generalInformation.fixPointArithmetic = data.fixPointArithmetic;
305
368
  return {
369
+ formatVersion: "1",
306
370
  header: {
307
- formatVersion: "1.0",
308
371
  description: prog.description ?? data.description ?? "",
309
- originalLanguage: data.language ?? data.masterLanguage,
310
- abapLanguageVersion: data.abapLanguageVersion
372
+ originalLanguage: lang,
373
+ ...data.abapLanguageVersion && data.abapLanguageVersion !== "standard" ? { abapLanguageVersion: data.abapLanguageVersion } : {}
311
374
  },
312
- program: {
313
- programType: data.programType ?? "executableProgram",
314
- fixedPointArithmetic: data.fixPointArithmetic === true,
315
- unicodeChecksActive: data.activeUnicodeCheck !== false
316
- }
375
+ generalInformation
317
376
  };
318
377
  },
319
378
  getSource: (obj) => obj.getSource(),
320
379
  fromMetadata: (meta) => ({
321
- name: (meta?.program?.name ?? "").toUpperCase(),
380
+ name: "",
322
381
  type: "PROG/P",
323
- description: meta?.header?.description,
324
- language: meta?.header?.originalLanguage,
325
- programType: meta?.program?.programType
382
+ description: meta.header.description,
383
+ language: meta.header.originalLanguage?.toUpperCase(),
384
+ masterLanguage: meta.header.originalLanguage?.toUpperCase(),
385
+ programType: meta.generalInformation?.programType,
386
+ sourceObjectStatus: meta.generalInformation?.programStatus ? STATUS_FROM_AFF[meta.generalInformation.programStatus] : void 0,
387
+ fixPointArithmetic: meta.generalInformation?.fixPointArithmetic,
388
+ abapLanguageVersion: meta.header.abapLanguageVersion
326
389
  }),
327
390
  setSources: (prog, sources) => {
328
391
  if (sources.main) prog._pendingSource = sources.main;
@@ -336,15 +399,20 @@ const programHandler = createHandler(AdkProgram, {
336
399
  *
337
400
  * Packages use a fixed filename (`package.devc.json`) — same convention as
338
401
  * abapGit's `package.devc.xml`.
402
+ *
403
+ * Note: DEVC has no AFF schema in SAP/abap-file-formats — this handler
404
+ * uses a gCTS-compatible shape with formatVersion "1". If AFF adds a DEVC
405
+ * schema later, align here.
339
406
  */
340
407
  const packageHandler = createHandler(AdkPackage, {
341
408
  metadataFileName: PACKAGE_FILENAME,
342
409
  toMetadata(pkg) {
343
410
  return {
411
+ formatVersion: "1",
344
412
  header: {
345
- formatVersion: "1.0",
346
413
  description: pkg.description ?? "",
347
- originalLanguage: pkg.dataSync?.language ?? pkg.dataSync?.masterLanguage
414
+ originalLanguage: (pkg.dataSync?.language ?? pkg.dataSync?.masterLanguage ?? "").toLowerCase(),
415
+ ...pkg.dataSync?.abapLanguageVersion && pkg.dataSync.abapLanguageVersion !== "standard" ? { abapLanguageVersion: pkg.dataSync.abapLanguageVersion } : {}
348
416
  },
349
417
  package: {
350
418
  softwareComponent: pkg.dataSync?.transport?.softwareComponent?.name,
@@ -355,7 +423,10 @@ const packageHandler = createHandler(AdkPackage, {
355
423
  },
356
424
  fromMetadata: (meta) => ({
357
425
  name: "",
358
- description: meta?.header?.description
426
+ description: meta?.header?.description,
427
+ language: meta?.header?.originalLanguage?.toUpperCase(),
428
+ masterLanguage: meta?.header?.originalLanguage?.toUpperCase(),
429
+ abapLanguageVersion: meta?.header?.abapLanguageVersion
359
430
  })
360
431
  });
361
432
 
@@ -363,79 +434,188 @@ const packageHandler = createHandler(AdkPackage, {
363
434
  //#region src/lib/handlers/objects/doma.ts
364
435
  /**
365
436
  * DOMA (domain) handler for gCTS / AFF format.
437
+ *
438
+ * Projects ADK domain data to the AFF doma-v1.json schema shape:
439
+ *
440
+ * {
441
+ * formatVersion: "1",
442
+ * header: { description, originalLanguage, abapLanguageVersion? },
443
+ * format: { dataType, length, decimals? } (required),
444
+ * outputCharacteristics?: { length?, caseSensitive?, conversionRoutine?, ... },
445
+ * valueTable?: { name }
446
+ * }
366
447
  */
367
448
  const domainHandler = createHandler(AdkDomain, {
368
449
  toMetadata(doma) {
369
450
  const data = doma.dataSync;
370
- return {
451
+ const content = data.content ?? {};
452
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
453
+ const ti = content.typeInformation ?? {};
454
+ const oi = content.outputInformation ?? {};
455
+ const vi = content.valueInformation ?? {};
456
+ const format = {
457
+ dataType: ti.datatype ?? "CHAR",
458
+ length: ti.length ?? 1
459
+ };
460
+ if (typeof ti.decimals === "number") format.decimals = ti.decimals;
461
+ const result = {
462
+ formatVersion: "1",
371
463
  header: {
372
- formatVersion: "1.0",
373
464
  description: doma.description ?? "",
374
- originalLanguage: data.language ?? data.masterLanguage
465
+ originalLanguage: lang
375
466
  },
376
- domain: {
377
- dataType: data.dataType,
378
- length: data.length,
379
- decimals: data.decimals,
380
- outputLength: data.outputLength,
381
- caseSensitive: data.caseSensitive === true,
382
- valueTable: data.valueTableRef?.name
383
- }
467
+ format
384
468
  };
469
+ const outChars = {};
470
+ if (typeof oi.length === "number") outChars.length = oi.length;
471
+ if (typeof oi.lowercase === "boolean") outChars.caseSensitive = !oi.lowercase;
472
+ if (oi.conversionExit) outChars.conversionRoutine = oi.conversionExit;
473
+ if (Object.keys(outChars).length > 0) result.outputCharacteristics = outChars;
474
+ const vtRef = vi.valueTableRef ?? {};
475
+ if (vtRef.name) result.valueTable = { name: vtRef.name };
476
+ return result;
385
477
  },
386
- fromMetadata: (meta) => ({
387
- name: (meta?.domain?.name ?? "").toUpperCase(),
388
- description: meta?.header?.description,
389
- dataType: meta?.domain?.dataType,
390
- length: meta?.domain?.length,
391
- decimals: meta?.domain?.decimals
392
- })
478
+ fromMetadata: (meta) => {
479
+ const result = {
480
+ name: "",
481
+ description: meta.header.description,
482
+ language: meta.header.originalLanguage?.toUpperCase(),
483
+ masterLanguage: meta.header.originalLanguage?.toUpperCase()
484
+ };
485
+ const content = { typeInformation: {
486
+ datatype: meta.format.dataType,
487
+ length: meta.format.length,
488
+ decimals: meta.format.decimals ?? 0
489
+ } };
490
+ if (meta.outputCharacteristics) content.outputInformation = {
491
+ length: meta.outputCharacteristics.length,
492
+ lowercase: meta.outputCharacteristics.caseSensitive ? false : void 0,
493
+ conversionExit: meta.outputCharacteristics.conversionRoutine
494
+ };
495
+ if (meta.valueTable?.name) content.valueInformation = { valueTableRef: { name: meta.valueTable.name } };
496
+ result.content = content;
497
+ return result;
498
+ }
393
499
  });
394
500
 
395
501
  //#endregion
396
502
  //#region src/lib/handlers/objects/dtel.ts
397
503
  /**
398
504
  * DTEL (data element) handler for gCTS / AFF format.
505
+ *
506
+ * Projects ADK data element data to the AFF dtel-v1.json schema shape:
507
+ *
508
+ * {
509
+ * formatVersion: "1",
510
+ * header: { description, originalLanguage, abapLanguageVersion? },
511
+ * dataTypeInformation: { domainName? } | { predefinedType: { dataType, length?, decimals? } },
512
+ * fieldLabels?: { short, shortLength, medium, mediumLength, long, longLength, heading, headingLength }
513
+ * }
399
514
  */
515
+ function buildDataTypeInformation(data) {
516
+ if (data.typeKind === "domain" && data.typeName) return { domainName: data.typeName };
517
+ if (data.dataType) {
518
+ const predefinedType = { dataType: data.dataType };
519
+ if (typeof data.dataTypeLength === "number") predefinedType.length = data.dataTypeLength;
520
+ if (typeof data.dataTypeDecimals === "number") predefinedType.decimals = data.dataTypeDecimals;
521
+ return { predefinedType };
522
+ }
523
+ return {};
524
+ }
525
+ function buildFieldLabels(data) {
526
+ const labels = {};
527
+ for (const [src, dst, lenKey] of [
528
+ [
529
+ "shortFieldLabel",
530
+ "short",
531
+ "shortFieldLength"
532
+ ],
533
+ [
534
+ "mediumFieldLabel",
535
+ "medium",
536
+ "mediumFieldLength"
537
+ ],
538
+ [
539
+ "longFieldLabel",
540
+ "long",
541
+ "longFieldLength"
542
+ ],
543
+ [
544
+ "headingFieldLabel",
545
+ "heading",
546
+ "headingFieldLength"
547
+ ]
548
+ ]) if (data[src]) {
549
+ labels[dst] = data[src];
550
+ if (typeof data[lenKey] === "number") labels[`${dst}Length`] = data[lenKey];
551
+ }
552
+ return labels;
553
+ }
400
554
  const dataElementHandler = createHandler(AdkDataElement, {
401
555
  toMetadata(dtel) {
402
556
  const data = dtel.dataSync;
403
- return {
557
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
558
+ const result = {
559
+ formatVersion: "1",
404
560
  header: {
405
- formatVersion: "1.0",
406
561
  description: dtel.description ?? "",
407
- originalLanguage: data.language ?? data.masterLanguage
562
+ originalLanguage: lang
408
563
  },
409
- dataElement: {
410
- typeKind: data.typeKind,
411
- dataType: data.dataType,
412
- length: data.length,
413
- decimals: data.decimals,
414
- domain: data.domainRef?.name,
415
- predefinedType: data.predefinedType
416
- }
564
+ dataTypeInformation: buildDataTypeInformation(data)
417
565
  };
566
+ const fieldLabels = buildFieldLabels(data);
567
+ if (Object.keys(fieldLabels).length > 0) result.fieldLabels = fieldLabels;
568
+ return result;
418
569
  },
419
- fromMetadata: (meta) => ({
420
- name: (meta?.dataElement?.name ?? "").toUpperCase(),
421
- description: meta?.header?.description,
422
- dataType: meta?.dataElement?.dataType
423
- })
570
+ fromMetadata: (meta) => {
571
+ const dti = meta.dataTypeInformation ?? {};
572
+ const result = {
573
+ name: "",
574
+ description: meta.header.description,
575
+ language: meta.header.originalLanguage?.toUpperCase(),
576
+ masterLanguage: meta.header.originalLanguage?.toUpperCase()
577
+ };
578
+ if (dti.domainName) {
579
+ result.typeKind = "domain";
580
+ result.typeName = dti.domainName;
581
+ } else if (dti.predefinedType) {
582
+ result.typeKind = "predefinedAbapType";
583
+ result.dataType = dti.predefinedType.dataType;
584
+ result.dataTypeLength = dti.predefinedType.length;
585
+ result.dataTypeDecimals = dti.predefinedType.decimals;
586
+ }
587
+ const fl = meta.fieldLabels;
588
+ if (fl) {
589
+ result.shortFieldLabel = fl.short;
590
+ result.shortFieldLength = fl.shortLength;
591
+ result.mediumFieldLabel = fl.medium;
592
+ result.mediumFieldLength = fl.mediumLength;
593
+ result.longFieldLabel = fl.long;
594
+ result.longFieldLength = fl.longLength;
595
+ result.headingFieldLabel = fl.heading;
596
+ result.headingFieldLength = fl.headingLength;
597
+ }
598
+ return result;
599
+ }
424
600
  });
425
601
 
426
602
  //#endregion
427
603
  //#region src/lib/handlers/objects/tabl.ts
428
604
  /**
429
605
  * TABL (table / structure) handler for gCTS / AFF format.
606
+ *
607
+ * Note: TABL has no AFF schema in SAP/abap-file-formats — this handler
608
+ * uses a gCTS-compatible shape with formatVersion "1". If AFF adds a TABL
609
+ * schema later, align here.
430
610
  */
431
611
  const tableHandler = createHandler(AdkTable, {
432
612
  toMetadata(tabl) {
433
613
  const data = tabl.dataSync;
434
614
  return {
615
+ formatVersion: "1",
435
616
  header: {
436
- formatVersion: "1.0",
437
617
  description: tabl.description ?? "",
438
- originalLanguage: data.language ?? data.masterLanguage
618
+ originalLanguage: (data.language ?? data.masterLanguage ?? "").toLowerCase()
439
619
  },
440
620
  table: {
441
621
  tableCategory: data.tableCategory,
@@ -446,8 +626,14 @@ const tableHandler = createHandler(AdkTable, {
446
626
  };
447
627
  },
448
628
  fromMetadata: (meta) => ({
449
- name: (meta?.table?.name ?? "").toUpperCase(),
450
- description: meta?.header?.description
629
+ name: "",
630
+ description: meta?.header?.description,
631
+ language: meta?.header?.originalLanguage?.toUpperCase(),
632
+ masterLanguage: meta?.header?.originalLanguage?.toUpperCase(),
633
+ tableCategory: meta?.table?.tableCategory,
634
+ deliveryClass: meta?.table?.deliveryClass,
635
+ dataMaintenance: meta?.table?.dataMaintenance,
636
+ fields: meta?.table?.fields
451
637
  })
452
638
  });
453
639
 
@@ -455,15 +641,19 @@ const tableHandler = createHandler(AdkTable, {
455
641
  //#region src/lib/handlers/objects/ttyp.ts
456
642
  /**
457
643
  * TTYP (table type) handler for gCTS / AFF format.
644
+ *
645
+ * Note: TTYP has no AFF schema in SAP/abap-file-formats — this handler
646
+ * uses a gCTS-compatible shape with formatVersion "1". If AFF adds a TTYP
647
+ * schema later, align here.
458
648
  */
459
649
  const tableTypeHandler = createHandler(AdkTableType, {
460
650
  toMetadata(ttyp) {
461
651
  const data = ttyp.dataSync;
462
652
  return {
653
+ formatVersion: "1",
463
654
  header: {
464
- formatVersion: "1.0",
465
655
  description: ttyp.description ?? "",
466
- originalLanguage: data.language ?? data.masterLanguage
656
+ originalLanguage: (data.language ?? data.masterLanguage ?? "").toLowerCase()
467
657
  },
468
658
  tableType: {
469
659
  accessMode: data.accessMode,
@@ -474,8 +664,9 @@ const tableTypeHandler = createHandler(AdkTableType, {
474
664
  };
475
665
  },
476
666
  fromMetadata: (meta) => ({
477
- name: (meta?.tableType?.name ?? "").toUpperCase(),
478
- description: meta?.header?.description
667
+ name: "",
668
+ description: meta?.header?.description,
669
+ language: meta?.header?.originalLanguage?.toUpperCase()
479
670
  })
480
671
  });
481
672
 
@@ -484,31 +675,418 @@ const tableTypeHandler = createHandler(AdkTableType, {
484
675
  /**
485
676
  * FUGR (function group) handler for gCTS / AFF format.
486
677
  *
678
+ * Projects ADK function group data to the AFF fugr-v1.json schema shape:
679
+ *
680
+ * {
681
+ * formatVersion: "1",
682
+ * header: { description, originalLanguage, abapLanguageVersion? },
683
+ * fixPointArithmetic: boolean (required),
684
+ * status?
685
+ * }
686
+ *
487
687
  * AFF's FUGR representation nests function modules under the group
488
- * directory. This plugin emits the group metadata only — per-module files
489
- * are produced by individual FUNC handlers once ADK exposes them.
688
+ * directory. This handler emits the group metadata plus per-function-module
689
+ * files when function module data is available:
690
+ * <fmname>.func.json — FM metadata (func-v1.json schema)
691
+ * <fmname>.func.abap — FM source
490
692
  */
693
+ function buildFuncMetadata(fm) {
694
+ const meta = {
695
+ formatVersion: "1",
696
+ header: { description: fm.description ?? fm.name ?? "" },
697
+ processingType: fm.processingType ?? "normal",
698
+ includeNumber: fm.includeNumber ?? "0000"
699
+ };
700
+ return JSON.stringify(meta, null, 2) + "\n";
701
+ }
491
702
  const functionGroupHandler = createHandler(AdkFunctionGroup, {
492
703
  toMetadata(fugr) {
493
- const data = fugr.dataSync;
704
+ const data = fugr.dataSync ?? {};
705
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
494
706
  return {
707
+ formatVersion: "1",
495
708
  header: {
496
- formatVersion: "1.0",
497
709
  description: fugr.description ?? "",
498
- originalLanguage: data.language ?? data.masterLanguage
710
+ originalLanguage: lang
711
+ },
712
+ fixPointArithmetic: data.fixPointArithmetic === true
713
+ };
714
+ },
715
+ getSource: (fugr) => typeof fugr.getSource === "function" ? fugr.getSource() : "",
716
+ getExtraFiles(fugr) {
717
+ const fms = fugr.functionModules ?? [];
718
+ const files = [];
719
+ for (const fm of fms) {
720
+ const fmName = fm.name.toLowerCase();
721
+ files.push({
722
+ path: `${fmName}.func.json`,
723
+ content: buildFuncMetadata(fm)
724
+ });
725
+ if (fm.source) files.push({
726
+ path: `${fmName}.func.abap`,
727
+ content: fm.source
728
+ });
729
+ }
730
+ return files;
731
+ },
732
+ fromMetadata: (meta) => ({
733
+ name: "",
734
+ description: meta.header.description,
735
+ language: meta.header.originalLanguage?.toUpperCase(),
736
+ masterLanguage: meta.header.originalLanguage?.toUpperCase(),
737
+ fixPointArithmetic: meta.fixPointArithmetic
738
+ })
739
+ });
740
+
741
+ //#endregion
742
+ //#region src/lib/handlers/objects/ddls.ts
743
+ /**
744
+ * DDLS (CDS view) handler for gCTS / AFF format.
745
+ *
746
+ * Source-driven: emits `<name>.ddls.asddls` (source) + `<name>.ddls.json`
747
+ * (metadata per ddls-v1.json schema).
748
+ *
749
+ * {
750
+ * formatVersion: "1",
751
+ * header: { description, originalLanguage, abapLanguageVersion? },
752
+ * sourceOrigin: "abapDevelopmentTools" (required),
753
+ * sourceType: "unknown" (required),
754
+ * parentName?: string
755
+ * }
756
+ */
757
+ const ddlSourceHandler = createHandler("DDLS", {
758
+ toMetadata(obj) {
759
+ const lang = (obj.originalLanguage ?? "").toLowerCase();
760
+ return {
761
+ formatVersion: "1",
762
+ header: {
763
+ description: obj.description ?? obj.name ?? "",
764
+ originalLanguage: lang,
765
+ ...obj.abapLanguageVersion ? { abapLanguageVersion: obj.abapLanguageVersion } : {}
766
+ },
767
+ sourceOrigin: obj.sourceOrigin ?? "abapDevelopmentTools",
768
+ sourceType: obj.sourceType ?? "unknown"
769
+ };
770
+ },
771
+ getSource: (obj) => typeof obj.getSource === "function" ? obj.getSource() : "",
772
+ fromMetadata: (meta) => ({
773
+ name: "",
774
+ description: meta.header.description,
775
+ language: meta.header.originalLanguage?.toUpperCase(),
776
+ masterLanguage: meta.header.originalLanguage?.toUpperCase(),
777
+ sourceOrigin: meta.sourceOrigin,
778
+ sourceType: meta.sourceType
779
+ })
780
+ });
781
+
782
+ //#endregion
783
+ //#region src/lib/handlers/objects/dcls.ts
784
+ /**
785
+ * DCLS (CDS access control) handler for gCTS / AFF format.
786
+ *
787
+ * Source-driven: emits `<name>.dcls.asdcls` (source) + `<name>.dcls.json`
788
+ * (metadata per dcls-v1.json schema).
789
+ *
790
+ * {
791
+ * formatVersion: "1",
792
+ * header: { description, originalLanguage, abapLanguageVersion? }
793
+ * }
794
+ */
795
+ const dclSourceHandler = createHandler("DCLS", {
796
+ toMetadata(obj) {
797
+ const lang = (obj.originalLanguage ?? "").toLowerCase();
798
+ return {
799
+ formatVersion: "1",
800
+ header: {
801
+ description: obj.description ?? obj.name ?? "",
802
+ originalLanguage: lang,
803
+ ...obj.abapLanguageVersion ? { abapLanguageVersion: obj.abapLanguageVersion } : {}
804
+ }
805
+ };
806
+ },
807
+ getSource: (obj) => typeof obj.getSource === "function" ? obj.getSource() : "",
808
+ fromMetadata: (meta) => ({
809
+ name: "",
810
+ description: meta.header.description,
811
+ language: meta.header.originalLanguage?.toUpperCase(),
812
+ masterLanguage: meta.header.originalLanguage?.toUpperCase()
813
+ })
814
+ });
815
+
816
+ //#endregion
817
+ //#region src/lib/handlers/objects/bdef.ts
818
+ /**
819
+ * BDEF (RAP behavior definition) handler for gCTS / AFF format.
820
+ *
821
+ * Source-driven: emits `<name>.bdef.abdl` (source) + `<name>.bdef.json`
822
+ * (metadata per bdef-v1.json schema).
823
+ *
824
+ * {
825
+ * formatVersion: "1",
826
+ * header: { description, originalLanguage, abapLanguageVersion? },
827
+ * extendedBehaviorDefintion?: { name?: string }
828
+ * }
829
+ */
830
+ const behaviorDefinitionHandler = createHandler("BDEF", {
831
+ toMetadata(obj) {
832
+ const lang = (obj.originalLanguage ?? "").toLowerCase();
833
+ return {
834
+ formatVersion: "1",
835
+ header: {
836
+ description: obj.description ?? obj.name ?? "",
837
+ originalLanguage: lang,
838
+ ...obj.abapLanguageVersion ? { abapLanguageVersion: obj.abapLanguageVersion } : {}
839
+ }
840
+ };
841
+ },
842
+ getSource: (obj) => typeof obj.getSource === "function" ? obj.getSource() : "",
843
+ fromMetadata: (meta) => ({
844
+ name: "",
845
+ description: meta.header.description,
846
+ language: meta.header.originalLanguage?.toUpperCase(),
847
+ masterLanguage: meta.header.originalLanguage?.toUpperCase()
848
+ })
849
+ });
850
+
851
+ //#endregion
852
+ //#region src/lib/handlers/objects/srvd.ts
853
+ /**
854
+ * SRVD (RAP service definition) handler for gCTS / AFF format.
855
+ *
856
+ * Source-driven: emits `<name>.srvd.acds` (source) + `<name>.srvd.json`
857
+ * (metadata per srvd-v1.json schema).
858
+ *
859
+ * {
860
+ * formatVersion: "1",
861
+ * header: { description, originalLanguage, abapLanguageVersion? },
862
+ * generalInformation: { sourceOrigin, sourceType }
863
+ * }
864
+ */
865
+ const serviceDefinitionHandler = createHandler("SRVD", {
866
+ toMetadata(obj) {
867
+ const lang = (obj.originalLanguage ?? "").toLowerCase();
868
+ return {
869
+ formatVersion: "1",
870
+ header: {
871
+ description: obj.description ?? obj.name ?? "",
872
+ originalLanguage: lang,
873
+ ...obj.abapLanguageVersion ? { abapLanguageVersion: obj.abapLanguageVersion } : {}
499
874
  },
500
- functionGroup: {
501
- fixedPointArithmetic: data.fixPointArithmetic === true,
502
- unicodeChecksActive: data.activeUnicodeCheck !== false
875
+ generalInformation: {
876
+ sourceOrigin: obj.sourceOrigin ?? "abapDevelopmentTools",
877
+ sourceType: obj.sourceType ?? "definition"
503
878
  }
504
879
  };
505
880
  },
881
+ getSource: (obj) => typeof obj.getSource === "function" ? obj.getSource() : "",
506
882
  fromMetadata: (meta) => ({
507
- name: (meta?.functionGroup?.name ?? "").toUpperCase(),
508
- description: meta?.header?.description
883
+ name: "",
884
+ description: meta.header.description,
885
+ language: meta.header.originalLanguage?.toUpperCase(),
886
+ masterLanguage: meta.header.originalLanguage?.toUpperCase(),
887
+ sourceOrigin: meta.generalInformation.sourceOrigin,
888
+ sourceType: meta.generalInformation.sourceType
509
889
  })
510
890
  });
511
891
 
892
+ //#endregion
893
+ //#region src/lib/handlers/objects/srvb.ts
894
+ /**
895
+ * SRVB (RAP service binding) handler for gCTS / AFF format.
896
+ *
897
+ * Metadata-only (no source): emits `<name>.srvb.json` only
898
+ * (metadata per srvb-v1.json schema).
899
+ *
900
+ * {
901
+ * formatVersion: "1",
902
+ * header: { description, originalLanguage, abapLanguageVersion? },
903
+ * bindingType: string (required),
904
+ * bindingTypeCategory: "ui" | "webApi" (required),
905
+ * services: [{ name, versions: [{ serviceVersion, serviceDefinition }] }]
906
+ * }
907
+ */
908
+ const serviceBindingHandler = createHandler("SRVB", {
909
+ toMetadata(obj) {
910
+ const lang = (obj.originalLanguage ?? "").toLowerCase();
911
+ return {
912
+ formatVersion: "1",
913
+ header: {
914
+ description: obj.description ?? obj.name ?? "",
915
+ originalLanguage: lang,
916
+ ...obj.abapLanguageVersion ? { abapLanguageVersion: obj.abapLanguageVersion } : {}
917
+ },
918
+ bindingType: obj.bindingType ?? "",
919
+ bindingTypeCategory: obj.bindingTypeCategory ?? "ui",
920
+ services: obj.services ?? []
921
+ };
922
+ },
923
+ fromMetadata: (meta) => ({
924
+ name: "",
925
+ description: meta.header.description,
926
+ language: meta.header.originalLanguage?.toUpperCase(),
927
+ masterLanguage: meta.header.originalLanguage?.toUpperCase(),
928
+ bindingType: meta.bindingType,
929
+ bindingTypeCategory: meta.bindingTypeCategory,
930
+ services: meta.services
931
+ })
932
+ });
933
+
934
+ //#endregion
935
+ //#region src/lib/handlers/objects/msag.ts
936
+ /**
937
+ * MSAG (message class) handler for gCTS / AFF format.
938
+ *
939
+ * Metadata-only (no source): emits `<name>.msag.json` only
940
+ * (metadata per msag-v1.json schema).
941
+ *
942
+ * {
943
+ * formatVersion: "1",
944
+ * header: { description, originalLanguage },
945
+ * messages: [{ number?: string, text?: string }]
946
+ * }
947
+ */
948
+ const messageClassHandler = createHandler("MSAG", {
949
+ toMetadata(obj) {
950
+ const lang = (obj.originalLanguage ?? "").toLowerCase();
951
+ return {
952
+ formatVersion: "1",
953
+ header: {
954
+ description: obj.description ?? obj.name ?? "",
955
+ originalLanguage: lang
956
+ },
957
+ messages: obj.messages ?? []
958
+ };
959
+ },
960
+ fromMetadata: (meta) => ({
961
+ name: "",
962
+ description: meta.header.description,
963
+ language: meta.header.originalLanguage?.toUpperCase(),
964
+ masterLanguage: meta.header.originalLanguage?.toUpperCase(),
965
+ messages: meta.messages
966
+ })
967
+ });
968
+
969
+ //#endregion
970
+ //#region src/lib/handlers/objects/wave2-generic.ts
971
+ /**
972
+ * Generic AFF handler stubs for the remaining object types.
973
+ *
974
+ * Wave 2 mass-generates handlers for all AFF types that have JSON schemas
975
+ * but no dedicated handler. Each stub emits the minimal AFF contract:
976
+ * { formatVersion: "1", header: { description, originalLanguage } }
977
+ *
978
+ * Type-specific fields can be added later by promoting a stub to a
979
+ * dedicated handler file (like clas.ts, ddls.ts, etc.) when the ADK
980
+ * object model exposes richer data for that type.
981
+ *
982
+ * All types here are registered via the string-type form of
983
+ * `createHandler` — no ADK class required.
984
+ */
985
+ function toMetadata(obj) {
986
+ const lang = (obj.originalLanguage ?? "").toLowerCase();
987
+ const meta = {
988
+ formatVersion: "1",
989
+ header: {
990
+ description: obj.description ?? obj.name ?? "",
991
+ originalLanguage: lang
992
+ }
993
+ };
994
+ if (obj.abapLanguageVersion) meta.header.abapLanguageVersion = obj.abapLanguageVersion;
995
+ return meta;
996
+ }
997
+ function fromMetadata(meta) {
998
+ return {
999
+ name: "",
1000
+ description: meta.header.description,
1001
+ language: meta.header.originalLanguage?.toUpperCase(),
1002
+ masterLanguage: meta.header.originalLanguage?.toUpperCase()
1003
+ };
1004
+ }
1005
+ function getSource(obj) {
1006
+ return typeof obj.getSource === "function" ? obj.getSource() : "";
1007
+ }
1008
+ for (const type of [
1009
+ "ADVC",
1010
+ "AIFA",
1011
+ "AIFD",
1012
+ "AIFF",
1013
+ "AIFI",
1014
+ "AIFN",
1015
+ "AIFP",
1016
+ "AIFR",
1017
+ "AOBJ",
1018
+ "APIC",
1019
+ "APLO",
1020
+ "APOB",
1021
+ "BGQC",
1022
+ "CDBO",
1023
+ "CFDF",
1024
+ "CHDO",
1025
+ "CHKC",
1026
+ "CHKO",
1027
+ "CHKV",
1028
+ "COTA",
1029
+ "CSNM",
1030
+ "DCAT",
1031
+ "DDLA",
1032
+ "DDLX",
1033
+ "DESD",
1034
+ "DMON",
1035
+ "DOBJ",
1036
+ "DRAS",
1037
+ "DRTY",
1038
+ "DSFD",
1039
+ "DSFI",
1040
+ "DTDC",
1041
+ "DTEB",
1042
+ "DTIX",
1043
+ "DTSC",
1044
+ "EDCC",
1045
+ "EDCK",
1046
+ "EDCR",
1047
+ "EDOI",
1048
+ "EEEC",
1049
+ "ENHO",
1050
+ "ENHS",
1051
+ "ENQU",
1052
+ "EVTB",
1053
+ "EVTO",
1054
+ "GSMP",
1055
+ "HTTP",
1056
+ "ILMB",
1057
+ "INTM",
1058
+ "INTS",
1059
+ "IWNG",
1060
+ "NONT",
1061
+ "NROB",
1062
+ "NTTA",
1063
+ "NTTY",
1064
+ "RONT",
1065
+ "RVBC",
1066
+ "SAIA",
1067
+ "SAJC",
1068
+ "SAJT",
1069
+ "SCP1",
1070
+ "SFPF",
1071
+ "SIAD",
1072
+ "SITO",
1073
+ "SMBC",
1074
+ "SMTG",
1075
+ "SPRV",
1076
+ "SRVC",
1077
+ "SUCO",
1078
+ "SUSI",
1079
+ "SWCR",
1080
+ "SXTG",
1081
+ "UIAD",
1082
+ "UIPG",
1083
+ "UIST"
1084
+ ]) createHandler(type, {
1085
+ toMetadata,
1086
+ getSource,
1087
+ fromMetadata
1088
+ });
1089
+
512
1090
  //#endregion
513
1091
  //#region src/lib/format/gcts-format.ts
514
1092
  const gctsFormatPlugin = {
@@ -528,7 +1106,7 @@ const gctsFormatPlugin = {
528
1106
  //#endregion
529
1107
  //#region src/lib/deserializer.ts
530
1108
  /** Source-file extensions emitted by gCTS handlers. */
531
- const SOURCE_EXTENSIONS = new Set([
1109
+ const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
532
1110
  "abap",
533
1111
  "asddls",
534
1112
  "asdcls"
@@ -579,7 +1157,8 @@ async function* deserialize(fileTree, client, options) {
579
1157
  for (const ext of SOURCE_EXTENSIONS) {
580
1158
  const paths = await fileTree.glob(`**/*.${ext}`);
581
1159
  for (const path of paths) {
582
- const parsed = parseGctsFilename(path.split("/").pop());
1160
+ const filename = path.split("/").pop();
1161
+ const parsed = parseGctsFilename(filename);
583
1162
  if (!parsed) continue;
584
1163
  const key = `${parsed.name.toUpperCase()}:${parsed.type.toUpperCase()}`;
585
1164
  const obj = objectMap.get(key);
@@ -702,6 +1281,10 @@ const gctsPlugin = createPlugin({
702
1281
  * throughout the `adt` CLI.
703
1282
  */
704
1283
  registerFormatPlugin(gctsFormatPlugin);
1284
+ registerFormatPlugin({
1285
+ ...gctsFormatPlugin,
1286
+ id: "aff"
1287
+ });
705
1288
 
706
1289
  //#endregion
707
1290
  export { gctsPlugin as GctsPlugin, gctsPlugin as default, gctsPlugin, METADATA_EXTENSION, PACKAGE_FILENAME, adtUriToGctsPath, createHandler, gctsFilename, gctsFormatPlugin, getHandler, getSupportedTypes, isSupported, parseGctsFilename };