@abapify/openai-codegen 0.4.33 → 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/README.md CHANGED
@@ -1,346 +1,37 @@
1
1
  # @abapify/openai-codegen
2
2
 
3
- **Deterministic OpenAPI → ABAP client code generator.** Reads an OpenAPI
4
- 3.x spec and emits three global ABAP interfaces, one global exception
5
- class, and one thin implementation class bundled with a small set of
6
- local helper classes. The result targets SAP BTP / Steampunk
7
- (`s4-cloud`) out of the box and has **zero Z-dependencies** — the
8
- bundled locals wrap kernel APIs (`cl_web_http_client_manager`,
9
- `cl_http_destination_provider`, `/ui2/cl_json`, `cl_abap_conv_codepage`,
10
- …) only.
3
+ [![npm](https://img.shields.io/npm/v/%40abapify%2Fopenai-codegen)](https://www.npmjs.com/package/@abapify/openai-codegen)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/abapify/adt-cli/blob/main/LICENSE)
11
5
 
12
- Re-running the generator with the same inputs produces byte-identical
13
- output. Generated artifacts are safe to commit and diff.
6
+ Deterministic OpenAPI → ABAP client code generator. Emits a single zero-dependency ABAP class per OpenAPI spec, targeting SAP Cloud / on-prem profiles, packaged as abapGit or gCTS.
14
7
 
15
8
  ## Install
16
9
 
17
10
  ```bash
18
- bun add -D @abapify/openai-codegen
19
- bunx openai-codegen --help
11
+ npm install -g @abapify/openai-codegen
20
12
  ```
21
13
 
22
- ## Quick start
14
+ ## Usage
23
15
 
24
16
  ```bash
25
- bunx openai-codegen \
17
+ npx @abapify/openai-codegen \
26
18
  --input ./petstore3.json \
27
19
  --out ./out/abapgit \
28
20
  --base petstore3 \
29
21
  --format abapgit \
30
- --target s4-cloud \
31
- --description "Petstore v3 client"
22
+ --target s4-cloud
32
23
  ```
33
24
 
34
- With the default `abapgit` layout this writes **11 files**:
25
+ ## Features
35
26
 
36
- ```text
37
- out/abapgit/
38
- ├── package.devc.xml
39
- └── src/
40
- ├── zif_petstore3_types.intf.abap # Layer 1 — TYPES interface
41
- ├── zif_petstore3_types.intf.xml
42
- ├── zif_petstore3.intf.abap # Layer 2 — operations interface
43
- ├── zif_petstore3.intf.xml
44
- ├── zcx_petstore3_error.clas.abap # global exception class
45
- ├── zcx_petstore3_error.clas.xml
46
- ├── zcl_petstore3.clas.abap # Layer 3 — thin implementation
47
- ├── zcl_petstore3.clas.xml
48
- ├── zcl_petstore3.clas.locals_def.abap # bundled helper locals
49
- └── zcl_petstore3.clas.locals_imp.abap
50
- ```
51
-
52
- ## Architecture
53
-
54
- ```text
55
- ZIF_<BASE>_TYPES ── all schemas as ABAP TYPES (+ @openapi-schema markers)
56
- ▲
57
- │
58
- ZIF_<BASE> ── all operations as typed METHODS (+ @openapi-operation markers)
59
- │ ── RAISING ZCX_<BASE>_ERROR
60
- │
61
- ZCL_<BASE> ── implements ZIF_<BASE>
62
- one private attribute: client TYPE REF TO lcl_http
63
- each method body = fetch(...) + CASE response->status( )
64
- ─────────────────────────────────────────────
65
- local classes (same .clas file, locals_def/imp):
66
- lcl_http (HTTP transport)
67
- lcl_response (status / body / headers)
68
- json (stringify / render / parse)
69
- lcl_json_parser (internal JSON parser)
70
-
71
- ZCX_<BASE>_ERROR ── inherits cx_static_check
72
- carries status / description / body / headers
73
- ```
74
-
75
- The implementation class is deliberately minimal:
76
-
77
- - exactly one private attribute (`client`),
78
- - no private helper methods besides `constructor`,
79
- - each operation method is **one `fetch` call** followed by a `CASE`
80
- block that decodes the success branch via `json=>parse( ... )->to( … )`
81
- and raises `ZCX_<BASE>_ERROR` for every other status.
82
-
83
- Schema `TYPES` intentionally live on their own **interface** — not on
84
- the class. ABAP's unified component namespace for types / attributes /
85
- methods on a class would otherwise force awkward name mangling; putting
86
- types on `ZIF_<BASE>_TYPES` keeps the main class and the operations
87
- interface collision-free.
88
-
89
- ## Generated API surface
90
-
91
- ### Layer 1 — types interface (`zif_petstore3_types.intf.abap`)
92
-
93
- ```abap
94
- "! Generated types for ZCL_PETSTORE3.
95
- INTERFACE ZIF_PETSTORE3_TYPES PUBLIC.
96
- "! @openapi-schema Category
97
- TYPES: BEGIN OF category,
98
- id TYPE int8,
99
- name TYPE string,
100
- END OF category.
101
- "! @openapi-schema Pet
102
- TYPES: BEGIN OF pet,
103
- id TYPE int8,
104
- name TYPE string,
105
- category TYPE category,
106
- photo_urls TYPE STANDARD TABLE OF string WITH EMPTY KEY,
107
- tags TYPE STANDARD TABLE OF tag WITH EMPTY KEY,
108
- status TYPE string,
109
- END OF pet.
110
- ENDINTERFACE.
111
- ```
112
-
113
- ### Layer 2 — operations interface (`zif_petstore3.intf.abap`)
114
-
115
- ```abap
116
- INTERFACE zif_petstore3 PUBLIC.
117
- "! @openapi-operation addPet
118
- "! @openapi-path POST /pet
119
- "! Add a new pet to the store.
120
- METHODS add_pet
121
- IMPORTING body TYPE zif_petstore3_types=>pet
122
- RETURNING VALUE(pet) TYPE zif_petstore3_types=>pet
123
- RAISING zcx_petstore3_error.
124
- "! @openapi-operation getPetById
125
- "! @openapi-path GET /pet/{petId}
126
- "! Find pet by ID.
127
- METHODS get_pet_by_id
128
- IMPORTING pet_id TYPE int8
129
- RETURNING VALUE(pet) TYPE zif_petstore3_types=>pet
130
- RAISING zcx_petstore3_error.
131
- ENDINTERFACE.
132
- ```
133
-
134
- ### Layer 3 — implementation class (`zcl_petstore3.clas.abap`)
135
-
136
- ```abap
137
- CLASS ZCL_PETSTORE3 DEFINITION PUBLIC CREATE PUBLIC.
138
- INTERFACES zif_petstore3.
139
- PUBLIC SECTION.
140
- METHODS constructor
141
- IMPORTING
142
- destination TYPE string
143
- server TYPE string DEFAULT '/api/v3'.
144
- PRIVATE SECTION.
145
- DATA client TYPE REF TO lcl_http.
146
- ENDCLASS.
147
-
148
- CLASS ZCL_PETSTORE3 IMPLEMENTATION.
149
- METHOD constructor.
150
- client = NEW lcl_http( destination = destination server = server ).
151
- ENDMETHOD.
152
-
153
- METHOD zif_petstore3~add_pet.
154
- DATA(response) = client->fetch(
155
- method = 'POST'
156
- path = '/pet'
157
- body = json=>stringify( body )
158
- headers = VALUE #( ( name = 'Content-Type' value = 'application/json' ) ) ).
159
- CASE response->status( ).
160
- WHEN 200.
161
- json=>parse( response->body( ) )->to( REF #( pet ) ).
162
- WHEN 400.
163
- RAISE EXCEPTION NEW zcx_petstore3_error(
164
- status = 400
165
- description = 'Invalid input'
166
- body = response->body( ) ).
167
- WHEN OTHERS.
168
- RAISE EXCEPTION NEW zcx_petstore3_error(
169
- status = response->status( )
170
- description = 'Unexpected error'
171
- body = response->body( ) ).
172
- ENDCASE.
173
- ENDMETHOD.
174
- ENDCLASS.
175
- ```
176
-
177
- ### Bundled locals (`zcl_petstore3.clas.locals_def.abap` / `locals_imp.abap`)
178
-
179
- The local classes expose a small, stable API that is **transport-only**
180
- for HTTP and **typed-by-reference** for JSON:
181
-
182
- ```abap
183
- " Transport (HTTP): lcl_http / lcl_response
184
- DATA(response) = client->fetch(
185
- method = 'GET'
186
- path = '/pet/123'
187
- query = VALUE #( ( name = 'detail' value = 'full' ) )
188
- headers = VALUE #( ( name = 'Accept' value = 'application/json' ) ) ).
189
- response->status( ). " -> i
190
- response->body( ). " -> xstring
191
- response->text( ). " -> string (decoded)
192
- response->header( 'etag' ).
193
- response->headers( ).
194
-
195
- " JSON: json helper (mirrors abapify/json)
196
- DATA(payload) = json=>stringify( pet ). " string
197
- DATA(raw) = json=>render( pet ). " xstring
198
- json=>parse( response->body( ) )->to( REF #( pet ) ).
199
- ```
200
-
201
- `lcl_http->fetch` is deliberately ignorant of JSON, status codes, and
202
- operation identities. All policy lives in the per-operation method body
203
- in `ZCL_<BASE>`. The JSON API surface mirrors
204
- [`abapify/json`](https://github.com/abapify) and wraps `/ui2/cl_json` +
205
- `cl_abap_conv_codepage`.
206
-
207
- ## Configuration
208
-
209
- All four global names and all four local names are configurable. The
210
- simplest path is to provide `--base`; every name then derives from it.
211
- Any individual override wins.
212
-
213
- | CLI flag | Library field (`NamesConfig`) | Default |
214
- | ------------------------------- | ----------------------------- | --------------------------------------- |
215
- | `--base <name>` | `base` | _required unless all 4 overrides given_ |
216
- | `--types-interface <name>` | `typesInterface` | `ZIF_<BASE>_TYPES` |
217
- | `--operations-interface <name>` | `operationsInterface` | `ZIF_<BASE>` |
218
- | `--class-name <name>` | `implementationClass` | `ZCL_<BASE>` |
219
- | `--exception-class <name>` | `exceptionClass` | `ZCX_<BASE>_ERROR` |
220
- | _(library only)_ | `localHttpClass` | `lcl_http` |
221
- | _(library only)_ | `localResponseClass` | `lcl_response` |
222
- | _(library only)_ | `localJsonClass` | `json` |
223
- | _(library only)_ | `localJsonParserClass` | `lcl_json_parser` |
224
-
225
- Other flags:
226
-
227
- | Flag | Purpose |
228
- | ------------------------- | ---------------------------------------------------------------- |
229
- | `-i, --input <path>` | Path / URL / file for the OpenAPI spec (JSON or YAML). Required. |
230
- | `-o, --out <dir>` | Output directory. Required. |
231
- | `-f, --format <list>` | `abapgit` (default), `gcts`, or `abapgit,gcts`. |
232
- | `-t, --target <profile>` | Target profile (`s4-cloud` only, default). |
233
- | `-d, --description <txt>` | Description stored in the generated `.clas.xml` / `.intf.xml`. |
234
-
235
- ## Library usage
236
-
237
- ```ts
238
- import { generate } from '@abapify/openai-codegen';
239
-
240
- const result = await generate({
241
- input: './petstore3.json',
242
- outDir: './out/abapgit',
243
- format: 'abapgit',
244
- target: 's4-cloud',
245
- names: { base: 'petstore3' },
246
- description: 'Petstore v3 client',
247
- });
248
-
249
- console.log(result.files); // sorted, deduped relative paths
250
- console.log(result.typeCount, result.operationCount);
251
- console.log(result.resolvedNames);
252
- ```
253
-
254
- ## Pipeline
255
-
256
- ```text
257
- OpenAPI spec
258
- → loadSpec ($ref-deref via @apidevtools/swagger-parser)
259
- → planTypes (topological sort of schemas + cycle detection)
260
- → resolveNames (NamesConfig → ResolvedNames; validates ABAP idents)
261
- → emitTypesInterface (Layer 1 — ZIF_<BASE>_TYPES AST)
262
- → emitOperationsInterface (Layer 2 — ZIF_<BASE> AST)
263
- → emitExceptionClass (ZCX_<BASE>_ERROR AST)
264
- → emitImplementationClass (Layer 3 — ZCL_<BASE> AST)
265
- → emitLocalClasses (locals_def + locals_imp templates)
266
- → print (four AST → ABAP source, via @abapify/abap-ast)
267
- → writeClientBundle (abapgit or gcts on-disk tree + envelopes)
268
- ```
269
-
270
- ## Type mapping
271
-
272
- | JSON Schema | ABAP |
273
- | ----------------------------------------- | ----------------------------------------------------------- |
274
- | `boolean` | `abap_bool` |
275
- | `integer` / `integer, format: int64` | `i` / `int8` |
276
- | `number` (default / `float` / `double`) | `decfloat34` / `f` |
277
- | `string` (default) | `string` |
278
- | `string, format: date` | `d` |
279
- | `string, format: date-time` | `timestampl` |
280
- | `string, format: uuid` | `sysuuid_x16` |
281
- | `string, format: byte` / `binary` | `xstring` |
282
- | `array of X` | `STANDARD TABLE OF <X> WITH EMPTY KEY` |
283
- | `object` (properties) | `BEGIN OF <name> … END OF <name>.` |
284
- | `object` with `additionalProperties` only | `_map` table of `BEGIN OF entry, key, value, END OF entry` |
285
- | `enum` | Field typed as base scalar; values documented via ABAPDoc. |
286
- | `allOf` | Flattened into one structure. |
287
- | `oneOf` / `anyOf` | Union merge — fields from every branch, optional semantics. |
288
- | `$ref` (forward) | `NamedTypeRef` into the existing plan entry. |
289
- | `$ref` (back-edge — cyclic) | `REF TO data` with `"! @openapi-ref <field>:<Target>`. |
290
- | `nullable: true` | Same ABAP type (nullability preserved at (de)serialise). |
291
-
292
- Types are emitted in semantic topological order. Back-edges introduced
293
- by cyclic schemas are broken with a `REF TO data` plus a structured
294
- `@openapi-ref` marker, so a future reader can reconstruct the original
295
- reference target.
296
-
297
- ## Round-trip markers
298
-
299
- Every generated ABAP declaration carries an ABAPDoc block that a
300
- downstream tool (for example a future `.aclass` parser) can use to
301
- rebuild the originating OpenAPI shape:
302
-
303
- | Marker | Attached to | Meaning |
304
- | ------------------------------------- | ------------------- | ---------------------------------------------------- |
305
- | `"! @openapi-schema <Name>` | `TYPES` declaration | Original JSON-Schema component name. |
306
- | `"! @openapi-operation <operationId>` | `METHODS` | Original OpenAPI `operationId`. |
307
- | `"! @openapi-path <VERB> <path>` | `METHODS` | HTTP verb + templated path. |
308
- | `"! @openapi-ref <field>:<Target>` | `TYPES` field | Field broken to `REF TO data` references `<Target>`. |
309
-
310
- Markers are plain lines under the printer's ABAPDoc rules; they are
311
- never rewritten or wrapped.
312
-
313
- ## Deploying to BTP Steampunk
314
-
315
- Short recipe (see
316
- [`samples/petstore3-client/e2e/README.md`](../../samples/petstore3-client/e2e/README.md)
317
- for the authoritative walk-through):
318
-
319
- ```bash
320
- bunx adt auth login
321
- bunx openai-codegen \
322
- --input ./spec/openapi.json \
323
- --out ./generated/abapgit \
324
- --format abapgit \
325
- --base petstore3
326
- bunx adt deploy --source ./generated/abapgit --package ZMY_PACKAGE --activate --unlock
327
- ```
328
-
329
- `S_ABPLNGVS` is granted per package on BTP. Deploy into a user-owned
330
- `Z*` package (for example `ZMY_PACKAGE` or `ZPEPL`); targeting `$TMP`
331
- often returns HTTP 403 on BTP.
27
+ - Deterministic OpenAPI → ABAP REST client generation
28
+ - Targets SAP BTP ABAP Cloud and on-prem profiles
29
+ - Emits abapGit or gCTS packages
332
30
 
333
- ## Not yet supported
31
+ ## Documentation
334
32
 
335
- - OpenAPI webhooks and callbacks.
336
- - Server-Sent Events / streaming responses.
337
- - Full OAuth2 `authorization_code` / `client_credentials` flows
338
- (`apiKey`, `http bearer`, `http basic` are emitted; `oauth2` exposes
339
- an overridable hook only).
340
- - `.aclass` DSL parser — tracked in `@abapify/abap-ast`.
341
- - Per-target runtime bundles beyond `s4-cloud` (profile slots exist for
342
- `s4-onprem-modern` and `on-prem-classic`; only `s4-cloud` emits in v1).
33
+ **[adt-cli.netlify.app/sdk/packages/openai-codegen](https://adt-cli.netlify.app/sdk/packages/openai-codegen)**
343
34
 
344
- ## License
35
+ ---
345
36
 
346
- MIT
37
+ Part of [**abapify**](https://github.com/abapify/adt-cli) — open, typed tooling for SAP ABAP Development Tools (ADT): CLI, MCP server, and SDK.
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":";;cAkBM;KACM,0BAA0B;;;;;;UAOrB;EACf;EACA;EACA,aAAa;EACb,OAAO;EACP,kBAAkB;EAClB;EACA;;;KAIU,gBAAgB;UAElB;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAyCc,mBAAmB,KAAK,gBAAgB;;;;;;cAsC3C,6BAAoB;iBAEjB,gBAAgB"}
1
+ {"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":";;cAiBM;KACM,0BAA0B;;;;;;UAOrB;EACf;EACA;EACA,aAAa;EACb,OAAO;EACP,kBAAkB;EAClB;EACA;;;KAIU,gBAAgB;UAElB;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAyCc,mBAAmB,KAAK,gBAAgB;;;;;;cAsC3C,6BAAoB;iBAEjB,gBAAgB"}
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { h as resolveNames, m as NamesConfigError, t as generate } from "./generate-DgXsowgP.mjs";
2
+ import { h as resolveNames, m as NamesConfigError, t as generate } from "./generate-BevNTSFF.mjs";
3
3
  import { resolve } from "node:path";
4
4
  import { Command, InvalidArgumentError, Option } from "commander";
5
5
 
@@ -60,8 +60,7 @@ function buildProgram() {
60
60
  program.name("openai-codegen").description("Deterministic OpenAPI -> ABAP client code generator (v2 pipeline).").requiredOption("--input <path>", "path to the OpenAPI spec (YAML or JSON)").requiredOption("--out <dir>", "output directory").option("--base <name>", "logical base name used to derive ZCL_<BASE> / ZIF_<BASE> / ZCX_<BASE>_ERROR").option("--types-interface <name>", "override for the types interface name (default: ZIF_<BASE>_TYPES)").option("--operations-interface <name>", "override for the operations interface name (default: ZIF_<BASE>)").option("--class-name <name>", "override for the implementation class name (default: ZCL_<BASE>)").option("--exception-class <name>", "override for the exception class name (default: ZCX_<BASE>_ERROR)").addOption(new Option("--format <list>", "comma-separated output formats (abapgit, gcts)").default("abapgit")).option("--target <profile>", "target profile (e.g. s4-cloud)", "s4-cloud").option("--description <text>", "description stored in the generated CLAS/INTF XML").action(async (opts) => {
61
61
  const runOptions = buildCliRunOptions(opts);
62
62
  const baseOutDir = resolve(process.cwd(), runOptions.out);
63
- const results = [];
64
- for (const format of runOptions.formats) {
63
+ const generated = await Promise.all(runOptions.formats.map(async (format) => {
65
64
  const formatOutDir = runOptions.formats.length === 1 ? baseOutDir : resolve(baseOutDir, format);
66
65
  const genOpts = {
67
66
  input: runOptions.input,
@@ -71,11 +70,23 @@ function buildProgram() {
71
70
  names: runOptions.namesConfig,
72
71
  description: runOptions.description
73
72
  };
74
- const result = await generate(genOpts);
75
- results.push({
76
- format,
77
- result
78
- });
73
+ try {
74
+ return {
75
+ format,
76
+ formatOutDir,
77
+ result: await generate(genOpts)
78
+ };
79
+ } catch (error) {
80
+ return {
81
+ format,
82
+ formatOutDir,
83
+ error
84
+ };
85
+ }
86
+ }));
87
+ for (const entry of generated) {
88
+ if ("error" in entry) throw entry.error;
89
+ const { format, formatOutDir, result } = entry;
79
90
  process.stdout.write(`[openai-codegen] ${format}: wrote ${result.files.length} files (${result.typeCount} types, ${result.operationCount} operations) -> ${formatOutDir}\n`);
80
91
  }
81
92
  });
@@ -109,7 +120,7 @@ if ((() => {
109
120
  } catch {
110
121
  return false;
111
122
  }
112
- })()) main(process.argv);
123
+ })()) await main(process.argv);
113
124
 
114
125
  export { buildCliRunOptions, buildGenerateOptions, buildProgram };
115
126
  //# sourceMappingURL=cli.mjs.map
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["resolvePath"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * openai-codegen CLI (v2).\n *\n * Parses command-line flags and calls the `generate()` pipeline for each\n * requested output format.\n */\n\nimport { Command, Option, InvalidArgumentError } from 'commander';\nimport { resolveNames, NamesConfigError } from './emit/naming';\nimport type { NamesConfig, ResolvedNames } from './emit/naming';\nimport {\n generate,\n type GenerateOptions as PipelineGenerateOptions,\n type GenerateResult,\n} from './generate';\nimport { resolve as resolvePath } from 'node:path';\n\nconst SUPPORTED_FORMATS = ['abapgit', 'gcts'] as const;\nexport type SupportedFormat = (typeof SUPPORTED_FORMATS)[number];\n\n/**\n * CLI-facing resolved options shape. Exposes both the raw {@link NamesConfig}\n * (to pass to `generate()`) and the resolved names (so tests / callers can\n * inspect final artifact names without re-resolving).\n */\nexport interface GenerateOptions {\n input: string;\n out: string;\n namesConfig: NamesConfig;\n names: ResolvedNames;\n formats: readonly SupportedFormat[];\n target: 's4-cloud' | 's4-onprem-modern' | 'on-prem-classic';\n description?: string;\n}\n\n/** Alias kept for newer code that prefers a run-style name. */\nexport type CliRunOptions = GenerateOptions;\n\ninterface RawCliOptions {\n input?: string;\n out?: string;\n base?: string;\n typesInterface?: string;\n operationsInterface?: string;\n className?: string;\n exceptionClass?: string;\n format?: string;\n target?: string;\n description?: string;\n}\n\nfunction parseFormats(raw: string): SupportedFormat[] {\n const parts = raw\n .split(',')\n .map((p) => p.trim().toLowerCase())\n .filter((p) => p.length > 0);\n if (parts.length === 0) {\n throw new InvalidArgumentError(\n 'at least one format is required (abapgit, gcts).',\n );\n }\n const result: SupportedFormat[] = [];\n for (const p of parts) {\n if (!(SUPPORTED_FORMATS as readonly string[]).includes(p)) {\n throw new InvalidArgumentError(\n `unknown format \"${p}\" (supported: ${SUPPORTED_FORMATS.join(', ')}).`,\n );\n }\n if (!result.includes(p as SupportedFormat)) {\n result.push(p as SupportedFormat);\n }\n }\n return result;\n}\n\nfunction parseTarget(raw: string): CliRunOptions['target'] {\n const normalized = raw.trim().toLowerCase();\n if (\n normalized !== 's4-cloud' &&\n normalized !== 's4-onprem-modern' &&\n normalized !== 'on-prem-classic'\n ) {\n throw new InvalidArgumentError(\n `unknown target \"${raw}\" (supported: s4-cloud, s4-onprem-modern, on-prem-classic).`,\n );\n }\n return normalized as CliRunOptions['target'];\n}\n\nexport function buildCliRunOptions(raw: RawCliOptions): CliRunOptions {\n if (!raw.input) {\n throw new Error('--input is required.');\n }\n if (!raw.out) {\n throw new Error('--out is required.');\n }\n\n const namesConfig: NamesConfig = {\n base: raw.base,\n typesInterface: raw.typesInterface,\n operationsInterface: raw.operationsInterface,\n implementationClass: raw.className,\n exceptionClass: raw.exceptionClass,\n };\n // Validate and resolve eagerly so the CLI fails fast with a useful message\n // AND so tests / downstream code can read resolved global names via `.names`.\n const names = resolveNames(namesConfig);\n\n const formats = parseFormats(raw.format ?? 'abapgit');\n const target = parseTarget(raw.target ?? 's4-cloud');\n\n return {\n input: raw.input,\n out: raw.out,\n namesConfig,\n names,\n formats,\n target,\n description: raw.description,\n };\n}\n\n/**\n * Back-compat shim: older callers used `buildGenerateOptions` and relied on\n * the `GenerateOptions` export. Keep both names working — the modern path\n * is `buildCliRunOptions`.\n */\nexport const buildGenerateOptions = buildCliRunOptions;\n\nexport function buildProgram(): Command {\n const program = new Command();\n program\n .name('openai-codegen')\n .description(\n 'Deterministic OpenAPI -> ABAP client code generator (v2 pipeline).',\n )\n .requiredOption('--input <path>', 'path to the OpenAPI spec (YAML or JSON)')\n .requiredOption('--out <dir>', 'output directory')\n .option(\n '--base <name>',\n 'logical base name used to derive ZCL_<BASE> / ZIF_<BASE> / ZCX_<BASE>_ERROR',\n )\n .option(\n '--types-interface <name>',\n 'override for the types interface name (default: ZIF_<BASE>_TYPES)',\n )\n .option(\n '--operations-interface <name>',\n 'override for the operations interface name (default: ZIF_<BASE>)',\n )\n .option(\n '--class-name <name>',\n 'override for the implementation class name (default: ZCL_<BASE>)',\n )\n .option(\n '--exception-class <name>',\n 'override for the exception class name (default: ZCX_<BASE>_ERROR)',\n )\n .addOption(\n new Option(\n '--format <list>',\n 'comma-separated output formats (abapgit, gcts)',\n ).default('abapgit'),\n )\n .option('--target <profile>', 'target profile (e.g. s4-cloud)', 's4-cloud')\n .option(\n '--description <text>',\n 'description stored in the generated CLAS/INTF XML',\n )\n .action(async (opts: RawCliOptions) => {\n const runOptions = buildCliRunOptions(opts);\n const baseOutDir = resolvePath(process.cwd(), runOptions.out);\n const results: Array<{\n format: SupportedFormat;\n result: GenerateResult;\n }> = [];\n for (const format of runOptions.formats) {\n const formatOutDir =\n runOptions.formats.length === 1\n ? baseOutDir\n : resolvePath(baseOutDir, format);\n const genOpts: PipelineGenerateOptions = {\n input: runOptions.input,\n outDir: formatOutDir,\n format,\n target: runOptions.target,\n names: runOptions.namesConfig,\n description: runOptions.description,\n };\n const result = await generate(genOpts);\n results.push({ format, result });\n process.stdout.write(\n `[openai-codegen] ${format}: wrote ${result.files.length} files ` +\n `(${result.typeCount} types, ${result.operationCount} operations) ` +\n `-> ${formatOutDir}\\n`,\n );\n }\n });\n\n // Do not let commander terminate the process during tests.\n program.exitOverride();\n return program;\n}\n\nasync function main(argv: string[]): Promise<void> {\n const program = buildProgram();\n try {\n await program.parseAsync(argv);\n } catch (err) {\n if (err instanceof NamesConfigError) {\n process.stderr.write(`error: ${err.message}\\n`);\n process.exit(1);\n }\n const anyErr = err as { code?: string; message?: string };\n if (\n anyErr.code === 'commander.helpDisplayed' ||\n anyErr.code === 'commander.version' ||\n anyErr.code === 'commander.help'\n ) {\n return;\n }\n if (anyErr.code?.startsWith('commander.')) {\n process.exit(1);\n }\n process.stderr.write(\n `error: ${anyErr.message ?? (err instanceof Error ? err.message : String(err))}\\n`,\n );\n process.exit(1);\n }\n}\n\n// Only run when invoked directly.\nconst invokedDirectly = (() => {\n try {\n const entry = process.argv[1];\n if (!entry) return false;\n const url = import.meta.url;\n if (!url.startsWith('file:')) return false;\n const urlPath = decodeURIComponent(new URL(url).pathname);\n return urlPath === entry || urlPath.endsWith('/cli.mjs');\n } catch {\n return false;\n }\n})();\n\nif (invokedDirectly) {\n void main(process.argv);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["resolvePath"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * openai-codegen CLI (v2).\n *\n * Parses command-line flags and calls the `generate()` pipeline for each\n * requested output format.\n */\n\nimport { Command, Option, InvalidArgumentError } from 'commander';\nimport { resolveNames, NamesConfigError } from './emit/naming';\nimport type { NamesConfig, ResolvedNames } from './emit/naming';\nimport {\n generate,\n type GenerateOptions as PipelineGenerateOptions,\n} from './generate';\nimport { resolve as resolvePath } from 'node:path';\n\nconst SUPPORTED_FORMATS = ['abapgit', 'gcts'] as const;\nexport type SupportedFormat = (typeof SUPPORTED_FORMATS)[number];\n\n/**\n * CLI-facing resolved options shape. Exposes both the raw {@link NamesConfig}\n * (to pass to `generate()`) and the resolved names (so tests / callers can\n * inspect final artifact names without re-resolving).\n */\nexport interface GenerateOptions {\n input: string;\n out: string;\n namesConfig: NamesConfig;\n names: ResolvedNames;\n formats: readonly SupportedFormat[];\n target: 's4-cloud' | 's4-onprem-modern' | 'on-prem-classic';\n description?: string;\n}\n\n/** Alias kept for newer code that prefers a run-style name. */\nexport type CliRunOptions = GenerateOptions;\n\ninterface RawCliOptions {\n input?: string;\n out?: string;\n base?: string;\n typesInterface?: string;\n operationsInterface?: string;\n className?: string;\n exceptionClass?: string;\n format?: string;\n target?: string;\n description?: string;\n}\n\nfunction parseFormats(raw: string): SupportedFormat[] {\n const parts = raw\n .split(',')\n .map((p) => p.trim().toLowerCase())\n .filter((p) => p.length > 0);\n if (parts.length === 0) {\n throw new InvalidArgumentError(\n 'at least one format is required (abapgit, gcts).',\n );\n }\n const result: SupportedFormat[] = [];\n for (const p of parts) {\n if (!(SUPPORTED_FORMATS as readonly string[]).includes(p)) {\n throw new InvalidArgumentError(\n `unknown format \"${p}\" (supported: ${SUPPORTED_FORMATS.join(', ')}).`,\n );\n }\n if (!result.includes(p as SupportedFormat)) {\n result.push(p as SupportedFormat);\n }\n }\n return result;\n}\n\nfunction parseTarget(raw: string): CliRunOptions['target'] {\n const normalized = raw.trim().toLowerCase();\n if (\n normalized !== 's4-cloud' &&\n normalized !== 's4-onprem-modern' &&\n normalized !== 'on-prem-classic'\n ) {\n throw new InvalidArgumentError(\n `unknown target \"${raw}\" (supported: s4-cloud, s4-onprem-modern, on-prem-classic).`,\n );\n }\n return normalized as CliRunOptions['target'];\n}\n\nexport function buildCliRunOptions(raw: RawCliOptions): CliRunOptions {\n if (!raw.input) {\n throw new Error('--input is required.');\n }\n if (!raw.out) {\n throw new Error('--out is required.');\n }\n\n const namesConfig: NamesConfig = {\n base: raw.base,\n typesInterface: raw.typesInterface,\n operationsInterface: raw.operationsInterface,\n implementationClass: raw.className,\n exceptionClass: raw.exceptionClass,\n };\n // Validate and resolve eagerly so the CLI fails fast with a useful message\n // AND so tests / downstream code can read resolved global names via `.names`.\n const names = resolveNames(namesConfig);\n\n const formats = parseFormats(raw.format ?? 'abapgit');\n const target = parseTarget(raw.target ?? 's4-cloud');\n\n return {\n input: raw.input,\n out: raw.out,\n namesConfig,\n names,\n formats,\n target,\n description: raw.description,\n };\n}\n\n/**\n * Back-compat shim: older callers used `buildGenerateOptions` and relied on\n * the `GenerateOptions` export. Keep both names working — the modern path\n * is `buildCliRunOptions`.\n */\nexport const buildGenerateOptions = buildCliRunOptions;\n\nexport function buildProgram(): Command {\n const program = new Command();\n program\n .name('openai-codegen')\n .description(\n 'Deterministic OpenAPI -> ABAP client code generator (v2 pipeline).',\n )\n .requiredOption('--input <path>', 'path to the OpenAPI spec (YAML or JSON)')\n .requiredOption('--out <dir>', 'output directory')\n .option(\n '--base <name>',\n 'logical base name used to derive ZCL_<BASE> / ZIF_<BASE> / ZCX_<BASE>_ERROR',\n )\n .option(\n '--types-interface <name>',\n 'override for the types interface name (default: ZIF_<BASE>_TYPES)',\n )\n .option(\n '--operations-interface <name>',\n 'override for the operations interface name (default: ZIF_<BASE>)',\n )\n .option(\n '--class-name <name>',\n 'override for the implementation class name (default: ZCL_<BASE>)',\n )\n .option(\n '--exception-class <name>',\n 'override for the exception class name (default: ZCX_<BASE>_ERROR)',\n )\n .addOption(\n new Option(\n '--format <list>',\n 'comma-separated output formats (abapgit, gcts)',\n ).default('abapgit'),\n )\n .option('--target <profile>', 'target profile (e.g. s4-cloud)', 's4-cloud')\n .option(\n '--description <text>',\n 'description stored in the generated CLAS/INTF XML',\n )\n .action(async (opts: RawCliOptions) => {\n const runOptions = buildCliRunOptions(opts);\n const baseOutDir = resolvePath(process.cwd(), runOptions.out);\n const generated = await Promise.all(\n runOptions.formats.map(async (format) => {\n const formatOutDir =\n runOptions.formats.length === 1\n ? baseOutDir\n : resolvePath(baseOutDir, format);\n const genOpts: PipelineGenerateOptions = {\n input: runOptions.input,\n outDir: formatOutDir,\n format,\n target: runOptions.target,\n names: runOptions.namesConfig,\n description: runOptions.description,\n };\n try {\n return { format, formatOutDir, result: await generate(genOpts) };\n } catch (error: unknown) {\n return { format, formatOutDir, error };\n }\n }),\n );\n for (const entry of generated) {\n if ('error' in entry) throw entry.error;\n const { format, formatOutDir, result } = entry;\n process.stdout.write(\n `[openai-codegen] ${format}: wrote ${result.files.length} files ` +\n `(${result.typeCount} types, ${result.operationCount} operations) ` +\n `-> ${formatOutDir}\\n`,\n );\n }\n });\n\n // Do not let commander terminate the process during tests.\n program.exitOverride();\n return program;\n}\n\nasync function main(argv: string[]): Promise<void> {\n const program = buildProgram();\n try {\n await program.parseAsync(argv);\n } catch (err) {\n if (err instanceof NamesConfigError) {\n process.stderr.write(`error: ${err.message}\\n`);\n process.exit(1);\n }\n const anyErr = err as { code?: string; message?: string };\n if (\n anyErr.code === 'commander.helpDisplayed' ||\n anyErr.code === 'commander.version' ||\n anyErr.code === 'commander.help'\n ) {\n return;\n }\n if (anyErr.code?.startsWith('commander.')) {\n process.exit(1);\n }\n process.stderr.write(\n `error: ${anyErr.message ?? (err instanceof Error ? err.message : String(err))}\\n`,\n );\n process.exit(1);\n }\n}\n\n// Only run when invoked directly.\nconst invokedDirectly = (() => {\n try {\n const entry = process.argv[1];\n if (!entry) return false;\n const url = import.meta.url;\n if (!url.startsWith('file:')) return false;\n const urlPath = decodeURIComponent(new URL(url).pathname);\n return urlPath === entry || urlPath.endsWith('/cli.mjs');\n } catch {\n return false;\n }\n})();\n\nif (invokedDirectly) {\n await main(process.argv);\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,MAAM,oBAAoB,CAAC,WAAW,MAAM;AAkC5C,SAAS,aAAa,KAAgC;CACpD,MAAM,QAAQ,IACX,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAClC,QAAQ,MAAM,EAAE,SAAS,CAAC;CAC7B,IAAI,MAAM,WAAW,GACnB,MAAM,IAAI,qBACR,kDACF;CAEF,MAAM,SAA4B,CAAC;CACnC,KAAK,MAAM,KAAK,OAAO;EACrB,IAAI,CAAE,kBAAwC,SAAS,CAAC,GACtD,MAAM,IAAI,qBACR,mBAAmB,EAAE,gBAAgB,kBAAkB,KAAK,IAAI,EAAE,GACpE;EAEF,IAAI,CAAC,OAAO,SAAS,CAAoB,GACvC,OAAO,KAAK,CAAoB;CAEpC;CACA,OAAO;AACT;AAEA,SAAS,YAAY,KAAsC;CACzD,MAAM,aAAa,IAAI,KAAK,CAAC,CAAC,YAAY;CAC1C,IACE,eAAe,cACf,eAAe,sBACf,eAAe,mBAEf,MAAM,IAAI,qBACR,mBAAmB,IAAI,4DACzB;CAEF,OAAO;AACT;AAEA,SAAgB,mBAAmB,KAAmC;CACpE,IAAI,CAAC,IAAI,OACP,MAAM,IAAI,MAAM,sBAAsB;CAExC,IAAI,CAAC,IAAI,KACP,MAAM,IAAI,MAAM,oBAAoB;CAGtC,MAAM,cAA2B;EAC/B,MAAM,IAAI;EACV,gBAAgB,IAAI;EACpB,qBAAqB,IAAI;EACzB,qBAAqB,IAAI;EACzB,gBAAgB,IAAI;CACtB;CAGA,MAAM,QAAQ,aAAa,WAAW;CAEtC,MAAM,UAAU,aAAa,IAAI,UAAU,SAAS;CACpD,MAAM,SAAS,YAAY,IAAI,UAAU,UAAU;CAEnD,OAAO;EACL,OAAO,IAAI;EACX,KAAK,IAAI;EACT;EACA;EACA;EACA;EACA,aAAa,IAAI;CACnB;AACF;;;;;;AAOA,MAAa,uBAAuB;AAEpC,SAAgB,eAAwB;CACtC,MAAM,UAAU,IAAI,QAAQ;CAC5B,QACG,KAAK,gBAAgB,CAAC,CACtB,YACC,oEACF,CAAC,CACA,eAAe,kBAAkB,yCAAyC,CAAC,CAC3E,eAAe,eAAe,kBAAkB,CAAC,CACjD,OACC,iBACA,6EACF,CAAC,CACA,OACC,4BACA,mEACF,CAAC,CACA,OACC,iCACA,kEACF,CAAC,CACA,OACC,uBACA,kEACF,CAAC,CACA,OACC,4BACA,mEACF,CAAC,CACA,UACC,IAAI,OACF,mBACA,gDACF,CAAC,CAAC,QAAQ,SAAS,CACrB,CAAC,CACA,OAAO,sBAAsB,kCAAkC,UAAU,CAAC,CAC1E,OACC,wBACA,mDACF,CAAC,CACA,OAAO,OAAO,SAAwB;EACrC,MAAM,aAAa,mBAAmB,IAAI;EAC1C,MAAM,aAAaA,QAAY,QAAQ,IAAI,GAAG,WAAW,GAAG;EAC5D,MAAM,YAAY,MAAM,QAAQ,IAC9B,WAAW,QAAQ,IAAI,OAAO,WAAW;GACvC,MAAM,eACJ,WAAW,QAAQ,WAAW,IAC1B,aACAA,QAAY,YAAY,MAAM;GACpC,MAAM,UAAmC;IACvC,OAAO,WAAW;IAClB,QAAQ;IACR;IACA,QAAQ,WAAW;IACnB,OAAO,WAAW;IAClB,aAAa,WAAW;GAC1B;GACA,IAAI;IACF,OAAO;KAAE;KAAQ;KAAc,QAAQ,MAAM,SAAS,OAAO;IAAE;GACjE,SAAS,OAAgB;IACvB,OAAO;KAAE;KAAQ;KAAc;IAAM;GACvC;EACF,CAAC,CACH;EACA,KAAK,MAAM,SAAS,WAAW;GAC7B,IAAI,WAAW,OAAO,MAAM,MAAM;GAClC,MAAM,EAAE,QAAQ,cAAc,WAAW;GACzC,QAAQ,OAAO,MACb,oBAAoB,OAAO,UAAU,OAAO,MAAM,OAAO,UACnD,OAAO,UAAU,UAAU,OAAO,eAAe,kBAC/C,aAAa,GACvB;EACF;CACF,CAAC;CAGH,QAAQ,aAAa;CACrB,OAAO;AACT;AAEA,eAAe,KAAK,MAA+B;CACjD,MAAM,UAAU,aAAa;CAC7B,IAAI;EACF,MAAM,QAAQ,WAAW,IAAI;CAC/B,SAAS,KAAK;EACZ,IAAI,eAAe,kBAAkB;GACnC,QAAQ,OAAO,MAAM,UAAU,IAAI,QAAQ,GAAG;GAC9C,QAAQ,KAAK,CAAC;EAChB;EACA,MAAM,SAAS;EACf,IACE,OAAO,SAAS,6BAChB,OAAO,SAAS,uBAChB,OAAO,SAAS,kBAEhB;EAEF,IAAI,OAAO,MAAM,WAAW,YAAY,GACtC,QAAQ,KAAK,CAAC;EAEhB,QAAQ,OAAO,MACb,UAAU,OAAO,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAAG,GACjF;EACA,QAAQ,KAAK,CAAC;CAChB;AACF;AAgBA,WAb+B;CAC7B,IAAI;EACF,MAAM,QAAQ,QAAQ,KAAK;EAC3B,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,MAAM,YAAY;EACxB,IAAI,CAAC,IAAI,WAAW,OAAO,GAAG,OAAO;EACrC,MAAM,UAAU,mBAAmB,IAAI,IAAI,GAAG,CAAC,CAAC,QAAQ;EACxD,OAAO,YAAY,SAAS,QAAQ,SAAS,UAAU;CACzD,QAAQ;EACN,OAAO;CACT;AACF,EAAC,CAEiB,GAChB,MAAM,KAAK,QAAQ,IAAI"}