@techspokes/typescript-wsdl-client 0.10.2 → 0.11.0

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.
Files changed (48) hide show
  1. package/README.md +28 -2
  2. package/dist/app/generateApp.d.ts +11 -5
  3. package/dist/app/generateApp.d.ts.map +1 -1
  4. package/dist/app/generateApp.js +262 -157
  5. package/dist/cli.js +67 -9
  6. package/dist/client/generateOperations.d.ts +13 -0
  7. package/dist/client/generateOperations.d.ts.map +1 -0
  8. package/dist/client/generateOperations.js +71 -0
  9. package/dist/compiler/schemaCompiler.d.ts.map +1 -1
  10. package/dist/compiler/schemaCompiler.js +15 -1
  11. package/dist/gateway/generateGateway.d.ts +1 -0
  12. package/dist/gateway/generateGateway.d.ts.map +1 -1
  13. package/dist/gateway/generateGateway.js +4 -2
  14. package/dist/gateway/generators.d.ts +2 -15
  15. package/dist/gateway/generators.d.ts.map +1 -1
  16. package/dist/gateway/generators.js +111 -27
  17. package/dist/gateway/helpers.d.ts +4 -2
  18. package/dist/gateway/helpers.d.ts.map +1 -1
  19. package/dist/gateway/helpers.js +4 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -0
  22. package/dist/loader/wsdlLoader.d.ts.map +1 -1
  23. package/dist/loader/wsdlLoader.js +30 -4
  24. package/dist/openapi/generateOpenAPI.d.ts +1 -0
  25. package/dist/openapi/generateOpenAPI.d.ts.map +1 -1
  26. package/dist/openapi/generateOpenAPI.js +1 -0
  27. package/dist/openapi/generateSchemas.d.ts +1 -0
  28. package/dist/openapi/generateSchemas.d.ts.map +1 -1
  29. package/dist/openapi/generateSchemas.js +4 -3
  30. package/dist/pipeline.d.ts +4 -0
  31. package/dist/pipeline.d.ts.map +1 -1
  32. package/dist/pipeline.js +10 -1
  33. package/dist/util/builder.d.ts.map +1 -1
  34. package/dist/util/builder.js +1 -0
  35. package/dist/util/cli.d.ts +3 -2
  36. package/dist/util/cli.d.ts.map +1 -1
  37. package/dist/util/cli.js +14 -4
  38. package/dist/util/errors.d.ts +37 -0
  39. package/dist/util/errors.d.ts.map +1 -0
  40. package/dist/util/errors.js +37 -0
  41. package/docs/README.md +1 -0
  42. package/docs/architecture.md +1 -1
  43. package/docs/cli-reference.md +46 -14
  44. package/docs/concepts.md +29 -2
  45. package/docs/gateway-guide.md +36 -2
  46. package/docs/generated-code.md +56 -0
  47. package/docs/testing.md +193 -0
  48. package/package.json +19 -13
@@ -1,23 +1,30 @@
1
1
  // noinspection HttpUrlsUsage
2
2
  /**
3
- * Fastify App Generator
3
+ * Fastify App Scaffold Generator
4
4
  *
5
5
  * This module generates a runnable Fastify application that imports and uses
6
6
  * the generated gateway plugin and SOAP client. The app serves the OpenAPI spec,
7
7
  * health checks, and all gateway routes.
8
8
  *
9
+ * The generated scaffold is intended as a one-time starting point. Developers
10
+ * should customize it freely after generation. Use --force-init to overwrite
11
+ * existing scaffold files.
12
+ *
9
13
  * Core capabilities:
10
14
  * - Generates server.ts with Fastify setup and plugin registration
11
15
  * - Generates config.ts with environment-based configuration
16
+ * - Generates package.json with required dependencies
17
+ * - Generates tsconfig.json with NodeNext/ES2022 settings
12
18
  * - Generates .env.example with required/optional environment variables
13
19
  * - Generates README.md with instructions for running the app
14
20
  * - Optionally copies OpenAPI spec into app directory
15
21
  * - Validates required inputs (client-dir, gateway-dir, openapi-file, catalog-file)
22
+ * - Skip-if-exists protection for scaffold files (override with force option)
16
23
  */
