# Contributing to Identity Common TypeScript Thank you for your interest in contributing to Identity Common TypeScript! This document provides guidelines and instructions for contributing to the project. ## Table of Contents - [Code of Conduct](#code-of-conduct) - [Getting Started](#getting-started) - [Project Structure](#project-structure) - [Development Workflow](#development-workflow) - [Adding a New Package](#adding-a-new-package) - [Coding Standards](#coding-standards) - [Testing](#testing) - [Submitting Changes](#submitting-changes) - [Release Process](#release-process) ## Code of Conduct This project follows the [OpenWallet Foundation Code of Conduct](https://tac.openwallet.foundation/governance/code-of-conduct/). By participating, you are expected to uphold this code. ## Getting Started ### Prerequisites - [Node.js](https://nodejs.org/) >= 20 - [pnpm](https://pnpm.io/) (version specified in `package.json` under `packageManager`) ### Installation 1. Clone the repository: ```bash git clone https://github.com/openwallet-foundation-labs/identity-common-ts.git cd identity-common-ts ``` 2. Install dependencies: ```bash pnpm install ``` 3. Run tests: ```bash pnpm test ``` Within the workspace, packages export their TypeScript source (`src/index.ts`), so tests, type checks, and examples don't need a build. The built `dist` entrypoints are only used when publishing, through `publishConfig`. ## Project Structure This is a monorepo managed with [pnpm workspaces](https://pnpm.io/workspaces). The structure is organized as follows: ```text identity-common-ts/ ├── packages/ # All publishable packages │ ├── identity-common/ # Core identity utilities (base package) │ ├── jose/ # JOSE/JWT implementation (planned) │ ├── cose/ # COSE/CWT implementation (planned) │ ├── x509/ # X.509 certificate utilities (planned) │ ├── token-status-list/ # JWT/CWT Token Status List (planned) │ ├── sd-jwt-*/ # SD-JWT and SD-JWT VC packages (@sd-jwt/*) │ ├── mdoc/ # ISO/IEC 18013-5 mDOC and mDL (@owf/mdoc) │ ├── dcql/ # Digital Credentials Query Language (dcql) │ ├── oauth2/ # OAuth 2.0 (@openid4vc/oauth2) │ ├── openid4vc*/ # OpenID4VCI, OpenID4VP and shared utilities (@openid4vc/*) │ └── eudi-*/ # EUDI-specific packages (planned) ├── examples/ # Runnable examples, grouped by topic (e.g. examples/sd-jwt) ├── docs/sd-jwt/ # sdjwt.js.org landing page (deployed to GitHub Pages) ├── tests/ # Integration tests ├── .changeset/ # Changeset configuration for versioning └── .github/ # GitHub Actions workflows ``` ### Package Categories Packages are organized into six main categories: 1. **Core Identity Utilities** (`@owf/identity-*`) - Generic, reusable utilities for any identity solution - Platform agnostic (Node.js, browsers, React Native) - Minimal dependencies 2. **EUDI-Specific Tools** (`@owf/eudi-*`) - Tools specific to the European Digital Identity Wallet ecosystem - Built on top of core utilities - Implements ETSI and ARF specifications 3. **SD-JWT** (`@sd-jwt/*`) - SD-JWT (RFC 9901) and SD-JWT VC implementations - Located in `packages/sd-jwt-` (or `packages/sd-jwt-vc`) - Versioned separately from the `@owf/*` packages (see `.changeset/config.json`) 4. **mDOC** (`@owf/mdoc`) - ISO/IEC 18013-5 mDOC and mDL implementation - Located in `packages/mdoc` - Versioned separately from the other `@owf/*` packages (see `.changeset/config.json`) 5. **DCQL** (`dcql`) - Digital Credentials Query Language (OpenID4VP) implementation - Located in `packages/dcql` - Published under the unscoped `dcql` name, so it is not part of a fixed version group and is versioned separately 6. **OpenID4VC** (`@openid4vc/*`) - OAuth 2.0, OpenID4VCI and OpenID4VP implementations - Located in `packages/oauth2`, `packages/openid4vci`, `packages/openid4vp` and `packages/openid4vc-utils` (`@openid4vc/utils`) - Versioned together, separately from the other packages (see `.changeset/config.json`) ## Development Workflow ### Available Scripts From the root of the repository: | Command | Description | | ------- | ----------- | | `pnpm build` | Build all packages | | `pnpm test` | Run all tests (`pnpm test packages/mdoc` to run a subset) | | `pnpm types:check` | Type-check all packages | | `pnpm packages:check` | Validate the `package.json` entrypoints of all packages | | `pnpm esm:check` | Validate the built packages can be imported and required (run `pnpm build` first) | | `pnpm style:check` | Check code style with Biome | | `pnpm style:fix` | Fix code style issues | | `pnpm md:check` | Check markdown files | | `pnpm md:fix` | Fix markdown issues | | `pnpm create-package [--eudi]` | Scaffold a new package | ### Working on a Package 1. Navigate to the package directory or work from the root 2. Make your changes 3. Run tests: `pnpm test` 4. Check types: `pnpm types:check` 5. Check/fix style: `pnpm style:fix` 6. Check/fix markdown: `pnpm md:fix` ## Adding a New Package Follow these steps to add a new package to the monorepo: ### 1. Scaffold the Package Prefer the package generator: ```bash pnpm create-package my-package pnpm create-package eudi-example --eudi ``` This creates the package directory, `package.json`, `tsconfig.json`, `README.md`, and starter test files with the repository defaults. If you need to create a package manually, follow the steps below. ### 2. Create `package.json` Create `packages/my-package/package.json`: ```json { "name": "@owf/my-package", "version": "0.0.0", "description": "Description of your package", "files": ["dist"], "license": "Apache-2.0", "exports": "./src/index.ts", "homepage": "https://github.com/openwallet-foundation-labs/identity-common-ts/tree/main/packages/my-package", "repository": { "type": "git", "url": "https://github.com/openwallet-foundation-labs/identity-common-ts", "directory": "packages/my-package" }, "publishConfig": { "access": "public", "main": "./dist/index.mjs", "module": "./dist/index.mjs", "types": "./dist/index.d.mts", "exports": { ".": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" }, "./package.json": "./package.json" } }, "scripts": { "build": "tsdown src/index.ts --format esm --dts --sourcemap" }, "dependencies": {}, "devDependencies": {} } ``` The top-level `exports` points to the TypeScript source, so other packages, tests, and examples in the workspace use the source directly. The published entrypoints are set in `publishConfig`, which pnpm applies when packing and publishing. New packages are ESM-only: `require` resolves to the ESM build through the `default` condition. Some existing packages also ship a CommonJS build (`dist/index.cjs`) while they are migrated. `pnpm packages:check` validates that each package follows one of these two layouts, and `pnpm esm:check` validates that each packed package can be imported and required. **Naming conventions:** - Core utilities: `@owf/identity-` or `@owf/` (e.g., `@owf/jose`, `@owf/x509`) - EUDI-specific: `@owf/eudi-` (e.g., `@owf/eudi-lote`, `@owf/eudi-payment`) - SD-JWT: `@sd-jwt/` in `packages/sd-jwt-` (e.g., `@sd-jwt/core` in `packages/sd-jwt-core`) - OpenID4VC: `@openid4vc/` in `packages/` (e.g., `@openid4vc/openid4vp` in `packages/openid4vp`), except `@openid4vc/utils` in `packages/openid4vc-utils` ### 3. Create TypeScript Configuration Create `packages/my-package/tsconfig.json`: ```json { "extends": "../../tsconfig.json" } ``` ### 4. Create Source Files Create `packages/my-package/src/index.ts`: ```typescript // Export your public API here export {} ``` ### 5. Create README Create `packages/my-package/README.md`: ```markdown # @owf/my-package Description of your package. ## Installation ```bash npm install @owf/my-package # or pnpm add @owf/my-package # or yarn add @owf/my-package ``` ## Usage ```typescript import { } from '@owf/my-package' ``` ## API Reference Document your public API here. ## License This project is licensed under the Apache License Version 2.0 (Apache-2.0). ### 6. Add Tests Create tests in `packages/my-package/src/__tests__/`: ```typescript // packages/my-package/src/__tests__/my-package.test.mts import { describe, it, expect } from 'vitest' describe('my-package', () => { it('should work', () => { expect(true).toBe(true) }) }) ``` ### 7. Update Root Configuration (if needed) Dependencies that are only used in tests, examples, or scripts are shared across packages and go in the `devDependencies` of the root `package.json`, not in the package itself. Use [msw](https://mswjs.io) to mock HTTP requests. If your package needs to be available in the root `package.json` for examples or integration tests: ```json { "devDependencies": { "@owf/my-package": "workspace:*" } } ``` ### 8. Install and Test ```bash pnpm install pnpm test ``` ## Coding Standards ### TypeScript - Use strict TypeScript (`"strict": true`) - Export types explicitly - Avoid `any` - use `unknown` when type is truly unknown - Document public APIs with JSDoc comments ### Platform Agnostic This library must work across Node.js, browsers, and React Native: - **No platform-specific APIs**: Don't use Node.js `crypto`, `fs`, `Buffer`, etc. - **Callback-based crypto**: Accept crypto operations as callbacks - **Standard APIs only**: Use `Uint8Array` instead of `Buffer`, `URL`/`URLSearchParams` for URLs - **No native dependencies**: Avoid packages with native bindings Example of platform-agnostic design: ```typescript // ❌ Don't do this import { createHash } from 'crypto' const hash = createHash('sha256').update(data).digest() // ✅ Do this instead interface CryptoCallbacks { sha256(data: Uint8Array): Promise } function myFunction(data: Uint8Array, crypto: CryptoCallbacks) { const hash = await crypto.sha256(data) // ... } ``` ### Code Style We use [Biome](https://biomejs.dev/) for linting and formatting: - 2 spaces for indentation - Single quotes for strings - No semicolons (except where required) - 120 character line width Run `pnpm style:fix` before committing. ## Testing We use [Vitest](https://vitest.dev/) for testing. ### Running Tests ```bash # Run all tests pnpm test # Run tests in watch mode pnpm test --watch # Run tests for a specific package pnpm test --filter @owf/my-package ``` ### Writing Tests - Place tests in `src/__tests__/` within each package - Use `.test.mts` extension for test files - Test public API behavior, not implementation details - Include edge cases and error conditions ## Submitting Changes ### Creating a Pull Request 1. Fork the repository 2. Create a feature branch: `git checkout -b feature/my-feature` 3. Make your changes 4. Run checks: `pnpm test && pnpm types:check && pnpm style:check` 5. Create a changeset (if your changes affect published packages) 6. Commit your changes 7. Push and create a Pull Request ### Changesets We use [Changesets](https://github.com/changesets/changesets) for version management. If your changes affect any published package, create a changeset: ```bash pnpm changeset ``` Follow the prompts to: 1. Select the packages that changed 2. Choose the semver bump type (major/minor/patch) 3. Write a summary of changes **When to create a changeset:** - Adding new features (minor) - Fixing bugs (patch) - Breaking changes (major) - Documentation improvements that affect the published package (patch) **When NOT to create a changeset:** - Changes to CI/CD - Changes to root-level dev dependencies - Changes to documentation outside packages ## Release Process Releases are automated via GitHub Actions: 1. Changesets accumulate in PRs 2. A "Version Packages" PR is automatically created/updated 3. Merging the Version PR triggers a release to npm ### Publishing New Packages > **Note:** Only project maintainers with publish access to the `@owf` npm scope can perform the initial package publish. If you're contributing a new package, coordinate with a maintainer to complete this step. This project uses npm's [trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC-based) for automated releases. However, trusted publishing only works for packages that already exist on npm. **New packages must be published manually first.** When adding a new package to the monorepo: 1. **Build the package locally:** ```bash pnpm build ``` 2. **Publish the package manually:** ```bash cd packages/your-new-package pnpm publish --access public ``` Use `pnpm publish`, not `npm publish`: only pnpm applies the `main`, `types`, and `exports` fields from `publishConfig`. With `npm publish`, the package would point to the TypeScript source, which isn't included in the published files. You must be logged into npm (`npm login`) with an account that has publish access to the `@owf` scope. 3. **Configure trusted publishing once:** Run this command for each package: ```bash npm trust github @owf/my-package --repo openwallet-foundation-labs/identity-common-ts --file release.yml --yes ``` Replace `@owf/my-package` with the actual package name. This requires npm >= 11.13 and 2FA enabled on your npm account. **Manual configuration (if npm trust is not available):** For each published package, visit: ```string https://www.npmjs.com/package/@owf//settings/access ``` Then in the "Trusted Publishers" section, add: - **Organization**: `openwallet-foundation-labs` - **Repository**: `identity-common-ts` - **Workflow filename**: `release.yml` `npm trust` only works after the package already exists on npm. 4. **Future releases are automatic** - CI will handle all subsequent publishes via trusted publishing. Maintainers will handle the release process. ## Questions? If you have questions, feel free to [open an issue](https://github.com/openwallet-foundation-labs/identity-common-ts/issues). Thank you for contributing! 🎉