upsun-sdk-checker
v0.1.1
Published
Compare Upsun SDK signatures across multiple language implementations (node, PHP, ...) via GitHub API
Downloads
63
Readme
SDK Signature Checker
A TypeScript tool to compare method signatures across different SDK implementations (Node.js, PHP, Python, and potentially other languages).
🎯 Objective
Verify that all Task classes in your SDKs have the same public methods with identical parameter signatures, making it easier to maintain parity across different implementations.
🏗️ Architecture
The project is organized in a modular way to facilitate adding new SDKs:
src/
├── types/ # Shared TypeScript types
├── github/ # GitHub API client
├── parsers/ # Language-specific parsers
│ ├── base.ts # Abstract Parser class
│ ├── typescript.ts # TypeScript/Node.js parser
│ ├── php.ts # PHP parser
│ ├── python.ts # Python parser
│ ├── type-normalizer.ts # Common type name normalization
│ └── factory.ts # Factory to get the right parser
├── reporters/ # Report generators
│ └── console.ts # Colored console report
├── loader.ts # Load SDKs from GitHub
├── comparison.ts # Comparison engine
├── config.ts # SDK configuration
└── index.ts # Main entry point📦 Installation
npm install🚀 Usage
npm run full:checksThe script will:
- Fetch files from GitHub (no cloning necessary)
- Parse Task classes from each SDK
- Compare methods and their signatures
- Display a detailed report in the terminal
🔧 Configuration
Add a new SDK
Edit src/config.ts:
export const SDK_CONFIGS: SDKConfig[] = [
{
language: 'node',
owner: 'upsun',
repo: 'upsun-sdk-node',
tasksPath: 'src/core/tasks',
modelsPath: 'src/models', // Optional: path to model/type files for parameter type resolution
branch: 'develop', // Optional: specify a branch (defaults to 'main' if not specified)
},
{
language: 'php',
owner: 'upsun',
repo: 'upsun-sdk-php',
tasksPath: 'src/Core/Tasks',
branch: 'main', // You can test different branches across different SDKs
},
{
language: 'python',
owner: 'upsun',
repo: 'upsun-sdk-python',
tasksPath: 'src/upsun/tasks',
// branch: 'develop', // Optional
},
// Example: Golang SDK
// {
// language: 'golang',
// owner: 'upsun',
// repo: 'upsun-sdk-go',
// tasksPath: 'core/tasks',
// },
];Specifying branches
The branch property is optional for each SDK configuration:
- If not specified, the tool defaults to the
mainbranch - You can specify different branches for each SDK to test cross-version compatibility
- For example, test the
developbranch of one SDK against themainbranch of another
This is useful for:
- Testing feature branches before release
- Validating compatibility between SDK versions
- Comparing different development branches
Overriding branches via environment variables
You can override the branches specified in src/config.ts using environment variables without modifying the configuration file. Environment variables follow the pattern:
SDK_<LANGUAGE>_BRANCH=branchnameExamples:
# Test specific branch of Node SDK
SDK_NODE_BRANCH=develop npm run full:checks
# Test specific branches of multiple SDKs
SDK_NODE_BRANCH=feature/new-api SDK_PHP_BRANCH=develop npm run full:checks
# Override Python SDK branch
SDK_PYTHON_BRANCH=feature/my-branch npm run full:checks
# Override one SDK, use configured branch for others
SDK_PHP_BRANCH=testing npm run full:checksThe environment variable name is constructed from the language:
node→SDK_NODE_BRANCHphp→SDK_PHP_BRANCHpython→SDK_PYTHON_BRANCHgolang→SDK_GOLANG_BRANCH
Comparison options
You can control what gets compared via environment variables (or a .env file):
| Variable | Default | Description |
| ------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| EXCLUDE_DEPRECATED | true | Exclude methods annotated with @deprecated from the comparison |
| EXCLUDE_FILTER_METHODS | false | Exclude methods that have a parameter whose name contains filter (useful when Node uses a single filters object but PHP/Python have flat parameters) |
| EXCLUDE_OPTIONAL_PARAMS | false | Ignore optional parameters when comparing signatures. A parameter is considered optional if: it is explicitly marked ?, its type is an interface where all fields are optional (resolved via modelsPath), or it uses an all-optional inline type |
# Include deprecated methods in the comparison
EXCLUDE_DEPRECATED=false npm run full:checks
# Ignore methods with a 'filter' parameter
EXCLUDE_FILTER_METHODS=true npm run full:checks
# Ignore optional parameters when comparing signatures
EXCLUDE_OPTIONAL_PARAMS=true npm run full:checks
# Combine options
EXCLUDE_DEPRECATED=false EXCLUDE_FILTER_METHODS=true EXCLUDE_OPTIONAL_PARAMS=true npm run full:checksDeprecation is detected from:
- PHP —
@deprecatedin the/** */docblock before the method - TypeScript / Node —
@deprecatedin the JSDoc before the method - Python —
@deprecateddecorator on the method
GitHub Token (optional)
To avoid GitHub API rate limits (60 req/h unauthenticated vs 5 000 req/h authenticated), define a token:
export GITHUB_TOKEN=your_github_token_here
npm run full:checksThe token also grants access to private repositories.
Add a parser for a new language
- Create a new parser in
src/parsers/that extends theParserclass:
// src/parsers/golang.ts
import { ClassInfo } from '../types/index.js';
import { Parser } from './base.js';
export class GolangParser extends Parser {
readonly language = 'Golang';
readonly fileExtension = '.go';
parseFile(fileName: string, content: string): ClassInfo | null {
// Implement your parsing logic here
}
}- Register the parser in
src/parsers/factory.ts:
import { GolangParser } from './golang.js';
export class ParserFactory {
private static parsers: Map<string, Parser> = new Map([
['typescript', new TypeScriptParser()],
['node', new TypeScriptParser()],
['php', new PHPParser()],
['python', new PythonParser()],
['golang', new GolangParser()], // New parser
]);
// ...
}📊 Report Example
════════════════════════════════════════════════════════════════════════════════
SDK Signature Comparison Report
════════════════════════════════════════════════════════════════════════════════
❌ Missing Classes
────────────────────────────────────────────────────────────────────────────────
📦 BackupTask
Missing from: node
📦 DomainTask
Missing from: php
────────────────────────────────────────────────────────────────────────────────
════════════════════════════════════════════════════════════════════════════════
📦 Class: ActivityTask
Languages: node, php
────────────────────────────────────────────────────────────────────────────────
⚠️ Missing Methods:
node:
- getStatus()
⚠️ Signature Differences:
Method: create()
node: (name, options?)
php: (name, config)
════════════════════════════════════════════════════════════════════════════════
Summary
────────────────────────────────────────────────────────────────────────════════
Total classes analyzed: 7
Missing classes: 2
⚠️ Found 5 issue(s)
❌ Missing Classes:
- BackupTask: missing from node
- DomainTask: missing from php
════════════════════════════════════════════════════════════════════════════════🧪 What is Compared
- ✅ Class presence: Detects when a class exists in some SDKs but not in others
- ✅ Missing classes report: Highlights classes that need to be implemented across all SDKs
- ✅ Presence of public methods: Identifies missing methods in each implementation
- ✅ Parameter names: Ensures method signatures use consistent parameter names
- ✅ Parameter order: Verifies the order of parameters across implementations
- ✅ Optional vs required parameters: Checks parameter optionality consistency
- ✅ Type normalization: Converts language-specific type names to a common vocabulary before comparing
- ✅ Deprecated methods: Automatically excluded (configurable via
EXCLUDE_DEPRECATED) - ✅ Filter methods: Optionally excluded when SDK conventions differ (configurable via
EXCLUDE_FILTER_METHODS) - ✅ Optional parameters: Optionally ignored during signature comparison (configurable via
EXCLUDE_OPTIONAL_PARAMS)
🔍 Technical Details
Parsers
Each parser is responsible for:
- Identifying classes in source files
- Extracting public methods and their parameters
- Detecting
@deprecatedannotations / decorators - Filtering out irrelevant methods (constructors, dunder methods, etc.)
| Language | Parser | Deprecation detection | Type extraction |
| ----------------- | ---------------------------------------------- | ---------------------------------- | ------------------------------------------ |
| TypeScript / Node | AST via @typescript-eslint/typescript-estree | @deprecated in JSDoc | Source text via AST range + models context |
| PHP | Regex | @deprecated in /** */ docblock | Inline type hints |
| Python | Line-by-line with decorator tracking | @deprecated decorator | Type annotations |
Model files context (modelsPath)
For TypeScript / Node SDKs, parameter types are often defined in separate model files (e.g. UpdateUserRequest, FilterListOrders). If you configure a modelsPath, the loader will fetch all model files and pass their content to the parser. The parser then scans them for interface/type declarations where every property is optional — those types are treated as optional parameters.
Important: A parameter is only treated as optional if all fields of its interface type are optional. If at least one field is required (e.g.
GrantProjectUserAccessRequestInner.userId: string), the parameter is kept as required in the comparison.
Type normalization
Each language uses different names for the same primitive types. All parsers run their extracted types through a shared normalizeType() function (src/parsers/type-normalizer.ts) before storing them, so the comparison engine always works with a common vocabulary.
| Raw (TypeScript AST) | Raw (Python) | Raw (PHP) | Normalized |
| -------------------- | --------------------------- | -------------- | ------------------------- |
| TSStringKeyword | str | string | string |
| TSNumberKeyword | int, float | int, float | number |
| TSBooleanKeyword | bool | bool | boolean |
| TSArrayType | list, List[X] | array | array / array<X> |
| TSObjectKeyword | dict, Dict[K,V] | — | object / object<K, V> |
| TSAnyKeyword | Any | mixed | any |
| — | Optional[X] / X \| None | ?string | string? |
Custom class types (e.g. ProjectFilters) are left as-is since they are already language-agnostic.
Comparison Engine
- Compares classes with the same name across all SDKs
- Applies
ComparisonOptionsto filter methods before comparison - Identifies missing methods in each SDK
- Detects signature differences (parameter names and order)
- Generates a comprehensive report:
- Missing classes section: Classes that don't exist in all SDKs (shown first)
- Class comparison section: Analyses classes present in multiple SDKs
- Missing methods: Lists methods missing in specific language implementations
- Signature differences: Shows parameter differences between implementations
- Summary: Total classes analyzed, missing class count, and total issues
🛠️ Available Scripts
npm run build: Compile TypeScriptnpm run full:checks: Launch the comparisonnpm run clean: Clean generated files
🔁 CI/CD
This repository includes two GitHub Actions workflows:
.github/workflows/qa.yml- Triggered on
push(main,develop) andpull_request - Runs linting, build, and the reference command:
- Triggered on
EXCLUDE_OPTIONAL_PARAMS=true EXCLUDE_DEPRECATED=true SDK_PHP_BRANCH=main SDK_NODE_BRANCH=main npm run full:checks.github/workflows/publish.yml- Triggered when a GitHub Release is published
- Builds the package and publishes it to npmjs
Required GitHub Secrets
NPM_TOKEN: npm automation token with publish permission for the target package
Release Flow
- Update
package.jsonversion - Push changes and tag
- Create a GitHub Release from the tag
publish.ymlpublishes the package to npmjs automatically
Using from another repository CI
After the package is published, a consuming repository can install and run it with:
npm install upsun-sdk-checker
EXCLUDE_OPTIONAL_PARAMS=true EXCLUDE_DEPRECATED=true SDK_PHP_BRANCH=main SDK_NODE_BRANCH=main npx upsun-sdk-checker full:checksfull:checks is exposed as a CLI alias for CI usage.
📝 Notes
- Constructors and destructors are automatically excluded from comparison
- Only public methods are compared
@deprecatedmethods are excluded by default (setEXCLUDE_DEPRECATED=falseto include them)- Methods with a
filterparameter can be excluded viaEXCLUDE_FILTER_METHODS=true - Optional parameters can be excluded from signature comparison via
EXCLUDE_OPTIONAL_PARAMS=true - The script returns exit code
1if there are differences,0otherwise (useful for CI/CD)
🤝 Contributing
To add support for a new language:
- Create a new parser in
src/parsers/extendingParser(seegolang.example.tsfor a template) - Register it in
src/parsers/factory.ts - Export it from
src/parsers/index.ts - Add the configuration in
src/config.ts - Test with
npm run full:checks
