@kalutskii/contract 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,370 @@
1
+ # contract
2
+
3
+ A TypeScript CLI tool for building contract packages that define shared types and interfaces for distribution via npm.
4
+
5
+ Instead of syncing contracts over HTTP between services, this library generates publishable npm packages containing bundled TypeScript type definitions. Services consume these packages via standard package managers.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ # npm / yarn / pnpm
13
+ npm install -g @kalutskii/contract
14
+
15
+ # bun
16
+ bun add -g @kalutskii/contract
17
+ ```
18
+
19
+ Or run without installing:
20
+
21
+ ```bash
22
+ bunx @kalutskii/contract <command>
23
+ ```
24
+
25
+ > Requires `typescript >= 5.9` and `jiti >= 2.6` as peer dependencies.
26
+
27
+ ---
28
+
29
+ ## What is a contract?
30
+
31
+ A contract is a versioned set of TypeScript type definitions that one service publishes and other services consume. For example:
32
+
33
+ - **Service A** defines types for "API responses", "request models", etc.
34
+ - **Service A** publishes a contract package `@company-contracts/service-a`
35
+ - **Service B** installs and imports: `import type * as ServiceAContracts from '@company-contracts/service-a/api'`
36
+
37
+ ---
38
+
39
+ ## Quick Start
40
+
41
+ ### Initialize
42
+
43
+ ```bash
44
+ bunx contract init
45
+ ```
46
+
47
+ This creates:
48
+
49
+ - `contract.config.ts` - Configuration file
50
+ - `contract/manifests/` - Directory for contract definitions
51
+ - `contract/generated/` - Output directory for bundled declarations
52
+ - `contract/package/` - Output directory for publishable package
53
+
54
+ ### Define Contracts
55
+
56
+ Edit `contract/manifests/contract.<name>.manifest.ts`:
57
+
58
+ ```typescript
59
+ // contract/manifests/contract.api.manifest.ts
60
+
61
+ export interface UserCreateRequest {
62
+ email: string;
63
+ name: string;
64
+ }
65
+
66
+ export interface UserCreateResponse {
67
+ id: string;
68
+ createdAt: string;
69
+ }
70
+ ```
71
+
72
+ For emitted runtime values, prefer this shape:
73
+
74
+ ```typescript
75
+ export { PBACPermissionsRecord } from '@/app/domain/permissions/permissions.constants';
76
+ ```
77
+
78
+ Keep `permissions.constants.ts` as a leaf runtime file with no unrelated runtime imports.
79
+
80
+ ### Build Declarations
81
+
82
+ ```bash
83
+ bunx contract build
84
+ ```
85
+
86
+ This bundles each manifest into a standalone `.d.ts` file using `dts-bundle-generator`.
87
+ If a contract name is listed in `emit` inside `contract.config.ts`, the build also emits a runtime `.js` file for that manifest.
88
+
89
+ For `emit` contracts, keep runtime exports narrow:
90
+
91
+ - re-export directly from the concrete file that owns the value
92
+ - avoid barrel files for runtime exports
93
+ - keep that source file free of unrelated runtime imports when possible
94
+
95
+ ### Prepare Package
96
+
97
+ ```bash
98
+ bunx contract prepare:package
99
+ ```
100
+
101
+ This creates a publishable package in `contract/package/`:
102
+
103
+ ```
104
+ contract/package/
105
+ ├── package.json # Package metadata
106
+ ├── index.d.ts # Exports all contracts
107
+ ├── index.js # Runtime entrypoint for emitted contracts
108
+ ├── api.d.ts # Contract: api
109
+ ├── api.js # Runtime contract module when emitted
110
+ ├── types.d.ts # Contract: types
111
+ └── types.js # Stub
112
+ ```
113
+
114
+ Hash state is stored at `contract/.contract-package-state.json`.
115
+
116
+ **Automatic versioning:**
117
+
118
+ The command automatically bumps the patch version if the generated contract files have changed:
119
+
120
+ - First run: stores a content hash, version unchanged
121
+ - Content unchanged: version stays the same
122
+ - Content changed: patch version bumps (e.g., `1.0.0 → 1.0.1`)
123
+
124
+ **Manual version overrides:**
125
+
126
+ ```bash
127
+ bunx contract prepare:package --bump minor
128
+ bunx contract prepare:package --bump major
129
+ bunx contract prepare:package --no-bump
130
+ ```
131
+
132
+ The `--no-bump` flag disables automatic version bumping.
133
+
134
+ ### Pack Package
135
+
136
+ ```bash
137
+ bunx contract pack:package
138
+ ```
139
+
140
+ Creates a `.tgz` archive of the prepared package in `contract/package/`.
141
+
142
+ ### Publish Package
143
+
144
+ ```bash
145
+ bunx contract publish:package
146
+ ```
147
+
148
+ Publishes the prepared package to npm. The CLI writes `.npmrc` inside `contract/package` and publishes with public access enabled.
149
+
150
+ If the current version already exists on npm, publishing fails with a clear message and you should run:
151
+
152
+ ```bash
153
+ bunx contract prepare:package --bump patch
154
+ ```
155
+
156
+ **Token priority:**
157
+
158
+ 1. `config.npm.token`
159
+ 2. `NPM_TOKEN`
160
+ 3. `NODE_AUTH_TOKEN`
161
+
162
+ The package can also be prepared and published in one step:
163
+
164
+ ```bash
165
+ bunx contract publish:package --prepare
166
+ ```
167
+
168
+ The `--prepare` flag will rebuild the package before publishing.
169
+
170
+ ---
171
+
172
+ ## Configuration
173
+
174
+ `contract.config.ts`:
175
+
176
+ ```typescript
177
+ import type { Config } from 'contract';
178
+
179
+ const contractConfig: Config = {
180
+ app: 'admin-service',
181
+ contracts: ['api', 'types', 'events'],
182
+ emit: ['events'],
183
+ package: {
184
+ name: '@company-contracts/admin-service',
185
+ version: '1.0.0',
186
+ },
187
+ npm: {
188
+ token: process.env.NPM_TOKEN ?? '',
189
+ },
190
+ };
191
+
192
+ export default contractConfig;
193
+ ```
194
+
195
+ **Fields:**
196
+
197
+ - `app` - Service/app name (used in generated filenames)
198
+ - `contracts` - List of contract names to generate
199
+ - `emit` - Subset of contracts that should also publish runtime JavaScript
200
+ - `package.name` - NPM package name
201
+ - `package.version` - Semantic version
202
+ - `package.exports` - (Optional) Custom export field configuration
203
+ - `npm.token` - (Optional) NPM auth token used for publishing
204
+
205
+ ---
206
+
207
+ ## Commands
208
+
209
+ | Command | Purpose |
210
+ | ----------------------------- | --------------------------------------------------------- |
211
+ | `contract init` | Initialize contract environment and create default config |
212
+ | `contract update:environment` | Update directories and manifests based on current config |
213
+ | `contract build` | Bundle manifest files into `.d.ts` declarations |
214
+ | `contract prepare:package` | Generate publishable npm package directory |
215
+ | `contract pack:package` | Pack prepared package into a `.tgz` archive |
216
+ | `contract publish:package` | Publish package to npm using config/env token |
217
+
218
+ ---
219
+
220
+ ## Directory Structure
221
+
222
+ ```
223
+ contract/
224
+ ├── manifests/ # Your contract definitions (source)
225
+ │ ├── contract.api.manifest.ts
226
+ │ └── contract.types.manifest.ts
227
+ ├── generated/ # Built .d.ts files (output)
228
+ │ ├── app.contract.api.d.ts
229
+ │ ├── app.contract.events.js
230
+ │ └── app.contract.types.d.ts
231
+ └── package/ # Publishable npm package (output)
232
+ ├── package.json
233
+ ├── index.d.ts
234
+ ├── index.js
235
+ ├── api.d.ts
236
+ ├── events.js
237
+ └── types.d.ts
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Consumer Usage
243
+
244
+ After publishing your contract package, consumers install and import it:
245
+
246
+ ```typescript
247
+ // Consumer service
248
+ import type { UserCreateRequest } from '@company-contracts/admin-service/api';
249
+
250
+ const user: UserCreateRequest = {
251
+ email: 'user@example.com',
252
+ name: 'John Doe',
253
+ };
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Development Workflow
259
+
260
+ ### Producer Service (publishes contracts)
261
+
262
+ ```bash
263
+ # Define contracts in contract/manifests/
264
+ # Update contract.config.ts
265
+
266
+ bunx contract update:environment # Sync manifest files
267
+ bunx contract build # Generate .d.ts from manifests
268
+ bunx contract prepare:package # Create npm package (auto-versions if content changed)
269
+ bunx contract publish:package # Publish to npm
270
+ ```
271
+
272
+ **Versioning behavior:**
273
+
274
+ - `prepare:package` detects content changes and bumps patch version automatically
275
+ - `publish:package` checks whether target version already exists on npm
276
+ - if version exists, publish fails and asks for manual bump (`--bump patch|minor|major`)
277
+ - Use `--bump major|minor` to manually override during prepare
278
+ - Use `--no-bump` to disable automatic bumping
279
+
280
+ **Requirements:**
281
+
282
+ - provide `npm.token` in `contract.config.ts`, or set `NPM_TOKEN` / `NODE_AUTH_TOKEN`
283
+
284
+ ### Consumer Service (uses contracts)
285
+
286
+ ```bash
287
+ # Install the contract package
288
+ bun add @company-contracts/admin-service
289
+
290
+ # Import types
291
+ import type * as AdminAPI from '@company-contracts/admin-service/api';
292
+ ```
293
+
294
+ ---
295
+
296
+ ## Notes
297
+
298
+ - This library is **local-only** — it does not perform remote synchronization or automatic publishing
299
+ - Publishing uses a temporary `.npmrc` in `contract/package` from `config.npm.token`, `NPM_TOKEN`, or `NODE_AUTH_TOKEN` and removes it after the publish attempt
300
+ - Contract manifests can export runtime values for contracts listed in `emit`
301
+ - For `emit`, import or re-export from direct leaf files instead of barrels or service modules with broader dependency graphs
302
+ - Use `contract update:environment` to regenerate missing files (e.g., after adding new contracts)
303
+ - Versions are automatically managed based on content changes and npm registry state
304
+ - Content hash is stored in `contract/.contract-package-state.json` for change detection
305
+ - If npm version already exists, bump version manually via `contract prepare:package --bump ...`
306
+
307
+ ---
308
+
309
+ ## Contributing
310
+
311
+ ### Prerequisites
312
+
313
+ - [Bun](https://bun.sh) >= 1.0
314
+ - Node.js >= 20 (for tooling compatibility)
315
+
316
+ ### Setup
317
+
318
+ ```bash
319
+ git clone https://github.com/kalutskii/contract.git
320
+ cd contract
321
+ bun install
322
+ ```
323
+
324
+ ### Scripts
325
+
326
+ | Script | Purpose |
327
+ | ------------------- | ---------------------------------------- |
328
+ | `bun run build` | Compile CLI and library via tsup |
329
+ | `bun run typecheck` | Run TypeScript compiler without emitting |
330
+ | `bun run lint` | Run ESLint across all TypeScript sources |
331
+
332
+ ### Project Layout
333
+
334
+ ```
335
+ src/
336
+ adapters/ # CLI framework wiring (Clipanion)
337
+ environment/ # Config loading, validation, and env helpers
338
+ modules/
339
+ build/ # contract build command
340
+ init/ # contract init command
341
+ pack/ # contract pack:package command
342
+ prepare/ # contract prepare:package command
343
+ publish/ # contract publish:package command
344
+ versioning/ # Content hashing and semver bump logic
345
+ utilities/ # Shared utility functions
346
+ cli.entrypoint.ts # CLI entry point
347
+ index.ts # Library public API
348
+ ```
349
+
350
+ ### Tech Stack
351
+
352
+ - **tsup** — bundle and emit TypeScript declarations
353
+ - **dts-bundle-generator** — bundle manifest files into single `.d.ts` files
354
+ - **Bun** — runtime and package manager
355
+ - **Clipanion** — CLI framework
356
+ - **Zod** — config schema validation
357
+ - **@clack/prompts** — interactive terminal prompts
358
+
359
+ ### Making Changes
360
+
361
+ 1. Edit source under `src/`
362
+ 2. Run `bun run typecheck` and `bun run lint` to validate
363
+ 3. Run `bun run build` to compile
364
+ 4. Test the CLI locally: `./dist/cli.entrypoint.js <command>`
365
+
366
+ ---
367
+
368
+ ## License
369
+
370
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env bun