diff --git a/.stateDoc.json b/.stateDoc.json index dc4d4d4..72ead8a 100644 --- a/.stateDoc.json +++ b/.stateDoc.json @@ -1,15 +1,15 @@ { - "$schema": "https://example.com/stateDoc.schema.json", - "projectTitle": "My FSM Docs", + "$schema": "https://example.com/praxisDoc.schema.json", + "projectTitle": "Praxis Application Documentation", "source": "src/fsm", "target": "docs/businessLogic", - "alwaysDisplayVisualizationAfterMachine": false, - "globs": ["**/*.machine.ts", "**/*.ts"], - "outline": "# [{{projectTitle}}]\n{{#each machines}}\n## {{name}}\n{{desc}}\n{{/each}}", + "alwaysDisplayVisualizationAfterLogic": false, + "globs": ["**/*.schema.ts", "**/*.schema.js", "**/*.machine.ts", "**/*.ts"], + "outline": "# [{{projectTitle}}]\n{{#each schemas}}\n## {{name}}\n{{desc}}\n{{/each}}", "templates": { - "machineIndex": "## {{name}}\n{{desc}}\n\nStates:\n{{#each states}}- [{{name}}](./states/{{slug}}.md) β€” {{desc}}\n{{/each}}\n", - "statePage": "# {{machine.name}} / {{name}}\n{{desc}}\n\nTransitions:\n{{#each on}}- {{event}} β†’ {{target}}\n{{/each}}\n" + "schemaIndex": "# {{name}}\n\n{{desc}}\n\n## Models\n{{#each models}}\n### {{name}}\n{{desc}}\n{{/each}}\n\n## Logic\n{{#each logic}}- [{{name}}](./logic/{{slug}}.md)\n{{/each}}\n", + "logicPage": "# {{schema.name}} / {{name}}\n{{desc}}\n\n## Events\n{{#each events}}- {{tag}}\n{{/each}}\n" }, "visualization": { "format": "mermaid", "exportPng": true } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a6b7d..1804dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.0.0] - 2025-12-27 + +### 🚨 Breaking Changes + +- **Complete refactor from XState to Praxis**: The project has been refactored to focus on Praxis application schemas instead of XState state machines +- **Configuration changes**: While `.stateDoc.json` is still supported, `.praxisDoc.json` is now recommended +- **Package renamed**: `@plures/statedoc` β†’ `@plures/praxisdoc` (package name will update in future release) +- **CLI command**: Recommend using `praxisdoc` instead of `statedoc` (both work for backwards compatibility) +- **Default globs**: Now defaults to `**/*.schema.ts` and `**/*.schema.js` for Praxis schemas +- **Output structure**: Documentation is now organized by schemas/logic instead of machines/states + +### Added - Praxis Support + +- **Praxis schema parsing**: Full support for Praxis application schemas with models, logic, and components +- **Schema documentation**: Generates documentation for Praxis models with fields and types +- **Logic documentation**: Documents events, facts, rules, and state transitions from Praxis logic definitions +- **Component documentation**: Lists Praxis UI components defined in schemas +- **Legacy XState support**: Automatic conversion of XState machines to Praxis schema format +- **Enhanced templates**: New templates for schema and logic documentation +- **Migration support**: Backwards compatible with v1.x XState projects + +### Changed + +- **Parser refactored**: Complete rewrite to support Praxis schema format +- **Type definitions**: Updated to `PraxisDocConfig`, `PraxisSchema`, and `PraxisLogic` +- **Documentation generator**: Restructured to generate schema-based documentation +- **CLI branding**: Updated help text and initialization to reflect Praxis focus +- **Example files**: Added `task.schema.ts` example demonstrating Praxis schema format +- **README**: Completely rewritten to focus on Praxis integration +- **ROADMAP**: Updated with Praxis-focused features and goals + +### Migration Guide + +Upgrading from v1.x to v2.0: + +1. **Config file**: Rename `.stateDoc.json` to `.praxisDoc.json` (optional) +2. **Update globs**: Add `**/*.schema.ts` to your globs pattern +3. **Legacy support**: Existing `.machine.ts` files will automatically convert to Praxis format +4. **Review output**: Documentation structure has changed - check the new `schemas/` directory structure +5. **Templates**: If you have custom templates, update them to use the new schema structure + +## [1.0.0] - 2025-01-XX + ### Added - Release Readiness Improvements - **Real-world example**: Shopping cart state machine example with before/after documentation @@ -35,17 +78,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - CI workflow expanded to test both Deno and npm builds - CONTRIBUTING.md enhanced with good first issue guidance -## [1.0.0] - 2025-01-XX - -### Added +### Initial Release Features -- Initial public release of state-docs - FSM documentation generator for XState projects - Dual runtime support (Deno and Node.js) from a single ESM codebase - Markdown documentation generation from state machine definitions - Mermaid state diagram generation - Configurable documentation generation via `.stateDoc.json` -- Template-based output customization using Eta templating engine +- Template-based output customization using Handlebars templating engine - Command-line interface with `init` and `gen` commands - Installation scripts for Linux/macOS (`install.sh`) and Windows (`install.ps1`) - Support for multiple installation methods: @@ -57,27 +97,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Comprehensive documentation including README, CONTRIBUTING guide, and ADR templates - GitHub Actions workflows for CI/CD -### Features - -- **Documentation Generation**: Automatically generates Markdown documentation from FSM definitions -- **State Diagrams**: Creates Mermaid state diagrams for visualization -- **Flexible Configuration**: JSON-based configuration with sensible defaults -- **Template Customization**: Customizable templates for machine and state documentation -- **Cross-Platform**: Works on Windows, Linux, and macOS -- **Dual Runtime**: Single codebase runs on both Deno and Node.js - -### Documentation - -- README with installation and quick start guide -- CONTRIBUTING guide for contributors -- ADR process documentation -- Example configuration file (`.stateDoc.json`) - -### Infrastructure - -- GitHub Actions CI workflow for linting and type checking -- Automated publishing workflow for version releases -- Support for publishing to both JSR (Deno) and npm registries - +[2.0.0]: https://github.com/plures/state-docs/releases/tag/v2.0.0 [1.0.0]: https://github.com/plures/state-docs/releases/tag/v1.0.0 - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d3a9dd..9853ceb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to state-docs +# Contributing to praxisdoc -Thank you for your interest in contributing to state-docs! This document provides guidelines and information to help you contribute effectively. +Thank you for your interest in contributing to praxisdoc! This document provides guidelines and information to help you contribute effectively. ## Quick Links @@ -29,7 +29,7 @@ Thank you for your interest in contributing to state-docs! This document provide - ✨ **Features** - Add new functionality (check roadmap first) - πŸ“ **Documentation** - Improve README, guides, or code comments - πŸ§ͺ **Tests** - Add test coverage for existing features -- 🎨 **Examples** - Create new example state machines +- 🎨 **Examples** - Create new Praxis schema examples - 🌍 **Translations** - Help us reach more developers - πŸ—£οΈ **Community** - Answer questions, review PRs, share the project @@ -43,11 +43,12 @@ We label issues with **`good first issue`** to help new contributors get started - βœ… Are self-contained and focused **Current Good First Issue areas:** -- Adding more examples (e.g., authentication flow, modal state machine) -- Improving error messages +- Adding more Praxis schema examples (e.g., e-commerce order, user authentication) +- Improving error messages for schema parsing - Adding JSDoc comments to functions - Writing integration tests - Improving CLI help text +- Supporting YAML schema parsing - Adding configuration validation [View all Good First Issues β†’](https://github.com/plures/state-docs/labels/good%20first%20issue) @@ -147,6 +148,25 @@ See [docs/adr/README.md](docs/adr/README.md) for more details on our ADR process Currently, the project uses end-to-end testing by running the documentation generator: +```sh +# Test with the example configuration +deno task gen + +# Test with the task management example +cd examples/task-management +deno run -A ../../cli.ts gen --config=.praxisDoc.json + +# Test with the shopping cart example (legacy XState) +cd examples/shopping-cart +deno run -A ../../cli.ts gen --config=.stateDoc.json +``` + +When adding new features: +- Test with both Praxis schemas and legacy XState machines +- Verify documentation output is correct +- Test with custom templates +- Test edge cases (empty schemas, malformed input, etc.) + ```sh deno task gen ``` diff --git a/README.md b/README.md index dbdd3f7..3e78607 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,28 @@ -# statedoc (Deno + Node) +# praxisdoc (Deno + Node) [![CI](https://github.com/plures/state-docs/actions/workflows/ci.yml/badge.svg)](https://github.com/plures/state-docs/actions/workflows/ci.yml) [![JSR](https://jsr.io/badges/@plures/statedoc)](https://jsr.io/@plures/statedoc) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -**FSM documentation generator for XState projects.** One ESM codebase. Runs on Deno or Node. Generates Markdown and Mermaid from machines and states. +**Automated documentation generator for Praxis applications.** One ESM codebase. Runs on Deno or Node. Generates Markdown and Mermaid diagrams from Praxis schemas. -> **⚠️ Version Status**: Alpha (v1.0.0) - Core functionality is stable, but API may change. See [Stability & Versioning](#stability--versioning) below. +> **⚠️ Version Status**: Alpha (v2.0.0) - Refactored for Praxis. API may change. See [Stability & Versioning](#stability--versioning) below. -## What is state-docs? +## What is praxisdoc? -state-docs automatically generates human-readable documentation from your [finite state machine (FSM)](https://en.wikipedia.org/wiki/Finite-state_machine) definitions. It transforms complex state machine code into clear documentation that anyone can understandβ€”perfect for product teams, QA engineers, and stakeholders. +praxisdoc automatically generates human-readable documentation from your [Praxis](https://github.com/plures/praxis) application schemas. It transforms declarative schema definitions into clear documentation that anyone can understandβ€”perfect for product teams, QA engineers, and stakeholders. -### What "State" Means Here +### About Praxis -In state-docs, **"state"** refers to the **behavioral state** of your application's finite state machines (FSMs): +**Praxis** is a schema-driven application framework for building local-first, distributed applications. It uses declarative schemas to define: -- **Application State Machines**: User workflows (authentication, checkout, onboarding) -- **Business Logic States**: Order processing, approval workflows, document lifecycles -- **UI Component States**: Modal dialogs, form wizards, navigation flows -- **System States**: Connection status, data sync states, error recovery flows +- **Models**: Data structures with fields, types, and indexes +- **Logic**: Business rules, events, facts, and state transitions +- **Components**: UI elements auto-generated from schemas +- **Documentation**: Automatically synchronized with your code -We focus on **XState-compatible state machines** that define discrete states and transitions between them. +praxisdoc leverages Praxis's schema format to generate comprehensive documentation including state diagrams, event flows, and data models. ## Installation @@ -42,7 +42,7 @@ irm https://raw.githubusercontent.com/plures/state-docs/main/install.ps1 | iex #### Via Deno (JSR) ```sh -deno install -A -n statedoc jsr:@plures/statedoc/cli +deno install -A -n praxisdoc jsr:@plures/statedoc/cli ``` #### Via npm @@ -52,33 +52,33 @@ npm install -g @plures/statedoc #### Via npx (No Installation Required) ```sh -npx @plures/statedoc gen --config=.stateDoc.json +npx @plures/statedoc gen --config=.praxisDoc.json ``` ## Quick Start After installation, generate documentation: ```sh -statedoc gen --config=.stateDoc.json +praxisdoc gen --config=.praxisDoc.json ``` -### Adding to an Existing Project +### Adding to an Existing Praxis Project -1. Install state-docs (choose one method from above) +1. Install praxisdoc (choose one method from above) 2. Initialize a configuration file: ```sh -statedoc init +praxisdoc init ``` -This creates a `.stateDoc.json` file with sensible defaults: +This creates a `.praxisDoc.json` file (or `.stateDoc.json` for backwards compatibility) with sensible defaults: ```json { "projectTitle": "My Project", "source": "./src", "target": "./docs", - "globs": ["**/*.ts", "**/*.js"], + "globs": ["**/*.schema.ts", "**/*.schema.js"], "visualization": { "format": "mermaid", "exportPng": false @@ -89,14 +89,14 @@ This creates a `.stateDoc.json` file with sensible defaults: 3. Edit the config file to match your project structure 4. Run the generator: ```sh -statedoc gen +praxisdoc gen ``` 5. (Optional) Add to your package.json scripts: ```json { "scripts": { - "docs": "statedoc gen" + "docs": "praxisdoc gen" } } ``` @@ -113,33 +113,34 @@ Node (after building with dnt): deno run -A scripts/build_npm.ts cd npm npm install -g . -statedoc gen --config=.stateDoc.json +praxisdoc gen --config=.praxisDoc.json ``` ## Real-World Example -See the [Shopping Cart Example](./examples/shopping-cart/README.md) for a comprehensive demonstration: +See the [Task Management Example](./examples/task-management/README.md) for a comprehensive demonstration: -- **Before**: Complex TypeScript state machine code +- **Before**: Praxis schema definitions in TypeScript - **After**: Clear Markdown documentation with Mermaid diagrams - **Use Cases**: Product planning, QA testing, stakeholder communication - **CLI Output**: See exactly what the tool generates -[View the complete example β†’](./examples/shopping-cart/README.md) +[View the complete example β†’](./examples/task-management/README.md) ## Supported Formats ### Input Formats **Currently Supported:** -- XState-compatible TypeScript state machines (`.ts`, `.tsx`) -- XState-compatible JavaScript state machines (`.js`, `.jsx`) +- Praxis schema files (`.schema.ts`, `.schema.js`) +- Legacy XState-compatible state machines (`.machine.ts`, `.machine.js`) - converted to Praxis format +- TypeScript and JavaScript (ES modules) **Planned (see [ROADMAP.md](./ROADMAP.md)):** +- YAML schema definitions - JSON state definitions - Robot Framework state machines - State Machine Cat format -- YAML state definitions ### Output Formats @@ -157,92 +158,109 @@ See the [Shopping Cart Example](./examples/shopping-cart/README.md) for a compre - **Runtime**: Deno 2.x, Node.js 18+ - **Input**: TypeScript, JavaScript (ES modules) -- **State Libraries**: XState-compatible syntax +- **Schema Libraries**: Praxis schemas, XState-compatible (legacy) - **Platforms**: Linux, macOS, Windows -## Why state-docs? +## Why praxisdoc? ### Comparison with Other Tools -| Feature | state-docs | XState Inspector | Stately Studio | Custom Docs | -|---------|-----------|------------------|----------------|-------------| -| **Auto-generate from code** | βœ… | ❌ | ❌ | ❌ | -| **Markdown output** | βœ… | ❌ | ❌ | βœ… (manual) | -| **Mermaid diagrams** | βœ… | ❌ | βœ… | ❌ | -| **No runtime required** | βœ… | ❌ (needs app) | βœ… | βœ… | -| **CLI tool** | βœ… | ❌ | ❌ | ❌ | -| **Free & open source** | βœ… | βœ… | πŸ’° (limited) | βœ… | -| **Customizable templates** | βœ… | ❌ | πŸ’° (Pro) | βœ… | -| **Version control friendly** | βœ… | ❌ | ❌ | βœ… | -| **CI/CD integration** | βœ… | ❌ | ❌ | βœ… | -| **Works offline** | βœ… | ❌ | ❌ | βœ… | +| Feature | praxisdoc | Praxis Built-in | XState Inspector | Stately Studio | Custom Docs | +|---------|-----------|-----------------|------------------|----------------|-------------| +| **Auto-generate from code** | βœ… | βœ… | ❌ | ❌ | ❌ | +| **Praxis schema support** | βœ… | βœ… | ❌ | ❌ | ❌ | +| **Markdown output** | βœ… | βœ… | ❌ | ❌ | βœ… (manual) | +| **Mermaid diagrams** | βœ… | βœ… | ❌ | βœ… | ❌ | +| **No runtime required** | βœ… | ❌ | ❌ (needs app) | βœ… | βœ… | +| **CLI tool** | βœ… | βœ… | ❌ | ❌ | ❌ | +| **Free & open source** | βœ… | βœ… | βœ… | πŸ’° (limited) | βœ… | +| **Customizable templates** | βœ… | ⚠️ | ❌ | πŸ’° (Pro) | βœ… | +| **Version control friendly** | βœ… | βœ… | ❌ | ❌ | βœ… | +| **CI/CD integration** | βœ… | βœ… | ❌ | ❌ | βœ… | +| **Works offline** | βœ… | βœ… | ❌ | ❌ | βœ… | +| **Legacy XState support** | βœ… | ❌ | βœ… | βœ… | ❌ | ### Unique Value Propositions -1. **Static Documentation**: Generate docs at build time, no runtime dependencies -2. **Git-Friendly**: Markdown output works with standard version control workflows -3. **Template-Based**: Fully customizable output to match your documentation style -4. **Dual Runtime**: Single codebase works on both Deno and Node.js -5. **CLI-First**: Designed for automation, scripting, and CI/CD pipelines -6. **Developer-Focused**: Built by developers, for developers, with extensibility in mind +1. **Praxis-First Design**: Built specifically to document Praxis applications with full schema support +2. **Static Documentation**: Generate docs at build time, no runtime dependencies +3. **Git-Friendly**: Markdown output works with standard version control workflows +4. **Template-Based**: Fully customizable output to match your documentation style +5. **Dual Runtime**: Single codebase works on both Deno and Node.js +6. **CLI-First**: Designed for automation, scripting, and CI/CD pipelines +7. **Legacy Support**: Converts XState machines to Praxis format automatically +8. **Developer-Focused**: Built by developers, for developers, with extensibility in mind -### When to Use state-docs +### When to Use praxisdoc -βœ… **Use state-docs when:** +βœ… **Use praxisdoc when:** +- You're building a Praxis application - You want documentation that lives in your repository - You need to generate docs automatically in CI/CD - You want to customize documentation templates -- You're using XState or XState-compatible state machines +- You're migrating from XState to Praxis - You need offline, static documentation ❌ **Consider alternatives when:** -- You need real-time state visualization during development (β†’ use XState Inspector) +- You need real-time state visualization during development (β†’ use Praxis built-in tools) - You prefer visual state machine editors (β†’ use Stately Studio) - You need collaborative, cloud-based diagramming (β†’ use Stately Studio) -- You're not using state machines at all +- You're not using Praxis or state machines at all ## Scope & Limitations -### What state-docs Does +### What praxisdoc Does -- βœ… Parses XState-compatible state machine definitions -- βœ… Generates Markdown documentation from state machines -- βœ… Creates Mermaid state diagrams +- βœ… Parses Praxis schema definitions (models, logic, components) +- βœ… Generates Markdown documentation from schemas +- βœ… Creates Mermaid state diagrams from logic transitions - βœ… Supports customizable templates - βœ… Works with TypeScript and JavaScript +- βœ… Converts legacy XState machines to Praxis format -### What state-docs Doesn't Do +### What praxisdoc Doesn't Do - ❌ **Runtime visualization**: We generate static docs, not interactive UIs -- ❌ **State machine validation**: We document existing machines, don't validate logic +- ❌ **Schema validation**: We document existing schemas, don't validate logic - ❌ **Execution**: We don't run or test your state machines -- ❌ **Visual editor**: We're a CLI tool, not a GUI for creating machines +- ❌ **Visual editor**: We're a CLI tool, not a GUI for creating schemas - ❌ **Multiple languages**: Currently only TypeScript/JavaScript (see roadmap) -### Known Limitations (v1.0.0) +### Known Limitations (v2.0.0) -- Parser is placeholder-based; full TypeScript parsing coming in v1.1.0 +- Parser is runtime-based; static TypeScript AST parsing coming in future - No PNG/SVG export yet; only Mermaid text format - No watch mode; must manually re-run generator -- Limited to XState syntax; other formats planned +- Limited to Praxis and XState formats; other formats planned See our [ROADMAP.md](./ROADMAP.md) for planned improvements. ## Stability & Versioning -### Current Stability: Alpha (v1.0.0) +### Current Stability: Alpha (v2.0.0) **What this means:** -- ⚠️ **API may change**: Configuration options and output formats may evolve +- ⚠️ **Major refactor**: Migrated from XState to Praxis +- ⚠️ **API has changed**: Configuration and output formats updated - βœ… **Core functionality works**: Basic documentation generation is stable - ⚠️ **Breaking changes possible**: Updates may require configuration changes +- βœ… **Legacy support**: XState machines automatically converted to Praxis format - ⚠️ **Use with caution in production**: Suitable for early adopters and testing +### Migration from v1.x + +If you're upgrading from state-docs v1.x (XState-based): + +1. **Config file**: Rename `.stateDoc.json` to `.praxisDoc.json` (optional but recommended) +2. **Update globs**: Add `**/*.schema.ts` to your globs pattern +3. **Legacy support**: Existing `.machine.ts` files will still work +4. **Review output**: Documentation structure has changed to support Praxis schemas + ### Stability Roadmap -- **v1.0.x - v1.4.x (Alpha)**: Rapid iteration, API refinements, feature additions -- **v1.5.x+ (Beta)**: Feature complete, stable API, minor changes only -- **v2.0.0+ (Stable)**: Production-ready, backward compatibility guaranteed +- **v2.0.x - v2.4.x (Alpha)**: Rapid iteration, API refinements, feature additions +- **v2.5.x+ (Beta)**: Feature complete, stable API, minor changes only +- **v3.0.0+ (Stable)**: Production-ready, backward compatibility guaranteed ### Versioning Policy @@ -328,7 +346,7 @@ The project includes an automated publishing pipeline that triggers when a new v After publishing, users can install via: - **Shell script**: `curl -fsSL https://raw.githubusercontent.com/plures/state-docs/main/install.sh | sh` -- **Deno/JSR**: `deno install -A -n statedoc jsr:@plures/statedoc/cli` +- **Deno/JSR**: `deno install -A -n praxisdoc jsr:@plures/statedoc/cli` - **npm global**: `npm install -g @plures/statedoc` - **npx**: `npx @plures/statedoc gen` diff --git a/ROADMAP.md b/ROADMAP.md index 0ee5ee7..fdba035 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,19 +1,20 @@ # Roadmap -This document outlines the planned features and improvements for state-docs. We use semantic versioning and prioritize community feedback in our development process. +This document outlines the planned features and improvements for praxisdoc. We use semantic versioning and prioritize community feedback in our development process. -## Current Version: 1.0.0 (Initial Release) +## Current Version: 2.0.0 (Praxis Refactor) -**Status**: Alpha - Core functionality is stable, API may change before 1.0 stable release +**Status**: Alpha - Major refactor from XState to Praxis. Core functionality is stable, API has changed. ### βœ… Completed Features -- FSM documentation generation from XState-compatible state machines +- Praxis schema documentation generation (models, logic, components) +- Legacy XState machine support with automatic conversion - Dual runtime support (Deno and Node.js) - Markdown output with customizable templates -- Mermaid diagram generation +- Mermaid diagram generation from state transitions - CLI with `init` and `gen` commands -- Configuration via `.stateDoc.json` +- Configuration via `.praxisDoc.json` (or `.stateDoc.json` for backwards compatibility) - Automated publishing to JSR and npm - Cross-platform installation scripts @@ -21,23 +22,24 @@ This document outlines the planned features and improvements for state-docs. We ## Upcoming Releases -### v1.1.0 - Parser Implementation (Q1 2025) +### v2.1.0 - Enhanced Praxis Integration (Q1 2025) -**Focus**: Replace placeholder parser with real TypeScript/XState parsing +**Focus**: Deeper integration with Praxis framework features -- [ ] Implement TypeScript compiler API integration -- [ ] Extract XState machine definitions from source files -- [ ] Support both `createMachine()` and object literal formats -- [ ] Parse machine context, guards, and actions -- [ ] Add validation for malformed state machines -- [ ] Improve error messages with file locations +- [ ] Support YAML schema definitions +- [ ] Support JSON schema definitions +- [ ] Parse Praxis facts and rules documentation +- [ ] Generate component documentation from schema +- [ ] Add support for Praxis constraints and validations +- [ ] Integrate with Praxis CLI for seamless workflow +- [ ] Add watch mode for continuous documentation generation **Good First Issues**: -- Add unit tests for parser utilities -- Improve error message formatting -- Add support for JSDoc comments in state definitions +- Add examples for YAML schemas +- Improve template customization options +- Add support for JSDoc comments in schema definitions -### v1.2.0 - Enhanced Visualization (Q2 2025) +### v2.2.0 - Enhanced Visualization (Q2 2025) **Focus**: Improve diagram generation and export options @@ -47,40 +49,41 @@ This document outlines the planned features and improvements for state-docs. We - [ ] Custom diagram themes and styling - [ ] Support for nested/hierarchical state visualization - [ ] Parallel states visualization -- [ ] History states representation +- [ ] Component relationship diagrams +- [ ] Model entity-relationship diagrams **Good First Issues**: - Add color customization options - Create diagram theme presets - Document custom styling examples -### v1.3.0 - Multiple Format Support (Q2 2025) +### v2.3.0 - Multiple Format Support (Q2 2025) -**Focus**: Support additional state machine formats and documentation outputs +**Focus**: Support additional schema formats and documentation outputs - [ ] Support Robot Framework state machines - [ ] Support State Machine Cat format -- [ ] Support JSON-based state definitions -- [ ] HTML documentation output +- [ ] HTML documentation output with navigation - [ ] PDF export via markdown conversion - [ ] OpenAPI integration for API state flows +- [ ] Praxis-native documentation export **Good First Issues**: - Add examples for different input formats - Create format conversion utilities - Document format specifications -### v1.4.0 - Advanced Features (Q3 2025) +### v2.4.0 - Advanced Features (Q3 2025) **Focus**: Enhanced documentation and developer experience -- [ ] Watch mode with hot reload - [ ] Integration with documentation sites (Docusaurus, VitePress, etc.) -- [ ] Type definition generation from state machines +- [ ] Type definition generation from Praxis schemas - [ ] Test scenario generation from state transitions - [ ] Code coverage visualization (which states/transitions are tested) -- [ ] Diff view for state machine changes -- [ ] Migration helpers for breaking state changes +- [ ] Diff view for schema changes +- [ ] Migration helpers for breaking schema changes +- [ ] Real-time collaboration features **Good First Issues**: - Add more template examples @@ -89,17 +92,17 @@ This document outlines the planned features and improvements for state-docs. We --- -## v2.0.0 - Major Features (Q4 2025) +## v3.0.0 - Major Features (Q4 2025) **Focus**: Enterprise features and scalability -- [ ] Multi-language support for state machine parsing (JavaScript, TypeScript, Python) -- [ ] State machine validation and linting +- [ ] Multi-language support for schema parsing (JavaScript, TypeScript, Python) +- [ ] Schema validation and linting - [ ] Performance optimization for large codebases - [ ] Plugin system for custom parsers and generators - [ ] Cloud integration (GitHub, GitLab, Bitbucket) -- [ ] Real-time collaboration features - [ ] Analytics and insights (state usage, transition frequency) +- [ ] Praxis framework tight integration (import schemas directly) --- @@ -108,27 +111,31 @@ This document outlines the planned features and improvements for state-docs. We These ideas are under consideration but not yet scheduled: ### Developer Tools -- VS Code extension for inline state visualization -- State machine debugger integration -- Real-time state machine testing playground +- VS Code extension for inline schema visualization +- Praxis schema debugger integration +- Real-time schema testing playground +- CodeCanvas integration for visual editing ### Documentation Features -- Automatic changelog generation from state changes +- Automatic changelog generation from schema changes - Multi-language documentation generation -- Interactive state machine tutorials +- Interactive schema tutorials - Version comparison and migration guides +- Business process documentation ### Enterprise Features - Team collaboration features -- State machine governance and approvals +- Schema governance and approvals - Compliance and audit trail - Custom deployment pipelines +- Multi-tenant documentation ### Integrations -- Jira/Linear integration for linking states to tickets -- Slack/Discord notifications for state changes +- Jira/Linear integration for linking logic to tickets +- Slack/Discord notifications for schema changes - CI/CD integrations (GitHub Actions, GitLab CI, CircleCI) - Monitoring integration (Datadog, New Relic) +- PluresDB integration for runtime state tracking --- @@ -175,32 +182,33 @@ Use our bug report template and include: We follow semantic versioning: -- **Major (X.0.0)**: Breaking changes, API redesigns -- **Minor (1.X.0)**: New features, backward-compatible -- **Patch (1.0.X)**: Bug fixes, documentation updates +- **Major (X.0.0)**: Breaking changes, API redesigns (e.g., v1β†’v2 Praxis refactor) +- **Minor (2.X.0)**: New features, backward-compatible +- **Patch (2.0.X)**: Bug fixes, documentation updates ### Release Schedule - **Patch releases**: As needed for bugs - **Minor releases**: Quarterly (every 3 months) -- **Major releases**: Annually or as needed +- **Major releases**: Annually or as needed for significant changes ### Stability Guarantees -- **Alpha (current)**: API may change, no backward compatibility guarantee -- **Beta (v1.5.0+)**: Feature complete, minor API changes possible -- **Stable (v2.0.0+)**: Guaranteed backward compatibility within major version +- **Alpha (v2.0.x-2.4.x)**: API may change, migration from v1.x supported +- **Beta (v2.5.0+)**: Feature complete, minor API changes possible +- **Stable (v3.0.0+)**: Guaranteed backward compatibility within major version --- ## Community Goals -We're building state-docs as a community-driven project: +We're building praxisdoc as a community-driven project: -- 🎯 **Target**: 50+ stars by v1.1.0 -- 🀝 **Target**: 10+ external contributors by v1.2.0 -- πŸ“ **Target**: 5+ production users by v2.0.0 -- 🌍 **Target**: Multi-language documentation by v2.0.0 +- 🎯 **Target**: 100+ stars by v2.2.0 +- 🀝 **Target**: 20+ external contributors by v2.3.0 +- πŸ“ **Target**: 10+ production Praxis users by v3.0.0 +- 🌍 **Target**: Multi-language documentation by v3.0.0 +- πŸ”— **Target**: Official Praxis framework integration by v3.0.0 --- @@ -213,7 +221,7 @@ We're building state-docs as a community-driven project: --- -**Last Updated**: 2025-01-XX +**Last Updated**: 2025-12-27 **Next Review**: Q2 2025 For questions about the roadmap, open a discussion or issue on GitHub. diff --git a/cli.ts b/cli.ts index 0160efa..51426a3 100644 --- a/cli.ts +++ b/cli.ts @@ -77,7 +77,7 @@ async function initConfig(path: string) { projectTitle: "My Project", source: "./src", target: "./docs", - globs: ["**/*.ts", "**/*.js"], + globs: ["**/*.schema.ts", "**/*.schema.js"], visualization: { format: "mermaid", exportPng: false @@ -88,10 +88,10 @@ async function initConfig(path: string) { console.log(`βœ“ Created config file: ${path}`); console.log("\nEdit the config file to customize:"); console.log(" - projectTitle: Your project name"); - console.log(" - source: Directory containing your FSM files"); + console.log(" - source: Directory containing your Praxis schema files"); console.log(" - target: Output directory for documentation"); - console.log(" - globs: File patterns to match"); - console.log("\nRun 'statedoc gen' to generate documentation."); + console.log(" - globs: File patterns to match (e.g., **/*.schema.ts)"); + console.log("\nRun 'praxisdoc gen' to generate documentation."); } // Main function to avoid top-level await which is incompatible with CommonJS diff --git a/deno.json b/deno.json index 5e46c68..f662d18 100644 --- a/deno.json +++ b/deno.json @@ -1,7 +1,7 @@ { "name": "@plures/state-docs", - "version": "1.0.2", + "version": "2.0.0", "exports": { ".": "./mod.ts", "./cli": "./cli.ts" diff --git a/examples/README.md b/examples/README.md index 1abd8d3..382cca7 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,17 +1,30 @@ # Examples -This directory contains real-world examples demonstrating how to use state-docs to generate documentation from state machines. +This directory contains real-world examples demonstrating how to use praxisdoc to generate documentation from Praxis schemas and state machines. ## Available Examples -### [Shopping Cart](./shopping-cart/) +### [Task Management](./task-management/) ⭐ **NEW: Praxis Schema Example** + +A Praxis application schema demonstrating: +- Schema-driven development with models, logic, and components +- State transitions for task lifecycle (new β†’ assigned β†’ in_progress β†’ completed) +- Events and facts documentation +- Component auto-generation metadata +- Native Praxis format integration + +**Use cases**: Product planning, business logic documentation, team onboarding, API design + +[View the task management example β†’](./task-management/README.md) + +### [Shopping Cart](./shopping-cart/) **Legacy XState Example** A comprehensive e-commerce checkout flow demonstrating: - Complex multi-state workflows (empty β†’ active β†’ checkout β†’ processing β†’ completed/failed) - Nested states (checkout with shipping and payment sub-states) - Error handling and retry flows +- Automatic conversion from XState to Praxis format - Before/after documentation comparison -- CLI output demonstration **Use cases**: Product planning, QA test scenario generation, stakeholder communication @@ -19,50 +32,56 @@ A comprehensive e-commerce checkout flow demonstrating: ## Running the Examples -Each example includes its own configuration file (`.stateDoc.json`) and state machine definitions. +Each example includes its own configuration file (`.praxisDoc.json` or `.stateDoc.json`) and schema/machine definitions. ### Using Deno ```bash -cd examples/shopping-cart -deno run -A ../../cli.ts gen --config=.stateDoc.json +cd examples/task-management +deno run -A ../../cli.ts gen --config=.praxisDoc.json ``` ### Using npm/npx ```bash -cd examples/shopping-cart -npx @plures/statedoc gen --config=.stateDoc.json +cd examples/task-management +npx @plures/statedoc gen --config=.praxisDoc.json ``` ### Expected Output After running the generator, you should see: -- `docs/index.md` - Overview of all machines -- `docs/machines/[machine-name]/README.md` - Machine documentation -- `docs/machines/[machine-name]/states/*.md` - Individual state pages -- `docs/machines/[machine-name]/diagram.mmd` - Mermaid diagram +- `docs/index.md` - Overview of all schemas +- `docs/schemas/[schema-name]/README.md` - Schema documentation with models and logic +- `docs/schemas/[schema-name]/logic/*.md` - Individual logic definition pages +- `docs/schemas/[schema-name]/logic/*.mmd` - Mermaid diagrams ## Contributing Examples We welcome new examples! Good examples to add: -- **Authentication flow**: Login, logout, session management +**Praxis Schema Examples:** +- **E-commerce order**: Order processing workflow with inventory, payment, shipping +- **User authentication**: Signup, login, verification, password reset +- **Content management**: Draft, review, publish, archive states +- **Multi-tenant SaaS**: Subscription, billing, feature access +- **IoT device**: Connection, data sync, firmware update states + +**Legacy XState Examples (will be auto-converted):** - **Modal dialog**: Opening, closing, confirmation states - **Form wizard**: Multi-step form with validation - **API request**: Loading, success, error states with retry - **Video player**: Playing, paused, buffering, ended states -- **Document workflow**: Draft, review, approved, published states See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines on submitting examples. ### Example Requirements Each example should include: -1. **State machine file(s)** - TypeScript/JavaScript with clear comments -2. **`.stateDoc.json`** - Configuration file +1. **Schema/machine file(s)** - Praxis schema (`.schema.ts`) or XState machine with clear comments +2. **`.praxisDoc.json` or `.stateDoc.json`** - Configuration file 3. **`README.md`** - Explanation of the example with: - - Overview of the state machine + - Overview of the schema/state machine - Use cases - Before/after comparison - Instructions for running diff --git a/examples/task-management/.praxisDoc.json b/examples/task-management/.praxisDoc.json new file mode 100644 index 0000000..142e904 --- /dev/null +++ b/examples/task-management/.praxisDoc.json @@ -0,0 +1,10 @@ +{ + "projectTitle": "Task Management System", + "source": ".", + "target": "./docs", + "globs": ["**/*.schema.ts"], + "visualization": { + "format": "mermaid", + "exportPng": false + } +} diff --git a/examples/task-management/README.md b/examples/task-management/README.md new file mode 100644 index 0000000..696c999 --- /dev/null +++ b/examples/task-management/README.md @@ -0,0 +1,88 @@ +# Task Management Example + +This example demonstrates how praxisdoc generates documentation from a Praxis application schema. + +## Overview + +The task management system is a simple application that tracks tasks through their lifecycle: +- New tasks are created +- Tasks are assigned to users +- Users start work on tasks +- Work can be paused and resumed +- Tasks are completed or cancelled + +This workflow is defined using a Praxis schema with models, logic, and components. + +## Files in This Example + +- `task.schema.ts` - Praxis schema definition with models, logic, and components +- `.praxisDoc.json` - Configuration file for praxisdoc +- `docs/` - Generated documentation (created when you run praxisdoc) + +## Running the Example + +From this directory, run: + +```bash +praxisdoc gen --config=.praxisDoc.json +``` + +Or if using npx: + +```bash +npx @plures/statedoc gen --config=.praxisDoc.json +``` + +This will generate documentation in the `docs/` directory. + +## What Gets Generated + +After running praxisdoc, you'll find: + +- `docs/index.md` - Overview of all schemas +- `docs/schemas/taskmanagement/README.md` - Schema documentation with models and logic +- `docs/schemas/taskmanagement/logic/task-state-machine.md` - Logic definition with events, facts, and transitions +- `docs/schemas/taskmanagement/logic/task-state-machine.mmd` - Mermaid state diagram + +## Praxis Schema Structure + +The Praxis schema in this example includes: + +### Models +- **Task**: The main data model with fields like id, title, status, assignee, etc. + +### Logic +- **Events**: Actions that trigger state changes (TASK_CREATE, TASK_ASSIGN, etc.) +- **Facts**: State changes that occurred (TaskCreated, TaskAssigned, etc.) +- **Transitions**: State machine transitions from one state to another + +### Components +- **TaskForm**: Form for creating/editing tasks +- **TaskList**: List view of tasks +- **TaskDetail**: Detailed view of a single task + +## Key Features Demonstrated + +1. **Schema-Driven Development**: Everything is defined in the schema +2. **State Transitions**: Clear workflow with explicit state changes +3. **Event-Driven Logic**: Actions trigger state transitions +4. **Auto-Generated Documentation**: No manual documentation needed +5. **Mermaid Diagrams**: Visual representation of state flows + +## Comparison with Legacy XState + +Unlike the shopping-cart example which uses XState format, this example uses the native Praxis schema format. Benefits include: + +- Unified schema for models, logic, and components +- Better integration with Praxis tools +- Richer metadata for documentation +- Support for facts and rules, not just states + +## Next Steps + +1. Modify the schema to add new states or transitions +2. Re-run praxisdoc to see updated documentation +3. Customize templates in `.praxisDoc.json` +4. Add your own Praxis schemas + +For more information, see the [main README](../../README.md). diff --git a/examples/task-management/task.schema.ts b/examples/task-management/task.schema.ts new file mode 100644 index 0000000..e3fdf18 --- /dev/null +++ b/examples/task-management/task.schema.ts @@ -0,0 +1,89 @@ +/** + * Example Praxis Schema - Task Management + * + * This demonstrates a Praxis application schema with models, logic, and components. + * Praxis uses a schema-driven approach where everything is defined declaratively. + */ + +export const taskSchema = { + version: '1.0.0', + name: 'TaskManagement', + description: 'Task management application with state transitions', + + models: [ + { + name: 'Task', + description: 'A task item in the system', + fields: [ + { name: 'id', type: 'string', description: 'Unique identifier' }, + { name: 'title', type: 'string', description: 'Task title' }, + { name: 'description', type: 'string', description: 'Detailed description' }, + { name: 'status', type: 'string', default: 'new', description: 'Current status' }, + { name: 'assignee', type: 'string', description: 'Assigned user' }, + { name: 'createdAt', type: 'date', description: 'Creation timestamp' }, + { name: 'completedAt', type: 'date', description: 'Completion timestamp' }, + ], + indexes: [ + { name: 'by_status', fields: ['status'] }, + { name: 'by_assignee', fields: ['assignee'] }, + { name: 'by_created', fields: ['createdAt'] }, + ], + }, + ], + + logic: [ + { + id: 'task-state-machine', + name: 'Task State Machine', + description: 'Manages task lifecycle from creation to completion', + + events: [ + { tag: 'TASK_CREATE', payload: { title: 'string', description: 'string' }, description: 'Create a new task' }, + { tag: 'TASK_ASSIGN', payload: { taskId: 'string', assignee: 'string' }, description: 'Assign task to a user' }, + { tag: 'TASK_START', payload: { taskId: 'string' }, description: 'Start working on task' }, + { tag: 'TASK_PAUSE', payload: { taskId: 'string' }, description: 'Pause work on task' }, + { tag: 'TASK_COMPLETE', payload: { taskId: 'string' }, description: 'Mark task as completed' }, + { tag: 'TASK_REOPEN', payload: { taskId: 'string' }, description: 'Reopen a completed task' }, + { tag: 'TASK_CANCEL', payload: { taskId: 'string' }, description: 'Cancel a task' }, + ], + + facts: [ + { tag: 'TaskCreated', payload: { taskId: 'string', title: 'string' }, description: 'Task was created' }, + { tag: 'TaskAssigned', payload: { taskId: 'string', assignee: 'string' }, description: 'Task was assigned' }, + { tag: 'TaskCompleted', payload: { taskId: 'string', completedAt: 'date' }, description: 'Task was completed' }, + ], + + transitions: [ + { from: 'new', event: 'TASK_ASSIGN', to: 'assigned', description: 'Assign a new task' }, + { from: 'assigned', event: 'TASK_START', to: 'in_progress', description: 'Start working on assigned task' }, + { from: 'in_progress', event: 'TASK_PAUSE', to: 'paused', description: 'Pause work' }, + { from: 'paused', event: 'TASK_START', to: 'in_progress', description: 'Resume work' }, + { from: 'in_progress', event: 'TASK_COMPLETE', to: 'completed', description: 'Complete the task' }, + { from: 'completed', event: 'TASK_REOPEN', to: 'assigned', description: 'Reopen completed task' }, + { from: 'new', event: 'TASK_CANCEL', to: 'cancelled', description: 'Cancel before assignment' }, + { from: 'assigned', event: 'TASK_CANCEL', to: 'cancelled', description: 'Cancel assigned task' }, + ], + }, + ], + + components: [ + { + name: 'TaskForm', + type: 'form', + model: 'Task', + description: 'Form for creating and editing tasks', + }, + { + name: 'TaskList', + type: 'list', + model: 'Task', + description: 'List view of all tasks with filtering', + }, + { + name: 'TaskDetail', + type: 'detail', + model: 'Task', + description: 'Detailed view of a single task', + }, + ], +}; diff --git a/install.ps1 b/install.ps1 index e590d8c..46ff1ba 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,5 +1,5 @@ -# State-Docs Installer Script for Windows -# This script installs state-docs using the best available method +# Praxis-Docs Installer Script for Windows +# This script installs praxisdoc using the best available method $ErrorActionPreference = "Stop" @@ -34,7 +34,7 @@ function Test-Command { return $? } -Write-Info "State-Docs Installer" +Write-Info "Praxis-Docs Installer (v2.0)" Write-Host "" # Check for Deno @@ -42,13 +42,14 @@ if (Test-Command "deno") { $denoVersion = & deno --version 2>&1 | Select-Object -First 1 Write-Info "Found Deno: $denoVersion" Write-Host "" - Write-Info "Installing state-docs via Deno JSR..." + Write-Info "Installing praxisdoc via Deno JSR..." try { - & deno install -A -f -n statedoc jsr:@plures/statedoc/cli - Write-Success "Successfully installed state-docs via Deno!" + & deno install -A -f -n praxisdoc jsr:@plures/praxisdoc/cli + Write-Success "Successfully installed praxisdoc via Deno!" Write-Host "" - Write-Info "You can now run: statedoc gen --config=.stateDoc.json" + Write-Info "You can now run: praxisdoc gen --config=.praxisDoc.json" + Write-Info "Or use legacy command: statedoc gen --config=.stateDoc.json" exit 0 } catch { Write-Warning "Failed to install via Deno JSR, trying alternative methods..." @@ -60,13 +61,14 @@ if (Test-Command "npm") { $npmVersion = & npm --version 2>&1 Write-Info "Found npm: $npmVersion" Write-Host "" - Write-Info "Installing state-docs via npm..." + Write-Info "Installing praxisdoc via npm..." try { - & npm install -g @plures/statedoc - Write-Success "Successfully installed state-docs via npm!" + & npm install -g @plures/praxisdoc + Write-Success "Successfully installed praxisdoc via npm!" Write-Host "" - Write-Info "You can now run: statedoc gen --config=.stateDoc.json" + Write-Info "You can now run: praxisdoc gen --config=.praxisDoc.json" + Write-Info "Or use legacy command: statedoc gen --config=.stateDoc.json" exit 0 } catch { Write-Warning "Failed to install via npm globally" @@ -75,9 +77,9 @@ if (Test-Command "npm") { # Check for npx if (Test-Command "npx") { - Write-Success "Found npx! You can use state-docs without installation:" + Write-Success "Found npx! You can use praxisdoc without installation:" Write-Host "" - Write-Info "Run: npx @plures/statedoc gen --config=.stateDoc.json" + Write-Info "Run: npx @plures/praxisdoc gen --config=.praxisDoc.json" Write-Host "" Write-Warning "Note: This will download and run the latest version each time." exit 0 diff --git a/install.sh b/install.sh index 199d6d2..bdc6ee8 100755 --- a/install.sh +++ b/install.sh @@ -1,6 +1,6 @@ #!/bin/sh -# State-Docs Installer Script -# This script installs state-docs using the best available method +# Praxis-Docs Installer Script +# This script installs praxisdoc using the best available method set -e @@ -44,7 +44,7 @@ detect_os() { esac } -info "State-Docs Installer" +info "Praxis-Docs Installer (v2.0)" echo "" OS=$(detect_os) @@ -54,13 +54,14 @@ info "Detected OS: $OS" if command_exists deno; then info "Found Deno: $(deno --version | head -n1)" echo "" - info "Installing state-docs via Deno JSR..." + info "Installing praxisdoc via Deno JSR..." # Install from JSR - if deno install -A -f -n statedoc jsr:@plures/statedoc/cli; then - success "Successfully installed state-docs via Deno!" + if deno install -A -f -n praxisdoc jsr:@plures/praxisdoc/cli; then + success "Successfully installed praxisdoc via Deno!" echo "" - info "You can now run: statedoc gen --config=.stateDoc.json" + info "You can now run: praxisdoc gen --config=.praxisDoc.json" + info "Or use legacy command: statedoc gen --config=.stateDoc.json" exit 0 else warning "Failed to install via Deno JSR, trying alternative methods..." @@ -71,12 +72,13 @@ fi if command_exists npm; then info "Found npm: $(npm --version)" echo "" - info "Installing state-docs via npm..." + info "Installing praxisdoc via npm..." - if npm install -g @plures/statedoc; then - success "Successfully installed state-docs via npm!" + if npm install -g @plures/praxisdoc; then + success "Successfully installed praxisdoc via npm!" echo "" - info "You can now run: statedoc gen --config=.stateDoc.json" + info "You can now run: praxisdoc gen --config=.praxisDoc.json" + info "Or use legacy command: statedoc gen --config=.stateDoc.json" exit 0 else warning "Failed to install via npm globally" @@ -85,9 +87,9 @@ fi # Check for npx (can be used without global install) if command_exists npx; then - success "Found npx! You can use state-docs without installation:" + success "Found npx! You can use praxisdoc without installation:" echo "" - info "Run: npx @plures/statedoc gen --config=.stateDoc.json" + info "Run: npx @plures/praxisdoc gen --config=.praxisDoc.json" echo "" warning "Note: This will download and run the latest version each time." exit 0 diff --git a/mod.ts b/mod.ts index 3c9a12d..678bd80 100644 --- a/mod.ts +++ b/mod.ts @@ -2,18 +2,18 @@ import { loadAdapters } from "./runtime.ts"; import { generateDocs } from "./src/generate.ts"; -export type StateDocConfig = { +export type PraxisDocConfig = { projectTitle?: string; source: string; target: string; outline?: string; - alwaysDisplayVisualizationAfterMachine?: boolean; + alwaysDisplayVisualizationAfterLogic?: boolean; globs?: string[]; templates?: Record; visualization?: { format?: "mermaid"; exportPng?: boolean }; }; -export async function runOnce(cfg: StateDocConfig): Promise { +export async function runOnce(cfg: PraxisDocConfig): Promise { const adapters = await loadAdapters(); return generateDocs(cfg, adapters); } diff --git a/package.json.template b/package.json.template index a09e98b..b84654e 100644 --- a/package.json.template +++ b/package.json.template @@ -1,11 +1,11 @@ { - "name": "@plures/statedoc", - "version": "1.0.2", - "description": "FSM documentation generator for Deno + Node", + "name": "@plures/praxisdoc", + "version": "2.0.0", + "description": "Praxis application documentation generator for Deno + Node", "license": "MIT", "type": "module", - "bin": { "statedoc": "./esm/cli.js" }, + "bin": { "praxisdoc": "./esm/cli.js" }, "exports": { ".": "./mod.js" }, "engines": { "node": ">=18" }, "dependencies": { diff --git a/src/fsm/task.schema.ts b/src/fsm/task.schema.ts new file mode 100644 index 0000000..e3fdf18 --- /dev/null +++ b/src/fsm/task.schema.ts @@ -0,0 +1,89 @@ +/** + * Example Praxis Schema - Task Management + * + * This demonstrates a Praxis application schema with models, logic, and components. + * Praxis uses a schema-driven approach where everything is defined declaratively. + */ + +export const taskSchema = { + version: '1.0.0', + name: 'TaskManagement', + description: 'Task management application with state transitions', + + models: [ + { + name: 'Task', + description: 'A task item in the system', + fields: [ + { name: 'id', type: 'string', description: 'Unique identifier' }, + { name: 'title', type: 'string', description: 'Task title' }, + { name: 'description', type: 'string', description: 'Detailed description' }, + { name: 'status', type: 'string', default: 'new', description: 'Current status' }, + { name: 'assignee', type: 'string', description: 'Assigned user' }, + { name: 'createdAt', type: 'date', description: 'Creation timestamp' }, + { name: 'completedAt', type: 'date', description: 'Completion timestamp' }, + ], + indexes: [ + { name: 'by_status', fields: ['status'] }, + { name: 'by_assignee', fields: ['assignee'] }, + { name: 'by_created', fields: ['createdAt'] }, + ], + }, + ], + + logic: [ + { + id: 'task-state-machine', + name: 'Task State Machine', + description: 'Manages task lifecycle from creation to completion', + + events: [ + { tag: 'TASK_CREATE', payload: { title: 'string', description: 'string' }, description: 'Create a new task' }, + { tag: 'TASK_ASSIGN', payload: { taskId: 'string', assignee: 'string' }, description: 'Assign task to a user' }, + { tag: 'TASK_START', payload: { taskId: 'string' }, description: 'Start working on task' }, + { tag: 'TASK_PAUSE', payload: { taskId: 'string' }, description: 'Pause work on task' }, + { tag: 'TASK_COMPLETE', payload: { taskId: 'string' }, description: 'Mark task as completed' }, + { tag: 'TASK_REOPEN', payload: { taskId: 'string' }, description: 'Reopen a completed task' }, + { tag: 'TASK_CANCEL', payload: { taskId: 'string' }, description: 'Cancel a task' }, + ], + + facts: [ + { tag: 'TaskCreated', payload: { taskId: 'string', title: 'string' }, description: 'Task was created' }, + { tag: 'TaskAssigned', payload: { taskId: 'string', assignee: 'string' }, description: 'Task was assigned' }, + { tag: 'TaskCompleted', payload: { taskId: 'string', completedAt: 'date' }, description: 'Task was completed' }, + ], + + transitions: [ + { from: 'new', event: 'TASK_ASSIGN', to: 'assigned', description: 'Assign a new task' }, + { from: 'assigned', event: 'TASK_START', to: 'in_progress', description: 'Start working on assigned task' }, + { from: 'in_progress', event: 'TASK_PAUSE', to: 'paused', description: 'Pause work' }, + { from: 'paused', event: 'TASK_START', to: 'in_progress', description: 'Resume work' }, + { from: 'in_progress', event: 'TASK_COMPLETE', to: 'completed', description: 'Complete the task' }, + { from: 'completed', event: 'TASK_REOPEN', to: 'assigned', description: 'Reopen completed task' }, + { from: 'new', event: 'TASK_CANCEL', to: 'cancelled', description: 'Cancel before assignment' }, + { from: 'assigned', event: 'TASK_CANCEL', to: 'cancelled', description: 'Cancel assigned task' }, + ], + }, + ], + + components: [ + { + name: 'TaskForm', + type: 'form', + model: 'Task', + description: 'Form for creating and editing tasks', + }, + { + name: 'TaskList', + type: 'list', + model: 'Task', + description: 'List view of all tasks with filtering', + }, + { + name: 'TaskDetail', + type: 'detail', + model: 'Task', + description: 'Detailed view of a single task', + }, + ], +}; diff --git a/src/generate.ts b/src/generate.ts index 1af723c..e171ae9 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -1,54 +1,58 @@ import type { Adapters } from "../runtime.ts"; -import type { StateDocConfig } from "../mod.ts"; +import type { PraxisDocConfig } from "../mod.ts"; import { renderTemplate } from "./tpl.ts"; -import { parseMachines } from "./parser.ts"; +import { parseSchemas } from "./parser.ts"; +import { slugify } from "./utils.ts"; -export async function generateDocs(cfg: StateDocConfig, adapters: Adapters) { - const machines = await parseMachines(cfg, adapters); +export async function generateDocs(cfg: PraxisDocConfig, adapters: Adapters) { + const schemas = await parseSchemas(cfg, adapters); // Ensure target dirs await adapters.fs.mkdirp(cfg.target); - await adapters.fs.mkdirp(adapters.join(cfg.target, "machines")); + await adapters.fs.mkdirp(adapters.join(cfg.target, "schemas")); - // Index - const outline = cfg.outline ?? "# {{projectTitle}}\n{{#each machines}}\n## {{name}}\n{{desc}}\n{{/each}}"; - const indexMd = renderTemplate(outline, { projectTitle: cfg.projectTitle ?? "FSM Docs", machines }); + // Index - list all schemas + const outline = cfg.outline ?? "# {{projectTitle}}\n{{#each schemas}}\n## {{name}}\n{{desc}}\n\n### Logic Definitions\n{{#each logic}}- [{{name}}](./schemas/{{../slug}}/logic/{{slug}}.md) β€” {{desc}}\n{{/each}}\n{{/each}}"; + const indexMd = renderTemplate(outline, { projectTitle: cfg.projectTitle ?? "Praxis Application Documentation", schemas }); await adapters.fs.writeFile(adapters.join(cfg.target, "index.md"), indexMd); - // Per-machine docs - const machineTpl = cfg.templates?.machineIndex ?? "## {{name}}\n{{desc}}\n\nStates:\n{{#each states}}- [{{name}}](./states/{{slug}}.md) β€” {{desc}}\n{{/each}}\n"; - const stateTpl = cfg.templates?.statePage ?? "# {{machine.name}} / {{name}}\n{{desc}}\n\nTransitions:\n{{#each on}}- {{event}} β†’ {{target}}\n{{/each}}\n"; - - for (const m of machines) { - const mdir = adapters.join(cfg.target, "machines", m.slug); - const sdir = adapters.join(mdir, "states"); - await adapters.fs.mkdirp(sdir); - - const mReadme = renderTemplate(machineTpl, m); - await adapters.fs.writeFile(adapters.join(mdir, "README.md"), mReadme); - - for (const s of m.states) { - const page = renderTemplate(stateTpl, { ...s, machine: m }); - await adapters.fs.writeFile(adapters.join(sdir, `${s.slug}.md`), page); - } - - // Mermaid diagram (text only for now) - const lines = [ - "stateDiagram-v2", - ` [*] --> ${m.states[0]?.slug ?? "idle"}`, - ...m.states.flatMap((st: { slug: string; on: { event: string; target: string }[] }) => - st.on.map((tr: { event: string; target: string }) => ` ${st.slug} --> ${tr.target}: ${tr.event}`) - ) - ]; - const mermaidText = lines.join("\n"); - await adapters.fs.writeFile(adapters.join(mdir, "diagram.mmd"), mermaidText); - - // Export PNG if configured (feature not yet implemented, will be null) - if (cfg.visualization?.exportPng) { - const pngData = await adapters.mermaid.toPng(mermaidText); - if (pngData) { - await adapters.fs.writeBinaryFile(adapters.join(mdir, "diagram.png"), pngData); + // Per-schema docs + const schemaTpl = cfg.templates?.schemaIndex ?? "# {{name}}\n\n{{desc}}\n\n## Models\n{{#each models}}\n### {{name}}\n{{desc}}\n\nFields:\n{{#each fields}}- **{{name}}** ({{type}}){{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/each}}\n\n## Logic Definitions\n{{#each logic}}- [{{name}}](./logic/{{slug}}.md) β€” {{desc}}\n{{/each}}\n"; + const logicTpl = cfg.templates?.logicPage ?? "# {{schema.name}} / {{name}}\n\n{{desc}}\n\n## Events\n{{#each events}}- **{{tag}}**{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n\n{{#if states}}## States\n{{#each states}}\n### {{name}}\n{{desc}}\n\nTransitions:\n{{#each on}}- {{event}} β†’ {{target}}{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/each}}\n{{/if}}\n{{#if facts}}## Facts\n{{#each facts}}- **{{tag}}**{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/if}}\n{{#if transitions}}## State Transitions\n{{#each transitions}}- {{from}} --[{{event}}]--> {{to}}{{#if description}} β€” {{description}}{{/if}}\n{{/each}}\n{{/if}}\n"; + + for (const schema of schemas) { + const sdir = adapters.join(cfg.target, "schemas", schema.slug); + const ldir = adapters.join(sdir, "logic"); + await adapters.fs.mkdirp(ldir); + + const sReadme = renderTemplate(schemaTpl, schema); + await adapters.fs.writeFile(adapters.join(sdir, "README.md"), sReadme); + + // Generate documentation for each logic definition + for (const logic of schema.logic) { + const page = renderTemplate(logicTpl, { ...logic, schema }); + await adapters.fs.writeFile(adapters.join(ldir, `${logic.slug}.md`), page); + + // Generate Mermaid diagram if states are present + if (logic.states && logic.states.length > 0) { + const lines = [ + "stateDiagram-v2", + ` [*] --> ${logic.states[0]?.slug ?? "start"}`, + ...logic.states.flatMap(st => + st.on.map(tr => ` ${st.slug} --> ${slugify(tr.target)}: ${tr.event}`) + ) + ]; + const mermaidText = lines.join("\n"); + await adapters.fs.writeFile(adapters.join(ldir, `${logic.slug}.mmd`), mermaidText); + + // Export PNG if configured + if (cfg.visualization?.exportPng) { + const pngData = await adapters.mermaid.toPng(mermaidText); + if (pngData) { + await adapters.fs.writeBinaryFile(adapters.join(ldir, `${logic.slug}.png`), pngData); + } + } } } } diff --git a/src/parser.ts b/src/parser.ts index d74cfdd..635915e 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,87 +1,140 @@ import type { Adapters } from "../runtime.ts"; -import type { StateDocConfig } from "../mod.ts"; +import type { PraxisDocConfig } from "../mod.ts"; +import { slugify } from "./utils.ts"; -export type Machine = { +// Praxis Schema Types +export type PraxisModel = { + name: string; + desc: string; + fields?: Array<{ name: string; type: string; description?: string; default?: any }>; +}; + +export type PraxisLogic = { + id: string; name: string; desc: string; slug: string; - states: { name: string; desc: string; slug: string; on: { event: string; target: string }[] }[]; + events: Array<{ tag: string; payload?: Record; description?: string }>; + facts?: Array<{ tag: string; payload?: Record; description?: string }>; + transitions?: Array<{ from: string; event: string; to: string; description?: string }>; + states?: Array<{ name: string; desc: string; slug: string; on: Array<{ event: string; target: string; description?: string }> }>; }; -function slugify(s: string): string { - return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); -} +export type PraxisSchema = { + version?: string; + name: string; + desc: string; + slug: string; + models?: PraxisModel[]; + logic: PraxisLogic[]; + components?: Array<{ name: string; type: string; model?: string; description?: string }>; +}; /** - * Parse a machine object extracted from source code + * Parse a Praxis schema object extracted from source code */ -function parseMachineObject(machineObj: any, varName: string): Machine { - const name = machineObj.id || varName; +function parsePraxisSchema(schemaObj: any, varName: string): PraxisSchema { + const name = schemaObj.name || varName; const slug = slugify(name); + const desc = schemaObj.description || `Praxis application schema for ${name}`; - // Flatten all states (including nested ones) - const states: Machine['states'] = []; + // Parse models + const models: PraxisModel[] = []; + if (schemaObj.models && Array.isArray(schemaObj.models)) { + for (const model of schemaObj.models) { + models.push({ + name: model.name || 'Unknown', + desc: model.description || `Model for ${model.name || 'Unknown'}`, + fields: model.fields || [] + }); + } + } - function processStates(statesObj: any, prefix = '') { - if (!statesObj || typeof statesObj !== 'object') return; - - for (const [stateName, stateConfig] of Object.entries(statesObj)) { - const config = stateConfig as any; - const fullName = prefix ? `${prefix}.${stateName}` : stateName; - const desc = config.description || config.desc || ''; - const on: { event: string; target: string }[] = []; + // Parse logic definitions (facts, events, rules, transitions) + const logic: PraxisLogic[] = []; + if (schemaObj.logic && Array.isArray(schemaObj.logic)) { + for (const logicDef of schemaObj.logic) { + const logicId = logicDef.id || 'unknown-logic'; + const logicName = logicDef.name || logicDef.id || 'Unknown Logic'; + const logicDesc = logicDef.description || `Logic definition for ${logicName}`; - if (config.on) { - for (const [event, transition] of Object.entries(config.on)) { - const t = transition as any; - let target = ''; - - if (typeof t === 'string') { - target = t; - } else if (t && typeof t === 'object' && t.target) { - target = t.target; + // Extract events + const events = (logicDef.events || []).map((evt: any) => ({ + tag: evt.tag || evt.type || 'UNKNOWN_EVENT', + payload: evt.payload, + description: evt.description || evt.desc + })); + + // Extract facts + const facts = (logicDef.facts || []).map((fact: any) => ({ + tag: fact.tag || fact.type || 'UNKNOWN_FACT', + payload: fact.payload, + description: fact.description || fact.desc + })); + + // Extract transitions (if defined) + const transitions = (logicDef.transitions || []).map((trans: any) => ({ + from: trans.from || '', + event: trans.event || '', + to: trans.to || trans.target || '', + description: trans.description || trans.desc + })); + + // Convert transitions to state representation for compatibility + const states: Array<{ name: string; desc: string; slug: string; on: Array<{ event: string; target: string; description?: string }> }> = []; + if (transitions.length > 0) { + const stateMap = new Map }>(); + + for (const trans of transitions) { + if (!stateMap.has(trans.from)) { + stateMap.set(trans.from, { + name: trans.from, + desc: `State: ${trans.from}`, + slug: slugify(trans.from), + on: [] + }); } - // Remove machine ID prefix from target (e.g., #shoppingCart.active -> active) - target = target.replace(/^#[^.]+\./, ''); - - if (target) { - on.push({ event, target }); - } + const state = stateMap.get(trans.from)!; + state.on.push({ + event: trans.event, + target: trans.to, + description: trans.description + }); } + + states.push(...Array.from(stateMap.values())); } - states.push({ - name: fullName, - desc, - slug: slugify(fullName), - on + logic.push({ + id: logicId, + name: logicName, + desc: logicDesc, + slug: slugify(logicId), + events, + facts, + transitions, + states: states.length > 0 ? states : undefined }); - - // Process nested states - if (config.states && typeof config.states === 'object') { - processStates(config.states, fullName); - } } } - if (machineObj.states) { - processStates(machineObj.states); - } - return { + version: schemaObj.version, name, - desc: `State machine for ${name}`, + desc, slug, - states + models, + logic, + components: schemaObj.components }; } /** - * Extract machine definitions from a JavaScript/TypeScript file - * This is a runtime evaluation approach - we import the file and extract exported machines + * Extract Praxis schemas from a JavaScript/TypeScript file + * This is a runtime evaluation approach - we import the file and extract exported schemas */ -async function extractMachinesFromFile(filePath: string, _adapters: Adapters): Promise { +async function extractSchemasFromFile(filePath: string, _adapters: Adapters): Promise { try { // Convert to absolute file:// URL let importPath = filePath; @@ -105,26 +158,36 @@ async function extractMachinesFromFile(filePath: string, _adapters: Adapters): P // Import the file as a module const module = await import(importPath); - const machines: Machine[] = []; + const schemas: PraxisSchema[] = []; - // Look for exported objects that look like state machines + // Look for exported objects that look like Praxis schemas or legacy XState machines for (const [exportName, exportValue] of Object.entries(module)) { if (exportValue && typeof exportValue === 'object') { const obj = exportValue as any; - // Check if it looks like a state machine (has id or states property) - if (obj.states || obj.id) { + // Check if it's a Praxis schema (has models or logic property) + if (obj.logic || obj.models) { try { - const machine = parseMachineObject(obj, exportName); - machines.push(machine); + const schema = parsePraxisSchema(obj, exportName); + schemas.push(schema); } catch (e) { - console.warn(`Warning: Failed to parse ${exportName} in ${filePath}:`, e); + console.warn(`Warning: Failed to parse Praxis schema ${exportName} in ${filePath}:`, e); + } + } + // Legacy support: Check if it looks like an XState machine (has id or states property) + else if (obj.states || obj.id) { + try { + // Convert XState machine to Praxis schema format + const machine = parseLegacyXStateMachine(obj, exportName); + schemas.push(machine); + } catch (e) { + console.warn(`Warning: Failed to parse legacy XState machine ${exportName} in ${filePath}:`, e); } } } } - return machines; + return schemas; } catch (e) { console.warn(`Warning: Failed to import ${filePath}:`, e); return []; @@ -132,22 +195,102 @@ async function extractMachinesFromFile(filePath: string, _adapters: Adapters): P } /** - * Parse machines from all files matching the configuration + * Parse legacy XState machine and convert to Praxis schema format + */ +function parseLegacyXStateMachine(machineObj: any, varName: string): PraxisSchema { + const name = machineObj.id || varName; + const slug = slugify(name); + + // Flatten all states (including nested ones) + const states: Array<{ name: string; desc: string; slug: string; on: Array<{ event: string; target: string; description?: string }> }> = []; + const events = new Set(); + + function processStates(statesObj: any, prefix = '') { + if (!statesObj || typeof statesObj !== 'object') return; + + for (const [stateName, stateConfig] of Object.entries(statesObj)) { + const config = stateConfig as any; + const fullName = prefix ? `${prefix}.${stateName}` : stateName; + const desc = config.description || config.desc || ''; + const on: { event: string; target: string; description?: string }[] = []; + + if (config.on) { + for (const [event, transition] of Object.entries(config.on)) { + events.add(event); + const t = transition as any; + let target = ''; + let description = ''; + + if (typeof t === 'string') { + target = t; + } else if (t && typeof t === 'object') { + target = t.target || ''; + description = t.description || t.desc || ''; + } + + // Remove machine ID prefix from target (e.g., #shoppingCart.active -> active) + target = target.replace(/^#[^.]+\./, ''); + + if (target) { + on.push({ event, target, description }); + } + } + } + + states.push({ + name: fullName, + desc, + slug: slugify(fullName), + on + }); + + // Process nested states + if (config.states && typeof config.states === 'object') { + processStates(config.states, fullName); + } + } + } + + if (machineObj.states) { + processStates(machineObj.states); + } + + // Create a Praxis-compatible logic definition from the XState machine + const logic: PraxisLogic = { + id: name, + name: name, + desc: `State machine for ${name} (converted from XState)`, + slug: slug, + events: Array.from(events).map(tag => ({ tag })), + states + }; + + return { + name, + desc: `State machine for ${name} (converted from XState)`, + slug, + logic: [logic] + }; +} + +/** + * Parse Praxis schemas from all files matching the configuration */ -export async function parseMachines(cfg: StateDocConfig, adapters: Adapters): Promise { - const globs = cfg.globs || ['**/*.machine.ts', '**/*.machine.js']; +export async function parseSchemas(cfg: PraxisDocConfig, adapters: Adapters): Promise { + const globs = cfg.globs || ['**/*.schema.ts', '**/*.schema.js', '**/*.machine.ts', '**/*.machine.js']; const files = await adapters.glob.glob(cfg.source, globs); - const allMachines: Machine[] = []; + const allSchemas: PraxisSchema[] = []; for (const file of files) { - const machines = await extractMachinesFromFile(file, adapters); - allMachines.push(...machines); + const schemas = await extractSchemasFromFile(file, adapters); + allSchemas.push(...schemas); } - if (allMachines.length === 0) { - console.warn('No state machines found. Check your source path and globs configuration.'); + if (allSchemas.length === 0) { + console.warn('No Praxis schemas found. Check your source path and globs configuration.'); + console.warn('Looking for files with .schema.ts/.schema.js extensions or legacy .machine.ts/.machine.js files.'); } - return allMachines; + return allSchemas; } diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..8d15925 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,7 @@ +/** + * Utility functions shared across the project + */ + +export function slugify(s: string): string { + return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); +}