@judo/codegen
v0.1.1
Published
Code generator for JUDO UI Runtime extensibility — produces type-safe customization interfaces from .model files
Readme
# @judo/codegen
> Build-time code generator for JUDO UI Runtime extensibility — produces type-safe TypeScript interfaces from .model files
## Purpose
Reads `.model` XML files and generates TypeScript source files providing:
1. **Typed action override interfaces** — per page, listing all actions and their lifecycle methods
2. **Typed component props** — for each `customImplementation` element
3. **Typed customization config** — a `createCustomizations()` factory with full type inference and name→sourceId resolution
4. **Translation artifacts** — baseline `en-US.json` locale file and a `TRANSLATION_KEY_MAP` for runtime label resolution
5. **Optional scaffolding stubs** — starter implementations as starting points
## Architecture Layer
**Build-time tooling** — used as a `devDependency`. Emits TypeScript source files consumed by runtime packages (`core`, `app-shell`).
## Dependencies
- `@judo/model-api` — model type definitions (workspace dependency)
- `fast-xml-parser ^5` — lightweight XML parsing for `.model` files
## File Structure
```
src/
├── index.ts # Barrel re-exports: generate, extractModel, types
├── cli.ts # CLI entry point (judo-codegen binary)
├── model-reader.ts # XML parser → ExtractedModel
├── model-reader.test.ts
├── generator.ts # ExtractedModel → generated TypeScript files
├── generator.test.ts
├── name-utils.ts # Name conversion utilities (PascalCase, camelCase, identifiers)
├── name-utils.test.ts
└── integration.test.ts # End-to-end tests against real example models
```
## Exports Summary
### Generator
| Export | Kind | Description |
| ----------------- | -------- | -------------------------------------------------------------------------------------------- |
| `generate` | function | Produces a `Map<string, string>` of relative file paths → TypeScript file contents |
| `GenerateOptions` | type | Options: `model`, `scaffoldStubs?`, `applicationFilter?`, `customComponentAnnotationPrefix?` |
| `GenerateResult` | type | Result containing `files: Map<string, string>` |
### Model Reader
| Export | Kind | Description |
| -------------------- | -------- | -------------------------------------------------------------------------------- |
| `extractModel` | function | Parses `.model` XML content into an `ExtractedModel` structure |
| `ExtractedModel` | type | Top-level model: applications, pages, components, navigationItems, subThemeNames |
| `ExtractedPage` | type | Page with name, sourceId, applicationName, actions, elements, metadata |
| `ExtractedAction` | type | Action with name, sourceId, actionDefinitionType, actionDefinitionSourceId |
| `ExtractedComponent` | type | Custom implementation element with name, sourceId, elementType, annotations |
| `ExtractedElement` | type | Named visual element with name, path, sourceId, elementType, label, annotations |
### CLI
Binary: `judo-codegen` — invoked via `npx @judo/codegen generate --model <path> --outDir <dir> [options]`
| Flag | Description |
| ----------------- | ----------------------------------------------------- |
| `--model <path>` | Path to the `.model` XML file (required) |
| `--outDir <dir>` | Output directory for generated files (required) |
| `--scaffoldStubs` | Generate starter implementation stubs |
| `--watch` | Watch the model file for changes and regenerate |
| `--app <name>` | Only generate for a specific application (actor) name |
## Generated Output Structure
```
generated/
├── index.ts # Re-exports pages, components, factory, translation key map
├── customizations.ts # createCustomizations() factory + TypedCustomizationsInput
├── _source-id-map.ts # INTERNAL name→sourceId mapping (not re-exported)
├── _translation-key-map.ts # sourceId→translation key mapping (TRANSLATION_KEY_MAP)
├── pages/
│ ├── index.ts # Re-exports all page types
│ └── <PageName>.ts # Per-page action overrides + element names union type
├── components/
│ ├── index.ts # Re-exports all component props types
│ └── <ComponentName>.ts # Per-component typed CustomComponentProps
├── locales/
│ └── en-US.json # Baseline translation file
└── stubs/ # Optional (--scaffoldStubs)
├── pages/
│ └── use<PageName>Actions.ts
└── components/
└── <ComponentName>.tsx
```
## Key Patterns
- **Name→sourceId isolation**: Developers use human-readable model names; `createCustomizations()` resolves to `xmi:id` sourceIds internally via `_source-id-map.ts`. Source IDs are never exposed.
- **Annotation-based component wiring**: Elements sharing a `use`-prefixed annotation (e.g., `useFancyText`) are grouped; a single custom component is registered for all of them. Annotation components are processed before individual `components` entries, so explicit overrides win.
- **Conditional generation**: `subThemeProviders`, `annotationComponents`, and per-page feature fields (`typeaheadProviders`, `tableRowHighlighting`, `enumOptionFilter`, `dateValidationProps`, `columnCustomizers`, `itemContainerConfigs`) are only emitted when the model has matching element types.
- **Identifier deduplication**: Pages and components with colliding names after PascalCase conversion get numeric suffixes (e.g., `Dashboard`, `Dashboard1`).
- **Element key shortening**: Customization keys use plain element names (e.g., `stars`) when unique within a page, falling back to shortest unique dot-suffix (e.g., `orders.price` vs `items.price`) for disambiguation. Section-specific keys (e.g., `tableRowHighlighting`) are disambiguated only among elements of the same type, avoiding cross-type collisions. Full hierarchical paths are retained internally for translation key generation.
- **Translation baseline**: `locales/en-US.json` contains page titles (with `derivePageLabel` for operation pages), element labels, navigation items (`nav.*`), and static system messages (`system.*`). Keys are sorted alphabetically.
- **Action definition type resolution**: Raw XML references like `TransferObjectViewDeleteActionDefinition` are mapped to canonical types (`DeleteActionDefinition`) via a pattern-matching table in `resolveActionDefinitionType`.
- **Tree-shaking friendly**: Generated types are type-only (zero JS). The factory and source-id map are the only runtime JS. Stubs are not auto-imported.