@ng-forge/openapi-generator 0.8.0-next.1 → 0.8.0-next.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/README.md CHANGED
@@ -129,19 +129,20 @@ ng-forge-generator --spec openapi.yaml --output src/generated
129
129
 
130
130
  ### Options
131
131
 
132
- | Option | Description |
133
- | ---------------------- | ---------------------------------------------------------------------------------------- |
134
- | `--spec <path>` | Path to OpenAPI spec file (required) |
135
- | `--output <path>` | Output directory for generated files (required) |
136
- | `--interactive <mode>` | `full` (prompt for endpoints + ambiguous types) or `none` (auto-select). Default: `full` |
137
- | `--endpoints <list>` | Comma-separated endpoints, e.g. `"POST:/users,PUT:/users/{id}"` |
138
- | `--read-only` | Generate GET endpoint forms with all fields disabled |
139
- | `--watch` | Watch spec file for changes and regenerate |
140
- | `--config <path>` | Directory for `.ng-forge-generator.json` config (defaults to `--output`) |
141
- | `--dry-run` | List files that would be generated without writing them |
142
- | `--skip-existing` | Skip files that already exist on disk |
143
- | `--verbose` | Show detailed output including field mapping decisions |
144
- | `--quiet` | Suppress info output; still shows success summary, warnings, and errors |
132
+ | Option | Description |
133
+ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
134
+ | `--spec <path>` | Path to OpenAPI spec file (required) |
135
+ | `--output <path>` | Output directory for generated files (required) |
136
+ | `--interactive <mode>` | `full` (prompt for endpoints + ambiguous types) or `none` (auto-select). Default: `full` |
137
+ | `--endpoints <list>` | Comma-separated endpoints, e.g. `"POST:/users,PUT:/users/{id}"` |
138
+ | `--read-only` | Generate GET endpoint forms with all fields disabled |
139
+ | `--barrel-extension <ext>` | Extension in barrel re-exports. Accepts `""` (default — no extension) or a dot-prefixed value like `".js"` (for Node ESM). Pass `""` to reset |
140
+ | `--watch` | Watch spec file for changes and regenerate |
141
+ | `--config <path>` | Directory for `.ng-forge-generator.json` config (defaults to `--output`) |
142
+ | `--dry-run` | List files that would be generated without writing them |
143
+ | `--skip-existing` | Skip files that already exist on disk |
144
+ | `--verbose` | Show detailed output including field mapping decisions |
145
+ | `--quiet` | Suppress info output; still shows success summary, warnings, and errors |
145
146
 
146
147
  ### Interactive Modes
147
148
 
@@ -164,6 +165,9 @@ ng-forge-generator --spec openapi.yaml --output src/generated \
164
165
  # Generate GET endpoints with disabled (read-only) fields
165
166
  ng-forge-generator --spec openapi.yaml --output src/generated --interactive none --read-only
166
167
 
168
+ # Node ESM / moduleResolution nodenext — emit .js extensions in barrel files
169
+ ng-forge-generator --spec openapi.yaml --output src/generated --barrel-extension .js
170
+
167
171
  # Preview without writing
168
172
  ng-forge-generator --spec openapi.yaml --output src/generated --dry-run
169
173
 
