Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"typescript": "^4.6.3"
},
"dependencies": {
"@types/glob": "8.0.0",
"@types/node": "^17.0.24",
"@types/supports-color": "^8.1.0",
"@types/tape": "^4.13.2",
Expand All @@ -58,6 +59,7 @@
"chalk": "^4.1.1",
"eslint": "^8.13.0",
"eslint-plugin-prettier": "^4.0.0",
"glob": "8.0.3",
"sanitize-filename": "^1.6.3",
"soap": "^0.43.0",
"supports-color": "^8.1.1",
Expand Down
32 changes: 21 additions & 11 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#!/usr/bin/env node
import yargs from "yargs";
import path from "path";
import glob from "glob";
import { promisify } from "util";
import { Logger } from "./utils/logger";
import { parseAndGenerate, Options } from "./index";
import { isUrl } from "./utils/url";
import packageJson from "../package.json";

const conf = yargs(process.argv.slice(2))
Expand Down Expand Up @@ -125,20 +128,27 @@ if (conf._ === undefined || conf._.length === 0) {
const outDir = path.resolve(conf.o);

let errorsCount = 0;
const matches = conf._ as string[];

if (matches.length > 1) {
Logger.debug(matches.map((m) => path.resolve(m)).join("\n"));
Logger.log(`Found ${matches.length} wsdl files`);
const patterns = conf._ as string[];
Logger.debug(`patterns: ${patterns}`);
const wsdlUris: string[] = [];
for (const pattern of patterns) {
if (isUrl(pattern)) {
wsdlUris.push(pattern);
} else {
const matches = await promisify(glob)(pattern);
wsdlUris.push(...matches);
}
}
if (wsdlUris.length > 1) {
Logger.log(`Found ${wsdlUris.length} wsdl URIs`);
Logger.debug(wsdlUris.join("\n"));
}
for (const match of matches) {
const wsdlPath = path.resolve(match);
const wsdlName = path.basename(wsdlPath);
Logger.log(`Generating soap client from "${wsdlName}"`);
for (const wsdlUri of wsdlUris) {
Logger.log(`Generating soap client from "${wsdlUri}"`);
try {
await parseAndGenerate(wsdlPath, path.join(outDir), options);
await parseAndGenerate(wsdlUri, path.join(outDir), options);
} catch (err) {
Logger.error(`Error occured while generating client "${wsdlName}"`);
Logger.error(`Error occured while generating client for "${wsdlUri}"`);
Logger.error(`\t${err}`);
errorsCount += 1;
}
Expand Down
45 changes: 27 additions & 18 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ const defaultOptions: GeneratorOptions = {
function addSafeImport(
imports: OptionalKind<ImportDeclarationStructure>[],
moduleSpecifier: string,
namedImport: string
namedImport: string,
isTypeOnly: boolean = false
) {
if (!imports.find((imp) => imp.moduleSpecifier == moduleSpecifier)) {
imports.push({
moduleSpecifier,
namedImports: [{ name: namedImport }],
isTypeOnly
});
}
}
Expand Down Expand Up @@ -105,7 +107,7 @@ function generateDefinitionFile(
}
// If a property is of the same type as its parent type, don't add import
if (prop.ref.name !== definition.name) {
addSafeImport(definitionImports, `./${prop.ref.name}`, prop.ref.name);
addSafeImport(definitionImports, `./${prop.ref.name}.js`, prop.ref.name, true);
}
definitionProperties.push(createProperty(prop.name, prop.ref.name, prop.sourceName, prop.isArray));
}
Expand Down Expand Up @@ -177,14 +179,16 @@ export async function generate(
);
addSafeImport(
clientImports,
`./definitions/${method.paramDefinition.name}`,
method.paramDefinition.name
`./definitions/${method.paramDefinition.name}.js`,
method.paramDefinition.name,
true
);
}
addSafeImport(
portImports,
`../definitions/${method.paramDefinition.name}`,
method.paramDefinition.name
`../definitions/${method.paramDefinition.name}.js`,
method.paramDefinition.name,
true
);
}
if (method.returnDefinition !== null) {
Expand All @@ -200,14 +204,16 @@ export async function generate(
);
addSafeImport(
clientImports,
`./definitions/${method.returnDefinition.name}`,
method.returnDefinition.name
`./definitions/${method.returnDefinition.name}.js`,
method.returnDefinition.name,
true
);
}
addSafeImport(
portImports,
`../definitions/${method.returnDefinition.name}`,
method.returnDefinition.name
`../definitions/${method.returnDefinition.name}.js`,
method.returnDefinition.name,
true
);
}
// TODO: Deduplicate PortMethods
Expand All @@ -230,7 +236,7 @@ export async function generate(
});
} // End of PortMethod
if (!mergedOptions.emitDefinitionsOnly) {
addSafeImport(serviceImports, `../ports/${port.name}`, port.name);
addSafeImport(serviceImports, `../ports/${port.name}.js`, port.name, true);
servicePorts.push({
name: sanitizePropName(port.name),
isReadonly: true,
Expand All @@ -252,7 +258,7 @@ export async function generate(
} // End of Port

if (!mergedOptions.emitDefinitionsOnly) {
addSafeImport(clientImports, `./services/${service.name}`, service.name);
addSafeImport(clientImports, `./services/${service.name}.js`, service.name, true);
clientServices.push({ name: sanitizePropName(service.name), type: service.name });

serviceFile.addImportDeclarations(serviceImports);
Expand Down Expand Up @@ -314,12 +320,12 @@ export async function generate(
{
isRestParameter: true,
name: "args",
type: "Parameters<typeof soapCreateClientAsync>",
type: "Parameters<typeof soapCreateClientAsync> extends [any, ...infer Rest] ? Rest : never",
},
],
returnType: `Promise<${parsedWsdl.name}Client>`, // TODO: `any` keyword is very dangerous
});
createClientDeclaration.setBodyText("return soapCreateClientAsync(args[0], args[1], args[2]) as any;");
createClientDeclaration.setBodyText(`return soapCreateClientAsync("${parsedWsdl.wsdlUri}", ...args) as any;`);
Logger.log(`Writing Client file: ${path.resolve(path.join(outDir, "client"))}.ts`);
clientFile.saveSync();
}
Expand All @@ -333,7 +339,8 @@ export async function generate(
indexFile.addExportDeclarations(
allDefinitions.map((def) => ({
namedExports: [def.name],
moduleSpecifier: `./definitions/${def.name}`,
moduleSpecifier: `./definitions/${def.name}.js`,
isTypeOnly: true
}))
);
if (!mergedOptions.emitDefinitionsOnly) {
Expand All @@ -342,19 +349,21 @@ export async function generate(
indexFile.addExportDeclarations([
{
namedExports: ["createClientAsync", `${parsedWsdl.name}Client`],
moduleSpecifier: "./client",
moduleSpecifier: "./client.js",
},
]);
indexFile.addExportDeclarations(
parsedWsdl.services.map((service) => ({
namedExports: [service.name],
moduleSpecifier: `./services/${service.name}`,
moduleSpecifier: `./services/${service.name}.js`,
isTypeOnly: true,
}))
);
indexFile.addExportDeclarations(
parsedWsdl.ports.map((port) => ({
namedExports: [port.name],
moduleSpecifier: `./ports/${port.name}`,
moduleSpecifier: `./ports/${port.name}.js`,
isTypeOnly: true,
}))
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const defaultOptions: Options = {
};

export async function parseAndGenerate(
wsdlPath: string,
wsdlUri: string,
outDir: string,
options: Partial<Options> = {}
): Promise<void> {
Expand All @@ -95,7 +95,7 @@ export async function parseAndGenerate(
// Logger.debug(`Options: ${JSON.stringify(mergedOptions, null, 2)}`);

const timeParseStart = process.hrtime();
const parsedWsdl = await parseWsdl(wsdlPath, mergedOptions);
const parsedWsdl = await parseWsdl(wsdlUri, mergedOptions);
Logger.debug(`Parser time: ${timeElapsed(process.hrtime(timeParseStart))}ms`);

const timeGenerateStart = process.hrtime();
Expand Down
11 changes: 6 additions & 5 deletions src/models/parsed-wsdl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,16 @@ const defaultOptions: Options = {

export class ParsedWsdl {
/**
* Name is always uppercased filename of wsdl without an extension.
* Name is always uppercased filename of wsdl without an extension
* or the last URL path component without the query string.
* Used to generate client name of interface
* @example "MyClient"
*/
name: string;
/** Original wsdl filename */
wsdlFilename: string;
/** Absolute basepath or url */
wsdlPath: string;
/** Original wsdl basename */
wsdlBasename: string;
/** Relative path or url */
wsdlUri: string;

definitions: Array<Definition> = [];
ports: Array<Port> = [];
Expand Down
24 changes: 15 additions & 9 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { changeCase } from "./utils/change-case";
import { stripExtension } from "./utils/file";
import { reservedKeywords } from "./utils/javascript";
import { Logger } from "./utils/logger";
import { isUrl, stripQuery } from "./utils/url";

interface ParserOptions {
modelNamePreffix: string;
Expand Down Expand Up @@ -220,16 +221,16 @@ function parseDefinition(
// TODO: Add comments for services, ports, methods and client
/**
* Parse WSDL to domain model `ParsedWsdl`
* @param wsdlPath - path or url to wsdl file
* @param wsdlUri - path or url to wsdl file
*/
export async function parseWsdl(wsdlPath: string, options: Partial<ParserOptions>): Promise<ParsedWsdl> {
export async function parseWsdl(wsdlUri: string, options: Partial<ParserOptions>): Promise<ParsedWsdl> {
const mergedOptions: ParserOptions = {
...defaultOptions,
...options,
};
return new Promise((resolve, reject) => {
open_wsdl(
wsdlPath,
wsdlUri,
{ namespaceArrayElements: false, ignoredNamespaces: ["tns", "targetNamespace", "typeNamespace"] },
function (err, wsdl) {
if (err) {
Expand All @@ -240,12 +241,17 @@ export async function parseWsdl(wsdlPath: string, options: Partial<ParserOptions
}

const parsedWsdl = new ParsedWsdl({ maxStack: options.maxRecursiveDefinitionName });
const filename = path.basename(wsdlPath);
parsedWsdl.name = changeCase(stripExtension(filename), {
pascalCase: true,
});
parsedWsdl.wsdlFilename = path.basename(filename);
parsedWsdl.wsdlPath = path.resolve(wsdlPath);
const basename = path.basename(wsdlUri);
parsedWsdl.name = changeCase(
isUrl(wsdlUri)
? stripQuery(basename)
: stripExtension(basename),
{
pascalCase: true,
}
);
parsedWsdl.wsdlBasename = basename;
parsedWsdl.wsdlUri = wsdlUri;

const visitedDefinitions: Array<VisitedDefinition> = [];

Expand Down
10 changes: 10 additions & 0 deletions src/utils/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @example isUrl("https://domain.com") -> true
*/
export function isUrl(uri: string): boolean {
return /^https?:\/\//.test(uri);
}

export function stripQuery(url: string): string {
return url.replace(/^(.*?)\?.*$/, "$1");
}