Skip to content

Contributing

Maarten Vroegindeweij edited this page Feb 18, 2026 · 1 revision

Contributing

Prerequisites

  • Node.js >= 20
  • pnpm >= 9

Setup

git clone https://github.com/OpenAEC-Foundation/Ifc-Factory.git
cd Ifc-Factory
pnpm install

Development Commands

Command Description
pnpm build Build all packages (respects dependency order)
pnpm build:schema Regenerate TypeScript from EXPRESS schema
pnpm test Run all tests
pnpm test:watch Run tests in watch mode
pnpm lint Check code with Biome
pnpm lint:fix Auto-fix lint issues
pnpm typecheck TypeScript type checking
pnpm clean Remove all dist directories

Package-specific commands

# Build a single package
pnpm --filter @ifc-factory/core build

# Test a single package
pnpm --filter @ifc-factory/express-parser test

# Run all tests in watch mode
pnpm test:watch

Build Order

Packages must be built in dependency order. The root pnpm build script handles this automatically.

1. express-parser    (Layer 0, no deps)
2. step-serializer   (Layer 0, no deps)
3. codegen           (Layer 1, depends on express-parser)
4. schema            (Layer 2, generated output — built separately via build:schema)
5. step-parser       (Layer 2, depends on step-serializer + schema)
6. core              (Layer 3, depends on schema + step-parser + step-serializer)
7. ifc-utils         (Layer 4, depends on core + schema)

Packages 1 and 2 can be built in parallel. Package 4 (schema) is typically already built and committed.

Project Structure

Ifc-Factory/
├── package.json                 # Root workspace (private: true)
├── pnpm-workspace.yaml          # packages: ["packages/*"]
├── tsconfig.json                # Shared TypeScript base config
├── tsconfig.build.json          # Project references for tsc --build
├── vitest.config.ts             # Root test config with projects
├── biome.json                   # Biome v2 linting/formatting config
├── .gitignore
├── .npmrc
├── schemas/
│   └── IFC4X3_ADD2.exp          # Official IFC EXPRESS schema
└── packages/
    ├── express-parser/          # @ifc-factory/express-parser
    ├── step-serializer/         # @ifc-factory/step-serializer
    ├── codegen/                 # @ifc-factory/codegen
    ├── schema/                  # @ifc-factory/schema (contains generated/)
    ├── step-parser/             # @ifc-factory/step-parser
    ├── core/                    # @ifc-factory/core
    └── ifc-utils/               # @ifc-factory/ifc-utils

Per-Package Structure

Each package follows the same pattern:

packages/<name>/
├── package.json          # npm package manifest (dual ESM/CJS exports)
├── tsconfig.json         # Extends root, sets rootDir/outDir/references
├── tsup.config.ts        # Build config (ESM + CJS + DTS + sourcemap)
├── vitest.config.ts      # Test project config
├── src/
│   ├── index.ts          # Barrel export (public API)
│   └── ...               # Implementation files
├── tests/
│   └── *.test.ts         # Vitest test files
└── dist/                 # Build output (gitignored)

Adding a New Feature

  1. Identify the correct package — see Package Reference for responsibilities
  2. Create or modify source files in src/
  3. Export new public API from src/index.ts
  4. Add tests in tests/
  5. Run verification:
    pnpm build && pnpm test && pnpm typecheck && pnpm lint

Regenerating the Schema

When the IFC EXPRESS schema is updated:

  1. Replace schemas/IFC4X3_ADD2.exp with the new schema
  2. Run pnpm build:schema
  3. Verify: pnpm build && pnpm test
  4. Commit the regenerated files in packages/schema/src/generated/

Code Style

  • Biome v2 for linting and formatting
  • 2 space indentation
  • Single quotes for strings
  • ESM (import/export, not require)
  • TypeScript strict mode enabled
  • Generated code (**/generated/**) is excluded from linting
  • Prefer import type for type-only imports

Testing

  • Vitest as test framework
  • Place tests in packages/<name>/tests/
  • Name test files *.test.ts
  • Use describe and it blocks
  • Run pnpm test from root for all packages
  • Run pnpm --filter @ifc-factory/<name> test for a single package

Test Example

import { describe, it, expect } from 'vitest';
import { parseExpress } from '../src/index.js';

describe('parseExpress', () => {
  it('should parse an empty schema', () => {
    const result = parseExpress('SCHEMA TEST; END_SCHEMA;');
    expect(result.name).toBe('TEST');
    expect(result.declarations).toHaveLength(0);
  });
});

Commit Messages

Use conventional commit style:

Prefix Usage
feat: New feature
fix: Bug fix
refactor: Code restructuring (no behavior change)
test: Test additions/changes
docs: Documentation
chore: Build, CI, dependency updates
perf: Performance improvement

Examples:

feat: add IfcSpace support to spatial structure builder
fix: handle nested comments in EXPRESS lexer
refactor: extract relationship index to separate class
test: add round-trip tests for step-serializer
docs: update API reference with new query methods
chore: upgrade vitest to v3

License

ISC

Clone this wiki locally