@@ -288,6 +292,7 @@ A `.ng-forge-generator.json` config file is saved in the output directory (or th
288
292
  - **Selected endpoints** — which `METHOD:/path` pairs to generate
289
293
  - **Field type decisions** — ambiguous field type choices (e.g., `"registerUser.acceptTerms": "checkbox"`)
290
294
  - **Read-only flag** — whether GET endpoint fields are disabled
295
+ - **Barrel extension** — extension used in generated `index.ts` re-exports (empty by default)
291
296
 
292
297
  This enables reproducible non-interactive re-runs. On subsequent runs, the generator reuses saved decisions without prompting.
293
298
 
@@ -309,6 +314,22 @@ This enables reproducible non-interactive re-runs. On subsequent runs, the gener
309
314
  - **With `operationId`**: kebab-cased operationId (e.g., `createPet` -> `create-pet.form.ts`)
310
315
  - **Without `operationId`**: method + path (e.g., `POST /users/register` -> `post-users-register.form.ts`)
311
316
 
317
+ ### Barrel Exports
318
+
319
+ By default, generated `index.ts` barrel files re-export modules without a file extension:
320
+
321
+ ```typescript
322
+ // forms/index.ts
323
+ export * from './create-pet.form';
324
+ export * from './update-pet.form';
325
+ ```
326
+
327
+ This matches the typical Angular app setup (`moduleResolution: bundler`). For Node ESM consumers running with `moduleResolution: node16` or `nodenext`, pass `--barrel-extension .js` to emit fully-qualified ESM imports.
328
+
329
+ The selected extension persists in `.ng-forge-generator.json` (only when non-empty) so future runs keep your choice. To clear a previously-saved `.js` and return to the default, pass `--barrel-extension ""` or delete the `barrelExtension` key from the config file.
330
+
331
+ > **Migrating from pre-1.x:** Earlier versions emitted `.js` suffixes by default. Upgrading will change generated barrel output for users who had not explicitly configured this — pass `--barrel-extension .js` on your next run to preserve the old behavior.
332
+
312
333
  ## Programmatic API
313
334
 
314
335
  The package exports all core functions for programmatic use:
@@ -348,8 +369,9 @@ generateFormConfig(fields: FieldConfig[], options: FormConfigGeneratorOptions):
348
369
  // Generate a TypeScript interface source string from a schema
349
370
  generateInterface(schema: SchemaObject, options: InterfaceGeneratorOptions): string
350
371
 
351
- // Generate an index.ts barrel file
352
- generateBarrel(fileNames: string[]): string
372
+ // Generate an index.ts barrel file. `options.extension` controls the re-export
373
+ // suffix (default: '' — no extension; use '.js' for Node ESM/nodenext).
374
+ generateBarrel(fileNames: string[], options?: BarrelOptions): string
353
375
  ```
354
376
 
355
377
  ### I/O
@@ -1003,7 +1003,7 @@ function generateBarrel(fileNames, options) {
1003
1003
  if (fileNames.length === 0) {
1004
1004
  return "";
1005
1005
  }
1006
- const ext = options?.extension ?? ".js";
1006
+ const ext = options?.extension ?? "";
1007
1007
  const lines = [
1008
1008
  "// @generated by @ng-forge/openapi-generator ",
1009
1009
  ...fileNames.map((name) => {
@@ -1176,7 +1176,16 @@ function registerGenerateOptions(cmd) {
1176
1176
  return value;
1177
1177
  },
1178
1178
  "full"
1179
- ).option("--endpoints <list>", 'Comma-separated endpoints, e.g. "POST:/users,PUT:/users/{id}"').option("--read-only", "Generate GET endpoint forms with all fields disabled").option("--watch", "Watch spec file for changes and regenerate").option("--config <path>", "Directory for .ng-forge-generator.json config (defaults to --output)").option("--dry-run", "List files that would be generated without writing them").option("--skip-existing", "Skip files that already exist on disk").option("--verbose", "Show detailed output including field mapping decisions").option("--quiet", "Suppress info output; still shows success summary, warnings, and errors").addHelpText("after", "\nNote: Generated files are not formatted. Run your project formatter (e.g. prettier) after generation.");
1179
+ ).option("--endpoints <list>", 'Comma-separated endpoints, e.g. "POST:/users,PUT:/users/{id}"').option("--read-only", "Generate GET endpoint forms with all fields disabled").option("--watch", "Watch spec file for changes and regenerate").option("--config <path>", "Directory for .ng-forge-generator.json config (defaults to --output)").option("--dry-run", "List files that would be generated without writing them").option("--skip-existing", "Skip files that already exist on disk").option(
1180
+ "--barrel-extension <ext>",
1181
+ 'Extension used in barrel file exports. Accepts "" (default \u2014 no extension, for bundler/Angular setups) or a dot-prefixed extension like ".js" (for Node ESM / moduleResolution node16|nodenext). Pass "" to clear a previously-saved value',
1182
+ (value) => {
1183
+ if (value !== "" && !/^\.[a-z0-9]+$/i.test(value)) {
1184
+ throw new Error(`Invalid --barrel-extension '${value}'. Expected "" or a dot-prefixed extension like ".js".`);
1185
+ }
1186
+ return value;
1187
+ }
1188
+ ).option("--verbose", "Show detailed output including field mapping decisions").option("--quiet", "Suppress info output; still shows success summary, warnings, and errors").addHelpText("after", "\nNote: Generated files are not formatted. Run your project formatter (e.g. prettier) after generation.");
1180
1189
  }
1181
1190
  async function runGenerateAction(options) {
1182
1191
  if (options.verbose && options.quiet) {
@@ -1195,6 +1204,7 @@ async function runGenerate(options) {
1195
1204
  const configDir = options.config ?? options.output;
1196
1205
  const existingConfig = await loadConfig(configDir);
1197
1206
  const decisions = existingConfig?.decisions ?? {};
1207
+ const barrelExtension = options.barrelExtension ?? existingConfig?.barrelExtension ?? "";
1198
1208
  if (existingConfig) {
1199
1209
  logger.info(`Loaded config from ${configDir}/.ng-forge-generator.json (${existingConfig.endpoints.length} saved endpoint(s))`);
1200
1210
  }
@@ -1333,12 +1343,12 @@ Ambiguous fields in ${endpoint.method} ${endpoint.path}:`);
1333
1343
  }