17
24
  import fs from "node:fs";
18
25
  import path from "node:path";
19
26
  import { deriveClientName } from "../util/tools.js";
20
- import { success } from "../util/cli.js";
27
+ import { info, success } from "../util/cli.js";
21
28
  /**
22
29
  * Validates that all required files and directories exist
23
30
  *
@@ -73,16 +80,13 @@ function getExtension(imports) {
73
80
  return "";
74
81
  }
75
82
  /**
76
- * Returns the file extension for executable app files (server, config)
77
- * These always need extensions even with bare imports since they are entry points
83
+ * Returns the file extension for scaffold app files (server, config).
84
+ * Always .ts the scaffold is TypeScript for full type safety.
78
85
  *
79
- * @param {string} imports - Import mode (js, ts, or bare)
80
86
  * @returns {string} - File extension with leading dot
81
87
  */
82
- function getAppFileExtension(imports) {
83
- if (imports === "ts")
84
- return ".ts";
85
- return ".js"; // Use .js for both "js" and "bare" modes
88
+ function getAppFileExtension() {
89
+ return ".ts";
86
90
  }
87
91
  /**
88
92
  * Computes a relative import path from source to target
@@ -105,6 +109,48 @@ function computeRelativeImport(from, to, imports) {
105
109
  }
106
110
  return prefixed;
107
111
  }
112
+ /**
113
+ * Checks if a string is a URL (http:// or https://)
114
+ */
115
+ function isUrl(value) {
116
+ return /^https?:\/\//i.test(value);
117
+ }
118
+ /**
119
+ * Normalizes a file path to POSIX separators
120
+ */
121
+ function toPosix(filePath) {
122
+ return filePath.split(path.sep).join("/");
123
+ }
124
+ /**
125
+ * Resolves a WSDL source path relative to the app directory.
126
+ * URLs are returned as-is. File paths are computed relative to appDir.
127
+ *
128
+ * @param {string} wsdlSource - Original WSDL source from catalog
129
+ * @param {string} appDir - Resolved app output directory
130
+ * @returns {string} - WSDL source suitable for use from the app directory
131
+ */
132
+ function resolveWsdlSourceForApp(wsdlSource, appDir) {
133
+ if (isUrl(wsdlSource))
134
+ return wsdlSource;
135
+ return toPosix(path.relative(appDir, path.resolve(wsdlSource)));
136
+ }
137
+ /**
138
+ * Checks whether a scaffold file should be written.
139
+ * Returns true if the file does not exist or force is enabled.
140
+ * Logs an info message and returns false if the file exists and force is disabled.
141
+ *
142
+ * @param {string} filePath - Absolute path to the file
143
+ * @param {boolean} force - Whether to overwrite existing files
144
+ * @returns {boolean} - Whether the file should be written
145
+ */
146
+ function shouldWriteScaffoldFile(filePath, force) {
147
+ if (!fs.existsSync(filePath))
148
+ return true;
149
+ if (force)
150
+ return true;
151
+ info(`Skipping ${path.basename(filePath)} (already exists, use --force-init to overwrite)`);
152
+ return false;
153
+ }
108
154
  /**
109
155
  * Reads and parses the catalog file
110
156
  *
@@ -135,10 +181,14 @@ function getCatalogWsdlSource(catalog) {
135
181
  * @param {string} appDir - App output directory
136
182
  * @param {GenerateAppOptions} opts - App generation options
137
183
  * @param {string} clientClassName - Derived client class name
184
+ * @param {boolean} force - Whether to overwrite existing files
138
185
  */
139
- function generateServerFile(appDir, opts, clientClassName) {
186
+ function generateServerFile(appDir, opts, clientClassName, force) {
140
187
  const imports = opts.imports || "js";
141
- const ext = getAppFileExtension(imports); // Use getAppFileExtension for executable entry point
188
+ const ext = getAppFileExtension();
189
+ const filePath = path.join(appDir, `server${ext}`);
190
+ if (!shouldWriteScaffoldFile(filePath, force))
191
+ return;
142
192
  const configImport = computeRelativeImport(appDir, path.join(appDir, "config"), imports);
143
193
  const gatewayPluginImport = computeRelativeImport(appDir, path.join(opts.gatewayDir, "plugin"), imports);
144
194
  const clientImport = computeRelativeImport(appDir, path.join(opts.clientDir, "client"), imports);
@@ -149,6 +199,11 @@ function generateServerFile(appDir, opts, clientClassName) {
149
199
  const openapiSpecPath = path.join(__dirname, "openapi.json");
150
200
  const openapiSpec = JSON.parse(fs.readFileSync(openapiSpecPath, "utf-8"));
151
201
 
202
+ // Override OpenAPI server URL at runtime if configured
203
+ if (config.openapiServerUrl) {
204
+ openapiSpec.servers = [{ url: config.openapiServerUrl }];
205
+ }
206
+
152
207
  // Serve OpenAPI specification
153
208
  fastify.get("/openapi.json", async () => {
154
209
  return openapiSpec;
@@ -158,11 +213,16 @@ function generateServerFile(appDir, opts, clientClassName) {
158
213
  const openapiSpecPath = path.resolve(__dirname, "${computeRelativeImport(appDir, opts.openapiFile, "bare")}");
159
214
  const openapiSpec = JSON.parse(fs.readFileSync(openapiSpecPath, "utf-8"));
160
215
 
216
+ // Override OpenAPI server URL at runtime if configured
217
+ if (config.openapiServerUrl) {
218
+ openapiSpec.servers = [{ url: config.openapiServerUrl }];
219
+ }
220
+
161
221
  fastify.get("/openapi.json", async () => {
162
222
  return openapiSpec;
163
223
  });`;
164
224
  const content = `/**
165
- * Generated Fastify Application
225
+ * Fastify Application
166
226
  *
167
227
  * This file bootstraps a Fastify server that:
168
228
  * - Loads configuration from environment variables
@@ -171,7 +231,7 @@ function generateServerFile(appDir, opts, clientClassName) {
171
231
  * - Serves the OpenAPI specification
172
232
  * - Provides health check endpoint
173
233
  *
174
- * Auto-generated - do not edit manually.
234
+ * Scaffolded by wsdl-tsc. Customize freely.
175
235
  */
176
236
  import fs from "node:fs";
177
237
  import path from "node:path";
@@ -245,7 +305,7 @@ main().catch((err) => {
245
305
  process.exit(1);
246
306
  });
247
307
  `;
248
- fs.writeFileSync(path.join(appDir, `server${ext}`), content, "utf-8");
308
+ fs.writeFileSync(filePath, content, "utf-8");
249
309
  }
250
310
  /**
251
311
  * Generates config.ts file
@@ -253,18 +313,35 @@ main().catch((err) => {
253
313
  * @param {string} appDir - App output directory
254
314
  * @param {GenerateAppOptions} opts - App generation options
255
315
  * @param {string|undefined} defaultWsdlSource - Default WSDL source from catalog
316
+ * @param {boolean} force - Whether to overwrite existing files
256
317
  */
257
- function generateConfigFile(appDir, opts, defaultWsdlSource) {
258
- const imports = opts.imports || "js";
259
- const ext = getAppFileExtension(imports); // Use getAppFileExtension for executable entry point
318
+ function generateConfigFile(appDir, opts, defaultWsdlSource, force) {
319
+ const ext = getAppFileExtension();
320
+ const filePath = path.join(appDir, `config${ext}`);
321
+ if (!shouldWriteScaffoldFile(filePath, force))
322
+ return;
260
323
  const defaultHost = opts.host || "127.0.0.1";
261
324
  const defaultPort = opts.port || 3000;
262
325
  const defaultPrefix = opts.prefix || "";
263
326
  const defaultLogger = opts.logger !== false;
264
- // For .js files, we need to generate plain JavaScript (no TypeScript types)
265
- // For .ts files, we can use TypeScript syntax
266
- const isTypeScript = ext === ".ts";
267
- const typeAnnotations = isTypeScript ? `
327
+ // Resolve WSDL source relative to app directory
328
+ const resolvedWsdlSource = defaultWsdlSource
329
+ ? resolveWsdlSourceForApp(defaultWsdlSource, appDir)
330
+ : undefined;
331
+ // For URL sources, use as fallback default. For file sources, require explicit WSDL_SOURCE.
332
+ const wsdlIsUrl = resolvedWsdlSource && isUrl(resolvedWsdlSource);
333
+ const wsdlFallback = wsdlIsUrl ? ` || "${resolvedWsdlSource}"` : "";
334
+ const content = `/**
335
+ * Application Configuration
336
+ *
337
+ * Loads configuration from environment variables with sensible defaults.
338
+ * Configuration precedence:
339
+ * 1. Environment variables (runtime overrides)
340
+ * 2. Hard defaults (defined in this file)
341
+ *
342
+ * Scaffolded by wsdl-tsc. Customize freely.
343
+ */
344
+
268
345
  /**
269
346
  * Application configuration interface
270
347
  */
@@ -274,6 +351,7 @@ export interface AppConfig {
274
351
  port: number;
275
352
  prefix: string;
276
353
  logger: boolean;
354
+ openapiServerUrl: string;
277
355
  }
278
356
 
279
357
  /**
@@ -281,30 +359,12 @@ export interface AppConfig {
281
359
  *
282
360
  * @returns {AppConfig} - Application configuration
283
361
  * @throws {Error} If required configuration is missing
284
- */` : `
285
- /**
286
- * Loads configuration from environment variables
287
- *
288
- * @returns {object} - Application configuration with wsdlSource, host, port, prefix, logger
289
- * @throws {Error} If required configuration is missing
290
- */`;
291
- const content = `/**
292
- * Application Configuration
293
- *
294
- * Loads configuration from environment variables with sensible defaults.
295
- * Configuration precedence:
296
- * 1. Environment variables (runtime overrides)
297
- * 2. Catalog defaults (generation-time recorded values)
298
- * 3. Hard defaults (defined in this file)
299
- *
300
- * Auto-generated - do not edit manually.
301
362
  */
302
- ${typeAnnotations}
303
- export function loadConfig() {
304
- // WSDL source: required from env or catalog default
305
- const wsdlSource = process.env.WSDL_SOURCE${defaultWsdlSource ? ` || "${defaultWsdlSource}"` : ""};
363
+ export function loadConfig(): AppConfig {
364
+ // WSDL source: required from env${wsdlIsUrl ? " or URL default" : ""}
365
+ const wsdlSource = process.env.WSDL_SOURCE${wsdlFallback};
306
366
  if (!wsdlSource) {
307
- throw new Error("WSDL_SOURCE environment variable is required");
367
+ throw new Error("WSDL_SOURCE environment variable is required${resolvedWsdlSource && !wsdlIsUrl ? ` (hint: ${resolvedWsdlSource})` : ""}");
308
368
  }
309
369
 
310
370
  // Host: default to ${defaultHost}
@@ -322,16 +382,20 @@ export function loadConfig() {
322
382
  // Logger: default to ${defaultLogger}
323
383
  const logger = process.env.LOGGER ? process.env.LOGGER === "true" : ${defaultLogger};
324
384
 
385
+ // OpenAPI server URL override (replaces servers in OpenAPI spec at runtime)
386
+ const openapiServerUrl = process.env.OPENAPI_SERVER_URL || "";
387
+
325
388
  return {
326
389
  wsdlSource,
327
390
  host,
328
391
  port,
329
392
  prefix,
330
393
  logger,
394
+ openapiServerUrl,
331
395
  };
332
396
  }
333
397
  `;
334
- fs.writeFileSync(path.join(appDir, `config${ext}`), content, "utf-8");
398
+ fs.writeFileSync(filePath, content, "utf-8");
335
399
  }
336
400
  /**
337
401
  * Generates .env.example file
@@ -339,23 +403,42 @@ export function loadConfig() {
339
403
  * @param {string} appDir - App output directory
340
404
  * @param {GenerateAppOptions} opts - App generation options
341
405
  * @param {string|undefined} defaultWsdlSource - Default WSDL source from catalog
406
+ * @param {boolean} force - Whether to overwrite existing files
342
407
  */
343
- function generateEnvExample(appDir, opts, defaultWsdlSource) {
408
+ function generateEnvExample(appDir, opts, defaultWsdlSource, force) {
409
+ const filePath = path.join(appDir, ".env.example");
410
+ if (!shouldWriteScaffoldFile(filePath, force))
411
+ return;
344
412
  const defaultHost = opts.host || "127.0.0.1";
345
413
  const defaultPort = opts.port || 3000;
346
414
  const defaultPrefix = opts.prefix || "";
347
415
  const defaultLogger = opts.logger !== false;
348
- const content = `# Generated Fastify Application Environment Variables
416
+ // Resolve WSDL source relative to app directory
417
+ const resolvedWsdlSource = defaultWsdlSource
418
+ ? resolveWsdlSourceForApp(defaultWsdlSource, appDir)
419
+ : undefined;
420
+ const wsdlIsUrl = resolvedWsdlSource && isUrl(resolvedWsdlSource);
421
+ let wsdlSection;
422
+ if (wsdlIsUrl) {
423
+ // URL source: show as commented default (it's the fallback in config.ts)
424
+ wsdlSection = `# WSDL source URL (default: ${resolvedWsdlSource})
425
+ #WSDL_SOURCE=${resolvedWsdlSource}`;
426
+ }
427
+ else if (resolvedWsdlSource) {
428
+ // File source: require explicit setting, show hint
429
+ wsdlSection = `# WSDL source (required — set to URL or file path)
430
+ # Generation-time path: ${resolvedWsdlSource}
431
+ WSDL_SOURCE=`;
432
+ }
433
+ else {
434
+ wsdlSection = `# WSDL source (required — set to URL or file path)
435
+ WSDL_SOURCE=`;
436
+ }
437
+ const content = `# Fastify Application Environment Variables
349
438
  #
350
439
  # Copy this file to .env and customize as needed.
351
- # Configuration precedence:
352
- # 1. Environment variables (runtime overrides)
353
- # 2. Catalog defaults (generation-time recorded values)
354
- # 3. Hard defaults (see config file)
355
440
 
356
- # WSDL source (required unless provided in catalog)
357
- ${defaultWsdlSource ? `# Default from catalog: ${defaultWsdlSource}` : "# Required: specify the WSDL URL or local file path"}
358
- ${defaultWsdlSource ? `#WSDL_SOURCE=${defaultWsdlSource}` : `WSDL_SOURCE=`}
441
+ ${wsdlSection}
359
442
 
360
443
  # Server host (default: ${defaultHost})
361
444
  HOST=${defaultHost}
@@ -369,151 +452,162 @@ PREFIX=${defaultPrefix}
369
452
  # Enable Fastify logger (default: ${defaultLogger})
370
453
  LOGGER=${defaultLogger}
371
454
 
455
+ # Override OpenAPI spec server URL at runtime (default: use generation-time value)
456
+ #OPENAPI_SERVER_URL=http://localhost:${defaultPort}
457
+
372
458
  # Optional: SOAP security settings (configure based on your client requirements)
373
459
  # SOAP_USERNAME=
374
460
  # SOAP_PASSWORD=
375
461
  # SOAP_ENDPOINT=
376
462
  `;
377
- fs.writeFileSync(path.join(appDir, ".env.example"), content, "utf-8");
463
+ fs.writeFileSync(filePath, content, "utf-8");
464
+ }
465
+ /**
466
+ * Generates package.json file (skipped if already exists)
467
+ *
468
+ * @param {string} appDir - App output directory
469
+ * @param {boolean} force - Whether to overwrite existing files
470
+ */
471
+ function generatePackageJson(appDir, force) {
472
+ const filePath = path.join(appDir, "package.json");
473
+ if (!shouldWriteScaffoldFile(filePath, force))
474
+ return;
475
+ const pkg = {
476
+ name: "wsdl-gateway-app",
477
+ version: "0.0.1",
478
+ private: true,
479
+ type: "module",
480
+ scripts: {
481
+ start: "tsx server.ts",
482
+ dev: "tsx watch server.ts",
483
+ },
484
+ dependencies: {
485
+ fastify: "^5.7.4",
486
+ "fastify-plugin": "^5.1.0",
487
+ soap: "^1.6.5",
488
+ },
489
+ devDependencies: {
490
+ tsx: "^4.21.0",
491
+ typescript: "^5.9.3",
492
+ },
493
+ };
494
+ fs.writeFileSync(filePath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
495
+ }
496
+ /**
497
+ * Generates tsconfig.json file (skipped if already exists)
498
+ *
499
+ * @param {string} appDir - App output directory
500
+ * @param {GenerateAppOptions} opts - App generation options
501
+ * @param {boolean} force - Whether to overwrite existing files
502
+ */
503
+ function generateTsConfig(appDir, opts, force) {
504
+ const filePath = path.join(appDir, "tsconfig.json");
505
+ if (!shouldWriteScaffoldFile(filePath, force))
506
+ return;
507
+ // Compute include paths relative to app directory
508
+ const clientInclude = toPosix(path.relative(appDir, opts.clientDir)) + "/**/*.ts";
509
+ const gatewayInclude = toPosix(path.relative(appDir, opts.gatewayDir)) + "/**/*.ts";
510
+ const tsconfig = {
511
+ compilerOptions: {
512
+ module: "NodeNext",
513
+ moduleResolution: "NodeNext",
514
+ target: "ES2022",
515
+ strict: true,
516
+ esModuleInterop: true,
517
+ skipLibCheck: true,
518
+ outDir: "dist",
519
+ rootDir: ".",
520
+ },
521
+ include: [
522
+ "*.ts",
523
+ clientInclude,
524
+ gatewayInclude,
525
+ ],
526
+ };
527
+ fs.writeFileSync(filePath, JSON.stringify(tsconfig, null, 2) + "\n", "utf-8");
378
528
  }
379
529
  /**
380
530
  * Generates README.md file
381
531
  *
382
532
  * @param {string} appDir - App output directory
383
533
  * @param {GenerateAppOptions} opts - App generation options
534
+ * @param {boolean} force - Whether to overwrite existing files
384
535
  */
385
- function generateReadme(appDir, opts) {
386
- const imports = opts.imports || "js";
387
- const ext = getExtension(imports);
388
- const runCommand = ext === ".ts"
389
- ? "npx tsx server.ts"
390
- : "node server.js";
536
+ function generateReadme(appDir, opts, force) {
537
+ const filePath = path.join(appDir, "README.md");
538
+ if (!shouldWriteScaffoldFile(filePath, force))
539
+ return;
391
540
  const content = `# Generated Fastify Application
392
541
 
393
- This application was auto-generated by \`wsdl-tsc app\`.
542
+ This application was scaffolded by \`wsdl-tsc\`. Customize freely.
394
543
 
395
- ## Overview
544
+ ## Quick Start
396
545
 
397
- This Fastify application provides a REST gateway to a SOAP service, automatically bridging between REST endpoints and SOAP operations.
546
+ \`\`\`bash
547
+ npm install
548
+ cp .env.example .env
549
+ # Edit .env — set WSDL_SOURCE to your WSDL URL or file path
550
+ npm start
551
+ \`\`\`
398
552
 
399
553
  ## Structure
400
554
 
401
- - \`server${ext}\` - Main application entry point
402
- - \`config${ext}\` - Configuration loader (environment-based)
555
+ - \`server.ts\` - Main application entry point
556
+ - \`config.ts\` - Configuration loader (environment-based)
557
+ - \`package.json\` - Dependencies and scripts
558
+ - \`tsconfig.json\` - TypeScript configuration
403
559
  - \`.env.example\` - Example environment configuration
404
560
  - \`openapi.json\` - OpenAPI specification${opts.openapiMode === "copy" ? " (copied)" : " (referenced)"}
405
561
 
406
- ## Prerequisites
407
-
408
- - Node.js >= 20.0.0
409
- - Dependencies installed (\`npm install\`)
410
-
411
- ## Quick Start
412
-
413
- 1. **Copy environment template**:
414
- \`\`\`bash
415
- cp .env.example .env
416
- \`\`\`
417
-
418
- 2. **Configure environment**:
419
- Edit \`.env\` and set required variables (especially \`WSDL_SOURCE\` if not provided via catalog).
420
-
421
- 3. **Run the server**:
422
- \`\`\`bash
423
- ${runCommand}
424
- \`\`\`
425
-
426
562
  ## Endpoints
427
563
 
428
- ### Health Check
429
- \`\`\`
430
- GET /health
431
- \`\`\`
432
- Returns: \`{ ok: true }\`
433
-
434
- ### OpenAPI Specification
435
- \`\`\`
436
- GET /openapi.json
437
- \`\`\`
438
- Returns: OpenAPI 3.1 specification document
439
-
440
- ### Gateway Routes
441
- All SOAP operations are exposed as REST endpoints. See \`openapi.json\` for complete API documentation.
564
+ | Endpoint | Method | Description |
565
+ |----------|--------|-------------|
566
+ | \`/health\` | GET | Health check — returns \`{ "ok": true }\` |
567
+ | \`/openapi.json\` | GET | OpenAPI 3.1 specification |
568
+ | All SOAP operations | POST | REST-to-SOAP gateway routes (see openapi.json) |
442
569
 
443
570
  ## Configuration
444
571
 
445
- Configuration is loaded from environment variables with the following precedence:
446
-
447
- 1. Environment variables (runtime overrides)
448
- 2. Catalog defaults (from generation-time)
449
- 3. Hard defaults (in config file)
450
-
451
- ### Environment Variables
452
-
453
572
  | Variable | Default | Description |
454
573
  |----------|---------|-------------|
455
- | \`WSDL_SOURCE\` | (from catalog) | WSDL URL or local file path (required) |
574
+ | \`WSDL_SOURCE\` | (see .env.example) | WSDL URL or local file path (required) |
456
575
  | \`HOST\` | ${opts.host || "127.0.0.1"} | Server bind address |
457
576
  | \`PORT\` | ${opts.port || 3000} | Server listen port |
458
- | \`PREFIX\` | ${opts.prefix || "(empty)"} | Route prefix |
577
+ | \`PREFIX\` | (empty) | Route prefix |
459
578
  | \`LOGGER\` | ${opts.logger !== false} | Enable Fastify logger |
579
+ | \`OPENAPI_SERVER_URL\` | (empty) | Override OpenAPI spec server URL at runtime |
460
580
 
461
581
  ## Development
462
582
 
463
- ### Running with watch mode
464
- \`\`\`bash
465
- npx tsx watch server${ext}
466
- \`\`\`
467
-
468
- ### Testing endpoints
469
583
  \`\`\`bash
470
- # Health check
471
- curl http://localhost:3000/health
472
-
473
- # OpenAPI spec
474
- curl http://localhost:3000/openapi.json
475
- \`\`\`
476
-
477
- ## Troubleshooting
478
-
479
- ### WSDL_SOURCE missing
480
- If you see "WSDL_SOURCE environment variable is required", set it in your \`.env\` file or export it:
481
- \`\`\`bash
482
- export WSDL_SOURCE=path/to/service.wsdl
483
- ${runCommand}
484
- \`\`\`
485
-
486
- ### Port already in use
487
- Change the \`PORT\` in your \`.env\` file or:
488
- \`\`\`bash
489
- PORT=8080 ${runCommand}
584
+ npm run dev # Start with file watching
585
+ curl localhost:3000/health
586
+ curl localhost:3000/openapi.json
490
587
  \`\`\`
491
588
 
492
- ## Notes
493
-
494
- - This app uses the generated client from: \`${opts.clientDir}\`
495
- - Gateway plugin from: \`${opts.gatewayDir}\`
496
- - OpenAPI spec from: \`${opts.openapiFile}\`
497
-
498
589
  ## Generated By
499
590
 
500
- - Tool: [@techspokes/typescript-wsdl-client](https://github.com/TechSpokes/typescript-wsdl-client)
501
- - Command: \`wsdl-tsc app\`
591
+ [@techspokes/typescript-wsdl-client](https://github.com/TechSpokes/typescript-wsdl-client)
502
592
  `;
503
- fs.writeFileSync(path.join(appDir, "README.md"), content, "utf-8");
593
+ fs.writeFileSync(filePath, content, "utf-8");
504
594
  }
505
595
  /**
506
- * Generates a runnable Fastify application
596
+ * Generates a runnable Fastify application scaffold
507
597
  *
508
- * This function orchestrates the complete app generation process:
598
+ * This function orchestrates the complete app scaffold process:
509
599
  * 1. Validates all required inputs exist
510
600
  * 2. Reads catalog.json for metadata
511
601
  * 3. Creates app directory
512
602
  * 4. Generates server.ts with Fastify setup
513
603
  * 5. Generates config.ts with environment loading
514
- * 6. Generates .env.example with configuration template
515
- * 7. Generates README.md with usage instructions
516
- * 8. Optionally copies OpenAPI spec into app directory
604
+ * 6. Generates package.json with dependencies
605
+ * 7. Generates tsconfig.json with TypeScript settings
606
+ * 8. Generates .env.example with configuration template
607
+ * 9. Generates README.md with usage instructions
608
+ * 10. Optionally copies OpenAPI spec into app directory
609
+ *
610
+ * Files that already exist are skipped unless force is true.
517
611
  *
518
612
  * @param {GenerateAppOptions} opts - App generation options
519
613
  * @returns {Promise<void>}
@@ -529,6 +623,7 @@ export async function generateApp(opts) {
529
623
  catalogFile: path.resolve(opts.catalogFile),
530
624
  appDir: path.resolve(opts.appDir),
531
625
  };
626
+ const force = resolvedOpts.force ?? false;
532
627
  // Validate required files and directories
533
628
  validateRequiredFiles(resolvedOpts);
534
629
  // Read catalog for metadata
@@ -537,17 +632,27 @@ export async function generateApp(opts) {
537
632
  const defaultWsdlSource = getCatalogWsdlSource(catalog);
538
633
  // Create app directory
539
634
  fs.mkdirSync(resolvedOpts.appDir, { recursive: true });
540
- // Generate app files
541
- generateServerFile(resolvedOpts.appDir, resolvedOpts, clientClassName);
542
- generateConfigFile(resolvedOpts.appDir, resolvedOpts, defaultWsdlSource);
543
- generateEnvExample(resolvedOpts.appDir, resolvedOpts, defaultWsdlSource);
544
- generateReadme(resolvedOpts.appDir, resolvedOpts);
635
+ // Generate scaffold files (each checks for existing files unless force is set)
636
+ generateServerFile(resolvedOpts.appDir, resolvedOpts, clientClassName, force);
637
+ generateConfigFile(resolvedOpts.appDir, resolvedOpts, defaultWsdlSource, force);
638
+ generatePackageJson(resolvedOpts.appDir, force);
639
+ generateTsConfig(resolvedOpts.appDir, resolvedOpts, force);
640
+ generateEnvExample(resolvedOpts.appDir, resolvedOpts, defaultWsdlSource, force);
641
+ generateReadme(resolvedOpts.appDir, resolvedOpts, force);
545
642
  // Handle OpenAPI file
546
643
  const openapiMode = resolvedOpts.openapiMode || "copy";
547
644
  if (openapiMode === "copy") {
548
645
  const destPath = path.join(resolvedOpts.appDir, "openapi.json");
549
- fs.copyFileSync(resolvedOpts.openapiFile, destPath);
550
- success(`Copied OpenAPI spec to ${destPath}`);
646
+ if (shouldWriteScaffoldFile(destPath, force)) {
647
+ const spec = JSON.parse(fs.readFileSync(resolvedOpts.openapiFile, "utf-8"));
648
+ const host = resolvedOpts.host || "127.0.0.1";
649
+ const port = resolvedOpts.port || 3000;
650
+ const prefix = resolvedOpts.prefix || "";
651
+ const urlHost = ["0.0.0.0", "127.0.0.1", "::", "::1"].includes(host) ? "localhost" : host;
652
+ spec.servers = [{ url: `http://${urlHost}:${port}${prefix}` }];
653
+ fs.writeFileSync(destPath, JSON.stringify(spec, null, 2) + "\n", "utf-8");
654
+ success(`Copied OpenAPI spec to ${destPath}`);
655
+ }
551
656
  }
552
- success(`Generated runnable Fastify app in ${resolvedOpts.appDir}`);
657
+ success(`Scaffolded Fastify app in ${resolvedOpts.appDir}`);
553
658
  }