@igniter-js/cli 0.4.93 → 0.4.95
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 +20 -10
- package/dist/index.mjs +349 -84
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# @igniter-js/
|
|
1
|
+
# @igniter-js/cli
|
|
2
2
|
|
|
3
3
|
The official Igniter.js command-line interface for scaffolding projects, generating features, wiring add-ons, and keeping your API docs in sync. It is designed for a fast developer experience, type-safe defaults, and seamless automation.
|
|
4
4
|
|
|
5
5
|
## Highlights
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- **Project bootstrapper** with interactive wizards, starter templates, and add-on setup (store, jobs, auth, bots, telemetry, MCP, database, Shadcn/UI).
|
|
8
|
+
- **Code generators** for features, controllers, procedures, schemas, and OpenAPI specs, all backed by Handlebars templates.
|
|
9
|
+
- **Schema-aware workflow** with pluggable providers (Prisma out of the box) that produce strongly typed controllers, procedures, and interfaces.
|
|
10
|
+
- **Development dashboard** powered by Ink that automatically regenerates schema/docs and streams app logs in a split view.
|
|
11
|
+
- **Template engine + registries** to extend starters, add-ons, and schema providers without touching the core.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -131,7 +131,7 @@ Generates a TypeScript client schema (const assertion + type) that mirrors your
|
|
|
131
131
|
|
|
132
132
|
### `igniter generate caller`
|
|
133
133
|
|
|
134
|
-
Generate
|
|
134
|
+
Generate an `IgniterCallerSchema` builder plus a ready-to-use Igniter Caller from an OpenAPI 3 spec.
|
|
135
135
|
|
|
136
136
|
```
|
|
137
137
|
# Remote spec
|
|
@@ -141,7 +141,19 @@ igniter generate caller --name facebook --url https://api.example.com/openapi.js
|
|
|
141
141
|
igniter generate caller --name billing --path ./openapi.yaml --output src/callers/billing
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
Outputs `schema.ts` (
|
|
144
|
+
Outputs `schema.ts` (path-first schema builder with registry, `$Infer` helpers, and derived types) and
|
|
145
|
+
`index.ts` (preconfigured caller) under `src/callers/<hostname>` by default, ready to use with
|
|
146
|
+
`@igniter-js/caller`.
|
|
147
|
+
|
|
148
|
+
Example usage:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
import { facebookCallerSchemas } from "./src/callers/api.example.com/schema"
|
|
152
|
+
|
|
153
|
+
type ProductsResponse = ReturnType<
|
|
154
|
+
typeof facebookCallerSchemas.$Infer.Response<"/products", "GET", 200>
|
|
155
|
+
>
|
|
156
|
+
```
|
|
145
157
|
|
|
146
158
|
### `igniter dev`
|
|
147
159
|
|
|
@@ -258,5 +270,3 @@ MIT © Felipe Barcelos and the Igniter.js contributors.
|
|
|
258
270
|
|
|
259
271
|
|
|
260
272
|
|
|
261
|
-
|
|
262
|
-
|
package/dist/index.mjs
CHANGED
|
@@ -3552,7 +3552,7 @@ import path23 from "path";
|
|
|
3552
3552
|
import * as p13 from "@clack/prompts";
|
|
3553
3553
|
import yaml from "js-yaml";
|
|
3554
3554
|
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
3555
|
-
var SUPPORTED_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
3555
|
+
var SUPPORTED_METHODS = ["get", "post", "put", "patch", "delete", "head"];
|
|
3556
3556
|
async function handleGenerateCallerAction(options) {
|
|
3557
3557
|
p13.intro("Generate Igniter Caller");
|
|
3558
3558
|
try {
|
|
@@ -3568,26 +3568,28 @@ async function handleGenerateCallerAction(options) {
|
|
|
3568
3568
|
);
|
|
3569
3569
|
const prefix = Casing.toPascalCase(callerName);
|
|
3570
3570
|
const converter = new SchemaConverter(prefix, parsedDoc.components);
|
|
3571
|
-
const pathsCode = buildSchemasObject(parsedDoc, converter);
|
|
3572
3571
|
const componentsCode = converter.renderComponentSchemas();
|
|
3573
|
-
const schemaConstName = `${
|
|
3574
|
-
const schemaTypeName = `${prefix}
|
|
3572
|
+
const schemaConstName = `${Casing.toCamelCase(callerName)}CallerSchemas`;
|
|
3573
|
+
const schemaTypeName = `${prefix}CallerSchemas`;
|
|
3574
|
+
const { builderCode, typeAliases } = buildSchemaBuilder(
|
|
3575
|
+
parsedDoc,
|
|
3576
|
+
converter,
|
|
3577
|
+
schemaConstName
|
|
3578
|
+
);
|
|
3575
3579
|
const schemaFileContents = [
|
|
3576
3580
|
"/* eslint-disable */",
|
|
3577
3581
|
"/* prettier-ignore */",
|
|
3578
|
-
"",
|
|
3579
3582
|
"/**",
|
|
3580
3583
|
" * @generated by @igniter-js/cli",
|
|
3581
3584
|
" * This file was automatically created from your OpenAPI spec.",
|
|
3582
3585
|
" * Do not edit manually; regenerate via `igniter generate caller`.",
|
|
3583
3586
|
" */",
|
|
3584
|
-
"",
|
|
3585
3587
|
'import { z } from "zod";',
|
|
3588
|
+
'import { IgniterCallerSchema } from "@igniter-js/caller";',
|
|
3586
3589
|
componentsCode,
|
|
3587
|
-
|
|
3588
|
-
"",
|
|
3589
|
-
`export const ${schemaConstName} = schemas;`,
|
|
3590
|
+
builderCode,
|
|
3590
3591
|
`export type ${schemaTypeName} = typeof ${schemaConstName};`,
|
|
3592
|
+
typeAliases,
|
|
3591
3593
|
""
|
|
3592
3594
|
].filter(Boolean).join("\n");
|
|
3593
3595
|
const baseUrl = inferBaseUrl(source, parsedDoc);
|
|
@@ -3613,9 +3615,7 @@ async function handleGenerateCallerAction(options) {
|
|
|
3613
3615
|
p13.log.success(
|
|
3614
3616
|
`Schemas and caller created at ${path23.relative(process.cwd(), outputDir)}`
|
|
3615
3617
|
);
|
|
3616
|
-
p13.log.info(
|
|
3617
|
-
`Exported caller: ${callerVar} (schemas: ${schemaConstName})`
|
|
3618
|
-
);
|
|
3618
|
+
p13.log.info(`Exported caller: ${callerVar} (schemas: ${schemaConstName})`);
|
|
3619
3619
|
} catch (error) {
|
|
3620
3620
|
const message = error instanceof Error ? error.message : String(error);
|
|
3621
3621
|
p13.log.error(message);
|
|
@@ -3684,13 +3684,12 @@ async function resolveSource(options) {
|
|
|
3684
3684
|
async function loadOpenApiDocument(source) {
|
|
3685
3685
|
const raw = await loadRawContent(source);
|
|
3686
3686
|
const parsed = parseOpenApi(raw);
|
|
3687
|
-
|
|
3687
|
+
const version = parsed.openapi ?? "";
|
|
3688
|
+
if (typeof version !== "string" || !version.startsWith("3.")) {
|
|
3688
3689
|
throw new Error("Only OpenAPI 3.x documents are supported.");
|
|
3689
3690
|
}
|
|
3690
|
-
const
|
|
3691
|
-
|
|
3692
|
-
);
|
|
3693
|
-
return dereferenced;
|
|
3691
|
+
const bundled = await SwaggerParser.bundle(parsed);
|
|
3692
|
+
return bundled;
|
|
3694
3693
|
}
|
|
3695
3694
|
async function loadRawContent(source) {
|
|
3696
3695
|
if (source.type === "url") {
|
|
@@ -3756,15 +3755,34 @@ var SchemaConverter = class {
|
|
|
3756
3755
|
}
|
|
3757
3756
|
renderComponentSchemas() {
|
|
3758
3757
|
const entries = [];
|
|
3759
|
-
const names =
|
|
3758
|
+
const names = this.listComponents();
|
|
3760
3759
|
for (const name of names) {
|
|
3761
3760
|
const identifier = this.componentIdentifier(name);
|
|
3762
3761
|
const expression = this.convertWithCache(name);
|
|
3763
|
-
|
|
3762
|
+
const registryKey = this.registryKey(name);
|
|
3763
|
+
entries.push(
|
|
3764
|
+
[
|
|
3765
|
+
"/**",
|
|
3766
|
+
` * Schema: ${registryKey}`,
|
|
3767
|
+
` * Source: openapi#/components/schemas/${name}`,
|
|
3768
|
+
" */",
|
|
3769
|
+
`const ${identifier} = ${expression};`,
|
|
3770
|
+
""
|
|
3771
|
+
].join("\n")
|
|
3772
|
+
);
|
|
3764
3773
|
}
|
|
3765
3774
|
return entries.length ? `${entries.join("\n")}
|
|
3766
3775
|
` : "";
|
|
3767
3776
|
}
|
|
3777
|
+
listComponents() {
|
|
3778
|
+
return Object.keys(this.components?.schemas ?? {}).sort();
|
|
3779
|
+
}
|
|
3780
|
+
registryKey(name) {
|
|
3781
|
+
return Casing.toPascalCase(name);
|
|
3782
|
+
}
|
|
3783
|
+
componentName(name) {
|
|
3784
|
+
return this.componentIdentifier(name);
|
|
3785
|
+
}
|
|
3768
3786
|
convert(schema) {
|
|
3769
3787
|
if ("$ref" in schema) {
|
|
3770
3788
|
const refName = this.refName(schema.$ref);
|
|
@@ -3772,13 +3790,15 @@ var SchemaConverter = class {
|
|
|
3772
3790
|
this.convertWithCache(refName);
|
|
3773
3791
|
return this.wrapLazyIfNeeded(refName, identifier);
|
|
3774
3792
|
}
|
|
3793
|
+
const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
3794
|
+
const nullable = isNullable(schema);
|
|
3775
3795
|
if (schema.oneOf?.length) {
|
|
3776
3796
|
const parts = schema.oneOf.map((item) => this.convert(item));
|
|
3777
|
-
return `z.union([${parts.join(", ")}])
|
|
3797
|
+
return wrapNullable(`z.union([${parts.join(", ")}])`, nullable);
|
|
3778
3798
|
}
|
|
3779
3799
|
if (schema.anyOf?.length) {
|
|
3780
3800
|
const parts = schema.anyOf.map((item) => this.convert(item));
|
|
3781
|
-
return `z.union([${parts.join(", ")}])
|
|
3801
|
+
return wrapNullable(`z.union([${parts.join(", ")}])`, nullable);
|
|
3782
3802
|
}
|
|
3783
3803
|
if (schema.allOf?.length) {
|
|
3784
3804
|
const [first, ...rest] = schema.allOf;
|
|
@@ -3786,31 +3806,29 @@ var SchemaConverter = class {
|
|
|
3786
3806
|
for (const part of rest) {
|
|
3787
3807
|
expr = `z.intersection(${expr}, ${this.convert(part)})`;
|
|
3788
3808
|
}
|
|
3789
|
-
return
|
|
3809
|
+
return wrapNullable(expr, nullable);
|
|
3790
3810
|
}
|
|
3791
|
-
switch (
|
|
3811
|
+
switch (schemaType) {
|
|
3792
3812
|
case "string":
|
|
3793
3813
|
if (schema.enum) {
|
|
3794
3814
|
const values = schema.enum.map((v) => JSON.stringify(String(v)));
|
|
3795
|
-
return `z.enum([${values.join(", ")}])
|
|
3815
|
+
return wrapNullable(`z.enum([${values.join(", ")}])`, nullable);
|
|
3796
3816
|
}
|
|
3797
|
-
return
|
|
3817
|
+
return wrapNullable("z.string()", nullable);
|
|
3798
3818
|
case "number":
|
|
3799
3819
|
case "integer": {
|
|
3800
3820
|
let expr = "z.number()";
|
|
3801
|
-
if (
|
|
3821
|
+
if (schemaType === "integer") {
|
|
3802
3822
|
expr = `${expr}.int()`;
|
|
3803
3823
|
}
|
|
3804
|
-
|
|
3805
|
-
expr = `${expr}.nullable()`;
|
|
3806
|
-
}
|
|
3807
|
-
return expr;
|
|
3824
|
+
return wrapNullable(expr, nullable);
|
|
3808
3825
|
}
|
|
3809
3826
|
case "boolean":
|
|
3810
|
-
return
|
|
3827
|
+
return wrapNullable("z.boolean()", nullable);
|
|
3811
3828
|
case "array": {
|
|
3812
|
-
const
|
|
3813
|
-
|
|
3829
|
+
const arraySchema = schema;
|
|
3830
|
+
const items = arraySchema.items ? this.convert(arraySchema.items) : "z.unknown()";
|
|
3831
|
+
return wrapNullable(`z.array(${items})`, nullable);
|
|
3814
3832
|
}
|
|
3815
3833
|
case "object": {
|
|
3816
3834
|
const properties = schema.properties ?? {};
|
|
@@ -3824,10 +3842,7 @@ var SchemaConverter = class {
|
|
|
3824
3842
|
const apValue = schema.additionalProperties === true ? "z.unknown()" : this.convert(schema.additionalProperties);
|
|
3825
3843
|
base = `${base}.catchall(${apValue})`;
|
|
3826
3844
|
}
|
|
3827
|
-
|
|
3828
|
-
base = `${base}.nullable()`;
|
|
3829
|
-
}
|
|
3830
|
-
return base;
|
|
3845
|
+
return wrapNullable(base, nullable);
|
|
3831
3846
|
}
|
|
3832
3847
|
default:
|
|
3833
3848
|
return "z.unknown()";
|
|
@@ -3848,9 +3863,8 @@ var SchemaConverter = class {
|
|
|
3848
3863
|
this.inProgress.add(name);
|
|
3849
3864
|
const expression = this.convert(schema);
|
|
3850
3865
|
this.inProgress.delete(name);
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
return finalized;
|
|
3866
|
+
this.generated.set(name, expression);
|
|
3867
|
+
return expression;
|
|
3854
3868
|
}
|
|
3855
3869
|
componentIdentifier(name) {
|
|
3856
3870
|
return `${this.prefix}${Casing.toPascalCase(name)}Schema`;
|
|
@@ -3869,71 +3883,271 @@ var SchemaConverter = class {
|
|
|
3869
3883
|
return identifier;
|
|
3870
3884
|
}
|
|
3871
3885
|
};
|
|
3872
|
-
function
|
|
3873
|
-
const
|
|
3886
|
+
function buildSchemaBuilder(doc, converter, schemaConstName) {
|
|
3887
|
+
const usedNames = /* @__PURE__ */ new Map();
|
|
3888
|
+
const operations = [];
|
|
3889
|
+
const pathBlocks = [];
|
|
3890
|
+
let needsVoid = false;
|
|
3874
3891
|
const sortedPaths = Object.keys(doc.paths || {}).sort();
|
|
3875
3892
|
for (const rawPath of sortedPaths) {
|
|
3876
3893
|
const pathItem = doc.paths?.[rawPath];
|
|
3877
3894
|
if (!pathItem) continue;
|
|
3878
|
-
const
|
|
3895
|
+
const methodBlocks = [];
|
|
3879
3896
|
for (const method of SUPPORTED_METHODS) {
|
|
3880
3897
|
const operation = pathItem[method];
|
|
3881
3898
|
if (!operation) continue;
|
|
3882
|
-
const
|
|
3883
|
-
const requestBody = buildRequestBody(
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
const
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3899
|
+
const normalizedPath2 = normalizePath(rawPath);
|
|
3900
|
+
const requestBody = buildRequestBody(
|
|
3901
|
+
operation.requestBody,
|
|
3902
|
+
converter,
|
|
3903
|
+
doc
|
|
3904
|
+
);
|
|
3905
|
+
const responses = buildResponses(operation.responses, converter, doc);
|
|
3906
|
+
const typeBaseName = ensureUniqueTypeName(
|
|
3907
|
+
buildOperationTypeName(rawPath, method, operation.operationId),
|
|
3908
|
+
usedNames
|
|
3909
|
+
);
|
|
3910
|
+
operations.push({
|
|
3911
|
+
path: normalizedPath2,
|
|
3912
|
+
method: method.toUpperCase(),
|
|
3913
|
+
request: Boolean(requestBody),
|
|
3914
|
+
responses: responses.entries,
|
|
3915
|
+
typeName: typeBaseName
|
|
3916
|
+
});
|
|
3917
|
+
const docBlock = buildMethodDocBlock({
|
|
3918
|
+
method: method.toUpperCase(),
|
|
3919
|
+
path: normalizedPath2,
|
|
3920
|
+
summary: operation.summary ?? operation.description,
|
|
3921
|
+
tags: operation.tags,
|
|
3922
|
+
operationId: operation.operationId,
|
|
3923
|
+
requestLabel: requestBody?.label,
|
|
3924
|
+
responses: responses.entries,
|
|
3925
|
+
source: `openapi#/paths/${encodeJsonPointerPath(rawPath)}/${method}`
|
|
3926
|
+
});
|
|
3927
|
+
const methodCall = buildMethodCall(method, {
|
|
3928
|
+
request: requestBody?.expression,
|
|
3929
|
+
responses: responses.code,
|
|
3930
|
+
doc: operation.summary ?? operation.description,
|
|
3931
|
+
tags: operation.tags,
|
|
3932
|
+
operationId: operation.operationId
|
|
3933
|
+
});
|
|
3934
|
+
methodBlocks.push(indent(`${docBlock}
|
|
3935
|
+
${methodCall}`, 3));
|
|
3936
|
+
needsVoid = needsVoid || responses.usesVoid;
|
|
3895
3937
|
}
|
|
3896
|
-
if (!
|
|
3938
|
+
if (!methodBlocks.length) continue;
|
|
3897
3939
|
const normalizedPath = normalizePath(rawPath);
|
|
3898
|
-
const
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
"
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
}
|
|
3940
|
+
const pathLines = [
|
|
3941
|
+
` .path(${JSON.stringify(normalizedPath)}, (path) =>`,
|
|
3942
|
+
" path",
|
|
3943
|
+
methodBlocks.join("\n"),
|
|
3944
|
+
" )"
|
|
3945
|
+
];
|
|
3946
|
+
pathBlocks.push(pathLines.join("\n"));
|
|
3947
|
+
}
|
|
3948
|
+
const builderLines = [];
|
|
3949
|
+
builderLines.push(`export const ${schemaConstName} = IgniterCallerSchema.create()`);
|
|
3950
|
+
const componentNames = converter.listComponents();
|
|
3951
|
+
for (const name of componentNames) {
|
|
3952
|
+
const registryKey = converter.registryKey(name);
|
|
3953
|
+
const identifier = converter.componentName(name);
|
|
3954
|
+
builderLines.push(` .schema(${JSON.stringify(registryKey)}, ${identifier})`);
|
|
3955
|
+
}
|
|
3956
|
+
if (needsVoid) {
|
|
3957
|
+
builderLines.push(` .schema("Void", z.void(), { internal: true })`);
|
|
3958
|
+
}
|
|
3959
|
+
if (pathBlocks.length) {
|
|
3960
|
+
builderLines.push(pathBlocks.join("\n"));
|
|
3961
|
+
}
|
|
3962
|
+
builderLines.push(" .build()");
|
|
3963
|
+
return {
|
|
3964
|
+
builderCode: `${builderLines.join("\n")}
|
|
3965
|
+
`,
|
|
3966
|
+
typeAliases: buildTypeAliases(operations, schemaConstName)
|
|
3967
|
+
};
|
|
3908
3968
|
}
|
|
3909
|
-
function
|
|
3910
|
-
|
|
3911
|
-
|
|
3969
|
+
function buildMethodDocBlock(params) {
|
|
3970
|
+
const lines = [];
|
|
3971
|
+
lines.push("/**");
|
|
3972
|
+
lines.push(` * ${params.method} ${params.path}`);
|
|
3973
|
+
if (params.summary) {
|
|
3974
|
+
lines.push(` * Summary: ${params.summary}`);
|
|
3975
|
+
}
|
|
3976
|
+
if (params.tags?.length) {
|
|
3977
|
+
lines.push(` * Tags: ${params.tags.join(", ")}`);
|
|
3978
|
+
}
|
|
3979
|
+
if (params.operationId) {
|
|
3980
|
+
lines.push(` * OperationId: ${params.operationId}`);
|
|
3981
|
+
}
|
|
3982
|
+
if (params.requestLabel) {
|
|
3983
|
+
lines.push(` * Request: ${params.requestLabel}`);
|
|
3984
|
+
}
|
|
3985
|
+
if (params.responses.length) {
|
|
3986
|
+
lines.push(" * Responses:");
|
|
3987
|
+
for (const response of params.responses) {
|
|
3988
|
+
lines.push(` * - ${response.status}: ${response.label}`);
|
|
3989
|
+
}
|
|
3912
3990
|
}
|
|
3991
|
+
lines.push(` * Source: ${params.source}`);
|
|
3992
|
+
lines.push(" */");
|
|
3993
|
+
return lines.join("\n");
|
|
3994
|
+
}
|
|
3995
|
+
function buildMethodCall(method, params) {
|
|
3996
|
+
const lines = [];
|
|
3997
|
+
lines.push(`.${method}({`);
|
|
3998
|
+
if (params.request) {
|
|
3999
|
+
lines.push(` request: ${params.request},`);
|
|
4000
|
+
}
|
|
4001
|
+
lines.push(" responses: {");
|
|
4002
|
+
lines.push(params.responses);
|
|
4003
|
+
lines.push(" },");
|
|
4004
|
+
if (params.doc) {
|
|
4005
|
+
lines.push(` doc: ${JSON.stringify(params.doc)},`);
|
|
4006
|
+
}
|
|
4007
|
+
if (params.tags?.length) {
|
|
4008
|
+
lines.push(` tags: ${JSON.stringify(params.tags)},`);
|
|
4009
|
+
}
|
|
4010
|
+
if (params.operationId) {
|
|
4011
|
+
lines.push(` operationId: ${JSON.stringify(params.operationId)},`);
|
|
4012
|
+
}
|
|
4013
|
+
lines.push("})");
|
|
4014
|
+
return lines.join("\n");
|
|
4015
|
+
}
|
|
4016
|
+
function buildResponses(responses, converter, doc) {
|
|
4017
|
+
const builder = new CodeBuilder();
|
|
3913
4018
|
const entries = [];
|
|
4019
|
+
let usesVoid = false;
|
|
4020
|
+
if (!responses || !Object.keys(responses).length) {
|
|
4021
|
+
const expression = `path.ref("Void").schema`;
|
|
4022
|
+
builder.line(indent(`200: ${expression},`, 2));
|
|
4023
|
+
entries.push({
|
|
4024
|
+
status: "200",
|
|
4025
|
+
statusLiteral: "200",
|
|
4026
|
+
label: "Void"
|
|
4027
|
+
});
|
|
4028
|
+
return { code: builder.toString(), entries, usesVoid: true };
|
|
4029
|
+
}
|
|
3914
4030
|
const sorted = Object.keys(responses).sort();
|
|
3915
4031
|
for (const status of sorted) {
|
|
3916
4032
|
const responseOrRef = responses[status];
|
|
3917
4033
|
if (!responseOrRef) continue;
|
|
3918
|
-
const
|
|
3919
|
-
const schema = resolveSchemaFromResponse(
|
|
3920
|
-
const
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
4034
|
+
const resolved = resolveResponse(responseOrRef, doc);
|
|
4035
|
+
const schema = resolveSchemaFromResponse(resolved);
|
|
4036
|
+
const resolvedSchema = resolveSchemaExpression(schema, converter);
|
|
4037
|
+
builder.line(indent(`${formatStatusKey(status)}: ${resolvedSchema.expression},`, 2));
|
|
4038
|
+
entries.push({
|
|
4039
|
+
status: String(status),
|
|
4040
|
+
statusLiteral: formatStatusType(status),
|
|
4041
|
+
label: resolvedSchema.label
|
|
4042
|
+
});
|
|
4043
|
+
usesVoid = usesVoid || resolvedSchema.usesVoid;
|
|
3925
4044
|
}
|
|
3926
|
-
return
|
|
3927
|
-
${indentLines(entries.join(",\n"), 2)}
|
|
3928
|
-
}`;
|
|
4045
|
+
return { code: builder.toString(), entries, usesVoid };
|
|
3929
4046
|
}
|
|
3930
|
-
function buildRequestBody(requestBody, converter) {
|
|
4047
|
+
function buildRequestBody(requestBody, converter, doc) {
|
|
3931
4048
|
if (!requestBody) return null;
|
|
3932
|
-
const resolved = "$ref" in requestBody ?
|
|
4049
|
+
const resolved = "$ref" in requestBody ? resolveRequestBody(requestBody, doc) : requestBody;
|
|
3933
4050
|
if (!resolved?.content) return null;
|
|
3934
4051
|
const schema = selectJsonSchema(resolved.content);
|
|
3935
4052
|
if (!schema) return null;
|
|
3936
|
-
return
|
|
4053
|
+
return resolveSchemaExpression(schema, converter);
|
|
4054
|
+
}
|
|
4055
|
+
function resolveSchemaExpression(schema, converter) {
|
|
4056
|
+
if (!schema) {
|
|
4057
|
+
return {
|
|
4058
|
+
expression: `path.ref("Void").schema`,
|
|
4059
|
+
label: "Void",
|
|
4060
|
+
usesVoid: true
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
const refName = extractSchemaRef(schema);
|
|
4064
|
+
if (refName) {
|
|
4065
|
+
const registryKey = converter.registryKey(refName);
|
|
4066
|
+
const base = `path.ref(${JSON.stringify(registryKey)}).schema`;
|
|
4067
|
+
return {
|
|
4068
|
+
expression: wrapNullable(base, isNullable(schema)),
|
|
4069
|
+
label: registryKey,
|
|
4070
|
+
usesVoid: false
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
const arrayRefName = extractArraySchemaRef(schema);
|
|
4074
|
+
if (arrayRefName) {
|
|
4075
|
+
const registryKey = converter.registryKey(arrayRefName);
|
|
4076
|
+
const base = `path.ref(${JSON.stringify(registryKey)}).array()`;
|
|
4077
|
+
return {
|
|
4078
|
+
expression: wrapNullable(base, isNullable(schema)),
|
|
4079
|
+
label: `${registryKey}[]`,
|
|
4080
|
+
usesVoid: false
|
|
4081
|
+
};
|
|
4082
|
+
}
|
|
4083
|
+
return {
|
|
4084
|
+
expression: converter.convert(schema),
|
|
4085
|
+
label: "InlineSchema",
|
|
4086
|
+
usesVoid: false
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
function buildTypeAliases(operations, schemaConstName) {
|
|
4090
|
+
if (!operations.length) return "";
|
|
4091
|
+
const lines = [];
|
|
4092
|
+
lines.push("// Derived types");
|
|
4093
|
+
for (const operation of operations) {
|
|
4094
|
+
const pathLiteral = JSON.stringify(operation.path);
|
|
4095
|
+
const methodLiteral = JSON.stringify(operation.method);
|
|
4096
|
+
if (operation.request) {
|
|
4097
|
+
lines.push(
|
|
4098
|
+
`export type ${operation.typeName}Request = ReturnType<typeof ${schemaConstName}.$Infer.Request<${pathLiteral}, ${methodLiteral}>>;`
|
|
4099
|
+
);
|
|
4100
|
+
}
|
|
4101
|
+
lines.push(
|
|
4102
|
+
`export type ${operation.typeName}Responses = ReturnType<typeof ${schemaConstName}.$Infer.Responses<${pathLiteral}, ${methodLiteral}>>;`
|
|
4103
|
+
);
|
|
4104
|
+
for (const response of operation.responses) {
|
|
4105
|
+
const suffix = formatStatusSuffix(response.status);
|
|
4106
|
+
lines.push(
|
|
4107
|
+
`export type ${operation.typeName}Response${suffix} = ReturnType<typeof ${schemaConstName}.$Infer.Response<${pathLiteral}, ${methodLiteral}, ${response.statusLiteral}>>;`
|
|
4108
|
+
);
|
|
4109
|
+
}
|
|
4110
|
+
lines.push("");
|
|
4111
|
+
}
|
|
4112
|
+
return lines.join("\n");
|
|
4113
|
+
}
|
|
4114
|
+
function buildOperationTypeName(rawPath, method, operationId) {
|
|
4115
|
+
if (operationId) {
|
|
4116
|
+
return Casing.toPascalCase(operationId);
|
|
4117
|
+
}
|
|
4118
|
+
const normalizedPath = normalizePath(rawPath);
|
|
4119
|
+
const segments = normalizedPath.split("/").filter(Boolean).map((segment) => segment.startsWith(":") ? `by-${segment.slice(1)}` : segment);
|
|
4120
|
+
return Casing.toPascalCase([...segments, method].join("-"));
|
|
4121
|
+
}
|
|
4122
|
+
function ensureUniqueTypeName(baseName, used) {
|
|
4123
|
+
const count = used.get(baseName) ?? 0;
|
|
4124
|
+
if (count === 0) {
|
|
4125
|
+
used.set(baseName, 1);
|
|
4126
|
+
return baseName;
|
|
4127
|
+
}
|
|
4128
|
+
const next = `${baseName}${count + 1}`;
|
|
4129
|
+
used.set(baseName, count + 1);
|
|
4130
|
+
return next;
|
|
4131
|
+
}
|
|
4132
|
+
function encodeJsonPointerPath(pathname) {
|
|
4133
|
+
return pathname.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
4134
|
+
}
|
|
4135
|
+
function extractSchemaRef(schema) {
|
|
4136
|
+
if (!schema || typeof schema !== "object") return null;
|
|
4137
|
+
if (!("$ref" in schema)) return null;
|
|
4138
|
+
return extractComponentSchemaName(schema.$ref);
|
|
4139
|
+
}
|
|
4140
|
+
function extractArraySchemaRef(schema) {
|
|
4141
|
+
if (!schema || typeof schema !== "object") return null;
|
|
4142
|
+
if (schema.type !== "array") return null;
|
|
4143
|
+
const items = schema.items;
|
|
4144
|
+
if (!items || typeof items !== "object") return null;
|
|
4145
|
+
if (!("$ref" in items)) return null;
|
|
4146
|
+
return extractComponentSchemaName(items.$ref);
|
|
4147
|
+
}
|
|
4148
|
+
function extractComponentSchemaName(ref) {
|
|
4149
|
+
const match = ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
4150
|
+
return match ? match[1] : null;
|
|
3937
4151
|
}
|
|
3938
4152
|
function selectJsonSchema(content) {
|
|
3939
4153
|
if (content["application/json"]?.schema) {
|
|
@@ -3951,10 +4165,61 @@ function resolveSchemaFromResponse(response) {
|
|
|
3951
4165
|
function normalizePath(pathname) {
|
|
3952
4166
|
return pathname.replace(/{(.*?)}/g, ":$1");
|
|
3953
4167
|
}
|
|
3954
|
-
function
|
|
4168
|
+
function indent(value, depth) {
|
|
3955
4169
|
const pad = " ".repeat(depth);
|
|
3956
4170
|
return value.split("\n").map((line) => line ? pad + line : line).join("\n");
|
|
3957
4171
|
}
|
|
4172
|
+
function isNullable(schema) {
|
|
4173
|
+
if (!schema || typeof schema !== "object") return false;
|
|
4174
|
+
return Boolean(schema.nullable);
|
|
4175
|
+
}
|
|
4176
|
+
function wrapNullable(expr, nullable) {
|
|
4177
|
+
return nullable ? `${expr}.nullable()` : expr;
|
|
4178
|
+
}
|
|
4179
|
+
function formatStatusKey(status) {
|
|
4180
|
+
const trimmed = String(status).trim();
|
|
4181
|
+
const numeric = Number(trimmed);
|
|
4182
|
+
if (!Number.isNaN(numeric) && `${numeric}` === trimmed) {
|
|
4183
|
+
return trimmed;
|
|
4184
|
+
}
|
|
4185
|
+
return JSON.stringify(trimmed);
|
|
4186
|
+
}
|
|
4187
|
+
function formatStatusType(status) {
|
|
4188
|
+
return formatStatusKey(status);
|
|
4189
|
+
}
|
|
4190
|
+
function formatStatusSuffix(status) {
|
|
4191
|
+
const safe = String(status).replace(/[^a-zA-Z0-9]+/g, "-");
|
|
4192
|
+
const pascal = Casing.toPascalCase(safe);
|
|
4193
|
+
return pascal || "Unknown";
|
|
4194
|
+
}
|
|
4195
|
+
function resolveResponse(response, doc) {
|
|
4196
|
+
if ("$ref" in response) {
|
|
4197
|
+
return getComponent(doc, response.$ref, "responses");
|
|
4198
|
+
}
|
|
4199
|
+
return response;
|
|
4200
|
+
}
|
|
4201
|
+
function resolveRequestBody(requestBody, doc) {
|
|
4202
|
+
return getComponent(doc, requestBody.$ref, "requestBodies");
|
|
4203
|
+
}
|
|
4204
|
+
function getComponent(doc, ref, type) {
|
|
4205
|
+
const match = ref.match(/^#\/components\/([^/]+)\/(.+)$/);
|
|
4206
|
+
if (!match) return void 0;
|
|
4207
|
+
const [, category, name] = match;
|
|
4208
|
+
if (category !== type) return void 0;
|
|
4209
|
+
const component = doc.components?.[type]?.[name];
|
|
4210
|
+
return component;
|
|
4211
|
+
}
|
|
4212
|
+
var CodeBuilder = class {
|
|
4213
|
+
constructor() {
|
|
4214
|
+
this.lines = [];
|
|
4215
|
+
}
|
|
4216
|
+
line(text4) {
|
|
4217
|
+
this.lines.push(text4);
|
|
4218
|
+
}
|
|
4219
|
+
toString() {
|
|
4220
|
+
return this.lines.join("\n");
|
|
4221
|
+
}
|
|
4222
|
+
};
|
|
3958
4223
|
|
|
3959
4224
|
// src/commands/generate/caller/index.ts
|
|
3960
4225
|
var callerCommand = new Command7().command("caller").description("Generate Igniter Caller schemas from an OpenAPI spec").option("--name <name>", "Name used to prefix generated schemas and caller export").option("--url <url>", "URL to the OpenAPI document").option("--path <path>", "Local path to the OpenAPI document").option(
|