Skip to content
Open
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
8 changes: 5 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ jobs:
id-token: write
attestations: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- uses: pnpm/action-setup@v6
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
with:
version: 12.4.0

- uses: actions/setup-node@v6
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: 26
registry-url: https://registry.npmjs.org/
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ jobs:
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
with:
version: 12.4.0

- uses: actions/setup-node@v4
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: ${{ matrix.version }}
cache: "pnpm"
Expand Down
69 changes: 36 additions & 33 deletions __tests__/codegen-regressions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { oas31 } from "openapi3-ts";
import { expect, test } from "vitest";
import { assert, expect, test } from "vitest";
import { processOpenApiDocument } from "../lib/process-document.ts";

const respOk = {
Expand Down Expand Up @@ -78,9 +78,10 @@ test("optional query params do not carry `| undefined` in their property type",

const result = await processOpenApiDocument("/tmp/whatever", schema);
const typesText = result.typesFile.getText();
const queryBlock =
typesText.match(/export type ListFilesCommandQuery = \{[\s\S]*?\};/)?.[0] ??
"";
const queryBlock = typesText.match(
/export type ListFilesCommandQuery = \{[\s\S]*?\};/,
)?.[0];
assert.isDefined(queryBlock, "ListFilesCommandQuery");

expect(queryBlock).toContain("purpose?: string");
expect(queryBlock).toContain("limit?: `${number}`");
Expand Down Expand Up @@ -138,11 +139,16 @@ test("AllInputs union includes every command's Input (no silent drops)", async (
const mainText = result.mainFile.getText();
const commandsText = result.commandsFile.getText();

const allInputsBlock = mainText.match(/type AllInputs =[\s\S]*?;/)?.[0] ?? "";
const allInputsBlock = mainText.match(/type AllInputs =[\s\S]*?;/)?.[0];
assert.isDefined(allInputsBlock, "AllInputs");

const commandNames = [
...commandsText.matchAll(/^export class (\w+Command) extends Command</gm),
].map((m) => m[1] ?? "");
].map(([, name]) => {
assert.isDefined(name, "command name");

return name;
});

expect(commandNames.length).toBeGreaterThan(0);
const missing = commandNames.filter(
Expand All @@ -151,10 +157,7 @@ test("AllInputs union includes every command's Input (no silent drops)", async (
expect(missing).toEqual([]);
});

function docWithSchema(
name: string,
schema: oas31.SchemaObject,
): oas31.OpenAPIObject {
function docWithSchema(name: string, schema: oas31.SchemaObject) {
return {
openapi: "3.1.0",
info: { title: "Test", version: "1.0.0" },
Expand Down Expand Up @@ -335,11 +338,11 @@ test("an array request body with parameters stays readable as both", async () =>
};

const result = await processOpenApiDocument("/tmp/whatever", schema);
const inputBlock =
result.typesFile
.getTypeAlias("PluginPullCommandInput")
?.getTypeNode()
?.getText() ?? "";
const inputBlock = result.typesFile
.getTypeAlias("PluginPullCommandInput")
?.getTypeNode()
?.getText();
assert.isDefined(inputBlock, "PluginPullCommandInput");

expect(inputBlock).toContain("PluginPullCommandBodyWrapper");
expect(result.commandsFile.getText()).toMatch(
Expand Down Expand Up @@ -390,11 +393,11 @@ test("nested query param members get the same stringish treatment as top-level o
};

const result = await processOpenApiDocument("/tmp/whatever", schema);
const queryBlock =
result.typesFile
.getTypeAlias("SearchCommandQuery")
?.getTypeNode()
?.getText() ?? "";
const queryBlock = result.typesFile
.getTypeAlias("SearchCommandQuery")
?.getTypeNode()
?.getText();
assert.isDefined(queryBlock, "SearchCommandQuery");

expect(queryBlock).toMatch(/limit\?: `\$\{number\}`/);
expect(queryBlock).toMatch(/"age"\?: `\$\{number\}`/);
Expand Down Expand Up @@ -441,11 +444,11 @@ test("json request body members keep their real JSON types, nested included", as
};

const result = await processOpenApiDocument("/tmp/whatever", schema);
const bodyBlock =
result.typesFile
.getTypeAlias("CreateContainerCommandJsonBody")
?.getTypeNode()
?.getText() ?? "";
const bodyBlock = result.typesFile
.getTypeAlias("CreateContainerCommandJsonBody")
?.getTypeNode()
?.getText();
assert.isDefined(bodyBlock, "CreateContainerCommandJsonBody");

expect(bodyBlock).toMatch(/"tty"\?: boolean/);
expect(bodyBlock).toMatch(/"retries"\?: number/);
Expand Down Expand Up @@ -502,14 +505,14 @@ test("a oneOf query param keeps the stringish wire types in every branch", async
};

const result = await processOpenApiDocument("/tmp/whatever", schema);
const queryBlock =
result.typesFile
.getTypeAlias("ListThingsCommandQuery")
?.getTypeNode()
?.getText() ?? "";

// The composition branch has to forward the codegen options the same way the
// array and object branches do, or a oneOf collapses back to the JSON types
const queryBlock = result.typesFile
.getTypeAlias("ListThingsCommandQuery")
?.getTypeNode()
?.getText();
assert.isDefined(queryBlock, "ListThingsCommandQuery");

// Composition has to forward the codegen options the same way the array
// and object branches do, or a oneOf collapses back to the JSON types
expect(queryBlock).toContain("`${number}`");
expect(queryBlock).toContain('"true" | "false"');
expect(queryBlock).not.toMatch(/size\?: number/);
Expand Down
10 changes: 6 additions & 4 deletions __tests__/nullables.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ test("RFC 3339 temporal formats", async () => {
expect(valibot.match(/v\.regex\(/g)?.length).toBe(8);
expect(valibot.match(/v\.custom</g)?.length).toBe(8);

// The hint type matches the generated TS type (date shown)
// hint type matches the generated TS type, shown here for date
expect(valibot).toContain(
"v.custom<`${number}-${number}-${number}`>(() => true)",
);
Expand All @@ -152,14 +152,14 @@ test("enums short-circuit type constraints (picklist only)", async () => {
paths: {},
components: {
schemas: {
// integer enum with a range constraint: must NOT emit minValue/integer
// integer enum with a range constraint, which skips minValue and integer
IntegerEnum: {
type: "integer",
enum: [0, 1, 2],
minimum: 0,
maximum: 9,
},
// string enum carrying minLength/format: must NOT emit minLength/regex
// string enum with minLength and format, which skips minLength and regex
StringEnum: {
type: "string",
format: "email",
Expand Down Expand Up @@ -355,7 +355,9 @@ test("header parameters", async () => {

expect(result.typesFile.getText()).toMatchSnapshot("types");
expect(result.commandsFile.getText()).toMatchSnapshot("commands");
expect(result.commandsValidatedFile.getText()).toMatchSnapshot("commands-validated");
expect(result.commandsValidatedFile.getText()).toMatchSnapshot(
"commands-validated",
);
expect(result.valibotFile.getText()).toMatchSnapshot("valibot");
expect(result.honoFile.getText()).toMatchSnapshot("hono");
});
Expand Down
8 changes: 1 addition & 7 deletions __tests__/openai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,7 @@ describe("OpenAI", () => {
fetcher: createIsomorphicNativeFetcher({
retry: { retries: 0 },
fetch: (input, init) =>
undiciFetch(
// @ts-expect-error @types/node resolves fetch types via undici-types@7, but we
// import undici@8 directly — Request.headers.keys() iterator types diverge.
// Fix: remove when @types/node ships undici-types@8
input,
{ ...init, dispatcher: mockAgent },
),
undiciFetch(input, { ...init, dispatcher: mockAgent }),
}),
});

Expand Down
8 changes: 1 addition & 7 deletions __tests__/petstore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,7 @@ describe("Petstore", () => {
fetcher: createIsomorphicNativeFetcher({
retry: { retries: 0 },
fetch: (input, init) =>
undiciFetch(
// @ts-expect-error @types/node resolves fetch types via undici-types@7, but we
// import undici@8 directly — Request.headers.keys() iterator types diverge.
// Fix: remove when @types/node ships undici-types@8
input,
{ ...init, dispatcher: mockAgent },
),
undiciFetch(input, { ...init, dispatcher: mockAgent }),
}),
});
const command = new FindPetsCommand({
Expand Down
Loading
Loading