From c33ff16377479daa91f2b08c009f3e2c2d871577 Mon Sep 17 00:00:00 2001 From: Viljami + Claude Date: Thu, 27 Aug 2026 07:28:03 +0000 Subject: [PATCH] fix(sdk): expose clients' additional-types through @epilot/sdk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v2 SDK generator only copied each client's generated `src/openapi.d.ts` into `packages/epilot-sdk-v2/src/types/`, so the hand-written types a client declares in `src/additional-types.ts` never reached `@epilot/sdk/`. Code migrating from `@epilot/-client` to the SDK therefore had to re-declare them — e.g. `@epilot/pricing` re-declaring `PriceTierEnhanced` after moving to `@epilot/sdk/pricing`. `generate-sdk-v2.ts` now copies `additional-types.ts` to `src/types/-additional.d.ts`, rewriting its `./openapi` import to the copied generated types, and re-exports it from `src/apis/.ts`. `@epilot/sdk/pricing` regains `PriceTierEnhanced`, `Cart` and `AvailabilityDate`. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JEMqupBjKVAzXWYDJvrdJc --- .changeset/sdk-additional-types.md | 11 ++++ .../__tests__/additional-types.test.ts | 47 +++++++++++++++ packages/epilot-sdk-v2/src/apis/pricing.ts | 1 + .../src/types/pricing-additional.d.ts | 59 +++++++++++++++++++ scripts/generate-sdk-v2.ts | 32 ++++++++++ 5 files changed, 150 insertions(+) create mode 100644 .changeset/sdk-additional-types.md create mode 100644 packages/epilot-sdk-v2/__tests__/additional-types.test.ts create mode 100644 packages/epilot-sdk-v2/src/types/pricing-additional.d.ts diff --git a/.changeset/sdk-additional-types.md b/.changeset/sdk-additional-types.md new file mode 100644 index 00000000..a0b52003 --- /dev/null +++ b/.changeset/sdk-additional-types.md @@ -0,0 +1,11 @@ +--- +"@epilot/sdk": patch +--- + +Expose the clients' hand-written `additional-types.ts` through the SDK + +`@epilot/sdk/` re-exported only the types generated from an API's OpenAPI specification. Types a client declares by hand in `src/additional-types.ts` — types the API no longer returns but consumers still need — were silently dropped, so code migrating from `@epilot/-client` to `@epilot/sdk/` had to re-declare them. + +`scripts/generate-sdk-v2.ts` now copies each client's `additional-types.ts` to `src/types/-additional.d.ts` (rewriting its `./openapi` import to point at the copied generated types) and re-exports it from `src/apis/.ts`. + +For `@epilot/sdk/pricing` this restores `PriceTierEnhanced`, `Cart`, and `AvailabilityDate`, matching the surface of `@epilot/pricing-client`. diff --git a/packages/epilot-sdk-v2/__tests__/additional-types.test.ts b/packages/epilot-sdk-v2/__tests__/additional-types.test.ts new file mode 100644 index 00000000..4dfafdc1 --- /dev/null +++ b/packages/epilot-sdk-v2/__tests__/additional-types.test.ts @@ -0,0 +1,47 @@ +import { existsSync, readdirSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +import type { AvailabilityDate, Cart, PriceTierEnhanced } from '../src/apis/pricing'; + +const CLIENTS_DIR = resolve(__dirname, '../../../clients'); +const TYPES_DIR = resolve(__dirname, '../src/types'); +const APIS_DIR = resolve(__dirname, '../src/apis'); + +/** + * Types that are hand-written in a client's `additional-types.ts` (i.e. not part + * of the OpenAPI specification) must stay reachable through the SDK, otherwise + * consumers migrating from `@epilot/-client` to `@epilot/sdk/` have to + * re-declare them. See clients/pricing-client/src/additional-types.ts. + */ +describe('additional types are exposed by the SDK', () => { + const clientsWithAdditionalTypes = readdirSync(CLIENTS_DIR, { withFileTypes: true }) + .filter((d) => d.isDirectory() && d.name.endsWith('-client')) + .filter((d) => existsSync(resolve(CLIENTS_DIR, d.name, 'src/additional-types.ts'))) + .map((d) => ({ dirName: d.name, kebabName: d.name.replace(/-client$/, '') })); + + it('finds at least one client with additional types', () => { + expect(clientsWithAdditionalTypes.length).toBeGreaterThan(0); + }); + + it.each(clientsWithAdditionalTypes)('$dirName additional types are copied and re-exported', ({ kebabName }) => { + const copied = resolve(TYPES_DIR, `${kebabName}-additional.d.ts`); + expect(existsSync(copied), `${copied} is missing — run pnpm generate-sdk`).toBe(true); + + // The copied file must not keep pointing at the client's own './openapi'. + expect(readFileSync(copied, 'utf-8')).not.toMatch(/from '\.\/openapi'/); + + const apiFile = readFileSync(resolve(APIS_DIR, `${kebabName}.ts`), 'utf-8'); + expect(apiFile).toContain(`export type * from '../types/${kebabName}-additional'`); + }); + + it('exposes the pricing additional types through @epilot/sdk/pricing', () => { + const tier: PriceTierEnhanced = { unit_amount_gross: 119, unit_amount_gross_decimal: '119.00' }; + const availability: AvailabilityDate = { available_start_date: '2017-07-21' }; + const cart: Cart = { id: 'cart-1', status: 'draft' }; + + expect(tier.unit_amount_gross).toBe(119); + expect(availability.available_start_date).toBe('2017-07-21'); + expect(cart.id).toBe('cart-1'); + }); +}); diff --git a/packages/epilot-sdk-v2/src/apis/pricing.ts b/packages/epilot-sdk-v2/src/apis/pricing.ts index 2fe330c3..1c12efa7 100644 --- a/packages/epilot-sdk-v2/src/apis/pricing.ts +++ b/packages/epilot-sdk-v2/src/apis/pricing.ts @@ -9,6 +9,7 @@ export { authorize } from '../authorize'; export type { TokenArg } from '../authorize'; import type { Client } from '../types/pricing'; export type * from '../types/pricing'; +export type * from '../types/pricing-additional'; export type { OpenAPIClient } from 'openapi-client-axios'; /* eslint-disable @typescript-eslint/no-require-imports */ diff --git a/packages/epilot-sdk-v2/src/types/pricing-additional.d.ts b/packages/epilot-sdk-v2/src/types/pricing-additional.d.ts new file mode 100644 index 00000000..ce5d8a91 --- /dev/null +++ b/packages/epilot-sdk-v2/src/types/pricing-additional.d.ts @@ -0,0 +1,59 @@ +/* Auto-copied from pricing-client/src/additional-types.ts */ +/** + * Additional legacy types that were removed from the OpenAPI specification + * because they are no longer used by the API. + * + * These types remain temporarily for backwards compatibility and will be + * removed once all dependent code has been migrated. + */ + +import type { Address, Amounts, Customer, MetaData, OrderStatus, PriceItems, PriceTier, TotalDetails } from './pricing'; + +export interface AvailabilityDate { + /** + * The availability interval start date + * example: + * 2017-07-21 + */ + available_start_date?: string; // date + /** + * The availability interval end date + * example: + * 2017-07-21 + */ + available_end_date?: string; // date +} + +export interface PriceTierEnhanced extends PriceTier { + unit_amount_gross?: number; + unit_amount_gross_decimal?: string; + flat_fee_amount_gross?: number; + flat_fee_amount_gross_decimal?: string; +} + +export interface Cart extends Amounts { + /** + * The cart identifier + */ + id?: string; + /** + * The user's Organization Id the cart belongs to + */ + org_id?: string; + /** + * The status of the Cart: + * - open - the cart checkout is still in progress. Payment processing has not started + * - complete - the cart checkout is complete. Payment processing may still be in progress + * - expired - the cart checkout has expired. No further processing will occur + * + */ + status?: OrderStatus; + customer?: Customer; + billing_address?: Address; + delivery_address?: Address; + metadata?: /* A set of key-value pairs used to store meta data information about an entity. */ MetaData; + line_items?: /* Tracks a set of product prices, quantities, (discounts) and taxes. */ PriceItems; + total_details?: /* The total details with tax (and discount) aggregated totals. */ TotalDetails; + created_at?: string; // date-time + updated_at?: string; // date-time +} diff --git a/scripts/generate-sdk-v2.ts b/scripts/generate-sdk-v2.ts index d3047f14..ad6e02dd 100644 --- a/scripts/generate-sdk-v2.ts +++ b/scripts/generate-sdk-v2.ts @@ -4,6 +4,7 @@ * Generates the epilot-sdk v2 package files from existing clients: * - Copies openapi-runtime.json definitions * - Copies openapi.d.ts type files + * - Copies hand-written additional-types.ts type files * - Generates per-API lazy loader files (apis/*.ts) * - Generates the API registry (apis/_registry.ts) * - Updates package.json subpath exports @@ -33,6 +34,7 @@ type ClientInfo = { kebabName: string; // e.g. "entity" or "entity-mapping" (kebab-case, used for file names & exports) hasDefinition: boolean; hasTypes: boolean; + hasAdditionalTypes: boolean; }; const discoverClients = (): ClientInfo[] => { @@ -51,6 +53,7 @@ const discoverClients = (): ClientInfo[] => { kebabName: baseName, // baseName is already kebab-case (e.g. "entity-mapping") hasDefinition: existsSync(resolve(clientDir, 'openapi-runtime.json')), hasTypes: existsSync(resolve(clientDir, 'openapi.d.ts')), + hasAdditionalTypes: existsSync(resolve(clientDir, 'additional-types.ts')), }; }); }; @@ -180,6 +183,30 @@ const copyTypes = (clients: ClientInfo[]) => { } }; +/** + * Copies the hand-written `additional-types.ts` of a client (types that are not + * part of the OpenAPI specification but are still part of the client's public + * API surface) into the SDK types directory, so the SDK exposes the exact same + * types as the standalone client package. + */ +const copyAdditionalTypes = (clients: ClientInfo[]) => { + mkdirSync(TYPES_DIR, { recursive: true }); + + for (const client of clients) { + if (!client.hasAdditionalTypes) continue; + const src = resolve(CLIENTS_DIR, client.dirName, 'src/additional-types.ts'); + let content = readFileSync(src, 'utf-8'); + + // The client resolves generated types via './openapi', in the SDK they live + // in a sibling file named after the API. + content = content.replace(/(\bfrom\s+')\.\/openapi(')/g, `$1./${client.kebabName}$2`); + content = `/* Auto-copied from ${client.dirName}/src/additional-types.ts */\n${content}`; + + const dest = resolve(TYPES_DIR, `${client.kebabName}-additional.d.ts`); + writeFileSync(dest, content); + } +}; + type SchemaDoc = { name: string; description: string; @@ -333,6 +360,9 @@ const generateApiFile = (client: ClientInfo): string => { lines.push(`import type { Client } from '../types/${client.kebabName}'`); lines.push(`export type * from '../types/${client.kebabName}'`); + if (client.hasAdditionalTypes) { + lines.push(`export type * from '../types/${client.kebabName}-additional'`); + } lines.push(`export type { OpenAPIClient } from 'openapi-client-axios'`); } else { lines.push(`import type { AxiosInstance } from 'axios'`); @@ -1039,6 +1069,7 @@ const main = () => { console.log('Copying types...'); copyTypes(clients); + copyAdditionalTypes(clients); console.log('Generating per-API files...'); mkdirSync(APIS_DIR, { recursive: true }); @@ -1076,6 +1107,7 @@ const main = () => { console.log(`\nGenerated:`); console.log(` - ${validClients.length} definition files`); console.log(` - ${clients.filter((c) => c.hasTypes).length} type files`); + console.log(` - ${clients.filter((c) => c.hasAdditionalTypes).length} additional type files`); console.log(` - ${validClients.length} API entry files`); console.log(` - 1 registry file`); console.log(` - ${docCount} API docs + index`);