npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@palantir/pack.sdkgen

v0.2.0

Published

Generic Typescript SDK generator CLI

Readme

@palantir/pack.sdkgen

A flexible CLI tool for generating TypeScript/JavaScript SDKs from schema definitions using customizable templates. The intention is for this to remain generic and not pack-specific, the pack-specific parts are up to the template scripts that are used!

Table of Contents

Overview

@palantir/pack.sdkgen is a scaffolding tool that generates SDK packages from schema files using pluggable templates. Similar to create-react-app, it allows you to:

  • Transform schema definitions into fully-configured SDK projects
  • Customize generation through configuration and hooks
  • Apply template substitutions and run initialization scripts

Usage

Basic Usage

# Create a new SDK using the default template
npx @palantir/pack.sdkgen create my-sdk --schema ./my-schema.json

# Use a specific template
npx @palantir/pack.sdkgen create my-sdk \
  --template @myorg/custom-template \
  --schema ./my-schema.json

# Use a local template
npx @palantir/pack.sdkgen create my-sdk \
  --template ./path/to/template \
  --schema ./my-schema.yaml

# Skip dependency installation
npx @palantir/pack.sdkgen create my-sdk \
  --schema ./my-schema.json \
  --skip-install

Available Commands

Usage: sdkgen [options] [command]

Generate TypeScript/JavaScript SDKs from schema definitions

Options:
  -V, --version                    output the version number
  -h, --help                       display help for command

Commands:
  create [options] <project-name>  Create a new SDK project
  help [command]                   display help for command

Create Command Options

Usage: sdkgen create [options] <project-name>

Create a new SDK project

Options:
  -t, --template <template>      template to use (default: "default")
  -s, --schema <path>            path to schema file
  --skip-install                 skip dependency installation
  --verbose                      enable verbose logging
  --dry-run                      preview without writing files
  --non-interactive              run in non-interactive mode using defaults
  --config <path>                path to JSON config file with template configuration
  --overwrite                    overwrite existing directory
  -h, --help                     display help for command

Creating Templates

Templates are npm packages or local directories with a specific structure that defines how to generate SDK code.

Template Structure

my-sdk-template/
├── package.json           # Template package metadata
├── template.config.js     # Template configuration
├── template/              # File templates
│   ├── package.json.ejs
│   ├── src/
│   │   ├── index.ts.ejs
│   │   └── types.ts.ejs
│   ├── README.md.ejs
│   └── .gitignore
├── scripts/               # Hook scripts
│   ├── pre-generate.js
│   └── post-generate.js
└── transformers/          # Schema transformers (optional)
    └── default.js

Minimal Template Example

Here's a simple template that generates a "Hello World" SDK:

template.config.js

module.exports = {
  name: "hello-world-template",
  description: "A simple Hello World SDK template",

  // Define prompts for user input
  prompts: [
    {
      type: "input",
      name: "greeting",
      message: "What greeting would you like to use?",
      default: "Hello, World!",
    },
    {
      type: "input",
      name: "author",
      message: "Author name?",
      default: "Palantir SDK Generator",
    },
  ],

  // Files to process as EJS templates
  templateFiles: ["**/*.ejs"],

  // Files to copy without processing
  staticFiles: [".gitignore", "**/*.md"],

  // Hooks for custom logic
  hooks: {
    beforeGenerate: "./scripts/pre-generate.js",
    afterGenerate: "./scripts/post-generate.js",
  },
};

template/package.json.ejs

{
  "name": "<%= projectName %>",
  "version": "1.0.0",
  "description": "SDK generated from schema",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "author": "<%= author %>",
  "license": "<%= license %>",
  "scripts": {
    "build": "tsc",
    "test": "jest"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "@types/node": "^20.0.0"
  }
}

template/src/helloWorld.ts.ejs

/**
 * Generated on <%= new Date().toISOString() %>
 * Author: <%= author %>
 */

export class HelloWorld {
  private greeting: string = '<%= greeting %>';

  constructor(customGreeting?: string) {
    if (customGreeting) {
      this.greeting = customGreeting;
    }
  }

  greet(name: string = 'World'): string {
    return `${this.greeting}, ${name}!`;
  }

  // Schema information
  getSchemaInfo(): object {
    return <%- JSON.stringify(schema, null, 2) %>;
  }
}

export default HelloWorld;

scripts/pre-generate.js

