@techspokes/typescript-wsdl-client 0.10.2 → 0.10.4
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 +2 -2
- package/dist/app/generateApp.d.ts +11 -5
- package/dist/app/generateApp.d.ts.map +1 -1
- package/dist/app/generateApp.js +262 -157
- package/dist/cli.js +49 -8
- package/dist/pipeline.d.ts +4 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +4 -0
- package/docs/architecture.md +1 -1
- package/docs/cli-reference.md +44 -14
- package/docs/gateway-guide.md +36 -2
- package/docs/generated-code.md +32 -0
- package/package.json +13 -12
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ npx wsdl-tsc pipeline \
|
|
|
35
35
|
--gateway-dir ./tmp/gateway \
|
|
36
36
|
--gateway-service-name weather \
|
|
37
37
|
--gateway-version-prefix v1 \
|
|
38
|
-
--
|
|
38
|
+
--init-app
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
This parses the WSDL, generates a typed SOAP client, creates an OpenAPI 3.1 spec, builds Fastify gateway handlers, and creates a runnable application.
|
|
@@ -43,7 +43,7 @@ This parses the WSDL, generates a typed SOAP client, creates an OpenAPI 3.1 spec
|
|
|
43
43
|
### Run and Test
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
cd tmp/app && cp .env.example .env &&
|
|
46
|
+
cd tmp/app && npm install && cp .env.example .env && npm start
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
```bash
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* @property {string} [prefix] - Route prefix (default: "")
|
|
14
14
|
* @property {boolean} [logger] - Enable Fastify logger (default: true)
|
|
15
15
|
* @property {"copy"|"reference"} [openapiMode] - How to handle OpenAPI file (default: "copy")
|
|
16
|
+
* @property {boolean} [force] - Overwrite existing scaffold files (default: false)
|
|
16
17
|
*/
|
|
17
18
|
export interface GenerateAppOptions {
|
|
18
19
|
clientDir: string;
|
|
@@ -26,19 +27,24 @@ export interface GenerateAppOptions {
|
|
|
26
27
|
prefix?: string;
|
|
27
28
|
logger?: boolean;
|
|
28
29
|
openapiMode?: "copy" | "reference";
|
|
30
|
+
force?: boolean;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
|
-
* Generates a runnable Fastify application
|
|
33
|
+
* Generates a runnable Fastify application scaffold
|
|
32
34
|
*
|
|
33
|
-
* This function orchestrates the complete app
|
|
35
|
+
* This function orchestrates the complete app scaffold process:
|
|
34
36
|
* 1. Validates all required inputs exist
|
|
35
37
|
* 2. Reads catalog.json for metadata
|
|
36
38
|
* 3. Creates app directory
|
|
37
39
|
* 4. Generates server.ts with Fastify setup
|
|
38
40
|
* 5. Generates config.ts with environment loading
|
|
39
|
-
* 6. Generates .
|
|
40
|
-
* 7. Generates
|
|
41
|
-
* 8.
|
|
41
|
+
* 6. Generates package.json with dependencies
|
|
42
|
+
* 7. Generates tsconfig.json with TypeScript settings
|
|
43
|
+
* 8. Generates .env.example with configuration template
|
|
44
|
+
* 9. Generates README.md with usage instructions
|
|
45
|
+
* 10. Optionally copies OpenAPI spec into app directory
|
|
46
|
+
*
|
|
47
|
+
* Files that already exist are skipped unless force is true.
|
|
42
48
|
*
|
|
43
49
|
* @param {GenerateAppOptions} opts - App generation options
|
|
44
50
|
* @returns {Promise<void>}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateApp.d.ts","sourceRoot":"","sources":["../../src/app/generateApp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generateApp.d.ts","sourceRoot":"","sources":["../../src/app/generateApp.ts"],"names":[],"mappings":"AA6BA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAymBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDzE"}
|
package/dist/app/generateApp.js
CHANGED
|
@@ -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
|
|
77
|
-
*
|
|
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(
|
|
83
|
-
|
|
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(
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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(
|
|
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
|
|
259
|
-
const
|
|
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
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
542
|
+
This application was scaffolded by \`wsdl-tsc\`. Customize freely.
|
|
394
543
|
|
|
395
|
-
##
|
|
544
|
+
## Quick Start
|
|
396
545
|
|
|
397
|
-
|
|
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
|
|
402
|
-
- \`config
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
GET
|
|
431
|
-
|
|
432
|
-
|
|
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\` | (
|
|
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\` |
|
|
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
|
-
#
|
|
471
|
-
curl
|
|
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
|
-
|
|
501
|
-
- Command: \`wsdl-tsc app\`
|
|
591
|
+
[@techspokes/typescript-wsdl-client](https://github.com/TechSpokes/typescript-wsdl-client)
|
|
502
592
|
`;
|
|
503
|
-
fs.writeFileSync(
|
|
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
|
|
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 .
|
|
515
|
-
* 7. Generates
|
|
516
|
-
* 8.
|
|
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
|
|
541
|
-
generateServerFile(resolvedOpts.appDir, resolvedOpts, clientClassName);
|
|
542
|
-
generateConfigFile(resolvedOpts.appDir, resolvedOpts, defaultWsdlSource);
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
550
|
-
|
|
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(`
|
|
657
|
+
success(`Scaffolded Fastify app in ${resolvedOpts.appDir}`);
|
|
553
658
|
}
|
package/dist/cli.js
CHANGED
|
@@ -457,6 +457,11 @@ if (rawArgs[0] === "app") {
|
|
|
457
457
|
choices: ["copy", "reference"],
|
|
458
458
|
default: "copy",
|
|
459
459
|
desc: "How to handle OpenAPI file: copy into app dir or reference original"
|
|
460
|
+
})
|
|
461
|
+
.option("force", {
|
|
462
|
+
type: "boolean",
|
|
463
|
+
default: false,
|
|
464
|
+
desc: "Overwrite existing scaffold files"
|
|
460
465
|
})
|
|
461
466
|
.strict()
|
|
462
467
|
.help()
|
|
@@ -500,6 +505,7 @@ if (rawArgs[0] === "app") {
|
|
|
500
505
|
prefix: String(appArgv.prefix),
|
|
501
506
|
logger: Boolean(appArgv.logger),
|
|
502
507
|
openapiMode: appArgv["openapi-mode"],
|
|
508
|
+
force: Boolean(appArgv.force),
|
|
503
509
|
});
|
|
504
510
|
process.exit(0);
|
|
505
511
|
}
|
|
@@ -602,11 +608,21 @@ if (rawArgs[0] === "pipeline") {
|
|
|
602
608
|
default: false,
|
|
603
609
|
desc: "Skip generating runtime.ts utilities"
|
|
604
610
|
})
|
|
605
|
-
// App
|
|
611
|
+
// App scaffold flags
|
|
612
|
+
.option("init-app", {
|
|
613
|
+
type: "boolean",
|
|
614
|
+
default: false,
|
|
615
|
+
desc: "Scaffold a runnable Fastify application (one-time setup, requires --client-dir, --gateway-dir, --openapi-file)"
|
|
616
|
+
})
|
|
617
|
+
.option("force-init", {
|
|
618
|
+
type: "boolean",
|
|
619
|
+
default: false,
|
|
620
|
+
desc: "Overwrite existing scaffold files when using --init-app"
|
|
621
|
+
})
|
|
606
622
|
.option("generate-app", {
|
|
607
623
|
type: "boolean",
|
|
608
624
|
default: false,
|
|
609
|
-
|
|
625
|
+
hidden: true, // deprecated, use --init-app
|
|
610
626
|
})
|
|
611
627
|
.option("app-dir", {
|
|
612
628
|
type: "string",
|
|
@@ -617,6 +633,21 @@ if (rawArgs[0] === "pipeline") {
|
|
|
617
633
|
choices: ["copy", "reference"],
|
|
618
634
|
default: "copy",
|
|
619
635
|
desc: "How to handle OpenAPI file in app: copy or reference"
|
|
636
|
+
})
|
|
637
|
+
.option("app-host", {
|
|
638
|
+
type: "string",
|
|
639
|
+
default: "127.0.0.1",
|
|
640
|
+
desc: "Default server host for app scaffold"
|
|
641
|
+
})
|
|
642
|
+
.option("app-port", {
|
|
643
|
+
type: "number",
|
|
644
|
+
default: 3000,
|
|
645
|
+
desc: "Default server port for app scaffold"
|
|
646
|
+
})
|
|
647
|
+
.option("app-prefix", {
|
|
648
|
+
type: "string",
|
|
649
|
+
default: "",
|
|
650
|
+
desc: "Route prefix for app scaffold"
|
|
620
651
|
})
|
|
621
652
|
.strict()
|
|
622
653
|
.help()
|
|
@@ -662,7 +693,14 @@ if (rawArgs[0] === "pipeline") {
|
|
|
662
693
|
fs.rmSync(clientOutDir, { recursive: true, force: true });
|
|
663
694
|
}
|
|
664
695
|
}
|
|
665
|
-
|
|
696
|
+
// Resolve --init-app (new name) or --generate-app (deprecated alias)
|
|
697
|
+
const initApp = pipelineArgv["init-app"] || pipelineArgv["generate-app"];
|
|
698
|
+
const forceInit = pipelineArgv["force-init"];
|
|
699
|
+
let servers = parseServers(pipelineArgv["openapi-servers"]);
|
|
700
|
+
// Default OpenAPI servers to localhost when scaffolding an app and no explicit servers provided
|
|
701
|
+
if (initApp && servers.length === 0) {
|
|
702
|
+
servers = ["http://localhost:3000"];
|
|
703
|
+
}
|
|
666
704
|
// Validate gateway requirements
|
|
667
705
|
validateGatewayRequirements(gatewayOut, openapiOut, pipelineArgv["gateway-service-name"], pipelineArgv["gateway-version-prefix"]);
|
|
668
706
|
// Parse gateway default response status codes if provided
|
|
@@ -680,11 +718,10 @@ if (rawArgs[0] === "pipeline") {
|
|
|
680
718
|
const openApiOptions = openapiOut
|
|
681
719
|
? buildOpenApiOptionsFromArgv(pipelineArgv, format, servers)
|
|
682
720
|
: undefined;
|
|
683
|
-
// Validate app
|
|
684
|
-
|
|
685
|
-
if (generateApp) {
|
|
721
|
+
// Validate app scaffold requirements
|
|
722
|
+
if (initApp) {
|
|
686
723
|
if (!clientOut || !gatewayOut || !openapiOut) {
|
|
687
|
-
handleCLIError("--
|
|
724
|
+
handleCLIError("--init-app requires --client-dir, --gateway-dir, and --openapi-file to be set");
|
|
688
725
|
}
|
|
689
726
|
}
|
|
690
727
|
await runGenerationPipeline({
|
|
@@ -707,11 +744,15 @@ if (rawArgs[0] === "pipeline") {
|
|
|
707
744
|
emitPlugin: pipelineArgv["gateway-skip-plugin"] ? false : undefined,
|
|
708
745
|
emitRuntime: pipelineArgv["gateway-skip-runtime"] ? false : undefined,
|
|
709
746
|
} : undefined,
|
|
710
|
-
app:
|
|
747
|
+
app: initApp ? {
|
|
711
748
|
appDir: pipelineArgv["app-dir"]
|
|
712
749
|
? path.resolve(pipelineArgv["app-dir"])
|
|
713
750
|
: path.join(path.dirname(path.resolve(gatewayOut)), "app"),
|
|
714
751
|
openapiMode: pipelineArgv["app-openapi-mode"],
|
|
752
|
+
force: forceInit,
|
|
753
|
+
host: pipelineArgv["app-host"],
|
|
754
|
+
port: pipelineArgv["app-port"],
|
|
755
|
+
prefix: pipelineArgv["app-prefix"],
|
|
715
756
|
} : undefined,
|
|
716
757
|
});
|
|
717
758
|
process.exit(0);
|
package/dist/pipeline.d.ts
CHANGED
package/dist/pipeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAkB,KAAK,sBAAsB,EAAC,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAkB,KAAK,sBAAsB,EAAC,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAC,KAAK,eAAe,EAAyB,MAAM,aAAa,CAAC;AAGzE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,aAAa,GAAG,iBAAiB,CAAC,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1G,OAAO,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,GAAG,iBAAiB,CAAC,GAAG;QAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,GAAG,CAAC,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAkB,KAAK,sBAAsB,EAAC,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAkB,KAAK,sBAAsB,EAAC,MAAM,8BAA8B,CAAC;AAC1F,OAAO,EAAC,KAAK,eAAe,EAAyB,MAAM,aAAa,CAAC;AAGzE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,aAAa,GAAG,iBAAiB,CAAC,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1G,OAAO,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,GAAG,iBAAiB,CAAC,GAAG;QAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,GAAG,CAAC,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;QACnC,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,CAAC;CAAE,CAAC,CAuHhH"}
|
package/dist/pipeline.js
CHANGED
|
@@ -117,6 +117,10 @@ export async function runGenerationPipeline(opts) {
|
|
|
117
117
|
appDir: path.resolve(opts.app.appDir),
|
|
118
118
|
imports: finalCompiler.imports,
|
|
119
119
|
openapiMode: opts.app.openapiMode || "copy",
|
|
120
|
+
force: opts.app.force,
|
|
121
|
+
host: opts.app.host,
|
|
122
|
+
port: opts.app.port,
|
|
123
|
+
prefix: opts.app.prefix,
|
|
120
124
|
});
|
|
121
125
|
}
|
|
122
126
|
// Return the compiled catalog and OpenAPI doc for potential further processing
|
package/docs/architecture.md
CHANGED
|
@@ -54,7 +54,7 @@ generateGateway.ts orchestrates all gateway file generation. generators.ts conta
|
|
|
54
54
|
|
|
55
55
|
### app/
|
|
56
56
|
|
|
57
|
-
generateApp.ts
|
|
57
|
+
generateApp.ts scaffolds server.ts, config.ts, package.json, tsconfig.json, .env.example, and README.md.
|
|
58
58
|
|
|
59
59
|
### Top-level Modules
|
|
60
60
|
|
package/docs/cli-reference.md
CHANGED
|
@@ -54,7 +54,6 @@ The catalog is auto-placed alongside the first available output directory: `{cli
|
|
|
54
54
|
| `--client-dir` | Generate TypeScript client in this directory |
|
|
55
55
|
| `--openapi-file` | Generate OpenAPI spec at this path |
|
|
56
56
|
| `--gateway-dir` | Generate Fastify gateway in this directory |
|
|
57
|
-
| `--generate-app` | Generate runnable app (requires gateway) |
|
|
58
57
|
|
|
59
58
|
### Client Flags
|
|
60
59
|
|
|
@@ -79,7 +78,10 @@ The catalog is auto-placed alongside the first available output directory: `{cli
|
|
|
79
78
|
| `--openapi-title` | (derived) | API title |
|
|
80
79
|
| `--openapi-version` | `0.0.0` | API version |
|
|
81
80
|
| `--openapi-description` | (empty) | API description |
|
|
82
|
-
| `--openapi-servers` | `/` | Comma-separated server URLs |
|
|
81
|
+
| `--openapi-servers` | `/` | Comma-separated server URLs (see note below) |
|
|
82
|
+
|
|
83
|
+
> When `--init-app` is used and `--openapi-servers` is not provided, servers default to `http://localhost:3000`. The app scaffold also rewrites the servers array in its local `openapi.json` copy to match the configured port and prefix.
|
|
84
|
+
|
|
83
85
|
| `--openapi-base-path` | (empty) | Base path prefix |
|
|
84
86
|
| `--openapi-path-style` | `kebab` | Path transform: kebab, asis, or lower |
|
|
85
87
|
| `--openapi-method` | `post` | Default HTTP method |
|
|
@@ -106,6 +108,18 @@ The catalog is auto-placed alongside the first available output directory: `{cli
|
|
|
106
108
|
| `--gateway-skip-plugin` | `false` | Skip plugin.ts generation |
|
|
107
109
|
| `--gateway-skip-runtime` | `false` | Skip runtime.ts generation |
|
|
108
110
|
|
|
111
|
+
### App Scaffold Flags
|
|
112
|
+
|
|
113
|
+
| Flag | Default | Description |
|
|
114
|
+
|------|---------|-------------|
|
|
115
|
+
| `--init-app` | `false` | Scaffold runnable Fastify app (requires client, gateway, OpenAPI) |
|
|
116
|
+
| `--force-init` | `false` | Overwrite existing scaffold files |
|
|
117
|
+
| `--app-dir` | sibling `app/` | Output directory for app scaffold |
|
|
118
|
+
| `--app-openapi-mode` | `copy` | How to handle OpenAPI file: copy or reference |
|
|
119
|
+
| `--app-host` | `127.0.0.1` | Default server host |
|
|
120
|
+
| `--app-port` | `3000` | Default server port |
|
|
121
|
+
| `--app-prefix` | (empty) | Route prefix |
|
|
122
|
+
|
|
109
123
|
### Pipeline Workflow
|
|
110
124
|
|
|
111
125
|
Steps execute in order:
|
|
@@ -116,7 +130,7 @@ Steps execute in order:
|
|
|
116
130
|
4. Generate client (if --client-dir)
|
|
117
131
|
5. Generate OpenAPI (if --openapi-file)
|
|
118
132
|
6. Generate gateway (if --gateway-dir)
|
|
119
|
-
7.
|
|
133
|
+
7. Scaffold app (if --init-app)
|
|
120
134
|
|
|
121
135
|
All steps share the same parsed WSDL and compiled catalog.
|
|
122
136
|
|
|
@@ -134,17 +148,17 @@ npx wsdl-tsc pipeline \
|
|
|
134
148
|
--gateway-version-prefix v1
|
|
135
149
|
```
|
|
136
150
|
|
|
137
|
-
With app
|
|
151
|
+
With app scaffold:
|
|
138
152
|
|
|
139
153
|
```bash
|
|
140
154
|
npx wsdl-tsc pipeline \
|
|
141
|
-
--wsdl-source
|
|
142
|
-
--client-dir
|
|
143
|
-
--openapi-file
|
|
144
|
-
--gateway-dir
|
|
155
|
+
--wsdl-source https://example.com/weather?wsdl \
|
|
156
|
+
--client-dir ./generated/client \
|
|
157
|
+
--openapi-file ./generated/openapi.json \
|
|
158
|
+
--gateway-dir ./generated/gateway \
|
|
145
159
|
--gateway-service-name weather \
|
|
146
160
|
--gateway-version-prefix v1 \
|
|
147
|
-
--
|
|
161
|
+
--init-app
|
|
148
162
|
```
|
|
149
163
|
|
|
150
164
|
Client and OpenAPI only:
|
|
@@ -371,7 +385,9 @@ npx wsdl-tsc gateway \
|
|
|
371
385
|
|
|
372
386
|
## app
|
|
373
387
|
|
|
374
|
-
|
|
388
|
+
Scaffold a runnable Fastify application integrating client, gateway, and OpenAPI spec. The generated app is TypeScript and includes `package.json` and `tsconfig.json` for immediate use.
|
|
389
|
+
|
|
390
|
+
Scaffold files that already exist are skipped by default. Use `--force` to overwrite.
|
|
375
391
|
|
|
376
392
|
### Usage
|
|
377
393
|
|
|
@@ -404,13 +420,16 @@ npx wsdl-tsc app \
|
|
|
404
420
|
| `--prefix` | (empty) | Route prefix |
|
|
405
421
|
| `--logger` | true | Enable Fastify logger |
|
|
406
422
|
| `--openapi-mode` | copy | copy or reference |
|
|
423
|
+
| `--force` | false | Overwrite existing scaffold files |
|
|
407
424
|
|
|
408
425
|
### Generated Structure
|
|
409
426
|
|
|
410
427
|
```text
|
|
411
428
|
app/
|
|
412
|
-
├── server.
|
|
413
|
-
├── config.
|
|
429
|
+
├── server.ts # Main entry point (TypeScript)
|
|
430
|
+
├── config.ts # Configuration with env support
|
|
431
|
+
├── package.json # Dependencies (fastify, soap, tsx)
|
|
432
|
+
├── tsconfig.json # NodeNext/ES2022 configuration
|
|
414
433
|
├── .env.example # Environment template
|
|
415
434
|
├── README.md # Usage instructions
|
|
416
435
|
└── openapi.json # OpenAPI spec (when --openapi-mode=copy)
|
|
@@ -420,11 +439,12 @@ app/
|
|
|
420
439
|
|
|
421
440
|
| Variable | Default | Description |
|
|
422
441
|
|----------|---------|-------------|
|
|
423
|
-
| `WSDL_SOURCE` |
|
|
442
|
+
| `WSDL_SOURCE` | URL fallback or required | WSDL URL or path |
|
|
424
443
|
| `HOST` | 127.0.0.1 | Server bind address |
|
|
425
444
|
| `PORT` | 3000 | Server listen port |
|
|
426
445
|
| `PREFIX` | (empty) | Route prefix |
|
|
427
446
|
| `LOGGER` | true | Fastify logger |
|
|
447
|
+
| `OPENAPI_SERVER_URL` | (empty) | Override OpenAPI spec server URL at runtime |
|
|
428
448
|
|
|
429
449
|
### Endpoints
|
|
430
450
|
|
|
@@ -432,7 +452,17 @@ app/
|
|
|
432
452
|
- `GET /openapi.json` returns the OpenAPI specification
|
|
433
453
|
- All SOAP operations are exposed as REST endpoints
|
|
434
454
|
|
|
435
|
-
|
|
455
|
+
### Quick Start
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
cd app/
|
|
459
|
+
npm install
|
|
460
|
+
cp .env.example .env
|
|
461
|
+
# Edit .env — set WSDL_SOURCE to your WSDL URL
|
|
462
|
+
npm start
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
The app can also be scaffolded via pipeline with `--init-app`.
|
|
436
466
|
|
|
437
467
|
## compile
|
|
438
468
|
|
package/docs/gateway-guide.md
CHANGED
|
@@ -63,17 +63,18 @@ Route handlers call the SOAP client automatically:
|
|
|
63
63
|
|
|
64
64
|
```typescript
|
|
65
65
|
import type { FastifyInstance } from "fastify";
|
|
66
|
+
import type { GetCityForecastByZIP } from "../../client/types.js";
|
|
66
67
|
import schema from "../schemas/operations/getcityforecastbyzip.json" with { type: "json" };
|
|
67
68
|
import { buildSuccessEnvelope } from "../runtime.js";
|
|
68
69
|
|
|
69
70
|
export async function registerRoute_v1_weather_getcityforecastbyzip(fastify: FastifyInstance) {
|
|
70
|
-
fastify.route({
|
|
71
|
+
fastify.route<{ Body: GetCityForecastByZIP }>({
|
|
71
72
|
method: "POST",
|
|
72
73
|
url: "/get-city-forecast-by-zip",
|
|
73
74
|
schema,
|
|
74
75
|
handler: async (request) => {
|
|
75
76
|
const client = fastify.weatherClient;
|
|
76
|
-
const result = await client.GetCityForecastByZIP(request.body);
|
|
77
|
+
const result = await client.GetCityForecastByZIP(request.body as GetCityForecastByZIP);
|
|
77
78
|
return buildSuccessEnvelope(result.response);
|
|
78
79
|
},
|
|
79
80
|
});
|
|
@@ -117,6 +118,39 @@ All generated JSON Schemas use deterministic URN identifiers:
|
|
|
117
118
|
|
|
118
119
|
Example: `urn:services:weather:v1:schemas:models:getcityweatherbyzipresponse`
|
|
119
120
|
|
|
121
|
+
## Multi-Service Setup
|
|
122
|
+
|
|
123
|
+
When integrating multiple SOAP services, each service gets its own client, gateway, and OpenAPI spec. Register each in its own Fastify encapsulation scope to prevent decorator collisions:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import Fastify from "fastify";
|
|
127
|
+
import weatherPlugin from "./generated/weather/gateway/plugin.js";
|
|
128
|
+
import inventoryPlugin from "./generated/inventory/gateway/plugin.js";
|
|
129
|
+
import { Weather } from "./generated/weather/client/client.js";
|
|
130
|
+
import { Inventory } from "./generated/inventory/client/client.js";
|
|
131
|
+
|
|
132
|
+
const app = Fastify({ logger: true });
|
|
133
|
+
|
|
134
|
+
// Each plugin gets its own scope to isolate decorators
|
|
135
|
+
await app.register(async (scope) => {
|
|
136
|
+
await scope.register(weatherPlugin, {
|
|
137
|
+
client: new Weather({ source: "https://example.com/weather?wsdl" }),
|
|
138
|
+
prefix: "/api/weather",
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await app.register(async (scope) => {
|
|
143
|
+
await scope.register(inventoryPlugin, {
|
|
144
|
+
client: new Inventory({ source: "https://example.com/inventory?wsdl" }),
|
|
145
|
+
prefix: "/api/inventory",
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await app.listen({ port: 3000 });
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
See [`examples/fastify-gateway/`](../examples/fastify-gateway/) for a complete example.
|
|
153
|
+
|
|
120
154
|
## Contract Assumptions
|
|
121
155
|
|
|
122
156
|
- All request/response bodies must use $ref to components.schemas
|
package/docs/generated-code.md
CHANGED
|
@@ -72,3 +72,35 @@ result.GetCityWeatherByZIPResult.Temperature;
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
Autocomplete and type checking work across all generated interfaces.
|
|
75
|
+
|
|
76
|
+
## Gateway Route Handlers
|
|
77
|
+
|
|
78
|
+
When generating a Fastify gateway (`--gateway-dir`), each SOAP operation gets a fully typed route handler. The handler imports the request type from the client, uses Fastify's `Body: T` generic for type inference, and wraps the SOAP response in a standard envelope.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import type { FastifyInstance } from "fastify";
|
|
82
|
+
import type { GetCityForecastByZIP } from "../../client/types.js";
|
|
83
|
+
import schema from "../schemas/operations/getcityforecastbyzip.json" with { type: "json" };
|
|
84
|
+
import { buildSuccessEnvelope } from "../runtime.js";
|
|
85
|
+
|
|
86
|
+
export async function registerRoute_v1_weather_getcityforecastbyzip(fastify: FastifyInstance) {
|
|
87
|
+
fastify.route<{ Body: GetCityForecastByZIP }>({
|
|
88
|
+
method: "POST",
|
|
89
|
+
url: "/get-city-forecast-by-zip",
|
|
90
|
+
schema,
|
|
91
|
+
handler: async (request) => {
|
|
92
|
+
const client = fastify.weatherClient;
|
|
93
|
+
const result = await client.GetCityForecastByZIP(request.body as GetCityForecastByZIP);
|
|
94
|
+
return buildSuccessEnvelope(result.response);
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Key features of the generated handlers:
|
|
101
|
+
|
|
102
|
+
- **`Body: T` generic** — Fastify infers `request.body` type from the route generic, enabling IDE autocomplete and compile-time checks
|
|
103
|
+
- **JSON Schema validation** — the `schema` import provides Fastify with request/response validation at runtime, before the handler runs
|
|
104
|
+
- **Envelope wrapping** — `buildSuccessEnvelope()` wraps the raw SOAP response in the standard `{ status, message, data, error }` envelope
|
|
105
|
+
|
|
106
|
+
See [Gateway Guide](gateway-guide.md) for the full architecture and [CLI Reference](cli-reference.md) for generation flags.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techspokes/typescript-wsdl-client",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "Generate type-safe TypeScript SOAP clients, OpenAPI 3.1 specs, and production-ready Fastify REST gateways from WSDL/XSD definitions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wsdl",
|
|
@@ -65,25 +65,26 @@
|
|
|
65
65
|
"smoke:client": "npm run smoke:reset && tsx src/cli.ts client --wsdl-source examples/minimal/weather.wsdl --client-dir tmp/client && tsc -p tsconfig.smoke.json",
|
|
66
66
|
"smoke:openapi": "npm run smoke:reset && tsx src/cli.ts openapi --wsdl-source examples/minimal/weather.wsdl --openapi-file tmp/openapi.json --openapi-format json && tsc -p tsconfig.smoke.json",
|
|
67
67
|
"smoke:gateway": "npm run smoke:reset && tsx src/cli.ts client --wsdl-source examples/minimal/weather.wsdl --client-dir tmp/client && tsx src/cli.ts openapi --catalog-file tmp/client/catalog.json --openapi-file tmp/openapi.json --openapi-format json && tsx src/cli.ts gateway --openapi-file tmp/openapi.json --client-dir tmp/client --gateway-dir tmp/gateway --gateway-service-name weather --gateway-version-prefix v1 && tsc -p tsconfig.smoke.json",
|
|
68
|
-
"smoke:pipeline": "npm run smoke:reset && tsx src/cli.ts pipeline --wsdl-source examples/minimal/weather.wsdl --client-dir tmp/client --openapi-file tmp/openapi.json --gateway-dir tmp/gateway --gateway-service-name weather --gateway-version-prefix v1 --openapi-format json --
|
|
69
|
-
"smoke:app": "npm run smoke:reset && tsx src/cli.ts pipeline --wsdl-source examples/minimal/weather.wsdl --client-dir tmp/client --openapi-file tmp/openapi.json --gateway-dir tmp/gateway --gateway-service-name weather --gateway-version-prefix v1 --openapi-format json --openapi-servers https://example.com/api && tsx src/cli.ts app --client-dir tmp/client --gateway-dir tmp/gateway --openapi-file tmp/openapi.json --app-dir tmp/app && tsc -p tsconfig.smoke.json",
|
|
70
|
-
"ci": "npm run clean && npm run build && npm run typecheck && npm run smoke:pipeline"
|
|
68
|
+
"smoke:pipeline": "npm run smoke:reset && tsx src/cli.ts pipeline --wsdl-source examples/minimal/weather.wsdl --client-dir tmp/client --openapi-file tmp/openapi.json --gateway-dir tmp/gateway --gateway-service-name weather --gateway-version-prefix v1 --openapi-format json --init-app && tsc -p tsconfig.smoke.json",
|
|
69
|
+
"smoke:app": "npm run smoke:reset && tsx src/cli.ts pipeline --wsdl-source examples/minimal/weather.wsdl --client-dir tmp/client --openapi-file tmp/openapi.json --gateway-dir tmp/gateway --gateway-service-name weather --gateway-version-prefix v1 --openapi-format json --openapi-servers https://example.com/api && tsx src/cli.ts app --client-dir tmp/client --gateway-dir tmp/gateway --openapi-file tmp/openapi.json --app-dir tmp/app --port 8080 && tsc -p tsconfig.smoke.json",
|
|
70
|
+
"ci": "npm run clean && npm run build && npm run typecheck && npm run smoke:pipeline",
|
|
71
|
+
"examples:regenerate": "tsx src/cli.ts pipeline --wsdl-source examples/minimal/weather.wsdl --client-dir examples/generated-output/client --openapi-file examples/generated-output/openapi.json --gateway-dir examples/generated-output/gateway --gateway-service-name weather --gateway-version-prefix v1 --openapi-format json"
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|
|
73
74
|
"@types/js-yaml": "^4.0.9",
|
|
74
|
-
"@types/node": "^25.0
|
|
75
|
-
"@types/yargs": "^17.0.
|
|
76
|
-
"fastify": "^5.
|
|
75
|
+
"@types/node": "^25.2.0",
|
|
76
|
+
"@types/yargs": "^17.0.35",
|
|
77
|
+
"fastify": "^5.7.0",
|
|
77
78
|
"fastify-plugin": "^5.1.0",
|
|
78
|
-
"rimraf": "^6.
|
|
79
|
-
"tsx": "^4.
|
|
80
|
-
"typescript": "^5.
|
|
79
|
+
"rimraf": "^6.1.0",
|
|
80
|
+
"tsx": "^4.21.0",
|
|
81
|
+
"typescript": "^5.9.0"
|
|
81
82
|
},
|
|
82
83
|
"dependencies": {
|
|
83
84
|
"@apidevtools/swagger-parser": "^12.1.0",
|
|
84
|
-
"fast-xml-parser": "^5.
|
|
85
|
+
"fast-xml-parser": "^5.3.0",
|
|
85
86
|
"js-yaml": "^4.1.1",
|
|
86
|
-
"soap": "^1.
|
|
87
|
+
"soap": "^1.6.0",
|
|
87
88
|
"yargs": "^18.0.0"
|
|
88
89
|
},
|
|
89
90
|
"funding": {
|