@abapify/adt-plugin-gcts 0.4.0 → 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 ADDED
@@ -0,0 +1,1291 @@
1
+ import { createPlugin, registerFormatPlugin } from "@abapify/adt-plugin";
2
+ import { AdkClass, AdkDataElement, AdkDomain, AdkFunctionGroup, AdkInterface, AdkPackage, AdkProgram, AdkTable, AdkTableType, createAdk, getMainType, getTypeForKind } from "@abapify/adk";
3
+ import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+
6
+ //#region src/lib/format/filename.ts
7
+ /**
8
+ * Extension used for the source-file part of each object type.
9
+ *
10
+ * Order of precedence: CDS types use AFF's `.asddls` / `.asdcls`; everything
11
+ * else uses `.abap`. Metadata is always `.json`.
12
+ */
13
+ const TYPE_TO_SOURCE_EXT = {
14
+ CLAS: "abap",
15
+ INTF: "abap",
16
+ PROG: "abap",
17
+ FUGR: "abap",
18
+ FUNC: "abap",
19
+ DDLS: "asddls",
20
+ DCLS: "asdcls",
21
+ BDEF: "abdl",
22
+ SRVD: "acds"
23
+ };
24
+ /** Metadata extension for all AFF/gCTS objects. */
25
+ const METADATA_EXTENSION = "json";
26
+ /** Fixed filename for package metadata (AFF / gCTS convention). */
27
+ const PACKAGE_FILENAME = "package.devc.json";
28
+ /**
29
+ * Build the AFF/gCTS on-disk filename for an object.
30
+ *
31
+ * @param name Object name (case preserved — AFF normalises to lowercase)
32
+ * @param type ABAP object type code (CLAS, INTF, ...)
33
+ * @param kind `"metadata"` (default) or `"source"`.
34
+ * @param suffix Optional sub-include suffix (e.g. `locals_def` for CLAS).
35
+ */
36
+ function gctsFilename(name, type, kind = "metadata", suffix) {
37
+ const base = name.toLowerCase();
38
+ const typeLower = mainType(type).toLowerCase();
39
+ if (kind === "metadata") return `${base}.${typeLower}.${METADATA_EXTENSION}`;
40
+ const ext = TYPE_TO_SOURCE_EXT[mainType(type).toUpperCase()] ?? "abap";
41
+ return suffix ? `${base}.${typeLower}.${suffix}.${ext}` : `${base}.${typeLower}.${ext}`;
42
+ }
43
+ /**
44
+ * Parse an AFF/gCTS filename back into its components.
45
+ *
46
+ * Returns `undefined` if the filename does not look like a known AFF/gCTS
47
+ * artefact — callers should skip unknown files (e.g. `README.md`).
48
+ */
49
+ function parseGctsFilename(filename) {
50
+ if (filename === "package.devc.json") return {
51
+ name: "",
52
+ type: "DEVC",
53
+ extension: METADATA_EXTENSION
54
+ };
55
+ const parts = filename.split(".");
56
+ if (parts.length < 3) return;
57
+ const extension = parts.at(-1);
58
+ const nameParts = (parts.length >= 4 ? parts.at(-2) : void 0) ? parts.slice(0, -3) : parts.slice(0, -2);
59
+ if (nameParts.length === 0) return;
60
+ if (extension === "json") return {
61
+ name: nameParts.length === 0 ? "" : parts.slice(0, -2).join("."),
62
+ type: parts.at(-2).toUpperCase(),
63
+ extension
64
+ };
65
+ if (!(/* @__PURE__ */ new Set([
66
+ "abap",
67
+ "asddls",
68
+ "asdcls",
69
+ "abdl",
70
+ "acds",
71
+ "properties"
72
+ ])).has(extension)) return;
73
+ if (parts.length >= 4) return {
74
+ name: parts.slice(0, -3).join("."),
75
+ type: parts.at(-3).toUpperCase(),
76
+ suffix: parts.at(-2),
77
+ extension
78
+ };
79
+ return {
80
+ name: parts.slice(0, -2).join("."),
81
+ type: parts.at(-2).toUpperCase(),
82
+ extension
83
+ };
84
+ }
85
+ /**
86
+ * Translate an ADT object URI (e.g. `/sap/bc/adt/oo/classes/zcl_foo`) to
87
+ * the AFF/gCTS on-disk path (e.g. `zcl_foo/zcl_foo.clas.json`).
88
+ *
89
+ * Matches the `adtUriToAbapGitPath` helper in the abapGit plugin.
90
+ */
91
+ function adtUriToGctsPath(uri) {
92
+ if (!uri.startsWith("/sap/bc/adt/")) return void 0;
93
+ const tail = uri.slice(12);
94
+ if (tail.length === 0) return void 0;
95
+ for (const [pattern, type] of [
96
+ [/^oo\/classes\/([^/]+)$/, "CLAS"],
97
+ [/^oo\/interfaces\/([^/]+)$/, "INTF"],
98
+ [/^programs\/programs\/([^/]+)$/, "PROG"],
99
+ [/^functions\/groups\/([^/]+)$/, "FUGR"],
100
+ [/^packages\/([^/]+)$/, "DEVC"],
101
+ [/^ddic\/domains\/([^/]+)$/, "DOMA"],
102
+ [/^ddic\/dataelements\/([^/]+)$/, "DTEL"],
103
+ [/^ddic\/structures\/([^/]+)$/, "TABL"],
104
+ [/^ddic\/tables\/([^/]+)$/, "TABL"],
105
+ [/^ddic\/tabletypes\/([^/]+)$/, "TTYP"],
106
+ [/^ddic\/ddl\/sources\/([^/]+)$/, "DDLS"],
107
+ [/^acm\/dcls\/sources\/([^/]+)$/, "DCLS"]
108
+ ]) {
109
+ const m = pattern.exec(tail);
110
+ if (m) {
111
+ const name = m[1];
112
+ if (type === "DEVC") return `${name.toLowerCase()}/${PACKAGE_FILENAME}`;
113
+ return `${name.toLowerCase()}/${gctsFilename(name, type)}`;
114
+ }
115
+ }
116
+ }
117
+ function mainType(type) {
118
+ return type.split("/")[0];
119
+ }
120
+
121
+ //#endregion
122
+ //#region src/lib/handlers/base.ts
123
+ const handlerRegistry = /* @__PURE__ */ new Map();
124
+ /** Look up a handler (full-type first, then main-type fallback). */
125
+ function getHandler(type) {
126
+ return handlerRegistry.get(type) ?? handlerRegistry.get(getMainType(type));
127
+ }
128
+ /** `true` iff the given object type has a registered handler. */
129
+ function isSupported(type) {
130
+ return handlerRegistry.has(type) || handlerRegistry.has(getMainType(type));
131
+ }
132
+ /** Snapshot of all registered object types. */
133
+ function getSupportedTypes() {
134
+ return [...handlerRegistry.keys()];
135
+ }
136
+ /**
137
+ * Build a JSON metadata file for the object. JSON is pretty-printed (2-space
138
+ * indent) so the output is diff-friendly and matches AFF's own tooling.
139
+ */
140
+ function buildMetadataFile(meta) {
141
+ return JSON.stringify(meta, null, 2) + "\n";
142
+ }
143
+ function createHandler(adkClassOrType, definition) {
144
+ let type;
145
+ if (typeof adkClassOrType === "string") type = adkClassOrType;
146
+ else {
147
+ const kind = adkClassOrType.kind;
148
+ const derived = getTypeForKind(kind);
149
+ if (!derived) throw new Error(`Unknown ADK kind: ${kind}. Ensure the ADK class has a static 'kind' property.`);
150
+ type = derived;
151
+ }
152
+ const fileExtension = getMainType(type).toLowerCase();
153
+ async function resolveSource(entry) {
154
+ return await (typeof entry.content === "function" ? entry.content() : entry.content);
155
+ }
156
+ const handler = {
157
+ type,
158
+ fileExtension,
159
+ schema: {
160
+ parse: (raw) => JSON.parse(raw),
161
+ build: (data) => JSON.stringify(data, null, 2) + "\n"
162
+ },
163
+ suffixToSourceKey: definition.suffixToSourceKey,
164
+ async serialize(object) {
165
+ const files = [];
166
+ const objectName = (object.name ?? "").toLowerCase();
167
+ if (definition.getSources) for (const entry of definition.getSources(object)) {
168
+ const content = await resolveSource(entry);
169
+ if (!content) continue;
170
+ files.push({
171
+ path: gctsFilename(objectName, type, "source", entry.suffix),
172
+ content
173
+ });
174
+ }
175
+ else if (definition.getSource) {
176
+ const content = await definition.getSource(object);
177
+ if (content) files.push({
178
+ path: gctsFilename(objectName, type, "source"),
179
+ content
180
+ });
181
+ }
182
+ const metadata = definition.toMetadata(object);
183
+ const metadataFile = typeof definition.metadataFileName === "function" ? definition.metadataFileName(object) : definition.metadataFileName ?? gctsFilename(objectName, type, "metadata");
184
+ files.push({
185
+ path: metadataFile,
186
+ content: buildMetadataFile(metadata)
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
+ }
199
+ return files;
200
+ },
201
+ fromAbapGit: definition.fromMetadata ? (values) => definition.fromMetadata(values) : void 0,
202
+ setSources: definition.setSources
203
+ };
204
+ handlerRegistry.set(type, handler);
205
+ return handler;
206
+ }
207
+
208
+ //#endregion
209
+ //#region src/lib/handlers/objects/clas.ts
210
+ /**
211
+ * CLAS handler for gCTS / AFF format.
212
+ *
213
+ * Projects ADK class data to the AFF clas-v1.json schema shape:
214
+ *
215
+ * {
216
+ * formatVersion: "1",
217
+ * header: { description, originalLanguage, abapLanguageVersion? },
218
+ * category?,
219
+ * fixPointArithmetic?,
220
+ * messageClass?,
221
+ * descriptions?: { types?, attributes?, events?, methods? }
222
+ * }
223
+ *
224
+ * Sources are emitted per include (main, definitions, implementations, ...).
225
+ * Abapgit suffix convention is reused for consistency — AFF does not
226
+ * standardise include filenames, but using the same suffixes as abapGit
227
+ * means downstream tooling (diff, round-trip) can treat both formats with
228
+ * the same suffix map.
229
+ */
230
+ const SUFFIX = {
231
+ main: void 0,
232
+ definitions: "locals_def",
233
+ implementations: "locals_imp",
234
+ localtypes: "locals_types",
235
+ macros: "macros",
236
+ testclasses: "testclasses"
237
+ };
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]));
258
+ const classHandler = createHandler(AdkClass, {
259
+ suffixToSourceKey: SUFFIX_TO_SOURCE_KEY,
260
+ toMetadata(cls) {
261
+ const data = cls.dataSync;
262
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
263
+ return {
264
+ formatVersion: "1",
265
+ header: {
266
+ description: cls.description ?? data.description ?? "",
267
+ originalLanguage: lang,
268
+ ...data.abapLanguageVersion && data.abapLanguageVersion !== "standard" ? { abapLanguageVersion: data.abapLanguageVersion } : {}
269
+ },
270
+ ...data.category ? { category: CATEGORY_TO_AFF[data.category] ?? "generalObjectType" } : {},
271
+ ...typeof data.fixPointArithmetic === "boolean" ? { fixPointArithmetic: data.fixPointArithmetic } : {}
272
+ };
273
+ },
274
+ getSources: (cls) => {
275
+ return (cls.dataSync.include ?? []).map((inc) => ({
276
+ suffix: SUFFIX[String(inc.includeType ?? "main")],
277
+ content: () => cls.getIncludeSource(String(inc.includeType ?? "main"))
278
+ }));
279
+ },
280
+ fromMetadata: (meta) => ({
281
+ name: "",
282
+ type: "CLAS/OC",
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
289
+ }),
290
+ setSources: (cls, sources) => {
291
+ cls._pendingSources = sources;
292
+ if (sources.main) cls._pendingSource = sources.main;
293
+ }
294
+ });
295
+
296
+ //#endregion
297
+ //#region src/lib/handlers/objects/intf.ts
298
+ /**
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
+ * }
310
+ */
311
+ const interfaceHandler = createHandler(AdkInterface, {
312
+ toMetadata(intf) {
313
+ const data = intf.dataSync;
314
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
315
+ return {
316
+ formatVersion: "1",
317
+ header: {
318
+ description: intf.description ?? data.description ?? "",
319
+ originalLanguage: lang,
320
+ ...data.abapLanguageVersion && data.abapLanguageVersion !== "standard" ? { abapLanguageVersion: data.abapLanguageVersion } : {}
321
+ }
322
+ };
323
+ },
324
+ getSource: (obj) => obj.getSource(),
325
+ fromMetadata: (meta) => ({
326
+ name: "",
327
+ type: "INTF/OI",
328
+ description: meta.header.description,
329
+ language: meta.header.originalLanguage?.toUpperCase(),
330
+ masterLanguage: meta.header.originalLanguage?.toUpperCase(),
331
+ abapLanguageVersion: meta.header.abapLanguageVersion
332
+ }),
333
+ setSources: (intf, sources) => {
334
+ if (sources.main) intf._pendingSource = sources.main;
335
+ }
336
+ });
337
+
338
+ //#endregion
339
+ //#region src/lib/handlers/objects/prog.ts
340
+ /**
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
+ * }
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]));
359
+ const programHandler = createHandler(AdkProgram, {
360
+ toMetadata(prog) {
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;
368
+ return {
369
+ formatVersion: "1",
370
+ header: {
371
+ description: prog.description ?? data.description ?? "",
372
+ originalLanguage: lang,
373
+ ...data.abapLanguageVersion && data.abapLanguageVersion !== "standard" ? { abapLanguageVersion: data.abapLanguageVersion } : {}
374
+ },
375
+ generalInformation
376
+ };
377
+ },
378
+ getSource: (obj) => obj.getSource(),
379
+ fromMetadata: (meta) => ({
380
+ name: "",
381
+ type: "PROG/P",
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
389
+ }),
390
+ setSources: (prog, sources) => {
391
+ if (sources.main) prog._pendingSource = sources.main;
392
+ }
393
+ });
394
+
395
+ //#endregion
396
+ //#region src/lib/handlers/objects/devc.ts
397
+ /**
398
+ * DEVC (package) handler for gCTS / AFF format.
399
+ *
400
+ * Packages use a fixed filename (`package.devc.json`) — same convention as
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.
406
+ */
407
+ const packageHandler = createHandler(AdkPackage, {
408
+ metadataFileName: PACKAGE_FILENAME,
409
+ toMetadata(pkg) {
410
+ return {
411
+ formatVersion: "1",
412
+ header: {
413
+ description: pkg.description ?? "",
414
+ originalLanguage: (pkg.dataSync?.language ?? pkg.dataSync?.masterLanguage ?? "").toLowerCase(),
415
+ ...pkg.dataSync?.abapLanguageVersion && pkg.dataSync.abapLanguageVersion !== "standard" ? { abapLanguageVersion: pkg.dataSync.abapLanguageVersion } : {}
416
+ },
417
+ package: {
418
+ softwareComponent: pkg.dataSync?.transport?.softwareComponent?.name,
419
+ applicationComponent: pkg.dataSync?.applicationComponent?.name,
420
+ packageType: pkg.dataSync?.attributes?.packageType
421
+ }
422
+ };
423
+ },
424
+ fromMetadata: (meta) => ({
425
+ name: "",
426
+ description: meta?.header?.description,
427
+ language: meta?.header?.originalLanguage?.toUpperCase(),
428
+ masterLanguage: meta?.header?.originalLanguage?.toUpperCase(),
429
+ abapLanguageVersion: meta?.header?.abapLanguageVersion
430
+ })
431
+ });
432
+
433
+ //#endregion
434
+ //#region src/lib/handlers/objects/doma.ts
435
+ /**
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
+ * }
447
+ */
448
+ const domainHandler = createHandler(AdkDomain, {
449
+ toMetadata(doma) {
450
+ const data = doma.dataSync;
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",
463
+ header: {
464
+ description: doma.description ?? "",
465
+ originalLanguage: lang
466
+ },
467
+ format
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;
477
+ },
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
+ }
499
+ });
500
+
501
+ //#endregion
502
+ //#region src/lib/handlers/objects/dtel.ts
503
+ /**
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
+ * }
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
+ }
554
+ const dataElementHandler = createHandler(AdkDataElement, {
555
+ toMetadata(dtel) {
556
+ const data = dtel.dataSync;
557
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
558
+ const result = {
559
+ formatVersion: "1",
560
+ header: {
561
+ description: dtel.description ?? "",
562
+ originalLanguage: lang
563
+ },
564
+ dataTypeInformation: buildDataTypeInformation(data)
565
+ };
566
+ const fieldLabels = buildFieldLabels(data);
567
+ if (Object.keys(fieldLabels).length > 0) result.fieldLabels = fieldLabels;
568
+ return result;
569
+ },
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
+ }
600
+ });
601
+
602
+ //#endregion
603
+ //#region src/lib/handlers/objects/tabl.ts
604
+ /**
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.
610
+ */
611
+ const tableHandler = createHandler(AdkTable, {
612
+ toMetadata(tabl) {
613
+ const data = tabl.dataSync;
614
+ return {
615
+ formatVersion: "1",
616
+ header: {
617
+ description: tabl.description ?? "",
618
+ originalLanguage: (data.language ?? data.masterLanguage ?? "").toLowerCase()
619
+ },
620
+ table: {
621
+ tableCategory: data.tableCategory,
622
+ deliveryClass: data.deliveryClass,
623
+ dataMaintenance: data.dataMaintenance,
624
+ fields: data.fields
625
+ }
626
+ };
627
+ },
628
+ fromMetadata: (meta) => ({
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
637
+ })
638
+ });
639
+
640
+ //#endregion
641
+ //#region src/lib/handlers/objects/ttyp.ts
642
+ /**
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.
648
+ */
649
+ const tableTypeHandler = createHandler(AdkTableType, {
650
+ toMetadata(ttyp) {
651
+ const data = ttyp.dataSync;
652
+ return {
653
+ formatVersion: "1",
654
+ header: {
655
+ description: ttyp.description ?? "",
656
+ originalLanguage: (data.language ?? data.masterLanguage ?? "").toLowerCase()
657
+ },
658
+ tableType: {
659
+ accessMode: data.accessMode,
660
+ rowType: data.rowTypeRef?.name,
661
+ primaryKey: data.primaryKey,
662
+ secondaryKeys: data.secondaryKeys
663
+ }
664
+ };
665
+ },
666
+ fromMetadata: (meta) => ({
667
+ name: "",
668
+ description: meta?.header?.description,
669
+ language: meta?.header?.originalLanguage?.toUpperCase()
670
+ })
671
+ });
672
+
673
+ //#endregion
674
+ //#region src/lib/handlers/objects/fugr.ts
675
+ /**
676
+ * FUGR (function group) handler for gCTS / AFF format.
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
+ *
687
+ * AFF's FUGR representation nests function modules under the group
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
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
+ }
702
+ const functionGroupHandler = createHandler(AdkFunctionGroup, {
703
+ toMetadata(fugr) {
704
+ const data = fugr.dataSync ?? {};
705
+ const lang = (data.language ?? data.masterLanguage ?? "").toLowerCase();
706
+ return {
707
+ formatVersion: "1",
708
+ header: {
709
+ description: fugr.description ?? "",
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 } : {}
874
+ },
875
+ generalInformation: {
876
+ sourceOrigin: obj.sourceOrigin ?? "abapDevelopmentTools",
877
+ sourceType: obj.sourceType ?? "definition"
878
+ }
879
+ };
880
+ },
881
+ getSource: (obj) => typeof obj.getSource === "function" ? obj.getSource() : "",
882
+ fromMetadata: (meta) => ({
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
889
+ })
890
+ });
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
+
1090
+ //#endregion
1091
+ //#region src/lib/format/gcts-format.ts
1092
+ const gctsFormatPlugin = {
1093
+ id: "gcts",
1094
+ description: "gCTS / AFF (abap-file-formats) serialization format (JSON metadata + .abap/.acds sources)",
1095
+ get supportedTypes() {
1096
+ return getSupportedTypes();
1097
+ },
1098
+ getHandler(type) {
1099
+ return getHandler(type);
1100
+ },
1101
+ parseFilename(filename) {
1102
+ return parseGctsFilename(filename);
1103
+ }
1104
+ };
1105
+
1106
+ //#endregion
1107
+ //#region src/lib/deserializer.ts
1108
+ /** Source-file extensions emitted by gCTS handlers. */
1109
+ const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
1110
+ "abap",
1111
+ "asddls",
1112
+ "asdcls"
1113
+ ]);
1114
+ /**
1115
+ * Map numeric abapLanguageVersion codes (`'5'` etc.) to the descriptive
1116
+ * string the ADT REST API expects (`'cloudDevelopment'`). Mirrors the
1117
+ * helper in `adt-plugin-abapgit` — kept local so this package does not
1118
+ * depend on its sibling.
1119
+ */
1120
+ const ABAP_LANG_VER_TO_ADT = {
1121
+ "2": "keyUser",
1122
+ "5": "cloudDevelopment"
1123
+ };
1124
+ function abapLangVerToAdt(code) {
1125
+ if (!code) return void 0;
1126
+ return ABAP_LANG_VER_TO_ADT[code] ?? code;
1127
+ }
1128
+ /**
1129
+ * Deserialise a gCTS/AFF file tree into `AdkObject` instances.
1130
+ *
1131
+ * @param fileTree Virtual FS abstraction (checkin supplies a real-fs impl).
1132
+ * @param client `AdtClient` — required by `createAdk` to build ADK objects.
1133
+ * @param options Export options. `rootPackage` is assigned as packageRef
1134
+ * to every discovered object that lacks one, mirroring the
1135
+ * abapGit behaviour when `.abapgit.xml` is absent.
1136
+ */
1137
+ async function* deserialize(fileTree, client, options) {
1138
+ const adk = createAdk(client);
1139
+ const jsonFiles = await fileTree.glob("**/*.json");
1140
+ const supportedTypes = new Set(getSupportedTypes().map((t) => t.toLowerCase()));
1141
+ const objectMap = /* @__PURE__ */ new Map();
1142
+ for (const jsonPath of jsonFiles) {
1143
+ const filename = jsonPath.split("/").pop();
1144
+ if (filename === "package.devc.json") continue;
1145
+ const parsed = parseGctsFilename(filename);
1146
+ if (!parsed) continue;
1147
+ if (parsed.extension !== "json") continue;
1148
+ if (!supportedTypes.has(parsed.type.toLowerCase())) continue;
1149
+ const key = `${parsed.name.toUpperCase()}:${parsed.type.toUpperCase()}`;
1150
+ if (!objectMap.has(key)) objectMap.set(key, {
1151
+ name: parsed.name.toUpperCase(),
1152
+ type: parsed.type.toUpperCase(),
1153
+ sourceFiles: []
1154
+ });
1155
+ objectMap.get(key).metadataFile = jsonPath;
1156
+ }
1157
+ for (const ext of SOURCE_EXTENSIONS) {
1158
+ const paths = await fileTree.glob(`**/*.${ext}`);
1159
+ for (const path of paths) {
1160
+ const filename = path.split("/").pop();
1161
+ const parsed = parseGctsFilename(filename);
1162
+ if (!parsed) continue;
1163
+ const key = `${parsed.name.toUpperCase()}:${parsed.type.toUpperCase()}`;
1164
+ const obj = objectMap.get(key);
1165
+ if (!obj) continue;
1166
+ obj.sourceFiles.push({
1167
+ path,
1168
+ suffix: parsed.suffix
1169
+ });
1170
+ }
1171
+ }
1172
+ for (const [, objFiles] of objectMap) {
1173
+ if (!objFiles.metadataFile) continue;
1174
+ const handler = getHandler(objFiles.type);
1175
+ if (!handler) continue;
1176
+ try {
1177
+ const raw = await fileTree.read(objFiles.metadataFile);
1178
+ const metadata = handler.schema.parse(raw);
1179
+ const sources = {};
1180
+ for (const { path, suffix } of objFiles.sourceFiles) {
1181
+ const content = await fileTree.read(path);
1182
+ const sourceKey = suffix ? handler.suffixToSourceKey?.[suffix] ?? suffix : "main";
1183
+ sources[sourceKey] = content;
1184
+ }
1185
+ const payload = handler.fromAbapGit ? handler.fromAbapGit(metadata) : { name: objFiles.name };
1186
+ const objectName = payload.name || objFiles.name;
1187
+ const fullData = {
1188
+ ...payload,
1189
+ name: objectName
1190
+ };
1191
+ const adkType = typeof payload.type === "string" ? payload.type : objFiles.type;
1192
+ const adkObject = adk.getWithData(fullData, adkType);
1193
+ if (Object.keys(sources).length > 0) {
1194
+ if (handler.setSources) handler.setSources(adkObject, sources);
1195
+ else if (sources.main) adkObject._pendingSource = sources.main;
1196
+ }
1197
+ if (payload.description) adkObject._pendingDescription = payload.description;
1198
+ adkObject._relDir = objFiles.metadataFile.split("/").slice(0, -1).join("/");
1199
+ if (options?.rootPackage) {
1200
+ const data = adkObject._data;
1201
+ if (data && !data.packageRef) data.packageRef = { name: options.rootPackage };
1202
+ }
1203
+ if (options?.abapLanguageVersion) {
1204
+ const data = adkObject._data;
1205
+ if (data && !data.abapLanguageVersion) data.abapLanguageVersion = abapLangVerToAdt(options.abapLanguageVersion) ?? options.abapLanguageVersion;
1206
+ }
1207
+ yield adkObject;
1208
+ } catch (error) {
1209
+ console.error(`Failed to deserialize ${objFiles.type} ${objFiles.name}:`, error);
1210
+ }
1211
+ }
1212
+ }
1213
+
1214
+ //#endregion
1215
+ //#region src/lib/gcts-plugin.ts
1216
+ /**
1217
+ * gCTS AdtPlugin.
1218
+ *
1219
+ * The `FormatPlugin` in `format/gcts-format.ts` provides the declarative
1220
+ * contract (used by registry consumers); this `AdtPlugin` provides the
1221
+ * behaviour expected by `adt-cli`'s higher-level import/export services.
1222
+ *
1223
+ * SAP → disk ("import") serialises ADK objects to AFF JSON + source files.
1224
+ * Disk → SAP ("export" in adt-cli terminology — Git → SAP direction) is
1225
+ * delegated to `./deserializer.ts`, which yields AdkObjects for the
1226
+ * `CheckinService` apply pipeline (E08). Together they close the
1227
+ * round-trip that the E06 v0.1 follow-ups called out.
1228
+ */
1229
+ const gctsPlugin = createPlugin({
1230
+ name: "gcts",
1231
+ version: "0.1.0",
1232
+ description: "gCTS / AFF format plugin — JSON metadata + .abap/.acds sources",
1233
+ registry: {
1234
+ isSupported,
1235
+ getSupportedTypes
1236
+ },
1237
+ format: {
1238
+ async import(object, targetPath, context) {
1239
+ try {
1240
+ const objPackage = object.type?.startsWith("DEVC") ? object.name : object.package || object.name || "ROOT";
1241
+ const packageDir = (await context.resolvePackagePath(objPackage)).map((p) => p.toLowerCase()).join("/");
1242
+ const handler = getHandler(object.type);
1243
+ if (!handler) return {
1244
+ success: false,
1245
+ filesCreated: [],
1246
+ errors: [`No gCTS handler registered for object type: ${object.type}`]
1247
+ };
1248
+ const files = await handler.serialize(object);
1249
+ const baseDir = join(targetPath, "src", packageDir);
1250
+ mkdirSync(baseDir, { recursive: true });
1251
+ const written = [];
1252
+ for (const f of files) {
1253
+ const full = join(baseDir, f.path);
1254
+ mkdirSync(dirname(full), { recursive: true });
1255
+ writeFileSync(full, f.content, f.encoding ?? "utf8");
1256
+ written.push(full);
1257
+ }
1258
+ return {
1259
+ success: true,
1260
+ filesCreated: written
1261
+ };
1262
+ } catch (error) {
1263
+ return {
1264
+ success: false,
1265
+ filesCreated: [],
1266
+ errors: [error instanceof Error ? error.message : String(error)]
1267
+ };
1268
+ }
1269
+ },
1270
+ export: (fileTree, client, options) => deserialize(fileTree, client, options)
1271
+ }
1272
+ });
1273
+
1274
+ //#endregion
1275
+ //#region src/index.ts
1276
+ /**
1277
+ * @abapify/adt-plugin-gcts — gCTS / AFF format plugin entry point.
1278
+ *
1279
+ * Importing this module self-registers the plugin into the global
1280
+ * `FormatPlugin` registry so that `--format gcts` becomes available
1281
+ * throughout the `adt` CLI.
1282
+ */
1283
+ registerFormatPlugin(gctsFormatPlugin);
1284
+ registerFormatPlugin({
1285
+ ...gctsFormatPlugin,
1286
+ id: "aff"
1287
+ });
1288
+
1289
+ //#endregion
1290
+ export { gctsPlugin as GctsPlugin, gctsPlugin as default, gctsPlugin, METADATA_EXTENSION, PACKAGE_FILENAME, adtUriToGctsPath, createHandler, gctsFilename, gctsFormatPlugin, getHandler, getSupportedTypes, isSupported, parseGctsFilename };
1291
+ //# sourceMappingURL=index.mjs.map