vite-plugin-openapi-codegen 2.0.0 → 3.0.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/dist/cli.mjs +77 -1
- package/dist/index.d.mts +9 -0
- package/dist/index.mjs +77 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -52,6 +52,40 @@ function renderOperationTypeAliases(typeAliases) {
|
|
|
52
52
|
if (typeAliases.length === 0) return "";
|
|
53
53
|
return `${typeAliases.map((alias) => `export type ${alias.typeName} = ${alias.definitionExpr};`).join("\n")}\n`;
|
|
54
54
|
}
|
|
55
|
+
function renderAccessPoliciesSource(entries, generatedHeader) {
|
|
56
|
+
if (entries.length === 0) return "";
|
|
57
|
+
const lines = [
|
|
58
|
+
...generatedHeader,
|
|
59
|
+
"",
|
|
60
|
+
"export type AccessPolicyKind = \"authenticated\" | \"internal\" | \"public\" | \"role\";",
|
|
61
|
+
"",
|
|
62
|
+
"export interface AccessPolicy {",
|
|
63
|
+
" kind: AccessPolicyKind;",
|
|
64
|
+
" roles?: readonly string[];",
|
|
65
|
+
"}",
|
|
66
|
+
"",
|
|
67
|
+
"export interface OperationAccessPolicy extends AccessPolicy {",
|
|
68
|
+
" apiPath: string;",
|
|
69
|
+
" method: string;",
|
|
70
|
+
" operationId: string;",
|
|
71
|
+
" path: string;",
|
|
72
|
+
"}",
|
|
73
|
+
"",
|
|
74
|
+
"export const accessPolicies = {"
|
|
75
|
+
];
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
lines.push(` ${entry.funcName}: {`);
|
|
78
|
+
lines.push(` apiPath: ${JSON.stringify(entry.apiPath)},`);
|
|
79
|
+
lines.push(` kind: ${JSON.stringify(entry.kind)},`);
|
|
80
|
+
lines.push(` method: ${JSON.stringify(entry.methodUpper)},`);
|
|
81
|
+
lines.push(` operationId: ${JSON.stringify(entry.operationId)},`);
|
|
82
|
+
lines.push(` path: ${JSON.stringify(entry.strippedPath)},`);
|
|
83
|
+
if (entry.roles.length > 0) lines.push(` roles: [${entry.roles.map((role) => JSON.stringify(role)).join(", ")}],`);
|
|
84
|
+
lines.push(" },");
|
|
85
|
+
}
|
|
86
|
+
lines.push("} as const satisfies Record<string, OperationAccessPolicy>;", "", "export type AccessPolicyKey = keyof typeof accessPolicies;", "");
|
|
87
|
+
return `${lines.join("\n")}`;
|
|
88
|
+
}
|
|
55
89
|
function printGeneratedFile(statements, generatedHeader) {
|
|
56
90
|
const sourceFile = ts.factory.createSourceFile(statements, ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
57
91
|
const printed = AST_PRINTER.printFile(sourceFile).trim();
|
|
@@ -613,6 +647,44 @@ function createClientRenderModel(model, useTypeAliases) {
|
|
|
613
647
|
}))
|
|
614
648
|
};
|
|
615
649
|
}
|
|
650
|
+
function createAccessPolicyEntries(operations, securitySchemes, topLevelSecurity) {
|
|
651
|
+
return operations.flatMap((entry) => {
|
|
652
|
+
const accessPolicy = readSecurityRequirement(entry, securitySchemes, topLevelSecurity);
|
|
653
|
+
if (!accessPolicy) return [];
|
|
654
|
+
return [{
|
|
655
|
+
apiPath: entry.apiPath,
|
|
656
|
+
funcName: entry.funcName,
|
|
657
|
+
kind: accessPolicy.kind,
|
|
658
|
+
methodUpper: entry.method.toUpperCase(),
|
|
659
|
+
operationId: entry.operationId,
|
|
660
|
+
roles: accessPolicy.roles ?? [],
|
|
661
|
+
strippedPath: entry.strippedPath
|
|
662
|
+
}];
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
function readSecurityRequirement(entry, securitySchemes, topLevelSecurity) {
|
|
666
|
+
const security = entry.operation.security ?? topLevelSecurity;
|
|
667
|
+
if (security === void 0) return null;
|
|
668
|
+
if (!Array.isArray(security)) throw new Error(`Operation "${entry.operationId}" has invalid security`);
|
|
669
|
+
if (security.length === 0) return { kind: "public" };
|
|
670
|
+
const requirement = security[0];
|
|
671
|
+
if (!isRecord(requirement)) throw new Error(`Operation "${entry.operationId}" has an invalid security requirement`);
|
|
672
|
+
for (const [schemeName, scopes] of Object.entries(requirement)) {
|
|
673
|
+
const type = securitySchemes?.[schemeName]?.type;
|
|
674
|
+
if (type === "apiKey") return { kind: "internal" };
|
|
675
|
+
if (type === "http") {
|
|
676
|
+
const roles = Array.isArray(scopes) ? scopes.filter((scope) => typeof scope === "string") : [];
|
|
677
|
+
return roles.length === 0 ? { kind: "authenticated" } : {
|
|
678
|
+
kind: "role",
|
|
679
|
+
roles
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
throw new Error(`Operation "${entry.operationId}" has an unrecognized security scheme`);
|
|
684
|
+
}
|
|
685
|
+
function isRecord(value) {
|
|
686
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
687
|
+
}
|
|
616
688
|
function resolveChannel(channel, useTypeAliases) {
|
|
617
689
|
if (!channel.typeRef) return channel;
|
|
618
690
|
return {
|
|
@@ -628,14 +700,17 @@ function renderGeneratedArtifacts(spec, options, preCollectedOperations) {
|
|
|
628
700
|
const stripPrefix = options.stripPrefix ?? true;
|
|
629
701
|
const httpClient = resolveHttpClientConfig(options.httpClient);
|
|
630
702
|
const useTypeAliases = options.typeAliases ?? false;
|
|
631
|
-
const
|
|
703
|
+
const operations = preCollectedOperations ?? collectOperations(spec, pathPrefix, stripPrefix);
|
|
704
|
+
const clientModel = buildClientRenderModelFromOperations(operations, spec, {
|
|
632
705
|
json: httpClient.jsonFunction,
|
|
633
706
|
void: httpClient.voidFunction
|
|
634
707
|
});
|
|
635
708
|
if (clientModel.operations.length === 0) throw new Error(`No paths matching prefix "${pathPrefix}" found in openapi.json`);
|
|
709
|
+
const accessPolicies = renderAccessPoliciesSource(createAccessPolicyEntries(operations, spec.components?.securitySchemes, spec.security), GENERATED_HEADER);
|
|
636
710
|
return {
|
|
637
711
|
api: renderApiSource(createApiEntries(clientModel.operations, useTypeAliases), GENERATED_HEADER),
|
|
638
712
|
client: renderClientSource(createClientRenderModel(clientModel, useTypeAliases), GENERATED_HEADER, httpClient),
|
|
713
|
+
...accessPolicies ? { accessPolicies } : {},
|
|
639
714
|
...useTypeAliases && clientModel.typeAliases.length > 0 ? { apiTypes: renderOperationTypeAliases(clientModel.typeAliases) } : {}
|
|
640
715
|
};
|
|
641
716
|
}
|
|
@@ -681,6 +756,7 @@ async function generateOpenAPIArtifacts(root, options) {
|
|
|
681
756
|
warnOnParameterLocationMismatch(operations);
|
|
682
757
|
await generateApiTypes(apiTypesSource, outputDir, options.typeAliases ?? false);
|
|
683
758
|
if (artifacts.apiTypes) writeFileSync(resolve(outputDir, "api-types.d.ts"), artifacts.apiTypes, { flag: "a" });
|
|
759
|
+
if (artifacts.accessPolicies) writeFileSync(resolve(outputDir, "access-policies.ts"), artifacts.accessPolicies);
|
|
684
760
|
writeFileSync(resolve(outputDir, "api.ts"), artifacts.api);
|
|
685
761
|
writeFileSync(resolve(outputDir, "client.ts"), artifacts.client);
|
|
686
762
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -26,12 +26,18 @@ interface OpenAPIResponse {
|
|
|
26
26
|
content?: Record<string, OpenAPIContent>;
|
|
27
27
|
description?: string;
|
|
28
28
|
}
|
|
29
|
+
type OpenAPISecurityRequirement = Record<string, string[]>;
|
|
30
|
+
interface OpenAPISecurityScheme {
|
|
31
|
+
type?: string;
|
|
32
|
+
scheme?: string;
|
|
33
|
+
}
|
|
29
34
|
interface OpenAPIOperation {
|
|
30
35
|
operationId?: string;
|
|
31
36
|
parameters?: OpenAPIParameter[];
|
|
32
37
|
requestBody?: OpenAPIRequestBody;
|
|
33
38
|
responses?: Record<string, OpenAPIResponse>;
|
|
34
39
|
tags?: string[];
|
|
40
|
+
security?: OpenAPISecurityRequirement[];
|
|
35
41
|
}
|
|
36
42
|
type OpenAPIPathItem = Partial<Record<HttpMethod, OpenAPIOperation>>;
|
|
37
43
|
interface OpenAPISchema {
|
|
@@ -42,8 +48,10 @@ interface OpenAPISchema {
|
|
|
42
48
|
}
|
|
43
49
|
interface OpenAPISpec {
|
|
44
50
|
paths?: Record<string, OpenAPIPathItem>;
|
|
51
|
+
security?: OpenAPISecurityRequirement[];
|
|
45
52
|
components?: {
|
|
46
53
|
schemas?: Record<string, OpenAPISchema>;
|
|
54
|
+
securitySchemes?: Record<string, OpenAPISecurityScheme>;
|
|
47
55
|
};
|
|
48
56
|
}
|
|
49
57
|
interface OperationEntry {
|
|
@@ -90,6 +98,7 @@ interface Options {
|
|
|
90
98
|
interface GeneratedArtifacts {
|
|
91
99
|
api: string;
|
|
92
100
|
apiTypes?: string;
|
|
101
|
+
accessPolicies?: string;
|
|
93
102
|
client: string;
|
|
94
103
|
}
|
|
95
104
|
declare function renderGeneratedArtifacts(spec: OpenAPISpec, options: Pick<Options, "httpClient" | "pathPrefix" | "stripPrefix" | "typeAliases">, preCollectedOperations?: OperationEntry[]): GeneratedArtifacts;
|
package/dist/index.mjs
CHANGED
|
@@ -50,6 +50,40 @@ function renderOperationTypeAliases(typeAliases) {
|
|
|
50
50
|
if (typeAliases.length === 0) return "";
|
|
51
51
|
return `${typeAliases.map((alias) => `export type ${alias.typeName} = ${alias.definitionExpr};`).join("\n")}\n`;
|
|
52
52
|
}
|
|
53
|
+
function renderAccessPoliciesSource(entries, generatedHeader) {
|
|
54
|
+
if (entries.length === 0) return "";
|
|
55
|
+
const lines = [
|
|
56
|
+
...generatedHeader,
|
|
57
|
+
"",
|
|
58
|
+
"export type AccessPolicyKind = \"authenticated\" | \"internal\" | \"public\" | \"role\";",
|
|
59
|
+
"",
|
|
60
|
+
"export interface AccessPolicy {",
|
|
61
|
+
" kind: AccessPolicyKind;",
|
|
62
|
+
" roles?: readonly string[];",
|
|
63
|
+
"}",
|
|
64
|
+
"",
|
|
65
|
+
"export interface OperationAccessPolicy extends AccessPolicy {",
|
|
66
|
+
" apiPath: string;",
|
|
67
|
+
" method: string;",
|
|
68
|
+
" operationId: string;",
|
|
69
|
+
" path: string;",
|
|
70
|
+
"}",
|
|
71
|
+
"",
|
|
72
|
+
"export const accessPolicies = {"
|
|
73
|
+
];
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
lines.push(` ${entry.funcName}: {`);
|
|
76
|
+
lines.push(` apiPath: ${JSON.stringify(entry.apiPath)},`);
|
|
77
|
+
lines.push(` kind: ${JSON.stringify(entry.kind)},`);
|
|
78
|
+
lines.push(` method: ${JSON.stringify(entry.methodUpper)},`);
|
|
79
|
+
lines.push(` operationId: ${JSON.stringify(entry.operationId)},`);
|
|
80
|
+
lines.push(` path: ${JSON.stringify(entry.strippedPath)},`);
|
|
81
|
+
if (entry.roles.length > 0) lines.push(` roles: [${entry.roles.map((role) => JSON.stringify(role)).join(", ")}],`);
|
|
82
|
+
lines.push(" },");
|
|
83
|
+
}
|
|
84
|
+
lines.push("} as const satisfies Record<string, OperationAccessPolicy>;", "", "export type AccessPolicyKey = keyof typeof accessPolicies;", "");
|
|
85
|
+
return `${lines.join("\n")}`;
|
|
86
|
+
}
|
|
53
87
|
function printGeneratedFile(statements, generatedHeader) {
|
|
54
88
|
const sourceFile = ts.factory.createSourceFile(statements, ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
55
89
|
const printed = AST_PRINTER.printFile(sourceFile).trim();
|
|
@@ -611,6 +645,44 @@ function createClientRenderModel(model, useTypeAliases) {
|
|
|
611
645
|
}))
|
|
612
646
|
};
|
|
613
647
|
}
|
|
648
|
+
function createAccessPolicyEntries(operations, securitySchemes, topLevelSecurity) {
|
|
649
|
+
return operations.flatMap((entry) => {
|
|
650
|
+
const accessPolicy = readSecurityRequirement(entry, securitySchemes, topLevelSecurity);
|
|
651
|
+
if (!accessPolicy) return [];
|
|
652
|
+
return [{
|
|
653
|
+
apiPath: entry.apiPath,
|
|
654
|
+
funcName: entry.funcName,
|
|
655
|
+
kind: accessPolicy.kind,
|
|
656
|
+
methodUpper: entry.method.toUpperCase(),
|
|
657
|
+
operationId: entry.operationId,
|
|
658
|
+
roles: accessPolicy.roles ?? [],
|
|
659
|
+
strippedPath: entry.strippedPath
|
|
660
|
+
}];
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
function readSecurityRequirement(entry, securitySchemes, topLevelSecurity) {
|
|
664
|
+
const security = entry.operation.security ?? topLevelSecurity;
|
|
665
|
+
if (security === void 0) return null;
|
|
666
|
+
if (!Array.isArray(security)) throw new Error(`Operation "${entry.operationId}" has invalid security`);
|
|
667
|
+
if (security.length === 0) return { kind: "public" };
|
|
668
|
+
const requirement = security[0];
|
|
669
|
+
if (!isRecord(requirement)) throw new Error(`Operation "${entry.operationId}" has an invalid security requirement`);
|
|
670
|
+
for (const [schemeName, scopes] of Object.entries(requirement)) {
|
|
671
|
+
const type = securitySchemes?.[schemeName]?.type;
|
|
672
|
+
if (type === "apiKey") return { kind: "internal" };
|
|
673
|
+
if (type === "http") {
|
|
674
|
+
const roles = Array.isArray(scopes) ? scopes.filter((scope) => typeof scope === "string") : [];
|
|
675
|
+
return roles.length === 0 ? { kind: "authenticated" } : {
|
|
676
|
+
kind: "role",
|
|
677
|
+
roles
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
throw new Error(`Operation "${entry.operationId}" has an unrecognized security scheme`);
|
|
682
|
+
}
|
|
683
|
+
function isRecord(value) {
|
|
684
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
685
|
+
}
|
|
614
686
|
function resolveChannel(channel, useTypeAliases) {
|
|
615
687
|
if (!channel.typeRef) return channel;
|
|
616
688
|
return {
|
|
@@ -626,14 +698,17 @@ function renderGeneratedArtifacts(spec, options, preCollectedOperations) {
|
|
|
626
698
|
const stripPrefix = options.stripPrefix ?? true;
|
|
627
699
|
const httpClient = resolveHttpClientConfig(options.httpClient);
|
|
628
700
|
const useTypeAliases = options.typeAliases ?? false;
|
|
629
|
-
const
|
|
701
|
+
const operations = preCollectedOperations ?? collectOperations(spec, pathPrefix, stripPrefix);
|
|
702
|
+
const clientModel = buildClientRenderModelFromOperations(operations, spec, {
|
|
630
703
|
json: httpClient.jsonFunction,
|
|
631
704
|
void: httpClient.voidFunction
|
|
632
705
|
});
|
|
633
706
|
if (clientModel.operations.length === 0) throw new Error(`No paths matching prefix "${pathPrefix}" found in openapi.json`);
|
|
707
|
+
const accessPolicies = renderAccessPoliciesSource(createAccessPolicyEntries(operations, spec.components?.securitySchemes, spec.security), GENERATED_HEADER);
|
|
634
708
|
return {
|
|
635
709
|
api: renderApiSource(createApiEntries(clientModel.operations, useTypeAliases), GENERATED_HEADER),
|
|
636
710
|
client: renderClientSource(createClientRenderModel(clientModel, useTypeAliases), GENERATED_HEADER, httpClient),
|
|
711
|
+
...accessPolicies ? { accessPolicies } : {},
|
|
637
712
|
...useTypeAliases && clientModel.typeAliases.length > 0 ? { apiTypes: renderOperationTypeAliases(clientModel.typeAliases) } : {}
|
|
638
713
|
};
|
|
639
714
|
}
|
|
@@ -679,6 +754,7 @@ async function generateOpenAPIArtifacts(root, options) {
|
|
|
679
754
|
warnOnParameterLocationMismatch(operations);
|
|
680
755
|
await generateApiTypes(apiTypesSource, outputDir, options.typeAliases ?? false);
|
|
681
756
|
if (artifacts.apiTypes) writeFileSync(resolve(outputDir, "api-types.d.ts"), artifacts.apiTypes, { flag: "a" });
|
|
757
|
+
if (artifacts.accessPolicies) writeFileSync(resolve(outputDir, "access-policies.ts"), artifacts.accessPolicies);
|
|
682
758
|
writeFileSync(resolve(outputDir, "api.ts"), artifacts.api);
|
|
683
759
|
writeFileSync(resolve(outputDir, "client.ts"), artifacts.client);
|
|
684
760
|
}
|