swaggie 1.8.4 → 1.8.6
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 +4 -4
- package/dist/browser.js +10 -0
- package/dist/cli.js +5 -0
- package/dist/gen/genTypes.js +68 -10
- package/dist/index.js +13 -1
- package/dist/swagger/typesExtractor.js +2 -0
- package/dist/types.d.ts +12 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div style="text-align: center; margin: 0px auto 30px; max-width: 600px">
|
|
2
|
-
<img src="./swaggie.
|
|
2
|
+
<img src="./docs/public/swaggie-full.png" alt="Swaggie logo">
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
5
|
# Swaggie
|
|
@@ -387,7 +387,7 @@ Swaggie only needs a JSON or YAML OpenAPI spec file — it does not require a ru
|
|
|
387
387
|
## Used By
|
|
388
388
|
|
|
389
389
|
<div style="display: flex; gap: 1rem;">
|
|
390
|
-
<a href="https://www.britishcouncil.org"><img alt="British Council" src="
|
|
391
|
-
<a href="https://kpmg.com/"><img alt="KPMG" src="
|
|
392
|
-
<a href="https://klarna.com/"><img alt="Klarna" src="
|
|
390
|
+
<a href="https://www.britishcouncil.org"><img alt="British Council" src="./docs/public/used-in/bc-logo.png" /></a>
|
|
391
|
+
<a href="https://kpmg.com/"><img alt="KPMG" src="./docs/public/used-in/kpmg-logo.png" /></a>
|
|
392
|
+
<a href="https://klarna.com/"><img alt="Klarna" src="./docs/public/used-in/klarna-logo.png" /></a>
|
|
393
393
|
</div>
|
package/dist/browser.js
CHANGED
|
@@ -72,6 +72,7 @@ async function generateCode(spec, options) {
|
|
|
72
72
|
mode,
|
|
73
73
|
schemaStyle,
|
|
74
74
|
enumStyle,
|
|
75
|
+
enumNamesStyle,
|
|
75
76
|
nullables,
|
|
76
77
|
template,
|
|
77
78
|
queryParamsSerialization = {},
|
|
@@ -96,6 +97,15 @@ async function generateCode(spec, options) {
|
|
|
96
97
|
_nullishCoalesce(_nullishCoalesce(schemaStyle, () => ( rest.schemaDeclarationStyle)), () => ( _swagger.APP_DEFAULTS.schemaDeclarationStyle)),
|
|
97
98
|
enumDeclarationStyle:
|
|
98
99
|
_nullishCoalesce(_nullishCoalesce(enumStyle, () => ( rest.enumDeclarationStyle)), () => ( _swagger.APP_DEFAULTS.enumDeclarationStyle)),
|
|
100
|
+
enumNamesStyle: normalizeEnumNamesStyle(enumNamesStyle),
|
|
99
101
|
queryParamsSerialization: mergedQueryParamsSerialization,
|
|
100
102
|
};
|
|
101
103
|
} exports.prepareAppOptions = prepareAppOptions;
|
|
104
|
+
|
|
105
|
+
function normalizeEnumNamesStyle(value) {
|
|
106
|
+
if (!value) return _swagger.APP_DEFAULTS.enumNamesStyle;
|
|
107
|
+
const lower = value.toLowerCase();
|
|
108
|
+
if (lower === 'pascal' || lower === 'pascalcase') return 'PascalCase';
|
|
109
|
+
if (lower === 'original') return 'original';
|
|
110
|
+
return _swagger.APP_DEFAULTS.enumNamesStyle;
|
|
111
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -27,6 +27,10 @@ const enumStyleOption = new (0, _commander.Option)(
|
|
|
27
27
|
'--enumStyle <style>',
|
|
28
28
|
'Enum declaration style for plain string enums'
|
|
29
29
|
).choices(['union', 'enum']);
|
|
30
|
+
const enumNamesStyleOption = new (0, _commander.Option)(
|
|
31
|
+
'--enumNamesStyle <style>',
|
|
32
|
+
'Controls how enum member names are formatted (only with --enumStyle enum)'
|
|
33
|
+
).choices(['original', 'PascalCase', 'pascal']);
|
|
30
34
|
const dateFormatOption = new (0, _commander.Option)(
|
|
31
35
|
'--dateFormat <format>',
|
|
32
36
|
'How date fields are emitted in generated types'
|
|
@@ -78,6 +82,7 @@ program
|
|
|
78
82
|
.addOption(modeOption)
|
|
79
83
|
.addOption(schemaStyleOption)
|
|
80
84
|
.addOption(enumStyleOption)
|
|
85
|
+
.addOption(enumNamesStyleOption)
|
|
81
86
|
.addOption(dateFormatOption)
|
|
82
87
|
.addOption(nullableStrategyOption);
|
|
83
88
|
|
package/dist/gen/genTypes.js
CHANGED
|
@@ -88,19 +88,30 @@ function renderSchema(
|
|
|
88
88
|
const objectContents = generateObjectTypeContents(mergedSchema, options, schemaContext);
|
|
89
89
|
const hasAdditionalProperties = !!mergedSchema.additionalProperties;
|
|
90
90
|
|
|
91
|
+
// Detect "orphan" required fields: top-level `required` entries that reference
|
|
92
|
+
// properties from $ref types rather than inline properties. For these we generate
|
|
93
|
+
// Required<Pick<...>> to enforce non-optionality in TypeScript.
|
|
94
|
+
const requiredPickType = getRequiredPickType(schema, mergedSchema, types);
|
|
95
|
+
|
|
91
96
|
if (hasAdditionalProperties) {
|
|
92
|
-
const compositeTypes = [...types, objectType].join(' & ');
|
|
97
|
+
const compositeTypes = [...types, requiredPickType, objectType].filter(Boolean).join(' & ');
|
|
93
98
|
result.push(`export type ${safeName} = ${compositeTypes};`);
|
|
94
99
|
return `${result.join('\n')}\n`;
|
|
95
100
|
}
|
|
96
101
|
|
|
97
|
-
if (useTypeAliases) {
|
|
98
|
-
|
|
102
|
+
if (useTypeAliases || requiredPickType) {
|
|
103
|
+
// When requiredPickType is present we must use type alias (intersection) style,
|
|
104
|
+
// because `interface extends` does not allow extending two types that declare the
|
|
105
|
+
// same property with different optionality (e.g. `Team` with `id?` and
|
|
106
|
+
// `Required<Pick<Team, 'id'>>` with `id` — TS2320).
|
|
107
|
+
const objectLiteral = objectContents ? `{\n${objectContents}\n}` : '';
|
|
108
|
+
const allTypes = [...types, requiredPickType, objectLiteral].filter(Boolean);
|
|
109
|
+
const compositeTypes = allTypes.join(' & ');
|
|
99
110
|
result.push(`export type ${safeName} = ${compositeTypes};`);
|
|
100
111
|
return `${result.join('\n')}\n`;
|
|
101
112
|
}
|
|
102
113
|
|
|
103
|
-
const extensions = types ? `extends ${types.join(', ')} ` : '';
|
|
114
|
+
const extensions = types.length ? `extends ${types.join(', ')} ` : '';
|
|
104
115
|
result.push(`export interface ${safeName} ${extensions}{`);
|
|
105
116
|
result.push(objectContents);
|
|
106
117
|
} else if ('oneOf' in schema || 'anyOf' in schema) {
|
|
@@ -190,7 +201,7 @@ function renderExtendedEnumType(name, def) {
|
|
|
190
201
|
*/
|
|
191
202
|
function renderEnumType(name, def, options) {
|
|
192
203
|
if (options.enumDeclarationStyle === 'enum' && shouldRenderStringEnumDeclaration(def)) {
|
|
193
|
-
return renderStringEnumDeclaration(name, def);
|
|
204
|
+
return renderStringEnumDeclaration(name, def, options);
|
|
194
205
|
}
|
|
195
206
|
|
|
196
207
|
const values = def.enum.map((v) => (typeof v === 'number' ? v : `"${v}"`)).join(' | ');
|
|
@@ -207,17 +218,38 @@ function shouldRenderStringEnumDeclaration(def)
|
|
|
207
218
|
);
|
|
208
219
|
}
|
|
209
220
|
|
|
210
|
-
function renderStringEnumDeclaration(
|
|
221
|
+
function renderStringEnumDeclaration(
|
|
222
|
+
name,
|
|
223
|
+
def,
|
|
224
|
+
options
|
|
225
|
+
) {
|
|
226
|
+
const usePascalCase = options.enumNamesStyle === 'PascalCase';
|
|
211
227
|
let res = `export enum ${name} {\n`;
|
|
212
228
|
for (let index = 0; index < def.enum.length; index++) {
|
|
213
229
|
const value = def.enum[index];
|
|
214
|
-
const
|
|
230
|
+
const rawName = usePascalCase ? toPascalCase(value) : value;
|
|
231
|
+
const memberName = _nullishCoalesce(_utils.escapePropName.call(void 0, rawName), () => ( `VALUE_${index}`));
|
|
215
232
|
res += ` ${memberName} = ${JSON.stringify(value)},\n`;
|
|
216
233
|
}
|
|
217
234
|
|
|
218
235
|
return `${res}}\n`;
|
|
219
236
|
}
|
|
220
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Converts a string to PascalCase.
|
|
240
|
+
* Splits on non-alphanumeric characters (spaces, hyphens, dots, underscores, etc.)
|
|
241
|
+
* and capitalizes the first letter of each segment.
|
|
242
|
+
*
|
|
243
|
+
* Examples: "org name" → "OrgName", "my-value" → "MyValue", "some.thing" → "SomeThing"
|
|
244
|
+
*/
|
|
245
|
+
function toPascalCase(value) {
|
|
246
|
+
return value
|
|
247
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
248
|
+
.filter(Boolean)
|
|
249
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
250
|
+
.join('');
|
|
251
|
+
}
|
|
252
|
+
|
|
221
253
|
/**
|
|
222
254
|
* OpenApi 3.1 introduced a new way to define enums that we support here.
|
|
223
255
|
*/
|
|
@@ -288,14 +320,40 @@ function isNullableAsOptional(
|
|
|
288
320
|
);
|
|
289
321
|
}
|
|
290
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Detects "orphan" required fields: top-level `required` entries that are not covered
|
|
325
|
+
* by inline `properties` in the merged schema. For these fields, we generate a
|
|
326
|
+
* `Required<Pick<RefType, 'prop1' | 'prop2'>>` expression to enforce non-optionality.
|
|
327
|
+
*
|
|
328
|
+
* @returns A `Required<Pick<...>>` type string, or an empty string if not applicable.
|
|
329
|
+
*/
|
|
330
|
+
function getRequiredPickType(
|
|
331
|
+
originalSchema,
|
|
332
|
+
mergedSchema,
|
|
333
|
+
refTypes
|
|
334
|
+
) {
|
|
335
|
+
const topLevelRequired = originalSchema.required || [];
|
|
336
|
+
if (!topLevelRequired.length || !refTypes.length) return '';
|
|
337
|
+
|
|
338
|
+
const inlineProps = Object.keys(mergedSchema.properties || {});
|
|
339
|
+
const orphanRequired = topLevelRequired.filter((r) => !inlineProps.includes(r));
|
|
340
|
+
if (!orphanRequired.length) return '';
|
|
341
|
+
|
|
342
|
+
const propsUnion = orphanRequired.map((p) => `'${p}'`).join(' | ');
|
|
343
|
+
const baseType = refTypes.length === 1 ? refTypes[0] : refTypes.join(' & ');
|
|
344
|
+
|
|
345
|
+
return `Required<Pick<${baseType}, ${propsUnion}>>`;
|
|
346
|
+
}
|
|
347
|
+
|
|
291
348
|
function getMergedCompositeObjects(schema) {
|
|
292
349
|
const { allOf, oneOf, anyOf, ...safeSchema } = schema;
|
|
293
350
|
const composite = allOf || oneOf || anyOf || [];
|
|
294
351
|
const subSchemas = composite.filter((v) => !('$ref' in v));
|
|
295
352
|
|
|
296
|
-
// This is the case where schema itself
|
|
297
|
-
// and at the same time has sub-schemas like `allOf` or similar
|
|
298
|
-
|
|
353
|
+
// This is the case where schema itself has properties, required fields,
|
|
354
|
+
// or is of type object — and at the same time has sub-schemas like `allOf` or similar.
|
|
355
|
+
// We include the parent schema data so that `required` and `properties` are not lost.
|
|
356
|
+
if ('properties' in safeSchema || 'required' in safeSchema) {
|
|
299
357
|
subSchemas.push(safeSchema);
|
|
300
358
|
}
|
|
301
359
|
|
package/dist/index.js
CHANGED
|
@@ -41,7 +41,9 @@ function gen(spec, options) {
|
|
|
41
41
|
return _gen2.default.call(void 0, spec, options);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
async function applyConfigFile(
|
|
44
|
+
async function applyConfigFile(
|
|
45
|
+
options
|
|
46
|
+
) {
|
|
45
47
|
try {
|
|
46
48
|
if (!options.config) {
|
|
47
49
|
return prepareAppOptions(options );
|
|
@@ -84,6 +86,7 @@ function readFile(filePath) {
|
|
|
84
86
|
mode,
|
|
85
87
|
schemaStyle,
|
|
86
88
|
enumStyle,
|
|
89
|
+
enumNamesStyle,
|
|
87
90
|
nullables,
|
|
88
91
|
template,
|
|
89
92
|
queryParamsSerialization = {},
|
|
@@ -108,6 +111,15 @@ function readFile(filePath) {
|
|
|
108
111
|
_nullishCoalesce(_nullishCoalesce(schemaStyle, () => ( rest.schemaDeclarationStyle)), () => ( _swagger.APP_DEFAULTS.schemaDeclarationStyle)),
|
|
109
112
|
enumDeclarationStyle:
|
|
110
113
|
_nullishCoalesce(_nullishCoalesce(enumStyle, () => ( rest.enumDeclarationStyle)), () => ( _swagger.APP_DEFAULTS.enumDeclarationStyle)),
|
|
114
|
+
enumNamesStyle: normalizeEnumNamesStyle(enumNamesStyle),
|
|
111
115
|
queryParamsSerialization: mergedQueryParamsSerialization,
|
|
112
116
|
};
|
|
113
117
|
} exports.prepareAppOptions = prepareAppOptions;
|
|
118
|
+
|
|
119
|
+
function normalizeEnumNamesStyle(value) {
|
|
120
|
+
if (!value) return _swagger.APP_DEFAULTS.enumNamesStyle;
|
|
121
|
+
const lower = value.toLowerCase();
|
|
122
|
+
if (lower === 'pascal' || lower === 'pascalcase') return 'PascalCase';
|
|
123
|
+
if (lower === 'original') return 'original';
|
|
124
|
+
return _swagger.APP_DEFAULTS.enumNamesStyle;
|
|
125
|
+
}
|
|
@@ -375,6 +375,7 @@ function isRequiredOnlyCompositeBranch(schema) {
|
|
|
375
375
|
generationMode: 'full',
|
|
376
376
|
schemaDeclarationStyle: 'interface',
|
|
377
377
|
enumDeclarationStyle: 'union',
|
|
378
|
+
enumNamesStyle: 'original',
|
|
378
379
|
queryParamsSerialization: {
|
|
379
380
|
allowDots: true,
|
|
380
381
|
arrayFormat: 'repeat',
|
|
@@ -395,6 +396,7 @@ function isRequiredOnlyCompositeBranch(schema) {
|
|
|
395
396
|
generationMode: _nullishCoalesce(opts.generationMode, () => ( exports.APP_DEFAULTS.generationMode)),
|
|
396
397
|
schemaDeclarationStyle: _nullishCoalesce(opts.schemaDeclarationStyle, () => ( exports.APP_DEFAULTS.schemaDeclarationStyle)),
|
|
397
398
|
enumDeclarationStyle: _nullishCoalesce(opts.enumDeclarationStyle, () => ( exports.APP_DEFAULTS.enumDeclarationStyle)),
|
|
399
|
+
enumNamesStyle: _nullishCoalesce(opts.enumNamesStyle, () => ( exports.APP_DEFAULTS.enumNamesStyle)),
|
|
398
400
|
queryParamsSerialization: {
|
|
399
401
|
...exports.APP_DEFAULTS.queryParamsSerialization,
|
|
400
402
|
...opts.queryParamsSerialization,
|
package/dist/types.d.ts
CHANGED
|
@@ -35,6 +35,13 @@ export interface ClientOptions {
|
|
|
35
35
|
schemaDeclarationStyle?: SchemaDeclarationStyle;
|
|
36
36
|
/** Controls whether plain string enums are emitted as unions or TypeScript enums */
|
|
37
37
|
enumDeclarationStyle?: EnumDeclarationStyle;
|
|
38
|
+
/**
|
|
39
|
+
* Controls how enum member names are formatted when generating TypeScript `enum` declarations.
|
|
40
|
+
* Only applies when `enumDeclarationStyle` is set to `'enum'`.
|
|
41
|
+
* - `'original'` — use the raw enum value as the member name (e.g. `org name = "org name"`)
|
|
42
|
+
* - `'PascalCase'` — convert values to PascalCase (e.g. `OrgName = "org name"`)
|
|
43
|
+
*/
|
|
44
|
+
enumNamesStyle?: EnumNamesStyle;
|
|
38
45
|
/** Offers ability to adjust the OpenAPI spec before it is processed */
|
|
39
46
|
modifiers?: {
|
|
40
47
|
/** Global-level modifiers for parameter with a given name */
|
|
@@ -43,12 +50,14 @@ export interface ClientOptions {
|
|
|
43
50
|
};
|
|
44
51
|
};
|
|
45
52
|
}
|
|
46
|
-
export interface CliOptions extends FullAppOptions {
|
|
53
|
+
export interface CliOptions extends Omit<FullAppOptions, 'enumNamesStyle'> {
|
|
47
54
|
allowDots?: boolean;
|
|
48
55
|
arrayFormat?: ArrayFormat;
|
|
49
56
|
mode?: GenerationMode;
|
|
50
57
|
schemaStyle?: SchemaDeclarationStyle;
|
|
51
58
|
enumStyle?: EnumDeclarationStyle;
|
|
59
|
+
/** Accepts 'original', 'PascalCase', or 'pascal' (normalized to 'PascalCase') */
|
|
60
|
+
enumNamesStyle?: string;
|
|
52
61
|
nullables?: NullableStrategy;
|
|
53
62
|
}
|
|
54
63
|
export interface FullAppOptions extends ClientOptions {
|
|
@@ -63,6 +72,7 @@ export type NullableStrategy = 'include' | 'nullableAsOptional' | 'ignore';
|
|
|
63
72
|
export type GenerationMode = 'full' | 'schemas';
|
|
64
73
|
export type SchemaDeclarationStyle = 'interface' | 'type';
|
|
65
74
|
export type EnumDeclarationStyle = 'union' | 'enum';
|
|
75
|
+
export type EnumNamesStyle = 'original' | 'PascalCase';
|
|
66
76
|
/**
|
|
67
77
|
* Internal options type used throughout the app after `prepareAppOptions` has run.
|
|
68
78
|
* All fields that have defaults are required here so the rest of the codebase never
|
|
@@ -75,6 +85,7 @@ export interface AppOptions extends ClientOptions {
|
|
|
75
85
|
generationMode: GenerationMode;
|
|
76
86
|
schemaDeclarationStyle: SchemaDeclarationStyle;
|
|
77
87
|
enumDeclarationStyle: EnumDeclarationStyle;
|
|
88
|
+
enumNamesStyle: EnumNamesStyle;
|
|
78
89
|
queryParamsSerialization: {
|
|
79
90
|
allowDots: boolean;
|
|
80
91
|
arrayFormat: ArrayFormat;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swaggie",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.6",
|
|
4
4
|
"description": "Generate a fully typed TypeScript API client from your OpenAPI 3 spec",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Piotr Dabrowski",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"openapi-types": "^12.1.3",
|
|
81
81
|
"sucrase": "3.35.1",
|
|
82
82
|
"typescript": "5.9.3",
|
|
83
|
-
"vitepress": "^2.0.0-alpha.16"
|
|
83
|
+
"vitepress": "^2.0.0-alpha.16",
|
|
84
|
+
"vitepress-plugin-tabs": "0.8.0"
|
|
84
85
|
}
|
|
85
86
|
}
|