UNPKG

@spotable/attio-sdk

Version:
177 lines (151 loc) 7.06 kB
import * as commander from "commander"; import logger from "../helpers/logger"; import { generateAttributeTypes, generateBaseTypes, generateCommentTypes, generateCustomObjectTypes, generateEntryTypes, generateListTypes, generateNoteTypes, generateObjectTypes, generateRecordTypes, generateSdkUtilityTypes, generateSystemDataTypes, generateTaskTypes, generateWebhookTypes, generateWorkspaceMemberTypes, } from "../generators/types"; import { ensureDirectoryExists, removeDirectoryRecursive } from "../helpers/fs"; import { fetchAttioSchema } from "../helpers/fetchAttioSchema"; import { join } from "node:path"; import { generateClient } from "../generators/client"; import ora from "ora"; import { generateIndexFile } from "../generators/indexFile"; interface GenerateCommandOptions { apiKeyEnvVar: string; output: string; standardTypes: boolean; verbose: boolean; } type AttioTypeEntry = { tsOutputType: string; tsInputType?: string; // Optional type when the input type differs from the output type isArray?: boolean; } & ( | { needsImport: false; } | { needsImport: true; importFrom: string; } ); const ATTIO_TYPE_MAP: Record<string, AttioTypeEntry> = { text: { tsOutputType: "string", isArray: false, needsImport: false }, boolean: { tsOutputType: "boolean", isArray: false, needsImport: false }, number: { tsOutputType: "number", isArray: false, needsImport: false }, date: { tsOutputType: "Date", isArray: false, needsImport: false }, email: { tsOutputType: "string", isArray: false, needsImport: false }, phone: { tsOutputType: "string", isArray: false, needsImport: false }, currency: { tsOutputType: "number", isArray: false, needsImport: false }, dropdown: { tsOutputType: "string", isArray: false, needsImport: false }, url: { tsOutputType: "string", isArray: false, needsImport: false }, person: { tsOutputType: "string", isArray: false, needsImport: false }, company: { tsOutputType: "string", isArray: false, needsImport: false }, deal: { tsOutputType: "string", isArray: false, needsImport: false }, user: { tsOutputType: "string", isArray: false, needsImport: false }, list_entry: { tsOutputType: "string", isArray: false, needsImport: false }, object: { tsOutputType: "string", isArray: false, needsImport: false }, status: { tsOutputType: "string", isArray: false, needsImport: false }, "personal-name": { tsOutputType: "string", isArray: false, needsImport: false }, "email-address": { tsOutputType: "string", isArray: false, needsImport: false }, "phone-number": { tsOutputType: "PhoneNumber", isArray: false, needsImport: true, importFrom: "./systemDataTypes" }, location: { tsOutputType: "Location", isArray: false, needsImport: true, importFrom: "./systemDataTypes" }, domain: { tsOutputType: "Domain", tsInputType: "DomainInput", isArray: false, needsImport: true, importFrom: "./systemDataTypes" }, select: { tsOutputType: "SelectOption", tsInputType: "string", isArray: false, needsImport: true, importFrom: "./systemDataTypes" }, "record-reference": { tsOutputType: "RecordReference", isArray: false, needsImport: true, importFrom: "./systemDataTypes" }, "actor-reference": { tsOutputType: "ActorReference", isArray: false, needsImport: true, importFrom: "./systemDataTypes" }, interaction: { tsOutputType: "Interaction", isArray: false, needsImport: true, importFrom: "./systemDataTypes" }, timestamp: { tsOutputType: "string", isArray: false, needsImport: false }, }; async function generateCommandHandler(options: GenerateCommandOptions): Promise<void> { logger.level = options.verbose ? "debug" : "info"; const apiKey = process.env[options.apiKeyEnvVar]; if (!apiKey) { logger.error(`API key not found in environment variable: ${options.apiKeyEnvVar}`); process.exit(1); } // Remove existing output directory removeDirectoryRecursive(options.output); const clientOutputDir = options.output; const typesOutputDir = join(options.output, "types"); const generateLoader = ora("Generating standard types").start(); // Recreate client and types directory ensureDirectoryExists(typesOutputDir); // Generate standard types if requested if (options.standardTypes) { generateBaseTypes(typesOutputDir); generateAttributeTypes(typesOutputDir); generateCommentTypes(typesOutputDir); generateEntryTypes(typesOutputDir); generateListTypes(typesOutputDir); generateNoteTypes(typesOutputDir); generateObjectTypes(typesOutputDir); generateRecordTypes(typesOutputDir); generateTaskTypes(typesOutputDir); generateWebhookTypes(typesOutputDir); generateWorkspaceMemberTypes(typesOutputDir); } const { objects, attributes } = await fetchAttioSchema(apiKey); // Generate system data and SDK Utility types generateSystemDataTypes(typesOutputDir); generateSdkUtilityTypes(objects, options.standardTypes, typesOutputDir); generateLoader.text = "Generating custom object types"; // Generate TypeScript types for (const object of objects) { const imports: Record< string, { file: string; types: Set<string>; } > = {}; const attributesWithTSTypes = (attributes[object.api_slug] || []).map((attribute) => { const typeInfo = ATTIO_TYPE_MAP[attribute.type] || { tsOutputType: "any", needsImport: false }; if (typeInfo.needsImport) { if (!imports[typeInfo.importFrom]) { imports[typeInfo.importFrom] = { file: typeInfo.importFrom, types: new Set(), }; } imports[typeInfo.importFrom].types.add(typeInfo.tsOutputType); if (typeInfo.tsInputType && !["string"].includes(typeInfo.tsInputType)) { imports[typeInfo.importFrom].types.add(typeInfo.tsInputType); } } return { ...attribute, tsOutputType: typeInfo.tsOutputType, tsInputType: typeInfo.tsInputType || typeInfo.tsOutputType, isArray: typeInfo.isArray || attribute.is_multiselect, }; }); generateCustomObjectTypes(typesOutputDir, { object, attributes: attributesWithTSTypes, imports: Object.values(imports) }); } generateIndexFile(typesOutputDir); generateLoader.succeed(`TypeScript types generated successfully in ${typesOutputDir}`); generateClient(clientOutputDir, objects, options.standardTypes); generateIndexFile(clientOutputDir); } export default function registerGenerateCommand(program: commander.Command): void { program .command("generate") .description("Generate a TypeScript client from your Attio workspace") .option("-a, --api-key-env-var <key>", "Environment variable containing the Attio API key", "ATTIO_API_KEY") .option("-o, --output <dir>", "Output directory for generated types", "./attio") .option("-s, --standard-types", "Generate types for standard Attio entities (lists, notes, tasks, etc.)", false) .option("--verbose", "Enable verbose logging", false) .action(generateCommandHandler); }