@techspokes/typescript-wsdl-client 0.6.3 → 0.7.1
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 +296 -166
- package/dist/cli.js +239 -1
- package/dist/compiler/schemaCompiler.d.ts +54 -0
- package/dist/compiler/schemaCompiler.d.ts.map +1 -1
- package/dist/compiler/schemaCompiler.js +74 -7
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/emit/catalogEmitter.d.ts +18 -0
- package/dist/emit/catalogEmitter.d.ts.map +1 -1
- package/dist/emit/catalogEmitter.js +31 -0
- package/dist/emit/clientEmitter.d.ts +17 -0
- package/dist/emit/clientEmitter.d.ts.map +1 -1
- package/dist/emit/clientEmitter.js +33 -3
- package/dist/emit/typesEmitter.d.ts +16 -5
- package/dist/emit/typesEmitter.d.ts.map +1 -1
- package/dist/emit/typesEmitter.js +30 -5
- package/dist/emit/utilsEmitter.d.ts +18 -0
- package/dist/emit/utilsEmitter.d.ts.map +1 -1
- package/dist/emit/utilsEmitter.js +30 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -1
- package/dist/loader/fetch.d.ts +31 -0
- package/dist/loader/fetch.d.ts.map +1 -1
- package/dist/loader/fetch.js +31 -0
- package/dist/loader/wsdlLoader.d.ts +32 -0
- package/dist/loader/wsdlLoader.d.ts.map +1 -1
- package/dist/loader/wsdlLoader.js +80 -9
- package/dist/openapi/buildPaths.d.ts +74 -0
- package/dist/openapi/buildPaths.d.ts.map +1 -0
- package/dist/openapi/buildPaths.js +66 -0
- package/dist/openapi/buildSchemas.d.ts +44 -0
- package/dist/openapi/buildSchemas.d.ts.map +1 -0
- package/dist/openapi/buildSchemas.js +196 -0
- package/dist/openapi/casing.d.ts +38 -0
- package/dist/openapi/casing.d.ts.map +1 -0
- package/dist/openapi/casing.js +49 -0
- package/dist/openapi/generateOpenAPI.d.ts +61 -0
- package/dist/openapi/generateOpenAPI.d.ts.map +1 -0
- package/dist/openapi/generateOpenAPI.js +315 -0
- package/dist/openapi/security.d.ts +82 -0
- package/dist/openapi/security.d.ts.map +1 -0
- package/dist/openapi/security.js +145 -0
- package/dist/pipeline.d.ts +37 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +72 -0
- package/dist/util/tools.d.ts +100 -7
- package/dist/util/tools.d.ts.map +1 -1
- package/dist/util/tools.js +85 -7
- package/dist/xsd/primitives.d.ts +33 -0
- package/dist/xsd/primitives.d.ts.map +1 -1
- package/dist/xsd/primitives.js +59 -7
- package/package.json +7 -2
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI 3.1 Generator from WSDL
|
|
3
|
+
*
|
|
4
|
+
* This module generates OpenAPI 3.1 specifications from WSDL documents or compiled catalogs.
|
|
5
|
+
* It bridges the gap between SOAP web services and REST APIs by creating an OpenAPI
|
|
6
|
+
* representation that mirrors the TypeScript model generated by the WSDL client generator.
|
|
7
|
+
*
|
|
8
|
+
* Core capabilities:
|
|
9
|
+
* - Multiple input sources (WSDL URL/path, pre-compiled catalog, or in-memory catalog)
|
|
10
|
+
* - Deterministic schema + path emission mirroring generated TypeScript
|
|
11
|
+
* - Security scheme + header parameter integration (via security.json)
|
|
12
|
+
* - Operation tagging heuristics + explicit tag + op override files
|
|
13
|
+
* - Multi-format output (json|yaml|both) with optional validation
|
|
14
|
+
* - Always-on Standard Response Envelope (since 0.7.1): automatically wraps all
|
|
15
|
+
* responses in a base envelope plus per-payload extensions, providing stable
|
|
16
|
+
* top-level fields (status, message, data, error). Naming is customizable via
|
|
17
|
+
* --envelope-namespace / --error-namespace.
|
|
18
|
+
*
|
|
19
|
+
* Naming Rules:
|
|
20
|
+
* - If a namespace flag is provided its value is used verbatim as the component name.
|
|
21
|
+
* - Otherwise `${serviceName}ResponseEnvelope` / `${serviceName}ErrorObject` are used.
|
|
22
|
+
* - Each operation output produces an extension schema named `<PayloadType|OpName><EnvelopeNamespace>`.
|
|
23
|
+
* - All schemas, paths, methods, securitySchemes, and parameters are alphabetically sorted for diff friendliness.
|
|
24
|
+
*/
|
|
25
|
+
import * as fs from "fs";
|
|
26
|
+
import * as path from "path";
|
|
27
|
+
import * as yaml from "js-yaml";
|
|
28
|
+
import { loadWsdl } from "../loader/wsdlLoader.js";
|
|
29
|
+
import { compileCatalog } from "../compiler/schemaCompiler.js";
|
|
30
|
+
import { buildSchemas } from "./buildSchemas.js";
|
|
31
|
+
import { buildPaths } from "./buildPaths.js";
|
|
32
|
+
import { buildSecurity, loadSecurityConfig } from "./security.js";
|
|
33
|
+
export async function generateOpenAPI(opts) {
|
|
34
|
+
// Normalize format (back-compat: asYaml overrides if provided and format not set)
|
|
35
|
+
let format = opts.format || (opts.asYaml ? "yaml" : "json");
|
|
36
|
+
if (format === "yaml" && opts.asYaml && opts.outFile && /\.json$/i.test(opts.outFile)) {
|
|
37
|
+
// user asked for yaml but provided .json path → we'll still switch extension
|
|
38
|
+
}
|
|
39
|
+
if (!opts.compiledCatalog && !opts.wsdl && !opts.catalogFile) {
|
|
40
|
+
throw new Error("Provide one of: compiledCatalog, wsdl, or catalogFile");
|
|
41
|
+
}
|
|
42
|
+
if ((opts.wsdl && opts.catalogFile) || (opts.compiledCatalog && (opts.wsdl || opts.catalogFile))) {
|
|
43
|
+
// Not strictly an error, but disallow ambiguous multi-source inputs to keep deterministic
|
|
44
|
+
// Users should supply only ONE source of truth.
|
|
45
|
+
throw new Error("Provide only one source: compiledCatalog OR wsdl OR catalogFile");
|
|
46
|
+
}
|
|
47
|
+
let compiled;
|
|
48
|
+
if (opts.compiledCatalog) {
|
|
49
|
+
compiled = opts.compiledCatalog;
|
|
50
|
+
}
|
|
51
|
+
else if (opts.catalogFile) {
|
|
52
|
+
const raw = fs.readFileSync(opts.catalogFile, "utf8");
|
|
53
|
+
compiled = JSON.parse(raw);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const wsdlCatalog = await loadWsdl(String(opts.wsdl));
|
|
57
|
+
compiled = compileCatalog(wsdlCatalog, {
|
|
58
|
+
// minimal compiler options (no generation side effects needed here)
|
|
59
|
+
wsdl: String(opts.wsdl),
|
|
60
|
+
out: "",
|
|
61
|
+
imports: "js",
|
|
62
|
+
catalog: false,
|
|
63
|
+
primitive: { int64As: "string", bigIntegerAs: "string", decimalAs: "string", dateAs: "string" },
|
|
64
|
+
choice: "all-optional",
|
|
65
|
+
failOnUnresolved: false,
|
|
66
|
+
attributesKey: "$attributes",
|
|
67
|
+
nillableAsOptional: false,
|
|
68
|
+
clientName: undefined,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const title = opts.title || (compiled.serviceName ? `${compiled.serviceName} SOAP API` : "Generated SOAP API");
|
|
72
|
+
const infoVersion = opts.version || "0.0.0";
|
|
73
|
+
// Load external config files (optional)
|
|
74
|
+
const tagsMap = opts.tagsFile ? safeJson(opts.tagsFile) : undefined;
|
|
75
|
+
const opsOverrides = opts.opsFile ? safeJson(opts.opsFile) : undefined;
|
|
76
|
+
const securityCfg = loadSecurityConfig(opts.securityConfigFile);
|
|
77
|
+
const securityBuilt = buildSecurity(securityCfg);
|
|
78
|
+
// Build components.schemas
|
|
79
|
+
const schemas = buildSchemas(compiled, {
|
|
80
|
+
closedSchemas: opts.closedSchemas,
|
|
81
|
+
pruneUnusedSchemas: opts.pruneUnusedSchemas,
|
|
82
|
+
});
|
|
83
|
+
// Build paths
|
|
84
|
+
const tagStyle = opts.tagStyle || "default";
|
|
85
|
+
const defaultTag = (() => {
|
|
86
|
+
if (tagStyle === "service")
|
|
87
|
+
return compiled.serviceName || "SOAP";
|
|
88
|
+
if (tagStyle === "first")
|
|
89
|
+
return "General"; // fallback; per-op derivation below
|
|
90
|
+
return compiled.serviceName || "SOAP";
|
|
91
|
+
})();
|
|
92
|
+
const paths = buildPaths(compiled, {
|
|
93
|
+
basePath: opts.basePath || "/",
|
|
94
|
+
pathStyle: opts.pathStyle || "kebab",
|
|
95
|
+
defaultMethod: opts.defaultMethod || "post",
|
|
96
|
+
tagsMap,
|
|
97
|
+
overrides: opsOverrides,
|
|
98
|
+
defaultTag,
|
|
99
|
+
opSecurity: securityBuilt.opSecurity,
|
|
100
|
+
opHeaderParameters: securityBuilt.opHeaderParameters,
|
|
101
|
+
});
|
|
102
|
+
// --- Standard Envelope (always enabled since 0.7.1) ---
|
|
103
|
+
const serviceName = compiled.serviceName || "Service";
|
|
104
|
+
const envelopeNamespace = opts.envelopeNamespace || "ResponseEnvelope";
|
|
105
|
+
const errorNamespace = opts.errorNamespace || "ErrorObject";
|
|
106
|
+
function leadingToken(ns) {
|
|
107
|
+
return ns.match(/^[A-Z][a-z0-9]*/)?.[0] || ns;
|
|
108
|
+
}
|
|
109
|
+
function joinWithNamespace(base, ns) {
|
|
110
|
+
const token = leadingToken(ns);
|
|
111
|
+
return base.endsWith(token) ? `${base}_${ns}` : `${base}${ns}`;
|
|
112
|
+
}
|
|
113
|
+
const baseEnvelopeName = opts.envelopeNamespace
|
|
114
|
+
? envelopeNamespace
|
|
115
|
+
: joinWithNamespace(serviceName, envelopeNamespace);
|
|
116
|
+
const errorSchemaName = opts.errorNamespace
|
|
117
|
+
? errorNamespace
|
|
118
|
+
: joinWithNamespace(serviceName, errorNamespace);
|
|
119
|
+
const errorSchema = {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
code: { type: "string" },
|
|
123
|
+
message: { type: "string" },
|
|
124
|
+
details: { anyOf: [{ type: "object", additionalProperties: true }, { type: "null" }] },
|
|
125
|
+
},
|
|
126
|
+
required: ["code", "message"],
|
|
127
|
+
additionalProperties: false,
|
|
128
|
+
description: "Standard error object for REST responses (not reused for underlying SOAP faults).",
|
|
129
|
+
};
|
|
130
|
+
const baseEnvelopeSchema = {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
status: { type: "string", description: "Machine-readable high-level status (e.g. SUCCESS, FAILURE, PENDING)." },
|
|
134
|
+
message: {
|
|
135
|
+
anyOf: [{ type: "string" }, { type: "null" }],
|
|
136
|
+
description: "Diagnostic/logging message (not for end-user display)."
|
|
137
|
+
},
|
|
138
|
+
data: { anyOf: [{}, { type: "null" }], description: "Primary payload; per-operation extension refines the shape." },
|
|
139
|
+
error: {
|
|
140
|
+
anyOf: [{ $ref: `#/components/schemas/${errorSchemaName}` }, { type: "null" }],
|
|
141
|
+
description: "Error details when status indicates failure; null otherwise."
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
required: ["status", "data", "error", "message"],
|
|
145
|
+
additionalProperties: true,
|
|
146
|
+
description: "Standard API response envelope base schema.",
|
|
147
|
+
};
|
|
148
|
+
const extensionSchemas = {};
|
|
149
|
+
for (const op of compiled.operations) {
|
|
150
|
+
const payloadType = op.outputElement?.local;
|
|
151
|
+
const payloadRef = payloadType && schemas[payloadType] ? { $ref: `#/components/schemas/${payloadType}` } : { type: "object" };
|
|
152
|
+
const baseForExt = payloadType || op.name;
|
|
153
|
+
const extName = joinWithNamespace(baseForExt, envelopeNamespace);
|
|
154
|
+
if (extensionSchemas[extName])
|
|
155
|
+
continue; // de-dupe
|
|
156
|
+
extensionSchemas[extName] = {
|
|
157
|
+
allOf: [
|
|
158
|
+
{ $ref: `#/components/schemas/${baseEnvelopeName}` },
|
|
159
|
+
{ type: "object", properties: { data: { anyOf: [payloadRef, { type: "null" }] } }, additionalProperties: true }
|
|
160
|
+
],
|
|
161
|
+
description: `Envelope for ${payloadType || op.name} operation output wrapping its payload in 'data'.`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Merge all schemas: add base + extensions + error then original schemas, then final alphabetical sort of all keys
|
|
165
|
+
const mergedSchemas = {
|
|
166
|
+
[baseEnvelopeName]: baseEnvelopeSchema,
|
|
167
|
+
...extensionSchemas,
|
|
168
|
+
[errorSchemaName]: errorSchema,
|
|
169
|
+
...schemas,
|
|
170
|
+
};
|
|
171
|
+
const sortedSchemaKeys = Object.keys(mergedSchemas).sort((a, b) => a.localeCompare(b));
|
|
172
|
+
const finalSchemas = {};
|
|
173
|
+
for (const k of sortedSchemaKeys)
|
|
174
|
+
finalSchemas[k] = mergedSchemas[k];
|
|
175
|
+
// Update paths response schemas to reference extension envelopes
|
|
176
|
+
for (const pathItem of Object.values(paths)) {
|
|
177
|
+
for (const methodObj of Object.values(pathItem)) {
|
|
178
|
+
const opId = methodObj.operationId;
|
|
179
|
+
if (!opId)
|
|
180
|
+
continue;
|
|
181
|
+
const op = compiled.operations.find(o => o.name === opId);
|
|
182
|
+
if (!op)
|
|
183
|
+
continue;
|
|
184
|
+
const payloadType = op.outputElement?.local;
|
|
185
|
+
const baseForExt = payloadType || op.name;
|
|
186
|
+
const extName = joinWithNamespace(baseForExt, envelopeNamespace);
|
|
187
|
+
if (methodObj.responses?.["200"]) {
|
|
188
|
+
methodObj.responses["200"].description = "Successful operation (standard envelope)";
|
|
189
|
+
methodObj.responses["200"].content = { "application/json": { schema: { $ref: `#/components/schemas/${extName}` } } };
|
|
190
|
+
}
|
|
191
|
+
if (methodObj.responses?.default) {
|
|
192
|
+
methodObj.responses.default.description = "Error response (standard envelope with populated error object)";
|
|
193
|
+
methodObj.responses.default.content = { "application/json": { schema: { $ref: `#/components/schemas/${baseEnvelopeName}` } } };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// --- End Standard Envelope ---
|
|
198
|
+
// Sort paths and methods alphabetically for diff-friendly output
|
|
199
|
+
const sortedPathKeys = Object.keys(paths).sort((a, b) => a.localeCompare(b));
|
|
200
|
+
const sortedPaths = {};
|
|
201
|
+
for (const p of sortedPathKeys) {
|
|
202
|
+
const ops = paths[p];
|
|
203
|
+
const methodKeys = Object.keys(ops).sort((a, b) => a.localeCompare(b));
|
|
204
|
+
const sortedOps = {};
|
|
205
|
+
for (const m of methodKeys)
|
|
206
|
+
sortedOps[m] = ops[m];
|
|
207
|
+
sortedPaths[p] = sortedOps;
|
|
208
|
+
}
|
|
209
|
+
// Sort securitySchemes & parameters if present
|
|
210
|
+
let sortedSecuritySchemes;
|
|
211
|
+
if (securityBuilt.securitySchemes) {
|
|
212
|
+
sortedSecuritySchemes = {};
|
|
213
|
+
for (const k of Object.keys(securityBuilt.securitySchemes).sort((a, b) => a.localeCompare(b))) {
|
|
214
|
+
sortedSecuritySchemes[k] = securityBuilt.securitySchemes[k];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
let sortedParameters;
|
|
218
|
+
if (Object.keys(securityBuilt.headerParameters).length) {
|
|
219
|
+
sortedParameters = {};
|
|
220
|
+
for (const k of Object.keys(securityBuilt.headerParameters).sort((a, b) => a.localeCompare(b))) {
|
|
221
|
+
sortedParameters[k] = securityBuilt.headerParameters[k];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Ensure operation tags arrays are deterministic (alphabetical unique) for diff-friendly output
|
|
225
|
+
for (const p of Object.keys(sortedPaths)) {
|
|
226
|
+
const pathItem = sortedPaths[p];
|
|
227
|
+
for (const m of Object.keys(pathItem)) {
|
|
228
|
+
const op = pathItem[m];
|
|
229
|
+
if (Array.isArray(op.tags)) {
|
|
230
|
+
const tags = Array.from(new Set(op.tags)).map(t => String(t));
|
|
231
|
+
tags.sort((a, b) => a.localeCompare(b));
|
|
232
|
+
op.tags = tags;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Build components object then sort its top-level keys for stable ordering
|
|
237
|
+
const componentsRaw = {
|
|
238
|
+
schemas: finalSchemas,
|
|
239
|
+
...(sortedSecuritySchemes ? { securitySchemes: sortedSecuritySchemes } : {}),
|
|
240
|
+
...(sortedParameters ? { parameters: sortedParameters } : {}),
|
|
241
|
+
};
|
|
242
|
+
const componentKeyOrder = Object.keys(componentsRaw).sort((a, b) => a.localeCompare(b));
|
|
243
|
+
const components = {};
|
|
244
|
+
for (const k of componentKeyOrder)
|
|
245
|
+
components[k] = componentsRaw[k];
|
|
246
|
+
const doc = {
|
|
247
|
+
openapi: "3.1.0",
|
|
248
|
+
// NOTE: jsonSchemaDialect intentionally omitted unless a future flag requires non-default dialect.
|
|
249
|
+
info: { title, version: infoVersion },
|
|
250
|
+
paths: sortedPaths,
|
|
251
|
+
components,
|
|
252
|
+
};
|
|
253
|
+
if (opts.description)
|
|
254
|
+
doc.info.description = opts.description;
|
|
255
|
+
if (opts.servers && opts.servers.length) {
|
|
256
|
+
doc.servers = opts.servers.map(u => ({ url: u }));
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
// Provide a deterministic default relative server for spec completeness & gateway friendliness.
|
|
260
|
+
doc.servers = [{ url: "/" }];
|
|
261
|
+
}
|
|
262
|
+
if (opts.skipValidate !== true) {
|
|
263
|
+
try {
|
|
264
|
+
const parser = await import("@apidevtools/swagger-parser");
|
|
265
|
+
await parser.default.validate(JSON.parse(JSON.stringify(doc)));
|
|
266
|
+
console.log("OpenAPI validation: OK");
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
console.error("OpenAPI validation failed:", e instanceof Error ? e.message : e);
|
|
270
|
+
throw e;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
console.log("OpenAPI validation skipped by flag");
|
|
275
|
+
}
|
|
276
|
+
// Determine base path for writing
|
|
277
|
+
let base = opts.outFile;
|
|
278
|
+
if (base) {
|
|
279
|
+
const extMatch = base.match(/\.(json|ya?ml)$/i);
|
|
280
|
+
if (extMatch) {
|
|
281
|
+
base = base.slice(0, -extMatch[0].length); // strip extension
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
let jsonPath;
|
|
285
|
+
let yamlPath;
|
|
286
|
+
if (opts.outFile) {
|
|
287
|
+
const dir = path.dirname(opts.outFile);
|
|
288
|
+
if (!fs.existsSync(dir))
|
|
289
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
290
|
+
if (format === "json") {
|
|
291
|
+
jsonPath = base ? `${base}.json` : opts.outFile;
|
|
292
|
+
fs.writeFileSync(jsonPath, JSON.stringify(doc, null, 2), "utf8");
|
|
293
|
+
}
|
|
294
|
+
else if (format === "yaml") {
|
|
295
|
+
yamlPath = base ? `${base}.yaml` : opts.outFile.replace(/\.(json)$/i, ".yaml");
|
|
296
|
+
fs.writeFileSync(yamlPath, yaml.dump(doc), "utf8");
|
|
297
|
+
}
|
|
298
|
+
else { // both
|
|
299
|
+
jsonPath = `${base}.json`;
|
|
300
|
+
yamlPath = `${base}.yaml`;
|
|
301
|
+
fs.writeFileSync(jsonPath, JSON.stringify(doc, null, 2), "utf8");
|
|
302
|
+
fs.writeFileSync(yamlPath, yaml.dump(doc), "utf8");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { doc, jsonPath, yamlPath };
|
|
306
|
+
}
|
|
307
|
+
function safeJson(file) {
|
|
308
|
+
try {
|
|
309
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
310
|
+
}
|
|
311
|
+
catch (e) {
|
|
312
|
+
console.warn(`⚠️ Failed to parse JSON file '${file}': ${e instanceof Error ? e.message : String(e)}`);
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication scheme types supported in the OpenAPI specification
|
|
3
|
+
*
|
|
4
|
+
* @property {"none"} string No authentication required
|
|
5
|
+
* @property {"basic"} string HTTP Basic authentication
|
|
6
|
+
* @property {"bearer"} string HTTP Bearer token authentication
|
|
7
|
+
* @property {"apiKey"} string API key authentication
|
|
8
|
+
* @property {"oauth2"} string OAuth 2.0 authentication
|
|
9
|
+
*/
|
|
10
|
+
export type AuthScheme = "none" | "basic" | "bearer" | "apiKey" | "oauth2";
|
|
11
|
+
/**
|
|
12
|
+
* Security configuration schema for the OpenAPI generation
|
|
13
|
+
*
|
|
14
|
+
* @interface SecurityConfig
|
|
15
|
+
* @property {Object} [global] - Global security settings applied to all operations by default
|
|
16
|
+
* @property {AuthScheme} [global.scheme] - Default authentication scheme
|
|
17
|
+
* @property {Object} [global.apiKey] - API key configuration (when scheme is "apiKey")
|
|
18
|
+
* @property {Object} [global.bearer] - Bearer token configuration (when scheme is "bearer")
|
|
19
|
+
* @property {Object} [global.basic] - Basic auth configuration (when scheme is "basic")
|
|
20
|
+
* @property {Object} [global.oauth2] - OAuth2 configuration (when scheme is "oauth2")
|
|
21
|
+
* @property {Array<Object>} [global.headers] - Global security headers
|
|
22
|
+
* @property {Record<string, Object>} [overrides] - Operation-specific security overrides
|
|
23
|
+
*/
|
|
24
|
+
export type SecurityConfig = {
|
|
25
|
+
global?: {
|
|
26
|
+
scheme?: AuthScheme;
|
|
27
|
+
apiKey?: {
|
|
28
|
+
in: "header" | "query" | "cookie";
|
|
29
|
+
name: string;
|
|
30
|
+
};
|
|
31
|
+
bearer?: {
|
|
32
|
+
bearerFormat?: string;
|
|
33
|
+
};
|
|
34
|
+
basic?: Record<string, never>;
|
|
35
|
+
oauth2?: {
|
|
36
|
+
flows: Record<string, any>;
|
|
37
|
+
};
|
|
38
|
+
headers?: Array<{
|
|
39
|
+
name: string;
|
|
40
|
+
required?: boolean;
|
|
41
|
+
schema?: any;
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
overrides?: Record<string, {
|
|
45
|
+
scheme?: AuthScheme;
|
|
46
|
+
headers?: Array<{
|
|
47
|
+
name: string;
|
|
48
|
+
required?: boolean;
|
|
49
|
+
schema?: any;
|
|
50
|
+
}>;
|
|
51
|
+
}>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Built security components for OpenAPI generation
|
|
55
|
+
*
|
|
56
|
+
* @interface BuiltSecurity
|
|
57
|
+
* @property {Record<string, any>} [securitySchemes] - Security schemes defined for the API
|
|
58
|
+
* @property {Record<string, any>} headerParameters - Header parameters for security
|
|
59
|
+
* @property {Record<string, any[]>} opSecurity - Operation security requirements
|
|
60
|
+
* @property {Record<string, string[]>} opHeaderParameters - Operation-specific header parameters
|
|
61
|
+
*/
|
|
62
|
+
export type BuiltSecurity = {
|
|
63
|
+
securitySchemes?: Record<string, any>;
|
|
64
|
+
headerParameters: Record<string, any>;
|
|
65
|
+
opSecurity: Record<string, any[] | undefined>;
|
|
66
|
+
opHeaderParameters: Record<string, string[]>;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Load security configuration from a JSON file
|
|
70
|
+
*
|
|
71
|
+
* @param {string} [filePath] - Path to the security configuration file
|
|
72
|
+
* @returns {SecurityConfig|undefined} Parsed security configuration object or undefined if loading failed
|
|
73
|
+
*/
|
|
74
|
+
export declare function loadSecurityConfig(filePath?: string): SecurityConfig | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Build security schemes and parameters for OpenAPI generation
|
|
77
|
+
*
|
|
78
|
+
* @param {SecurityConfig} [cfg] - Security configuration object
|
|
79
|
+
* @returns {BuiltSecurity} Object containing built security schemes and parameters
|
|
80
|
+
*/
|
|
81
|
+
export declare function buildSecurity(cfg?: SecurityConfig): BuiltSecurity;
|
|
82
|
+
//# sourceMappingURL=security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/openapi/security.ts"],"names":[],"mappings":"AAkBA;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3E;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,UAAU,CAAC;QACpB,MAAM,CAAC,EAAE;YAAE,EAAE,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7D,MAAM,CAAC,EAAE;YAAE,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACnC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC9B,MAAM,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;SAAE,CAAC;QACxC,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;YAAC,MAAM,CAAC,EAAE,GAAG,CAAA;SAAE,CAAC,CAAC;KACrE,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QACzB,MAAM,CAAC,EAAE,UAAU,CAAC;QACpB,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;YAAC,MAAM,CAAC,EAAE,GAAG,CAAA;SAAE,CAAC,CAAA;KACpE,CAAC,CAAC;CACJ,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACtC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;IAC9C,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC9C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAShF;AAeD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,cAAc,GAAG,aAAa,CA+FjE"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI Security Configuration
|
|
3
|
+
*
|
|
4
|
+
* This module handles security scheme generation for the OpenAPI specification.
|
|
5
|
+
* It provides functionality to define authentication and authorization requirements
|
|
6
|
+
* for the API, including:
|
|
7
|
+
*
|
|
8
|
+
* - Basic authentication
|
|
9
|
+
* - Bearer token authentication
|
|
10
|
+
* - API key authentication
|
|
11
|
+
* - OAuth2 flows
|
|
12
|
+
* - Custom security headers
|
|
13
|
+
*
|
|
14
|
+
* The module supports both global security requirements and operation-specific
|
|
15
|
+
* security overrides through an external JSON configuration file.
|
|
16
|
+
*/
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
/**
|
|
19
|
+
* Load security configuration from a JSON file
|
|
20
|
+
*
|
|
21
|
+
* @param {string} [filePath] - Path to the security configuration file
|
|
22
|
+
* @returns {SecurityConfig|undefined} Parsed security configuration object or undefined if loading failed
|
|
23
|
+
*/
|
|
24
|
+
export function loadSecurityConfig(filePath) {
|
|
25
|
+
if (!filePath)
|
|
26
|
+
return undefined;
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
console.warn(`⚠️ Failed to read security config '${filePath}': ${e instanceof Error ? e.message : String(e)}`);
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate a canonical parameter component name from a header name
|
|
38
|
+
*
|
|
39
|
+
* @param {string} headerName - Original header name
|
|
40
|
+
* @returns {string} Canonicalized component name
|
|
41
|
+
*/
|
|
42
|
+
function makeParamComponentName(headerName) {
|
|
43
|
+
return headerName
|
|
44
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
45
|
+
.replace(/^_+|_+$/g, "")
|
|
46
|
+
|| "X_Header";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build security schemes and parameters for OpenAPI generation
|
|
50
|
+
*
|
|
51
|
+
* @param {SecurityConfig} [cfg] - Security configuration object
|
|
52
|
+
* @returns {BuiltSecurity} Object containing built security schemes and parameters
|
|
53
|
+
*/
|
|
54
|
+
export function buildSecurity(cfg) {
|
|
55
|
+
const securitySchemes = {};
|
|
56
|
+
const headerParameters = {};
|
|
57
|
+
const opSecurity = {};
|
|
58
|
+
const opHeaderParameters = {};
|
|
59
|
+
if (!cfg || !cfg.global) {
|
|
60
|
+
return { securitySchemes: undefined, headerParameters, opSecurity, opHeaderParameters };
|
|
61
|
+
}
|
|
62
|
+
const global = cfg.global;
|
|
63
|
+
const schemeName = "defaultAuth";
|
|
64
|
+
const scheme = global.scheme || "none";
|
|
65
|
+
const hasGlobal = scheme !== "none";
|
|
66
|
+
if (scheme !== "none") {
|
|
67
|
+
switch (scheme) {
|
|
68
|
+
case "basic":
|
|
69
|
+
securitySchemes[schemeName] = { type: "http", scheme: "basic" };
|
|
70
|
+
break;
|
|
71
|
+
case "bearer":
|
|
72
|
+
securitySchemes[schemeName] = { type: "http", scheme: "bearer", ...(global.bearer || {}) };
|
|
73
|
+
break;
|
|
74
|
+
case "apiKey":
|
|
75
|
+
securitySchemes[schemeName] = { type: "apiKey", ...(global.apiKey || { in: "header", name: "X-API-Key" }) };
|
|
76
|
+
break;
|
|
77
|
+
case "oauth2":
|
|
78
|
+
securitySchemes[schemeName] = { type: "oauth2", ...(global.oauth2 || { flows: {} }) };
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Global headers
|
|
83
|
+
for (const h of global.headers || []) {
|
|
84
|
+
const compName = makeParamComponentName(h.name);
|
|
85
|
+
headerParameters[compName] = {
|
|
86
|
+
name: h.name,
|
|
87
|
+
in: "header",
|
|
88
|
+
required: !!h.required,
|
|
89
|
+
schema: h.schema || { type: "string" },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Default op security (inherit global scheme)
|
|
93
|
+
// (If scheme is none we omit entirely)
|
|
94
|
+
// Overrides can set scheme to none to remove security
|
|
95
|
+
for (const [opName, override] of Object.entries(cfg.overrides || {})) {
|
|
96
|
+
const oScheme = override.scheme ?? scheme;
|
|
97
|
+
if (oScheme === "none") {
|
|
98
|
+
opSecurity[opName] = undefined;
|
|
99
|
+
}
|
|
100
|
+
else if (oScheme === scheme) {
|
|
101
|
+
opSecurity[opName] = hasGlobal ? [{ [schemeName]: [] }] : undefined;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Different scheme per operation -> create ad-hoc component name
|
|
105
|
+
const altName = `${schemeName}_${oScheme}`;
|
|
106
|
+
if (!securitySchemes[altName]) {
|
|
107
|
+
switch (oScheme) {
|
|
108
|
+
case "basic":
|
|
109
|
+
securitySchemes[altName] = { type: "http", scheme: "basic" };
|
|
110
|
+
break;
|
|
111
|
+
case "bearer":
|
|
112
|
+
securitySchemes[altName] = { type: "http", scheme: "bearer" };
|
|
113
|
+
break;
|
|
114
|
+
case "apiKey":
|
|
115
|
+
securitySchemes[altName] = { type: "apiKey", ...(global.apiKey || { in: "header", name: "X-API-Key" }) };
|
|
116
|
+
break;
|
|
117
|
+
case "oauth2":
|
|
118
|
+
securitySchemes[altName] = { type: "oauth2", ...(global.oauth2 || { flows: {} }) };
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
opSecurity[opName] = [{ [altName]: [] }];
|
|
123
|
+
}
|
|
124
|
+
// Per-op headers merge global + override specific headers
|
|
125
|
+
const headers = [...(global.headers || []), ...(override.headers || [])];
|
|
126
|
+
opHeaderParameters[opName] = headers.map(h => makeParamComponentName(h.name));
|
|
127
|
+
for (const h of override.headers || []) {
|
|
128
|
+
const compName = makeParamComponentName(h.name);
|
|
129
|
+
if (!headerParameters[compName]) {
|
|
130
|
+
headerParameters[compName] = {
|
|
131
|
+
name: h.name,
|
|
132
|
+
in: "header",
|
|
133
|
+
required: !!h.required,
|
|
134
|
+
schema: h.schema || { type: "string" },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
securitySchemes: Object.keys(securitySchemes).length ? securitySchemes : undefined,
|
|
141
|
+
headerParameters,
|
|
142
|
+
opSecurity,
|
|
143
|
+
opHeaderParameters
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type GenerateOpenAPIOptions } from "./openapi/generateOpenAPI.js";
|
|
2
|
+
import type { CompilerOptions } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the generation pipeline
|
|
5
|
+
*
|
|
6
|
+
* @interface PipelineOptions
|
|
7
|
+
* @property {string} wsdl - Path or URL to the WSDL file to process
|
|
8
|
+
* @property {string} outDir - Output directory for generated TypeScript artifacts
|
|
9
|
+
* @property {Partial<CompilerOptions>} [compiler] - Compiler options for type generation
|
|
10
|
+
* @property {object} [openapi] - OpenAPI generation configuration (optional)
|
|
11
|
+
* @property {string} [openapi.outFile] - Optional output path for OpenAPI specification
|
|
12
|
+
*/
|
|
13
|
+
export interface PipelineOptions {
|
|
14
|
+
wsdl: string;
|
|
15
|
+
outDir: string;
|
|
16
|
+
compiler?: Partial<CompilerOptions>;
|
|
17
|
+
openapi?: Omit<GenerateOpenAPIOptions, "wsdl" | "catalogFile" | "compiledCatalog"> & {
|
|
18
|
+
outFile?: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs the complete generation pipeline from WSDL to TypeScript artifacts and optionally OpenAPI
|
|
23
|
+
*
|
|
24
|
+
* This function orchestrates the entire process:
|
|
25
|
+
* 1. Loads and parses the WSDL from file or URL
|
|
26
|
+
* 2. Compiles the WSDL into an internal catalog representation
|
|
27
|
+
* 3. Emits TypeScript client code, types, and utilities
|
|
28
|
+
* 4. Optionally emits a JSON catalog for introspection
|
|
29
|
+
* 5. Optionally generates an OpenAPI 3.1 specification
|
|
30
|
+
*
|
|
31
|
+
* @param {PipelineOptions} opts - Configuration options for the pipeline
|
|
32
|
+
* @returns {Promise<{compiled: any}>} - The compiled catalog for potential further processing
|
|
33
|
+
*/
|
|
34
|
+
export declare function runGenerationPipeline(opts: PipelineOptions): Promise<{
|
|
35
|
+
compiled: import("./compiler/schemaCompiler.js").CompiledCatalog;
|
|
36
|
+
}>;
|
|
37
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +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,KAAK,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AAGjD;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,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;CAC3G;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,eAAe;;GAgDhE"}
|
package/dist/pipeline.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript WSDL Client Generation Pipeline
|
|
3
|
+
*
|
|
4
|
+
* This file implements the high-level generation pipeline that orchestrates the entire
|
|
5
|
+
* process of converting a WSDL file into TypeScript client code and optionally an OpenAPI
|
|
6
|
+
* specification. It serves as an integration layer between the various stages of the generation
|
|
7
|
+
* process.
|
|
8
|
+
*/
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import { loadWsdl } from "./loader/wsdlLoader.js";
|
|
12
|
+
import { compileCatalog } from "./compiler/schemaCompiler.js";
|
|
13
|
+
import { emitClient } from "./emit/clientEmitter.js";
|
|
14
|
+
import { emitTypes } from "./emit/typesEmitter.js";
|
|
15
|
+
import { emitUtils } from "./emit/utilsEmitter.js";
|
|
16
|
+
import { emitCatalog } from "./emit/catalogEmitter.js";
|
|
17
|
+
import { generateOpenAPI } from "./openapi/generateOpenAPI.js";
|
|
18
|
+
import { TYPESCRIPT_WSDL_CLIENT_DEFAULT_COMPLIER_OPTIONS } from "./config.js";
|
|
19
|
+
/**
|
|
20
|
+
* Runs the complete generation pipeline from WSDL to TypeScript artifacts and optionally OpenAPI
|
|
21
|
+
*
|
|
22
|
+
* This function orchestrates the entire process:
|
|
23
|
+
* 1. Loads and parses the WSDL from file or URL
|
|
24
|
+
* 2. Compiles the WSDL into an internal catalog representation
|
|
25
|
+
* 3. Emits TypeScript client code, types, and utilities
|
|
26
|
+
* 4. Optionally emits a JSON catalog for introspection
|
|
27
|
+
* 5. Optionally generates an OpenAPI 3.1 specification
|
|
28
|
+
*
|
|
29
|
+
* @param {PipelineOptions} opts - Configuration options for the pipeline
|
|
30
|
+
* @returns {Promise<{compiled: any}>} - The compiled catalog for potential further processing
|
|
31
|
+
*/
|
|
32
|
+
export async function runGenerationPipeline(opts) {
|
|
33
|
+
// Merge provided compiler options with defaults, ensuring required fields are set
|
|
34
|
+
const finalCompiler = {
|
|
35
|
+
...TYPESCRIPT_WSDL_CLIENT_DEFAULT_COMPLIER_OPTIONS,
|
|
36
|
+
catalog: opts.compiler?.catalog ?? true, // default to emitting catalog in pipeline mode
|
|
37
|
+
...(opts.compiler || {}),
|
|
38
|
+
wsdl: opts.wsdl,
|
|
39
|
+
out: opts.outDir,
|
|
40
|
+
};
|
|
41
|
+
// Step 1: Load and parse the WSDL document
|
|
42
|
+
const wsdlCatalog = await loadWsdl(opts.wsdl);
|
|
43
|
+
// Step 2: Compile the WSDL into a structured catalog
|
|
44
|
+
const compiled = compileCatalog(wsdlCatalog, finalCompiler);
|
|
45
|
+
// Step 3: Ensure the output directory exists
|
|
46
|
+
fs.mkdirSync(opts.outDir, { recursive: true });
|
|
47
|
+
// Step 4: Emit TypeScript artifacts
|
|
48
|
+
emitClient(path.join(opts.outDir, "client.ts"), compiled);
|
|
49
|
+
emitTypes(path.join(opts.outDir, "types.ts"), compiled);
|
|
50
|
+
emitUtils(path.join(opts.outDir, "utils.ts"), compiled);
|
|
51
|
+
// Step 5: Optionally emit the JSON catalog for introspection
|
|
52
|
+
if (finalCompiler.catalog) {
|
|
53
|
+
emitCatalog(path.join(opts.outDir, "catalog.json"), compiled);
|
|
54
|
+
}
|
|
55
|
+
// Step 6: Optionally generate OpenAPI specification
|
|
56
|
+
if (opts.openapi) {
|
|
57
|
+
// Determine output path for OpenAPI specification
|
|
58
|
+
let resolvedOut = opts.openapi.outFile;
|
|
59
|
+
if (!resolvedOut) {
|
|
60
|
+
const yamlPreferred = !!opts.openapi.asYaml;
|
|
61
|
+
resolvedOut = path.join(opts.outDir, yamlPreferred ? "openapi.yaml" : "openapi.json");
|
|
62
|
+
}
|
|
63
|
+
// Generate the OpenAPI specification using the compiled catalog
|
|
64
|
+
await generateOpenAPI({
|
|
65
|
+
...opts.openapi,
|
|
66
|
+
compiledCatalog: compiled,
|
|
67
|
+
outFile: resolvedOut,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// Return the compiled catalog for potential further processing
|
|
71
|
+
return { compiled };
|
|
72
|
+
}
|