6.7 KiB
Contributing to Start SDK
This guide covers developing the SDK itself. If you're building a service package using the SDK, see the packaging docs.
For contributing to the broader StartOS project, see the root CONTRIBUTING.md.
Prerequisites
- Node.js v22+ (via nvm recommended)
- npm (ships with Node.js)
- GNU Make
Verify your setup:
node --version # v22.x or higher
npm --version
make --version
Repository Layout
sdk/
├── base/ # @start9labs/start-sdk-base (core types, ABI, effects)
│ ├── lib/ # TypeScript source
│ ├── package.json
│ ├── tsconfig.json
│ └── jest.config.js
├── package/ # @start9labs/start-sdk (full developer-facing SDK)
│ ├── lib/ # TypeScript source
│ ├── package.json
│ ├── tsconfig.json
│ └── jest.config.js
├── baseDist/ # Build output for base (generated)
├── dist/ # Build output for package (generated, published to npm)
├── Makefile # Build orchestration
├── README.md
├── ARCHITECTURE.md
└── CLAUDE.md
Getting Started
Install dependencies for both sub-packages:
cd sdk
make node_modules
This runs npm ci in both base/ and package/.
Building
Full Build
make bundle
This runs the complete pipeline: TypeScript compilation, hand-written pair copying, node_modules bundling, formatting, and tests. Outputs land in baseDist/ (base) and dist/ (package).
Individual Targets
| Target | Description |
|---|---|
make bundle |
Full build: compile + format + test |
make baseDist |
Compile base package only |
make dist |
Compile full package (depends on base) |
make fmt |
Run Prettier on all .ts files |
make check |
Type-check without emitting (both packages) |
make clean |
Remove all build artifacts and node_modules |
What the Build Does
- Peggy parser generation —
base/lib/exver/exver.pegjsis compiled toexver.ts(the ExVer version parser) - TypeScript compilation — Strict mode, CommonJS output, declaration files
base/compiles tobaseDist/package/compiles todist/
- Hand-written pair copying —
.js/.d.tsfiles without a corresponding.tssource are copied into the output directories. These are manually maintained JavaScript files with hand-written type declarations. - Dependency bundling —
node_modules/is rsynced into both output directories so the published package is self-contained - Formatting — Prettier formats all TypeScript source
- Testing — Jest runs both test suites
Testing
# Run all tests (base + package)
make test
# Run base tests only
make base/test
# Run package tests only
make package/test
# Run a specific test file directly
cd base && npx jest --testPathPattern=exver
cd package && npx jest --testPathPattern=host
Tests use Jest with ts-jest for TypeScript support. Configuration is in each sub-package's jest.config.js.
Test Files
Tests live alongside their subjects or in dedicated test/ directories:
base/lib/test/— ExVer parsing, input spec types, deep merge, graph utilities, type validationbase/lib/util/inMs.test.ts— Time conversion utilitypackage/lib/test/— Health checks, host binding, input spec builder
Test files use the .test.ts extension and are excluded from compilation via tsconfig.json.
Formatting
make fmt
Runs Prettier with the project config (single quotes, no semicolons, trailing commas, 2-space indent). The Prettier config lives in each sub-package's package.json:
{
"trailingComma": "all",
"tabWidth": 2,
"semi": false,
"singleQuote": true
}
Type Checking
To check types without building:
make check
Or directly per package:
cd base && npm run check
cd package && npm run check
Both packages use strict TypeScript ("strict": true) targeting ES2021 with CommonJS module output.
Local Development with a Service Package
To test SDK changes against a local service package without publishing to npm:
# Build and create a local npm link
make link
# In your service package directory
npm link @start9labs/start-sdk
This symlinks the built dist/ into your global node_modules so your service package picks up local changes.
Publishing
make publish
This builds the full bundle, then runs npm publish --access=public --tag=latest from dist/. The published package is @start9labs/start-sdk.
Only the dist/ directory is published — it contains the compiled JavaScript, declaration files, bundled dependencies, and package metadata.
Adding New Features
Base vs Package
Decide where new code belongs:
base/— Types, interfaces, ABI contracts, OS bindings, and low-level builders that have no dependency on the package layer. Code here should be usable independently.package/— Developer-facing API, convenience wrappers, runtime helpers (daemons, health checks, backups, file helpers, subcontainers). Code here imports from base and adds higher-level abstractions.
Key Conventions
- Builder pattern — Most APIs use immutable builder chains (
.addDaemon(),.mountVolume(),.addAction()). Each call returns a new type that accumulates configuration. - Effects boundary — All runtime interactions go through the
Effectsinterface. Never call system APIs directly. - Manifest type threading — The manifest type flows through generics so that volume names, image IDs, and dependency IDs are type-constrained.
- Re-export from package — If you add a new export to base, also re-export it from
package/lib/index.ts(or expose it throughStartSdk.build()).
Adding OS Bindings
Types in base/lib/osBindings/ mirror Rust types from the StartOS core. When Rust types change, the corresponding TypeScript bindings need updating. These are re-exported through base/lib/osBindings/index.ts.
Writing Tests
- Place test files next to the code they test, or in the
test/directory - Use the
.test.tsextension - Tests run in Node.js with ts-jest — no browser environment
Commit Messages
Follow Conventional Commits:
feat(sdk): add WebSocket health check
fix(sdk): correct ExVer range parsing for pre-release versions
test(sdk): add coverage for MultiHost port binding
See the root CONTRIBUTING.md for the full convention.