module.exports = async function preGenerate(context) {
  const { projectName, schema, answers, templateConfig } = context;

  console.log(`🚀 Preparing to generate ${projectName}`);
  console.log(`📋 Using template: ${templateConfig.name}`);
  console.log(`👤 Author: ${answers.author}`);

  // Validate schema
  if (!schema || Object.keys(schema).length === 0) {
    console.warn("⚠️  Warning: Schema is empty");
  }

  // Modify context if needed
  context.additionalData = {
    generatedAt: new Date().toISOString(),
    nodeVersion: process.version,
  };

  return context;
};

scripts/post-generate.js

const fs = require("fs-extra");
const path = require("path");

module.exports = async function postGenerate(context, outputPath) {
  const { projectName, answers } = context;

  console.log(`✨ Finalizing ${projectName}`);

  // Create additional directories
  await fs.ensureDir(path.join(outputPath, "dist"));
  await fs.ensureDir(path.join(outputPath, "tests"));

  // Create a simple test file
  const testContent = `
import HelloWorld from '../src/helloWorld';

describe('HelloWorld', () => {
  it('should greet correctly', () => {
    const hw = new HelloWorld();
    expect(hw.greet('SDK')).toBe('${answers.greeting}, SDK!');
  });
});
`;

  await fs.writeFile(
    path.join(outputPath, "tests", "helloWorld.test.ts"),
    testContent.trim(),
  );

  // Create TypeScript config
  const tsConfig = {
    compilerOptions: {
      target: "ES2020",
      module: "commonjs",
      declaration: true,
      outDir: "./dist",
      rootDir: "./src",
      strict: true,
      esModuleInterop: true,
      skipLibCheck: true,
      forceConsistentCasingInFileNames: true,
    },
    include: ["src/**/*"],
    exclude: ["node_modules", "dist", "tests"],
  };

  await fs.writeJSON(
    path.join(outputPath, "tsconfig.json"),
    tsConfig,
    { spaces: 2 },
  );

  console.log("📦 Package structure created successfully!");
};

Hook Execution Model

Important: Template hooks run as separate Node.js processes in the template's directory context. This means:

  1. Dependency Isolation: Hooks have access to the template package's node_modules, not the CLI's
  2. Version Independence: The template can depend on a specific version of @palantir/pack.sdkgen and other tools
  3. Context Passing: Hook context is serialized to JSON and passed via command-line arguments
  4. Return Values: Hooks can return modified context (for beforeGenerate) via stdout

Example hook that uses template dependencies:

// hooks/afterGenerate.js
import { execSync } from "child_process";

export default async function afterGenerate(context) {
  const { outputPath, schemaPath } = context;

  // This uses the template's own dependencies
  const typeGenPath = require.resolve("@my-org/type-generator/bin/cli.js");

  execSync(
    `node "${typeGenPath}" -i "${schemaPath}" -o "${outputPath}/types"`,
    {
      stdio: "inherit",
      cwd: outputPath,
    },
  );
}

Advanced Template Features

Custom Schema Transformers

// transformers/default.js
module.exports = {
  async transform(schema, context) {
    // Transform the raw schema into a format your templates can use
    return {
      version: schema.version || "1.0.0",
      endpoints: extractEndpoints(schema),
      types: extractTypes(schema),
      metadata: {
        generatedAt: new Date().toISOString(),
        sourceFile: context.schemaPath,
      },
    };
  },
};

function extractEndpoints(schema) {
  // Your schema parsing logic
  return [];
}

function extractTypes(schema) {
  // Your type extraction logic
  return [];
}

Template Variables

Templates have access to these variables:

  • projectName - The name of the project being created
  • schema - The transformed schema data
  • answers - User responses to prompts
  • utils - Helper functions (see below)
  • Any additional data added by hooks

Utility Functions

// Available in templates as `utils`
{
  camelCase: (str) => string,      // Convert to camelCase
  pascalCase: (str) => string,     // Convert to PascalCase
  kebabCase: (str) => string,      // Convert to kebab-case
  snakeCase: (str) => string,      // Convert to snake_case
  pluralize: (str) => string,      // Pluralize a word
  singularize: (str) => string,    // Singularize a word
  capitalize: (str) => string,     // Capitalize first letter
  lower: (str) => string,          // Convert to lowercase
  upper: (str) => string           // Convert to uppercase
}

CLI Development Guide

Project Structure

