@stackwright-pro/openapi 0.3.0-alpha.5 → 0.3.0-alpha.7
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/dist/index.d.mts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +137 -70
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +137 -70
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -110,6 +110,8 @@ interface OpenAPIConfig {
|
|
|
110
110
|
slug_field: string;
|
|
111
111
|
/** HTTP method for this collection endpoint — defaults to 'GET' */
|
|
112
112
|
method?: string;
|
|
113
|
+
/** Transport protocol — 'websocket' collections are skipped during prebuild */
|
|
114
|
+
transport?: string;
|
|
113
115
|
/** Optional filters */
|
|
114
116
|
filters?: Record<string, unknown>;
|
|
115
117
|
}>;
|
|
@@ -468,8 +470,13 @@ declare class ActionGenerator {
|
|
|
468
470
|
private generateActionExports;
|
|
469
471
|
private generateRegistry;
|
|
470
472
|
private generateEmptyFile;
|
|
471
|
-
/** Derives the client method name from endpoint + method
|
|
473
|
+
/** Derives the client method name from endpoint + method.
|
|
474
|
+
* Prefers operationId (matching ClientGenerator.getMethodName()), falls back
|
|
475
|
+
* to path+method derivation for specs that omit operationIds. */
|
|
472
476
|
private endpointToClientMethod;
|
|
477
|
+
/** Convert any string to camelCase — handles operationId values like
|
|
478
|
+
* createOrder, list_items, get-by-id, etc. */
|
|
479
|
+
private camelCase;
|
|
473
480
|
private toCamelCase;
|
|
474
481
|
private toPascalCase;
|
|
475
482
|
private capitalize;
|
|
@@ -521,6 +528,7 @@ declare class ClientGenerator {
|
|
|
521
528
|
private schemaMapping?;
|
|
522
529
|
private requiredSchemas;
|
|
523
530
|
private generatedRequestSchemas;
|
|
531
|
+
private generatedRequestTypes;
|
|
524
532
|
constructor(document: OpenAPIDocument, schemaMapping?: SchemaMapping);
|
|
525
533
|
/**
|
|
526
534
|
* Generate typed API client code from OpenAPI document
|
package/dist/index.d.ts
CHANGED
|
@@ -110,6 +110,8 @@ interface OpenAPIConfig {
|
|
|
110
110
|
slug_field: string;
|
|
111
111
|
/** HTTP method for this collection endpoint — defaults to 'GET' */
|
|
112
112
|
method?: string;
|
|
113
|
+
/** Transport protocol — 'websocket' collections are skipped during prebuild */
|
|
114
|
+
transport?: string;
|
|
113
115
|
/** Optional filters */
|
|
114
116
|
filters?: Record<string, unknown>;
|
|
115
117
|
}>;
|
|
@@ -468,8 +470,13 @@ declare class ActionGenerator {
|
|
|
468
470
|
private generateActionExports;
|
|
469
471
|
private generateRegistry;
|
|
470
472
|
private generateEmptyFile;
|
|
471
|
-
/** Derives the client method name from endpoint + method
|
|
473
|
+
/** Derives the client method name from endpoint + method.
|
|
474
|
+
* Prefers operationId (matching ClientGenerator.getMethodName()), falls back
|
|
475
|
+
* to path+method derivation for specs that omit operationIds. */
|
|
472
476
|
private endpointToClientMethod;
|
|
477
|
+
/** Convert any string to camelCase — handles operationId values like
|
|
478
|
+
* createOrder, list_items, get-by-id, etc. */
|
|
479
|
+
private camelCase;
|
|
473
480
|
private toCamelCase;
|
|
474
481
|
private toPascalCase;
|
|
475
482
|
private capitalize;
|
|
@@ -521,6 +528,7 @@ declare class ClientGenerator {
|
|
|
521
528
|
private schemaMapping?;
|
|
522
529
|
private requiredSchemas;
|
|
523
530
|
private generatedRequestSchemas;
|
|
531
|
+
private generatedRequestTypes;
|
|
524
532
|
constructor(document: OpenAPIDocument, schemaMapping?: SchemaMapping);
|
|
525
533
|
/**
|
|
526
534
|
* Generate typed API client code from OpenAPI document
|
package/dist/index.js
CHANGED
|
@@ -45,8 +45,18 @@ var OpenAPIParser = class {
|
|
|
45
45
|
await SwaggerParser__default.default.validate(api);
|
|
46
46
|
}
|
|
47
47
|
let document;
|
|
48
|
+
let dereferenced = false;
|
|
48
49
|
if (dereference) {
|
|
49
|
-
|
|
50
|
+
try {
|
|
51
|
+
document = await SwaggerParser__default.default.dereference(api);
|
|
52
|
+
dereferenced = true;
|
|
53
|
+
} catch {
|
|
54
|
+
console.warn(
|
|
55
|
+
`[OpenAPIParser] Warning: Could not fully dereference "${specPath}" (dangling $ref or circular ref). Proceeding without full dereferencing \u2014 some schema references may not resolve.`
|
|
56
|
+
);
|
|
57
|
+
document = api;
|
|
58
|
+
dereferenced = false;
|
|
59
|
+
}
|
|
50
60
|
} else {
|
|
51
61
|
document = api;
|
|
52
62
|
}
|
|
@@ -54,7 +64,7 @@ var OpenAPIParser = class {
|
|
|
54
64
|
return {
|
|
55
65
|
document,
|
|
56
66
|
version,
|
|
57
|
-
dereferenced
|
|
67
|
+
dereferenced
|
|
58
68
|
};
|
|
59
69
|
} catch (error) {
|
|
60
70
|
throw this.enhanceError(error, specPath);
|
|
@@ -150,8 +160,14 @@ var SchemaResolver = class {
|
|
|
150
160
|
const response = responses[responseCode];
|
|
151
161
|
if (!response) return void 0;
|
|
152
162
|
for (const contentType of SUPPORTED_CONTENT_TYPES) {
|
|
153
|
-
const
|
|
154
|
-
if (!
|
|
163
|
+
const responseContent = response.content;
|
|
164
|
+
if (!responseContent) continue;
|
|
165
|
+
const exactContent = responseContent[contentType];
|
|
166
|
+
const normalizedEntry = exactContent ? void 0 : Object.entries(responseContent).find(
|
|
167
|
+
([k]) => k === contentType || k.startsWith(`${contentType};`)
|
|
168
|
+
);
|
|
169
|
+
const content = exactContent ?? normalizedEntry?.[1];
|
|
170
|
+
if (!content || !content.schema) continue;
|
|
155
171
|
const schema = content.schema;
|
|
156
172
|
if ("$ref" in schema) {
|
|
157
173
|
console.warn(
|
|
@@ -281,7 +297,7 @@ ${body}`;
|
|
|
281
297
|
const isNullable = "nullable" in schema && schema.nullable === true;
|
|
282
298
|
let baseSchema = this.schemaTypeToZod(schema);
|
|
283
299
|
if (schema.description) {
|
|
284
|
-
const escapedDesc = schema.description.replace(/'/g, "\\'");
|
|
300
|
+
const escapedDesc = schema.description.replace(/'/g, "\\'").replace(/\r?\n/g, " ");
|
|
285
301
|
baseSchema += `.describe('${escapedDesc}')`;
|
|
286
302
|
}
|
|
287
303
|
if (isNullable) {
|
|
@@ -419,7 +435,7 @@ ${body}`;
|
|
|
419
435
|
if (!schema.properties || Object.keys(schema.properties).length === 0) {
|
|
420
436
|
if (schema.additionalProperties) {
|
|
421
437
|
const valueSchema = typeof schema.additionalProperties === "object" ? this.schemaToZod(schema.additionalProperties) : "z.unknown()";
|
|
422
|
-
return `z.record(${valueSchema})`;
|
|
438
|
+
return `z.record(z.string(), ${valueSchema})`;
|
|
423
439
|
}
|
|
424
440
|
return "z.object({})";
|
|
425
441
|
}
|
|
@@ -616,10 +632,10 @@ var CollectionProviderGenerator = class {
|
|
|
616
632
|
const authHeader = this.generateAuthHeader(auth);
|
|
617
633
|
const arraySchemaName = `${schemaName.replace(/Schema$/, "")}ArraySchema`;
|
|
618
634
|
const validationSchema = unknownFallback ? "z.unknown()" : isArray ? arraySchemaName : schemaName;
|
|
619
|
-
const imports = bare ? "" : unknownFallback ? `import type { CollectionProvider,
|
|
635
|
+
const imports = bare ? "" : unknownFallback ? `import type { CollectionProvider, CollectionEntry, CollectionListOptions, CollectionListResult } from '@stackwright/collections';
|
|
620
636
|
import { z } from 'zod';
|
|
621
637
|
|
|
622
|
-
` : `import type { CollectionProvider,
|
|
638
|
+
` : `import type { CollectionProvider, CollectionEntry, CollectionListOptions, CollectionListResult } from '@stackwright/collections';
|
|
623
639
|
import { ${isArray ? arraySchemaName : schemaName} } from './schemas';
|
|
624
640
|
|
|
625
641
|
`;
|
|
@@ -638,29 +654,20 @@ export class ${providerName} implements CollectionProvider {
|
|
|
638
654
|
}
|
|
639
655
|
|
|
640
656
|
/**
|
|
641
|
-
*
|
|
657
|
+
* List available collection names.
|
|
642
658
|
*/
|
|
643
|
-
|
|
644
|
-
return '${collectionName}';
|
|
659
|
+
async collections(): Promise<string[]> {
|
|
660
|
+
return ['${collectionName}'];
|
|
645
661
|
}
|
|
646
662
|
|
|
647
663
|
/**
|
|
648
664
|
* List all items in the collection
|
|
649
665
|
*/
|
|
650
|
-
async list(
|
|
651
|
-
limit?: number;
|
|
652
|
-
offset?: number;
|
|
653
|
-
filters?: Record<string, unknown>;
|
|
654
|
-
}): Promise<CollectionItem[]> {
|
|
666
|
+
async list(_collection: string, opts?: CollectionListOptions): Promise<CollectionListResult> {
|
|
655
667
|
const url = new URL(\`\${this.baseUrl}${endpoint}\`);
|
|
656
668
|
|
|
657
|
-
|
|
658
|
-
if (
|
|
659
|
-
url.searchParams.set('limit', String(options.limit));
|
|
660
|
-
}
|
|
661
|
-
if (options?.offset) {
|
|
662
|
-
url.searchParams.set('offset', String(options.offset));
|
|
663
|
-
}
|
|
669
|
+
if (opts?.limit) url.searchParams.set('limit', String(opts.limit));
|
|
670
|
+
if (opts?.offset) url.searchParams.set('offset', String(opts.offset));
|
|
664
671
|
|
|
665
672
|
const response = await fetch(url.toString(), {
|
|
666
673
|
method: '${method.toUpperCase()}',
|
|
@@ -676,29 +683,24 @@ export class ${providerName} implements CollectionProvider {
|
|
|
676
683
|
// Validate response with Zod schema
|
|
677
684
|
const validated = ${validationSchema}.parse(data);
|
|
678
685
|
|
|
679
|
-
|
|
680
|
-
return
|
|
686
|
+
const entries = ${isArray ? "validated" : "[validated]"}.map((item: any) => this.toCollectionEntry(item));
|
|
687
|
+
return { entries, total: entries.length };
|
|
681
688
|
}
|
|
682
689
|
|
|
683
690
|
/**
|
|
684
691
|
* Get a single item by slug
|
|
685
692
|
*/
|
|
686
|
-
async get(slug: string): Promise<
|
|
693
|
+
async get(_collection: string, slug: string): Promise<CollectionEntry | null> {
|
|
687
694
|
${this.generateGetMethod(endpoint, slugField, isArray, collectionName, schemaName, unknownFallback)}
|
|
688
695
|
}
|
|
689
696
|
|
|
690
697
|
/**
|
|
691
|
-
* Convert API response to
|
|
698
|
+
* Convert API response to CollectionEntry
|
|
692
699
|
*/
|
|
693
|
-
private
|
|
700
|
+
private toCollectionEntry(item: any): CollectionEntry {
|
|
694
701
|
return {
|
|
695
702
|
slug: String(item.${slugField}),
|
|
696
|
-
|
|
697
|
-
data: item,
|
|
698
|
-
metadata: {
|
|
699
|
-
source: '${collectionName}',
|
|
700
|
-
_raw: item,
|
|
701
|
-
},
|
|
703
|
+
...item,
|
|
702
704
|
};
|
|
703
705
|
}
|
|
704
706
|
|
|
@@ -741,10 +743,10 @@ export class ${providerName} implements CollectionProvider {
|
|
|
741
743
|
const data = await response.json();
|
|
742
744
|
const validated = ${validationExpr}.parse(data);
|
|
743
745
|
|
|
744
|
-
return this.
|
|
746
|
+
return this.toCollectionEntry(validated);`;
|
|
745
747
|
} else {
|
|
746
|
-
return `const
|
|
747
|
-
return
|
|
748
|
+
return `const result = await this.list(_collection);
|
|
749
|
+
return result.entries.find((item) => item.slug === slug) ?? null;`;
|
|
748
750
|
}
|
|
749
751
|
}
|
|
750
752
|
/**
|
|
@@ -992,13 +994,25 @@ import type { CollectionActionRegistry } from '@stackwright-pro/workflow';
|
|
|
992
994
|
export const actions: CollectionActionRegistry = {};
|
|
993
995
|
`;
|
|
994
996
|
}
|
|
995
|
-
/** Derives the client method name from endpoint + method
|
|
997
|
+
/** Derives the client method name from endpoint + method.
|
|
998
|
+
* Prefers operationId (matching ClientGenerator.getMethodName()), falls back
|
|
999
|
+
* to path+method derivation for specs that omit operationIds. */
|
|
996
1000
|
endpointToClientMethod(endpoint, method) {
|
|
1001
|
+
const pathItem = this.document.paths?.[endpoint];
|
|
1002
|
+
const operation = pathItem?.[method.toLowerCase()];
|
|
1003
|
+
if (operation?.operationId) {
|
|
1004
|
+
return this.camelCase(operation.operationId);
|
|
1005
|
+
}
|
|
997
1006
|
const parts = endpoint.split("/").filter(Boolean).filter((p) => !p.startsWith("{")).map((p) => p.replace(/-/g, "_"));
|
|
998
1007
|
const prefix = method.toLowerCase();
|
|
999
1008
|
const suffix = parts.map((p) => this.capitalize(p)).join("");
|
|
1000
1009
|
return `${prefix}${suffix}`;
|
|
1001
1010
|
}
|
|
1011
|
+
/** Convert any string to camelCase — handles operationId values like
|
|
1012
|
+
* createOrder, list_items, get-by-id, etc. */
|
|
1013
|
+
camelCase(str) {
|
|
1014
|
+
return str.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()).replace(/^[A-Z]/, (chr) => chr.toLowerCase());
|
|
1015
|
+
}
|
|
1002
1016
|
toCamelCase(str) {
|
|
1003
1017
|
const parts = str.split(/[-_]/);
|
|
1004
1018
|
return parts.map((p, i) => i === 0 ? p.toLowerCase() : this.capitalize(p)).join("");
|
|
@@ -1019,11 +1033,13 @@ var ClientGenerator = class {
|
|
|
1019
1033
|
}
|
|
1020
1034
|
this.requiredSchemas = /* @__PURE__ */ new Set();
|
|
1021
1035
|
this.generatedRequestSchemas = /* @__PURE__ */ new Set();
|
|
1036
|
+
this.generatedRequestTypes = /* @__PURE__ */ new Set();
|
|
1022
1037
|
}
|
|
1023
1038
|
resolver;
|
|
1024
1039
|
schemaMapping;
|
|
1025
1040
|
requiredSchemas;
|
|
1026
1041
|
generatedRequestSchemas;
|
|
1042
|
+
generatedRequestTypes;
|
|
1027
1043
|
/**
|
|
1028
1044
|
* Generate typed API client code from OpenAPI document
|
|
1029
1045
|
*/
|
|
@@ -1042,12 +1058,11 @@ var ClientGenerator = class {
|
|
|
1042
1058
|
}
|
|
1043
1059
|
this.requiredSchemas.clear();
|
|
1044
1060
|
this.generatedRequestSchemas.clear();
|
|
1061
|
+
this.generatedRequestTypes.clear();
|
|
1045
1062
|
let code = this.generateImports(!!schemaMapping, validateResponses);
|
|
1046
1063
|
code += "\n";
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
code += "\n";
|
|
1050
|
-
}
|
|
1064
|
+
code += this.generateRequestSchemas(endpoints);
|
|
1065
|
+
code += "\n";
|
|
1051
1066
|
code += this.generateTypes(endpoints, schemaMapping);
|
|
1052
1067
|
code += "\n";
|
|
1053
1068
|
code += this.generateErrorClasses();
|
|
@@ -1078,9 +1093,9 @@ var ClientGenerator = class {
|
|
|
1078
1093
|
*/
|
|
1079
1094
|
|
|
1080
1095
|
`;
|
|
1081
|
-
|
|
1082
|
-
code += `import { z } from 'zod';
|
|
1096
|
+
code += `import { z } from 'zod';
|
|
1083
1097
|
`;
|
|
1098
|
+
if (useSchemas && validateResponses) {
|
|
1084
1099
|
code += `import * as schemas from './schemas';
|
|
1085
1100
|
`;
|
|
1086
1101
|
}
|
|
@@ -1113,6 +1128,9 @@ var ClientGenerator = class {
|
|
|
1113
1128
|
const schemaName = `${typeName}RequestSchema`;
|
|
1114
1129
|
const schemaCode = this.generateRequestSchemaForEndpoint(endpoint);
|
|
1115
1130
|
if (schemaCode) {
|
|
1131
|
+
if (this.generatedRequestSchemas.has(schemaName)) {
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1116
1134
|
code += `export const ${schemaName} = ${schemaCode};
|
|
1117
1135
|
|
|
1118
1136
|
`;
|
|
@@ -1164,7 +1182,8 @@ ${parts.join(",\n")}
|
|
|
1164
1182
|
for (const param of params) {
|
|
1165
1183
|
const zodSchema = this.parameterSchemaToZod(param);
|
|
1166
1184
|
const desc = param.description ? `.describe('${this.escapeString(param.description)}')` : "";
|
|
1167
|
-
|
|
1185
|
+
const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
|
|
1186
|
+
paramSchemas.push(` ${safeParamKey}: ${zodSchema}${desc}`);
|
|
1168
1187
|
}
|
|
1169
1188
|
return `z.object({
|
|
1170
1189
|
${paramSchemas.join(",\n")}
|
|
@@ -1183,7 +1202,8 @@ ${paramSchemas.join(",\n")}
|
|
|
1183
1202
|
if (!param.required) {
|
|
1184
1203
|
zodSchema += ".optional()";
|
|
1185
1204
|
}
|
|
1186
|
-
|
|
1205
|
+
const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
|
|
1206
|
+
paramSchemas.push(` ${safeParamKey}: ${zodSchema}`);
|
|
1187
1207
|
}
|
|
1188
1208
|
return `z.object({
|
|
1189
1209
|
${paramSchemas.join(",\n")}
|
|
@@ -1256,7 +1276,7 @@ ${paramSchemas.join(",\n")}
|
|
|
1256
1276
|
}
|
|
1257
1277
|
if (schema.type === "object") {
|
|
1258
1278
|
if (!schema.properties) {
|
|
1259
|
-
return "z.record(z.unknown())";
|
|
1279
|
+
return "z.record(z.string(), z.unknown())";
|
|
1260
1280
|
}
|
|
1261
1281
|
const props = [];
|
|
1262
1282
|
const required = schema.required || [];
|
|
@@ -1266,7 +1286,8 @@ ${paramSchemas.join(",\n")}
|
|
|
1266
1286
|
if (!isRequired) {
|
|
1267
1287
|
propZod += ".optional()";
|
|
1268
1288
|
}
|
|
1269
|
-
|
|
1289
|
+
const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
|
|
1290
|
+
props.push(`${safeKey}: ${propZod}`);
|
|
1270
1291
|
}
|
|
1271
1292
|
return `z.object({ ${props.join(", ")} })`;
|
|
1272
1293
|
}
|
|
@@ -1424,13 +1445,24 @@ ${paramSchemas.join(",\n")}
|
|
|
1424
1445
|
const operationId = endpoint.operationId || this.getMethodName(endpoint);
|
|
1425
1446
|
const mapping = schemaMapping[operationId];
|
|
1426
1447
|
if (!mapping) {
|
|
1448
|
+
const typeName2 = this.getOperationTypeName(endpoint);
|
|
1449
|
+
const requestSchemaName2 = `${typeName2}RequestSchema`;
|
|
1450
|
+
if (this.generatedRequestSchemas.has(requestSchemaName2) && !this.generatedRequestTypes.has(requestSchemaName2)) {
|
|
1451
|
+
code += `export type ${typeName2}Request = z.infer<typeof ${requestSchemaName2}>;
|
|
1452
|
+
`;
|
|
1453
|
+
this.generatedRequestTypes.add(requestSchemaName2);
|
|
1454
|
+
}
|
|
1455
|
+
code += `export type ${typeName2}Response = unknown;
|
|
1456
|
+
|
|
1457
|
+
`;
|
|
1427
1458
|
continue;
|
|
1428
1459
|
}
|
|
1429
1460
|
const typeName = this.getOperationTypeName(endpoint);
|
|
1430
1461
|
const requestSchemaName = mapping.requestSchema || typeName + "RequestSchema";
|
|
1431
|
-
if (this.generatedRequestSchemas.has(requestSchemaName)) {
|
|
1462
|
+
if (this.generatedRequestSchemas.has(requestSchemaName) && !this.generatedRequestTypes.has(requestSchemaName)) {
|
|
1432
1463
|
this.addRequiredSchema(requestSchemaName);
|
|
1433
1464
|
code += "export type " + typeName + "Request = z.infer<typeof " + requestSchemaName + ">;\n";
|
|
1465
|
+
this.generatedRequestTypes.add(requestSchemaName);
|
|
1434
1466
|
}
|
|
1435
1467
|
this.addRequiredSchema(mapping.responseSchema);
|
|
1436
1468
|
code += `export type ${typeName}Response = z.infer<typeof schemas.${mapping.responseSchema}>;
|
|
@@ -1475,8 +1507,9 @@ ${paramSchemas.join(",\n")}
|
|
|
1475
1507
|
const type = this.getTypeFromSchemaLegacy(param.schema);
|
|
1476
1508
|
const desc = param.description ? ` /** ${param.description} */
|
|
1477
1509
|
` : "";
|
|
1510
|
+
const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
|
|
1478
1511
|
code += desc;
|
|
1479
|
-
code += ` ${
|
|
1512
|
+
code += ` ${safeParamKey}${required}: ${type};
|
|
1480
1513
|
`;
|
|
1481
1514
|
}
|
|
1482
1515
|
code += " };\n";
|
|
@@ -1489,8 +1522,9 @@ ${paramSchemas.join(",\n")}
|
|
|
1489
1522
|
const type = this.getTypeFromSchemaLegacy(param.schema);
|
|
1490
1523
|
const desc = param.description ? ` /** ${param.description} */
|
|
1491
1524
|
` : "";
|
|
1525
|
+
const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
|
|
1492
1526
|
code += desc;
|
|
1493
|
-
code += ` ${
|
|
1527
|
+
code += ` ${safeParamKey}${required}: ${type};
|
|
1494
1528
|
`;
|
|
1495
1529
|
}
|
|
1496
1530
|
code += " };\n";
|
|
@@ -1503,8 +1537,9 @@ ${paramSchemas.join(",\n")}
|
|
|
1503
1537
|
const type = this.getTypeFromSchemaLegacy(param.schema);
|
|
1504
1538
|
const desc = param.description ? ` /** ${param.description} */
|
|
1505
1539
|
` : "";
|
|
1540
|
+
const safeParamKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? param.name : `'${param.name}'`;
|
|
1506
1541
|
code += desc;
|
|
1507
|
-
code += ` ${
|
|
1542
|
+
code += ` ${safeParamKey}${required}: ${type};
|
|
1508
1543
|
`;
|
|
1509
1544
|
}
|
|
1510
1545
|
code += " };\n";
|
|
@@ -1851,9 +1886,10 @@ ${paramSchemas.join(",\n")}
|
|
|
1851
1886
|
code += ` if (request.query) {
|
|
1852
1887
|
`;
|
|
1853
1888
|
for (const param of queryParams) {
|
|
1854
|
-
|
|
1889
|
+
const safeAccess = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? `.${param.name}` : `['${param.name}']`;
|
|
1890
|
+
code += ` if (request.query${safeAccess} != null) {
|
|
1855
1891
|
`;
|
|
1856
|
-
code += ` searchParams.append('${param.name}', String(request.query
|
|
1892
|
+
code += ` searchParams.append('${param.name}', String(request.query${safeAccess}));
|
|
1857
1893
|
`;
|
|
1858
1894
|
code += ` }
|
|
1859
1895
|
`;
|
|
@@ -1877,7 +1913,7 @@ ${paramSchemas.join(",\n")}
|
|
|
1877
1913
|
code += ` method: '${method.toUpperCase()}',
|
|
1878
1914
|
`;
|
|
1879
1915
|
if (hasBody) {
|
|
1880
|
-
code += ` body: request.body ? JSON.stringify(request.body) :
|
|
1916
|
+
code += ` body: request.body ? JSON.stringify(request.body) : undefined,
|
|
1881
1917
|
`;
|
|
1882
1918
|
}
|
|
1883
1919
|
if (headerParams.length > 0) {
|
|
@@ -1994,9 +2030,10 @@ ${paramSchemas.join(",\n")}
|
|
|
1994
2030
|
code += ` if (request.query) {
|
|
1995
2031
|
`;
|
|
1996
2032
|
for (const param of queryParams) {
|
|
1997
|
-
|
|
2033
|
+
const safeAccess = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(param.name) ? `.${param.name}` : `['${param.name}']`;
|
|
2034
|
+
code += ` if (request.query${safeAccess} != null) {
|
|
1998
2035
|
`;
|
|
1999
|
-
code += ` searchParams.append('${param.name}', String(request.query
|
|
2036
|
+
code += ` searchParams.append('${param.name}', String(request.query${safeAccess}));
|
|
2000
2037
|
`;
|
|
2001
2038
|
code += ` }
|
|
2002
2039
|
`;
|
|
@@ -2018,7 +2055,7 @@ ${paramSchemas.join(",\n")}
|
|
|
2018
2055
|
code += ` method: '${method.toUpperCase()}',
|
|
2019
2056
|
`;
|
|
2020
2057
|
if (hasBody) {
|
|
2021
|
-
code += ` body: request.body ? JSON.stringify(request.body) :
|
|
2058
|
+
code += ` body: request.body ? JSON.stringify(request.body) : undefined,
|
|
2022
2059
|
`;
|
|
2023
2060
|
}
|
|
2024
2061
|
const headerParams = params.filter((p) => p.in === "header");
|
|
@@ -2184,7 +2221,7 @@ ${paramSchemas.join(",\n")}
|
|
|
2184
2221
|
if (endpoint.operationId) {
|
|
2185
2222
|
return this.camelCase(endpoint.operationId);
|
|
2186
2223
|
}
|
|
2187
|
-
const pathParts = endpoint.path.split("/").filter((p) => p
|
|
2224
|
+
const pathParts = endpoint.path.split("/").filter((p) => p).map((p) => p.startsWith("{") && p.endsWith("}") ? p.slice(1, -1) : p).join("-");
|
|
2188
2225
|
return this.camelCase(`${endpoint.method}-${pathParts}`);
|
|
2189
2226
|
}
|
|
2190
2227
|
/**
|
|
@@ -2841,6 +2878,23 @@ var OpenAPIPlugin = class {
|
|
|
2841
2878
|
async processIntegration(config, projectRoot) {
|
|
2842
2879
|
const { name, spec, auth, mockUrl, collections, endpoints, actions } = config;
|
|
2843
2880
|
console.log(` - Processing integration: ${name}`);
|
|
2881
|
+
const httpCollections = (collections || []).filter((c) => {
|
|
2882
|
+
if (c.transport === "websocket") {
|
|
2883
|
+
console.warn(
|
|
2884
|
+
` > Skipping collection "${c.endpoint}" (transport: websocket \u2014 not yet supported)`
|
|
2885
|
+
);
|
|
2886
|
+
return false;
|
|
2887
|
+
}
|
|
2888
|
+
return true;
|
|
2889
|
+
});
|
|
2890
|
+
const hasEndpoints = endpoints && (endpoints.include?.length || endpoints.exclude?.length);
|
|
2891
|
+
const hasActions = actions && actions.length > 0;
|
|
2892
|
+
if (httpCollections.length === 0 && !hasEndpoints && !hasActions) {
|
|
2893
|
+
console.log(
|
|
2894
|
+
" > No HTTP endpoints or REST collections found \u2014 skipping (WebSocket-only integration not yet supported)"
|
|
2895
|
+
);
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2844
2898
|
const specPath = spec.startsWith("http") ? spec : path2__default.default.resolve(projectRoot, spec);
|
|
2845
2899
|
const parser = new OpenAPIParser();
|
|
2846
2900
|
const { document } = await parser.parse(specPath);
|
|
@@ -2859,13 +2913,13 @@ var OpenAPIPlugin = class {
|
|
|
2859
2913
|
fs2__default.default.mkdirSync(outputDir, { recursive: true });
|
|
2860
2914
|
const schemaMapping = await this.generateSchemas(
|
|
2861
2915
|
document,
|
|
2862
|
-
|
|
2916
|
+
httpCollections,
|
|
2863
2917
|
outputDir,
|
|
2864
2918
|
name,
|
|
2865
2919
|
endpointFilter
|
|
2866
2920
|
);
|
|
2867
|
-
await this.generateTypes(document,
|
|
2868
|
-
if (
|
|
2921
|
+
await this.generateTypes(document, httpCollections, outputDir, name);
|
|
2922
|
+
if (httpCollections.length > 0) {
|
|
2869
2923
|
await this.generateProvider(document, config, outputDir, name);
|
|
2870
2924
|
}
|
|
2871
2925
|
await this.generateClient(document, outputDir, name, schemaMapping, endpointFilter);
|
|
@@ -2973,6 +3027,7 @@ import { z } from 'zod';
|
|
|
2973
3027
|
if (!operation) continue;
|
|
2974
3028
|
const opId = operation.operationId || this.generateOperationId(pathStr, method);
|
|
2975
3029
|
const responseSchemaName = `${this.getOperationTypeName(opId)}ResponseSchema`;
|
|
3030
|
+
if (!generatedSchemas.has(responseSchemaName)) continue;
|
|
2976
3031
|
schemaMapping[opId] = {
|
|
2977
3032
|
requestSchema: null,
|
|
2978
3033
|
// ClientGenerator handles request schema generation + type inference
|
|
@@ -2997,7 +3052,13 @@ import type * as schemas from './schemas';
|
|
|
2997
3052
|
|
|
2998
3053
|
`;
|
|
2999
3054
|
if (collections) {
|
|
3055
|
+
const resolver = new SchemaResolver(document);
|
|
3000
3056
|
for (const collection of collections) {
|
|
3057
|
+
const schema = resolver.getResponseSchema(
|
|
3058
|
+
collection.endpoint,
|
|
3059
|
+
collection.method?.toLowerCase() ?? "get"
|
|
3060
|
+
);
|
|
3061
|
+
if (!schema) continue;
|
|
3001
3062
|
const collectionName = this.sanitizeName(collection.endpoint);
|
|
3002
3063
|
const typeName = this.capitalize(collectionName);
|
|
3003
3064
|
const schemaName = `${typeName}Schema`;
|
|
@@ -3043,13 +3104,20 @@ import type * as schemas from './schemas';
|
|
|
3043
3104
|
collection.endpoint,
|
|
3044
3105
|
collection.method?.toLowerCase() ?? "get"
|
|
3045
3106
|
);
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3107
|
+
if (schema) {
|
|
3108
|
+
const isArray = schema.type === "array";
|
|
3109
|
+
if (isArray) {
|
|
3110
|
+
schemaImports.add(`${this.capitalize(collectionName)}ArraySchema`);
|
|
3111
|
+
} else {
|
|
3112
|
+
schemaImports.add(`${this.capitalize(collectionName)}Schema`);
|
|
3113
|
+
}
|
|
3051
3114
|
}
|
|
3052
3115
|
}
|
|
3116
|
+
const needsZodImport = classBlocks.some((block) => block.includes("z.unknown()"));
|
|
3117
|
+
const zodImportLine = needsZodImport ? `import { z } from 'zod';
|
|
3118
|
+
` : "";
|
|
3119
|
+
const schemaImportLine = schemaImports.size > 0 ? `import { ${Array.from(schemaImports).join(", ")} } from './schemas';
|
|
3120
|
+
` : "";
|
|
3053
3121
|
let providerCode = `/**
|
|
3054
3122
|
* Generated CollectionProvider from OpenAPI spec
|
|
3055
3123
|
* Integration: ${integrationName}
|
|
@@ -3058,9 +3126,8 @@ import type * as schemas from './schemas';
|
|
|
3058
3126
|
* Regenerate by running: pnpm prebuild
|
|
3059
3127
|
*/
|
|
3060
3128
|
|
|
3061
|
-
import type { CollectionProvider,
|
|
3062
|
-
|
|
3063
|
-
|
|
3129
|
+
import type { CollectionProvider, CollectionEntry, CollectionListOptions, CollectionListResult } from '@stackwright/collections';
|
|
3130
|
+
${zodImportLine}${schemaImportLine}
|
|
3064
3131
|
`;
|
|
3065
3132
|
providerCode += classBlocks.join("\n");
|
|
3066
3133
|
fs2__default.default.writeFileSync(path2__default.default.join(outputDir, "provider.ts"), providerCode);
|