1334
1344
  allFiles.push({
1335
1345
  fileName: "index.ts",
1336
- content: generateBarrel(allFormFileNames),
1346
+ content: generateBarrel(allFormFileNames, { extension: barrelExtension }),
1337
1347
  subdirectory: "forms"
1338
1348
  });
1339
1349
  allFiles.push({
1340
1350
  fileName: "index.ts",
1341
- content: generateBarrel(allInterfaceFileNames),
1351
+ content: generateBarrel(allInterfaceFileNames, { extension: barrelExtension }),
1342
1352
  subdirectory: "types"
1343
1353
  });
1344
1354
  if (allFormFileNames.length === 0) {
@@ -1375,7 +1385,11 @@ Ambiguous fields in ${endpoint.method} ${endpoint.path}:`);
1375
1385
  output: options.output,
1376
1386
  endpoints: configEndpoints,
1377
1387
  decisions: updatedDecisions,
1378
- readOnly: options.readOnly
1388
+ readOnly: options.readOnly,
1389
+ // Only persist when non-default so the empty (default) state is representable
1390
+ // as "field absent" — letting users clear a previously-saved .js setting by
1391
+ // passing --barrel-extension "" (or deleting the key manually).
1392
+ ...barrelExtension !== "" && { barrelExtension }
1379
1393
  };
1380
1394
  await saveConfig(configDir, config);
1381
1395
  if (options.watch) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-forge/openapi-generator",
3
- "version": "0.8.0-next.1",
3
+ "version": "0.8.0-next.3",
4
4
  "description": "Generate @ng-forge/dynamic-forms configurations from OpenAPI specs",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,7 +40,7 @@
40
40
  "README.md"
41
41
  ],
42
42
  "engines": {
43
- "node": ">=22.0.0"
43
+ "node": ">=24.0.0"
44
44
  },
45
45
  "sideEffects": false,
46
46
  "dependencies": {
@@ -12,6 +12,7 @@ interface GenerateOptions {
12
12
  skipExisting?: boolean;
13
13
  verbose?: boolean;
14
14
  quiet?: boolean;
15
+ barrelExtension?: string;
15
16
  }
16
17
  /**
17
18
  * Register generate options directly on the given Command (no subcommand).
@@ -1 +1 @@
1
- {"version":3,"file":"generate.command.d.ts","sourceRoot":"","sources":["../../../../../../packages/openapi-generator/src/cli/commands/generate.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAkBvE,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAwB1D;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/E;AA6RD,wBAAgB,eAAe,CAAC,YAAY,EAAE,YAAY,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,CAQ7F"}
1
+ {"version":3,"file":"generate.command.d.ts","sourceRoot":"","sources":["../../../../../../packages/openapi-generator/src/cli/commands/generate.command.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAkBvE,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAkC1D;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/E;AAkSD,wBAAgB,eAAe,CAAC,YAAY,EAAE,YAAY,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,CAQ7F"}
@@ -4,6 +4,7 @@ export interface GeneratorConfig {
4
4
  endpoints: string[];
5
5
  decisions: Record<string, string>;
6
6
  readOnly?: boolean;
7
+ barrelExtension?: string;
7
8
  }
8
9
  export declare function loadConfig(dir: string): Promise<GeneratorConfig | null>;
9
10
  export declare function saveConfig(dir: string, config: GeneratorConfig): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"generator-config.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/config/generator-config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAID,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAiB7E;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpF"}
1
+ {"version":3,"file":"generator-config.d.ts","sourceRoot":"","sources":["../../../../../packages/openapi-generator/src/config/generator-config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAID,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAiB7E;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpF"}
package/src/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export type { FormConfigGeneratorOptions } from './generator/form-config-generat
15
15
  export { generateInterface } from './generator/interface-generator.js';
16
16
  export type { InterfaceGeneratorOptions } from './generator/interface-generator.js';
17
17
  export { generateBarrel } from './generator/barrel-generator.js';
18
+ export type { BarrelOptions } from './generator/barrel-generator.js';
18
19
  export { writeGeneratedFiles } from './generator/file-writer.js';
19
20
  export type { GeneratedFile, WriteOptions, WriteResult } from './generator/file-writer.js';
20
21
  export { loadConfig, saveConfig } from './config/generator-config.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../packages/openapi-generator/src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACvF,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,YAAY,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE/G,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,YAAY,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAEvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,YAAY,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAEpF,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE3F,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AACtE,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../packages/openapi-generator/src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACvF,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,YAAY,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE/G,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,YAAY,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAEvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,YAAY,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAEpF,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,YAAY,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE3F,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AACtE,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC"}
package/src/index.js CHANGED
@@ -995,7 +995,7 @@ function generateBarrel(fileNames, options) {
995
995
  if (fileNames.length === 0) {
996
996
  return "";
997
997
  }
998
- const ext = options?.extension ?? ".js";
998
+ const ext = options?.extension ?? "";
999
999
  const lines = [
1000
1000
  "// @generated by @ng-forge/openapi-generator ",
1001
1001
  ...fileNames.map((name) => {
@@ -1174,7 +1174,16 @@ function registerGenerateOptions(cmd) {
1174
1174
  return value;
1175
1175
  },
1176
1176
  "full"
1177
- ).option("--endpoints <list>", 'Comma-separated endpoints, e.g. "POST:/users,PUT:/users/{id}"').option("--read-only", "Generate GET endpoint forms with all fields disabled").option("--watch", "Watch spec file for changes and regenerate").option("--config <path>", "Directory for .ng-forge-generator.json config (defaults to --output)").option("--dry-run", "List files that would be generated without writing them").option("--skip-existing", "Skip files that already exist on disk").option("--verbose", "Show detailed output including field mapping decisions").option("--quiet", "Suppress info output; still shows success summary, warnings, and errors").addHelpText("after", "\nNote: Generated files are not formatted. Run your project formatter (e.g. prettier) after generation.");
1177
+ ).option("--endpoints <list>", 'Comma-separated endpoints, e.g. "POST:/users,PUT:/users/{id}"').option("--read-only", "Generate GET endpoint forms with all fields disabled").option("--watch", "Watch spec file for changes and regenerate").option("--config <path>", "Directory for .ng-forge-generator.json config (defaults to --output)").option("--dry-run", "List files that would be generated without writing them").option("--skip-existing", "Skip files that already exist on disk").option(
1178
+ "--barrel-extension <ext>",
1179
+ 'Extension used in barrel file exports. Accepts "" (default \u2014 no extension, for bundler/Angular setups) or a dot-prefixed extension like ".js" (for Node ESM / moduleResolution node16|nodenext). Pass "" to clear a previously-saved value',
1180
+ (value) => {
1181
+ if (value !== "" && !/^\.[a-z0-9]+$/i.test(value)) {
1182
+ throw new Error(`Invalid --barrel-extension '${value}'. Expected "" or a dot-prefixed extension like ".js".`);
1183
+ }
1184
+ return value;
1185
+ }
1186
+ ).option("--verbose", "Show detailed output including field mapping decisions").option("--quiet", "Suppress info output; still shows success summary, warnings, and errors").addHelpText("after", "\nNote: Generated files are not formatted. Run your project formatter (e.g. prettier) after generation.");
1178
1187
  }
1179
1188
  async function runGenerateAction(options) {
1180
1189
  if (options.verbose && options.quiet) {
@@ -1193,6 +1202,7 @@ async function runGenerate(options) {
1193
1202
  const configDir = options.config ?? options.output;
1194
1203
  const existingConfig = await loadConfig(configDir);
1195
1204
  const decisions = existingConfig?.decisions ?? {};
1205
+ const barrelExtension = options.barrelExtension ?? existingConfig?.barrelExtension ?? "";
1196
1206
  if (existingConfig) {
1197
1207
  logger.info(`Loaded config from ${configDir}/.ng-forge-generator.json (${existingConfig.endpoints.length} saved endpoint(s))`);
1198
1208
  }
@@ -1331,12 +1341,12 @@ Ambiguous fields in ${endpoint.method} ${endpoint.path}:`);
1331
1341
  }