sdkgen/
├── src/
│   ├── cli.ts                 # CLI entry point
│   ├── commands/
│   │   └── create.ts          # Create command implementation
│   ├── core/
│   │   ├── generator.ts       # Core generation engine
│   │   ├── template-loader.ts # Template loading logic
│   │   └── schema-parser.ts   # Schema parsing utilities
│   ├── utils/
│   │   ├── prompts.ts         # User prompt utilities
│   │   ├── logger.ts          # Logging utilities
│   │   └── file-system.ts     # File system helpers
│   └── types/
│       └── index.ts           # TypeScript type definitions
├── templates/
│   └── default/               # Default template
├── bin/
│   └── cli.js                 # Executable entry point
├── package.json
├── tsconfig.json
└── README.md

Implementation Steps

  1. Set up the project

    mkdir sdkgen
    cd sdkgen
    npm init -y
    npm install commander inquirer ejs fs-extra glob chalk
    npm install -D typescript @types/node @types/inquirer @types/ejs
  2. Create the CLI entry point

    // src/cli.ts
    #!/usr/bin/env node
    import { Command } from 'commander';
    import { create } from './commands/create';
    
    const program = new Command();
    
    program
      .name('sdkgen')
      .description('Generate SDKs from schema definitions')
      .version('1.0.0');
    
    program
      .command('create <project-name>')
      .description('Create a new SDK project')
      .option('-t, --template <template>', 'template to use', 'default')
      .option('-s, --schema <path>', 'path to schema file')
      .option('--skip-install', 'skip dependency installation')
      .option('--verbose', 'enable verbose logging')
      .option('--dry-run', 'preview without writing files')
      .action(create);
    
    program.parse();
  3. Implement the generator core

    // src/core/generator.ts
    import ejs from "ejs";
    import fs from "fs-extra";
    import glob from "glob";
    import path from "path";
    
    export class Generator {
      constructor(private context: GeneratorContext) {}
    
      async generate(outputPath: string): Promise<void> {
        // Implementation as shown in architecture section
      }
    }
  4. Build and link for testing

    npm run build
    npm link
    npx @palantir/pack.sdkgen create test-project --schema ./test.json

API Reference

Generator Context

interface GeneratorContext {
  projectName: string;
  schema: any;
  answers: Record<string, any>;
  templateConfig: TemplateConfig;
  schemaPath: string;
  outputPath: string;
  options: {
    skipInstall?: boolean;
    verbose?: boolean;
    dryRun?: boolean;
  };
}

Template Configuration

interface TemplateConfig {
  name: string;
  description: string;
  prompts?: PromptQuestion[];
  templateFiles?: string[];
  staticFiles?: string[];
  hooks?: {
    beforeGenerate?: string | Function;
    afterGenerate?: string | Function;
    beforeInstall?: string | Function;
    afterInstall?: string | Function;
  };
  transformers?: {
    [key: string]: string | Function;
  };
  dependencies?: Record<string, string>;
  devDependencies?: Record<string, string>;
}

Hook Functions

// Pre-generate hook
type PreGenerateHook = (context: GeneratorContext) => Promise<GeneratorContext>;

// Post-generate hook
type PostGenerateHook = (
  context: GeneratorContext,
  outputPath: string,
) => Promise<void>;

Examples

Creating a REST API Client Template

// template.config.js
module.exports = {
  name: "rest-api-client",
  prompts: [
    {
      type: "input",
      name: "baseUrl",
      message: "API base URL?",
      default: "https://api.example.com",
    },
    {
      type: "list",
      name: "httpClient",
      message: "HTTP client library?",
      choices: ["fetch", "axios", "node-fetch"],
    },
  ],
  hooks: {
    afterGenerate: async (context, outputPath) => {
      // Install selected HTTP client
      const { httpClient } = context.answers;
      if (httpClient === "axios") {
        // Add axios to package.json dependencies
      }
    },
  },
};

Using Environment-Specific Configuration

# Development
TEMPLATE_ENV=dev npx @palantir/pack.sdkgen create my-sdk \
  --schema ./schema.json

# Production
TEMPLATE_ENV=prod npx @palantir/pack.sdkgen create my-sdk \
  --schema ./schema.json

Troubleshooting

Common Issues

Template not found

  • Ensure the template package is published to npm
  • Check that the template path is correct for local templates
  • Verify the template has a valid template.config.js

Schema parsing errors

  • Validate your schema file is valid JSON/YAML
  • Check that the schema transformer matches your schema format
  • Use --verbose flag for detailed error messages

Generation fails

  • Run with --dry-run first to preview changes
  • Check hook scripts for errors
  • Ensure all required template variables are provided