Skip to main content

Adding a Package

This guide explains how to create a new shared TypeScript package under packages/ and make it available to all apps and other packages via the @gospelib/* scope.

When to Create a Package

Create a shared package when:

  • Multiple apps need the same code (types, utilities, components)
  • You want a clean contract boundary between layers
  • The code has no service-specific business logic
tip

If the code is only used by one app, keep it local to that app. Extract to a package when a second consumer appears.

Existing Packages

PackagePurpose
@gospelib/typesShared TypeScript types (auto-generated from OpenAPI + manual)
@gospelib/uiCross-platform React Native component library
@gospelib/sdkClient SDK using openapi-fetch
@gospelib/configESLint/TSConfig presets + Zod env schemas
@gospelib/testingShared test fixtures, mocks, and helpers

Step-by-Step

1. Create the directory structure

packages/<name>/
├── src/
│ └── index.ts # Barrel export
├── package.json
├── project.json
├── tsconfig.json
└── vitest.config.ts # If the package has tests

2. Create package.json

{
"name": "@gospelib/<name>",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"test": "vitest run",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.5"
}
}
info

Packages use "private": true because they are consumed via pnpm workspace protocol, not published to npm.

3. Create tsconfig.json

Extend the base config:

{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}

4. Create project.json

Register the package with Nx:

{
"name": "<name>",
"projectType": "library",
"sourceRoot": "packages/<name>/src",
"targets": {
"test": {
"command": "cd packages/<name> && vitest run"
},
"lint": {
"command": "cd packages/<name> && eslint ."
},
"typecheck": {
"command": "cd packages/<name> && tsc --noEmit"
}
}
}

5. Add the path alias

In tsconfig.base.json at the repo root, add:

{
"compilerOptions": {
"paths": {
"@gospelib/<name>": ["packages/<name>/src/index.ts"]
}
}
}

6. Write your barrel export

// packages/<name>/src/index.ts
export { someFunction } from './someModule';
export type { SomeType } from './types';

Consume the Package

In any app or package that depends on it:

# From the repo root
pnpm add @gospelib/<name> --filter @gospelib/web --workspace

This adds a workspace protocol dependency:

{
"dependencies": {
"@gospelib/<name>": "workspace:*"
}
}

For Next.js apps, add the package to transpilePackages in next.config.ts:

const nextConfig: NextConfig = {
transpilePackages: ['@gospelib/<name>'],
};

Dependency Rules

Follow these constraints to keep the dependency graph clean:

  • Apps can import any package
  • @gospelib/sdk imports only @gospelib/types
  • Packages should not import app code
  • Services (Go/Python) never import TS packages at runtime

Verify It Works

  1. Run pnpm install from the repo root to link the workspace package
  2. Import from a consumer: import { something } from '@gospelib/<name>'
  3. Type check: pnpm typecheck
  4. Run tests: cd packages/<name> && pnpm test

Checklist

  • package.json with @gospelib/<name> name and "private": true
  • tsconfig.json extending ../../tsconfig.base.json
  • project.json with Nx targets
  • Path alias in tsconfig.base.json
  • Barrel export in src/index.ts
  • Consumer added to transpilePackages (if Next.js)
  • Commit scope added to commitlint.config.mjs
  • Release Please component added to release-please-config.json