1332
1342
  allFiles.push({
1333
1343
  fileName: "index.ts",
1334
- content: generateBarrel(allFormFileNames),
1344
+ content: generateBarrel(allFormFileNames, { extension: barrelExtension }),
1335
1345
  subdirectory: "forms"
1336
1346
  });
1337
1347
  allFiles.push({
1338
1348
  fileName: "index.ts",
1339
- content: generateBarrel(allInterfaceFileNames),
1349
+ content: generateBarrel(allInterfaceFileNames, { extension: barrelExtension }),
1340
1350
  subdirectory: "types"
1341
1351
  });
1342
1352
  if (allFormFileNames.length === 0) {
@@ -1373,7 +1383,11 @@ Ambiguous fields in ${endpoint.method} ${endpoint.path}:`);
1373
1383
  output: options.output,
1374
1384
  endpoints: configEndpoints,
1375
1385
  decisions: updatedDecisions,
1376
- readOnly: options.readOnly
1386
+ readOnly: options.readOnly,
1387
+ // Only persist when non-default so the empty (default) state is representable
1388
+ // as "field absent" — letting users clear a previously-saved .js setting by
1389
+ // passing --barrel-extension "" (or deleting the key manually).
1390
+ ...barrelExtension !== "" && { barrelExtension }
1377
1391
  };
1378
1392
  await saveConfig(configDir, config);
1379
1393
  if (options.watch) {