UNPKG

jsforce

Version:

Salesforce API Library for JavaScript

279 lines (266 loc) 7.67 kB
import os from 'os'; import fs from 'node:fs'; import path from 'path'; import { DescribeSObjectResult } from '../types'; import { Cli } from '../cli/cli'; import { Connection, VERSION } from '..'; import { Command } from 'commander'; import { mkdir } from 'fs'; type UnwrapPromise<T> = T extends Promise<infer U> ? U : never; function getCacheFileDir() { return path.join(os.tmpdir(), 'jsforce-gen-schema-cache'); } function getCacheFilePath(orgId: string) { return path.join(getCacheFileDir(), orgId, 'describe.json'); } async function readDescribedCache( orgId: string, ): Promise<UnwrapPromise<ReturnType<typeof loadDescribeResult>> | null> { try { const cacheFile = getCacheFilePath(orgId); const data = await fs.promises.readFile(cacheFile, 'utf8'); return JSON.parse(data); } catch (e) { return null; } } async function loadDescribeResult( conn: Connection, orgId: string, cache?: boolean, ) { console.info('describing global'); const { sobjects: sos } = await conn.describeGlobal(); const sobjects = []; for (const { name } of sos) { console.info('describing ' + name); const so = await conn.describe(name); sobjects.push(so); } if (cache) { const cacheFile = getCacheFilePath(orgId); await fs.promises.mkdir(path.dirname(cacheFile), { recursive: true }); await fs.promises.writeFile( cacheFile, JSON.stringify(sobjects, null, 2), 'utf8', ); } return sobjects; } function getParentReferences(sobject: DescribeSObjectResult) { const parentReferences = []; for (const { type, nillable, relationshipName, referenceTo, } of sobject.fields) { if ( type === 'reference' && relationshipName && referenceTo && referenceTo.length > 0 ) { const parentSObject = referenceTo.length > 1 ? 'Name' : referenceTo[0]; parentReferences.push({ nillable, parentSObject, relationshipName }); } } return parentReferences; } function getTSTypeString(type: string): string { return type === 'double' || type === 'int' || type === 'currency' || type === 'percent' ? 'number' : type === 'boolean' ? 'boolean' : type === 'date' || type === 'datetime' || type === 'time' ? 'DateString' : type === 'base64' ? 'BlobString' : type === 'address' ? 'Address' : type === 'complexvalue' ? 'any' : 'string'; } async function dumpSchema( conn: Connection, orgId: string, outputFile: string, schemaName: string, cache?: boolean, filterObjects?: Set<string>, ) { const sobjects = (cache ? await readDescribedCache(orgId) : null) || (await loadDescribeResult(conn, orgId, cache)); if (!fs.existsSync(outputFile)) { await fs.promises.mkdir(path.dirname(outputFile), { recursive: true }); await fs.promises.writeFile(outputFile, '', 'utf8'); } const out = fs.createWriteStream(outputFile, 'utf8'); return new Promise((resolve, reject) => { out.on('error', (err) => reject(err)); out.on('finish', resolve); const writeLine = (message: string) => out.write(message + '\n'); writeLine( "import { Schema, SObjectDefinition, DateString, BlobString, Address } from 'jsforce';", ); writeLine(''); for (const sobject of sobjects) { if (filterObjects && !filterObjects.has(sobject.name)) { continue; } const { name, fields, childRelationships } = sobject; writeLine(`type Fields$${name} = {`); writeLine(' //'); for (const { name, type, nillable } of fields) { const tsType = getTSTypeString(type); const orNull = nillable ? ' | null' : ''; writeLine(` ${name}: ${tsType}${orNull};`); } writeLine('};'); writeLine(''); writeLine(`type ParentReferences$${name} = {`); writeLine(' //'); const parentReferences = getParentReferences(sobject); for (const { nillable, parentSObject, relationshipName, } of parentReferences) { if (filterObjects && !filterObjects.has(parentSObject)) { continue; } const orNull = nillable ? ' | null' : ''; writeLine( ` ${relationshipName}: SObjectDefinition$${parentSObject}${orNull};`, ); } writeLine('};'); writeLine(''); writeLine(`type ChildRelationships$${name} = {`); writeLine(' //'); for (const { field, childSObject, relationshipName, } of childRelationships) { if (filterObjects && !filterObjects.has(childSObject)) { continue; } if ( field && childSObject && relationshipName && !field.endsWith('__c') ) { writeLine( ` ${relationshipName}: SObjectDefinition$${childSObject};`, ); } } writeLine('};'); writeLine(''); writeLine( `interface SObjectDefinition$${name} extends SObjectDefinition<'${name}'> { Name: '${name}'; Fields: Fields$${name}; ParentReferences: ParentReferences$${name}; ChildRelationships: ChildRelationships$${name}; }`, ); writeLine(''); } writeLine(''); writeLine(`export interface ${schemaName} extends Schema {`); writeLine(' SObjects: {'); for (const { name } of sobjects) { if (filterObjects && !filterObjects.has(name)) { continue; } writeLine(` ${name}: SObjectDefinition$${name};`); } writeLine(' };'); writeLine('}'); out.end(); }); } type GeneratorCommand = { connection?: string; username?: string; password?: string; loginUrl?: string; sandbox?: boolean; outputFile: string; cache?: boolean; clearCache?: boolean; filterObjects?: Set<string>; } & Command; function commaSeparatedList(value: string, _dummyPrevious: unknown) { return new Set(value.split(',')); } /** * */ function readCommand(): GeneratorCommand { return new Command() .option('-u, --username [username]', 'Salesforce username') .option( '-p, --password [password]', 'Salesforce password (and security token, if available)', ) .option( '-c, --connection [connection]', 'Connection name stored in connection registry', ) .option('-l, --loginUrl [loginUrl]', 'Salesforce login url') .option('-n, --schemaName [schemaName]', 'Name of schema type', 'MySchema') .requiredOption( '-o, --outputFile <outputFile>', 'Generated schema file path', './schema.d.ts', ) .option('--sandbox', 'Login to Salesforce sandbox') .option( '--no-cache', 'Do not generate cache file for described result in tmp directory', ) .option('--clearCache', 'Clear all existing described cache files') .option( '--filterObjects <filterObjects>', 'Only output schema for specified objects', commaSeparatedList, ) .version(VERSION) .parse(process.argv) as GeneratorCommand; } /** * */ export default async function main() { const program = readCommand(); const cli = new Cli(); await cli.connect(program); const conn = cli.getCurrentConnection(); if (!conn.userInfo) { console.error('Cannot connect to Salesforce'); return; } await dumpSchema( conn, conn.userInfo.organizationId, program.outputFile, program.schemaName, program.cache, program.filterObjects, ); if (program.clearCache) { console.log('removing cache files'); await fs.promises.rm(getCacheFileDir(), { recursive: true }); } console.log(`Dumped to: ${program.outputFile}`); }