From 9c52707f9915a24350154121b9f301a10c5374ad Mon Sep 17 00:00:00 2001 From: yihao Date: Mon, 26 Jan 2026 15:07:02 +0800 Subject: [PATCH] Add skills used opencode to read devGuide and generated relevant skills --- .opencode/skills/README.md | 268 +++++++++ .../skills/markbind-architecture/SKILL.md | 257 +++++++++ .../references/core-package.md | 267 +++++++++ .../references/external-libraries.md | 378 +++++++++++++ .../references/vue-integration.md | 401 +++++++++++++ .../markbind-component-patterns/SKILL.md | 432 ++++++++++++++ .../references/dependency-management.md | 528 ++++++++++++++++++ .../references/ssr-patterns.md | 495 ++++++++++++++++ .../skills/markbind-dev-workflow/SKILL.md | 214 +++++++ .../references/git-hooks.md | 126 +++++ .../references/test-patterns.md | 211 +++++++ .../skills/markbind-maintainer-tasks/SKILL.md | 398 +++++++++++++ .../references/release-procedures.md | 472 ++++++++++++++++ .../markbind-typescript-migration/SKILL.md | 257 +++++++++ .../references/adapt-commit.md | 480 ++++++++++++++++ .../references/import-export-syntax.md | 437 +++++++++++++++ .../references/preparation.md | 321 +++++++++++ .../references/rename-commit.md | 313 +++++++++++ .../references/troubleshooting.md | 403 +++++++++++++ 19 files changed, 6658 insertions(+) create mode 100644 .opencode/skills/README.md create mode 100644 .opencode/skills/markbind-architecture/SKILL.md create mode 100644 .opencode/skills/markbind-architecture/references/core-package.md create mode 100644 .opencode/skills/markbind-architecture/references/external-libraries.md create mode 100644 .opencode/skills/markbind-architecture/references/vue-integration.md create mode 100644 .opencode/skills/markbind-component-patterns/SKILL.md create mode 100644 .opencode/skills/markbind-component-patterns/references/dependency-management.md create mode 100644 .opencode/skills/markbind-component-patterns/references/ssr-patterns.md create mode 100644 .opencode/skills/markbind-dev-workflow/SKILL.md create mode 100644 .opencode/skills/markbind-dev-workflow/references/git-hooks.md create mode 100644 .opencode/skills/markbind-dev-workflow/references/test-patterns.md create mode 100644 .opencode/skills/markbind-maintainer-tasks/SKILL.md create mode 100644 .opencode/skills/markbind-maintainer-tasks/references/release-procedures.md create mode 100644 .opencode/skills/markbind-typescript-migration/SKILL.md create mode 100644 .opencode/skills/markbind-typescript-migration/references/adapt-commit.md create mode 100644 .opencode/skills/markbind-typescript-migration/references/import-export-syntax.md create mode 100644 .opencode/skills/markbind-typescript-migration/references/preparation.md create mode 100644 .opencode/skills/markbind-typescript-migration/references/rename-commit.md create mode 100644 .opencode/skills/markbind-typescript-migration/references/troubleshooting.md diff --git a/.opencode/skills/README.md b/.opencode/skills/README.md new file mode 100644 index 0000000000..4ec8043c24 --- /dev/null +++ b/.opencode/skills/README.md @@ -0,0 +1,268 @@ +# MarkBind Agent Skills + +This directory contains Agent Skills for AI agents working on the MarkBind project. These skills provide domain-specific expertise about MarkBind's architecture, development workflows, and best practices. + +## Available Skills + +### 1. markbind-dev-workflow +**Purpose**: Day-to-day development workflows and testing procedures + +**Use when**: +- Setting up MarkBind for development +- Making backend (TypeScript/JavaScript) changes +- Making frontend (Vue components) changes +- Running tests and updating test sites +- Preparing pull requests + +**Key topics**: +- Environment setup and prerequisites +- Backend compilation (TypeScript → JavaScript) +- Frontend development with `-d` flag +- Testing workflow (linting, unit, functional tests) +- Git workflow and keeping fork updated +- Debugging configurations + +**Reference files**: +- `references/test-patterns.md` - Detailed test site procedures +- `references/git-hooks.md` - Pre-commit hook details + +--- + +### 2. markbind-architecture +**Purpose**: Understanding MarkBind's internal architecture and code organization + +**Use when**: +- Understanding how MarkBind works internally +- Navigating the codebase +- Implementing features that interact with core processing +- Understanding package relationships + +**Key topics**: +- Monorepo structure (4 packages) +- Content processing flow (Nunjucks → Markdown → HTML) +- Key classes (Site, Page, Layout, External, NodeProcessor) +- Package responsibilities and relationships + +**Reference files**: +- `references/core-package.md` - Deep dive into core package +- `references/vue-integration.md` - Vue and SSR details +- `references/external-libraries.md` - Key dependencies and patches + +--- + +### 3. markbind-component-patterns +**Purpose**: Implementation patterns for components and features + +**Use when**: +- Adding new MarkBind components +- Modifying existing components +- Implementing features +- Ensuring SSR compatibility + +**Key topics**: +- Component implementation approaches (node transformation, Vue, plugin) +- Vue component guidelines (props, slots, registration) +- SSR compatibility requirements +- Dependency management strategies + +**Reference files**: +- `references/ssr-patterns.md` - SSR rules and hydration issues +- `references/dependency-management.md` - Adding/updating dependencies + +--- + +### 4. markbind-typescript-migration +**Purpose**: Complete guide for migrating JavaScript to TypeScript in MarkBind + +**Use when**: +- Migrating .js files to .ts in MarkBind's core package +- Preparing TypeScript migration pull requests +- Understanding the Rename+Adapt commit workflow +- Converting import/export syntax from CommonJS to TypeScript + +**Key topics**: +- Two-commit strategy (Rename + Adapt) +- Preserving git history (similarity index) +- Import/export syntax conversion +- TypeScript error fixing patterns +- Testing migrated code + +**Reference files**: +- `references/preparation.md` - Planning migration scope, installing types +- `references/rename-commit.md` - Creating the "Rename" commit +- `references/adapt-commit.md` - Creating the "Adapt" commit +- `references/import-export-syntax.md` - CommonJS ↔ TypeScript/ES6 conversion +- `references/troubleshooting.md` - Common issues and solutions + +--- + +### 5. markbind-maintainer-tasks +**Purpose**: Maintainer-specific tasks and procedures + +**Use when**: +- Reviewing pull requests +- Merging PRs with appropriate strategy +- Preparing releases +- Managing contributors +- Performing repository management tasks + +**Key topics**: +- PR review and approval requirements +- Merge strategies (squash, merge commit, rebase) +- Post-merge tasks (version labels, release notes) +- Release procedures +- Contributor management (all-contributors) +- Repository management (projects, teams) + +**Reference files**: +- `references/release-procedures.md` - Complete release guide + +--- + +## Skill Architecture + +Each skill follows the Agent Skills best practices: + +### Level 1: Metadata (always loaded) +- YAML frontmatter with `name` and `description` +- ~100 tokens per skill +- Used by Claude to determine when to load the skill + +### Level 2: Instructions (loaded when triggered) +- SKILL.md body with core guidance +- Kept under 500 lines for context efficiency +- References to level 3 resources for details + +### Level 3: Resources (loaded as needed) +- Detailed reference files in `references/` subdirectory +- Loaded only when Claude determines they're needed +- No practical size limit due to on-demand loading + +## Design Principles + +### Concise Focus +Each skill body stays under 500 lines, with detailed information in reference files. + +### Progressive Disclosure +Information is split into files that load on-demand: +- Quick start info in SKILL.md +- Detailed procedures in reference files +- No duplication between files + +### Clear Triggers +Each description includes: +- What the skill does +- Specific contexts for when to use it +- Keywords that should trigger the skill + +### No Duplication +Information lives in one place only - either in SKILL.md or a reference file, never both. + +## Usage Notes + +### For AI Agents + +These skills are automatically discovered and loaded when relevant to your task. You don't need to explicitly invoke them. + +When a skill triggers: +1. The SKILL.md body loads into context +2. Reference files are loaded as needed +3. Use bash commands to read files when referenced + +### For Developers + +To add or update skills: +1. Edit the relevant SKILL.md or reference files +2. Keep SKILL.md bodies concise (<500 lines) +3. Move detailed content to reference files +4. Update descriptions if trigger conditions change +5. Test by asking Claude questions that should trigger the skill + +## Skill Coverage + +**Development lifecycle covered**: +- ✅ Initial setup and environment configuration +- ✅ Daily development (backend and frontend) +- ✅ Testing and test site management +- ✅ Component and feature implementation +- ✅ TypeScript migration procedures +- ✅ SSR compatibility and hydration +- ✅ PR submission and review +- ✅ Release management +- ✅ Repository administration + +**Not covered** (intentionally): +- General git/npm knowledge (Claude already knows) +- Project management for non-maintainers +- User-facing documentation (in main docs) + +## File Structure + +``` +.claude/skills/ +├── README.md +├── markbind-dev-workflow/ +│ ├── SKILL.md +│ └── references/ +│ ├── test-patterns.md +│ └── git-hooks.md +├── markbind-architecture/ +│ ├── SKILL.md +│ └── references/ +│ ├── core-package.md +│ ├── vue-integration.md +│ └── external-libraries.md +├── markbind-component-patterns/ +│ ├── SKILL.md +│ └── references/ +│ ├── ssr-patterns.md +│ └── dependency-management.md +├── markbind-typescript-migration/ +│ ├── SKILL.md +│ └── references/ +│ ├── preparation.md +│ ├── rename-commit.md +│ ├── adapt-commit.md +│ ├── import-export-syntax.md +│ └── troubleshooting.md +└── markbind-maintainer-tasks/ + ├── SKILL.md + └── references/ + └── release-procedures.md +``` + +## Maintenance + +### Updating Skills + +When MarkBind's processes or architecture change: + +1. Identify which skill(s) need updating +2. Update the relevant SKILL.md or reference file +3. Keep descriptions current with trigger conditions +4. Test that Claude loads and uses the updated skill correctly + +### Adding New Skills + +Consider adding a new skill when: + +1. A distinct domain of knowledge emerges +2. Existing skills become too large (>500 lines in SKILL.md) +3. A new workflow or process is established +4. The new content doesn't fit existing skill boundaries + +Follow the Agent Skills best practices for structure and format. + +## Questions or Issues + +If you encounter issues with these skills or have suggestions: + +1. File an issue in the MarkBind repository +2. Tag with appropriate labels +3. Provide context about what you were trying to do +4. Include any error messages or unexpected behavior + +--- + +*Last updated: January 2026* +*Skills version: 1.0.0* diff --git a/.opencode/skills/markbind-architecture/SKILL.md b/.opencode/skills/markbind-architecture/SKILL.md new file mode 100644 index 0000000000..7ba1a9422e --- /dev/null +++ b/.opencode/skills/markbind-architecture/SKILL.md @@ -0,0 +1,257 @@ +--- +name: markbind-architecture +description: Internal architecture and code organization of the MarkBind project including monorepo structure, content processing flow, key classes, and package relationships. Use when understanding how MarkBind works internally, navigating the codebase, implementing features that interact with core processing logic, or understanding the relationship between packages (cli, core, core-web, vue-components). +--- + +# MarkBind Architecture + +## Monorepo Structure + +MarkBind uses a monorepo managed by [lerna](https://github.com/lerna/lerna) with 4 packages: + +### packages/cli +Command-line interface application that: +- Accepts user commands (`init`, `serve`, `build`, `deploy`) +- Uses the core library to process content +- Provides live reload development server +- Manages site deployment + +**Key dependencies**: commander.js, live-server (patched) + +### packages/core +Core processing library that: +- Parses and processes MarkBind syntax +- Implements the content processing flow +- Manages pages, layouts, and external files +- Handles plugins and extensions + +**Key dependencies**: markdown-it, cheerio, htmlparser2, nunjucks (all patched) + +### packages/core-web +Client-side bundle containing: +- Vue runtime and setup scripts +- Bootstrap and FontAwesome bundles +- Custom stylesheets +- UI component library +- SSR render logic + +**Outputs**: +- `markbind.min.js` - Main client bundle +- `markbind.min.css` - Styles +- `vuecommonappfactory.min.js` - SSR setup +- `vuecommonappfactory.min.css` - SSR styles + +### packages/vue-components +UI components library with: +- Bootstrap-based components (modified) +- Custom MarkBind components (panels, modals, tooltips, etc.) +- Vue directives (closeable, float) + +**Key dependencies**: Vue 3, Bootstrap 5, floating-vue, vue-final-modal + +## Content Processing Flow + +Every Page, Layout, and External follows the same three-stage flow: + +``` +Nunjucks → Markdown → HTML +``` + +### Stage 1: Nunjucks (VariableProcessor) +- Processes template variables and expressions +- Expands loops and conditionals +- Resolves `{% set %}` and `{{ variable }}` syntax +- Output: Markdown/HTML with variables resolved + +### Stage 2: Markdown (NodeProcessor) +- Renders Markdown to HTML +- Processes markdown-it plugins +- Handles custom MarkBind syntax +- Output: HTML with some MarkBind components + +### Stage 3: HTML (NodeProcessor) +- Traverses HTML node tree +- Processes MarkBind components +- Handles includes recursively +- Applies plugins +- Output: Final HTML ready for layout injection + +### Important Notes + +- **Predictable order**: Always Nunjucks first, Markdown second, HTML last +- **Nunjucks compatibility**: Processed first so templating works on all content +- **Markdown before HTML**: Prevents Markdown syntax from conflicting with HTML parsing +- **External generation**: Files referenced by `` generate separate output files processed by ExternalManager **after** the referencing page + +## Key Classes + +### Site (packages/core/src/Site/) +The main orchestrator that: +- Manages all pages, layouts, and configuration +- Coordinates the build process +- Copies assets to output folder +- Handles site-wide operations + +**Location**: `packages/core/src/Site/index.ts` + +### Page (packages/core/src/Page/) +Represents a single page: +- Processes page content through the content flow +- Manages page-specific frontmatter +- Generates output HTML file +- Directly managed by Site instance + +**Location**: `packages/core/src/Page/index.ts` + +### Layout (packages/core/src/Layout/) +Stores layout templates: +- Processed similarly to Pages +- Stores intermediate results +- Does not generate output files directly +- Used by Pages for final rendering +- Managed by LayoutManager + +**Location**: `packages/core/src/Layout/` + +### External (packages/core/src/External/) +Handles dynamically-loaded content: +- Content referenced in `` or similar +- Generates separate `_include_.html` files +- Loaded on-demand in browser +- Managed by ExternalManager + +**Location**: `packages/core/src/External/` + +### NodeProcessor (packages/core/src/html/NodeProcessor.ts) +The core HTML processing engine: +- Traverses HTML node tree +- Matches node names to components +- Applies transformations in three phases: + - `preProcessNode()` - Before main processing + - `processNode()` - Main processing + - `postProcessNode()` - After processing +- Handles includes and plugins + +**Location**: `packages/core/src/html/NodeProcessor.ts` + +### VariableProcessor (packages/core/src/variables/) +Manages Nunjucks templating: +- Processes site and page variables +- Handles frontmatter +- Manages variable scoping +- Integrates with Nunjucks + +**Location**: `packages/core/src/variables/` + +## Package Relationships + +``` +User → CLI → Core → Core-Web (client-side) + ↓ + Vue-Components (bundled into Core-Web) +``` + +### Build-time Dependencies +- CLI depends on Core (uses Site class) +- Core-Web bundles Vue-Components +- Core uses Core-Web bundles (copies to output) + +### Runtime Dependencies +- Browser loads Core-Web bundles +- Core-Web initializes Vue with components +- Vue components hydrate SSR HTML + +For detailed information about specific packages, see: +- [references/core-package.md](references/core-package.md) - Core package deep dive +- [references/vue-integration.md](references/vue-integration.md) - Vue and SSR details +- [references/external-libraries.md](references/external-libraries.md) - Key dependencies + +## Processing Flow Example + +Given this MarkBind file: +```markdown +{% set myVariable = "Item" %} + +# Header + +
    +{% for item in [1, 2, 3] %} +
  • {{ myVariable }} #{{ item }}
  • +{% endfor %} +
+ + +``` + +### After Nunjucks Stage: +```markdown +# Header + +
    +
  • Item #1
  • +
  • Item #2
  • +
  • Item #3
  • +
+ + +``` + +### After Markdown Stage: +```html +

Header

+

+
    +
  • Item #1
  • +
  • Item #2
  • +
  • Item #3
  • +
+ + +``` + +### After HTML Stage: +```html +

Header

+

+
    +
  • Item #1
  • +
  • Item #2
  • +
  • Item #3
  • +
+ +
+ +
+``` + +Then injected into layout template for final output. + +## File Locations Reference + +### Core Processing Logic +- `packages/core/src/html/` - HTML processing and NodeProcessor +- `packages/core/src/variables/` - Nunjucks and variable handling +- `packages/core/src/Page/` - Page processing +- `packages/core/src/Layout/` - Layout handling +- `packages/core/src/External/` - External file management +- `packages/core/src/Site/` - Site orchestration + +### Plugin System +- `packages/core/src/plugins/` - Plugin interface and default plugins +- `packages/core/src/plugins/default/` - Built-in plugins (PlantUML, tree, etc.) + +### Utilities +- `packages/core/src/lib/` - Helper libraries +- `packages/core/src/utils/` - Utility functions +- `packages/core/src/patches/` - Patched external libraries + +### Templates +- `packages/core/template/` - Site initialization templates + +### CLI +- `packages/cli/src/` - CLI commands and implementation +- `packages/cli/src/lib/live-server/` - Patched live-server + +### Frontend +- `packages/core-web/src/` - Client-side setup and scripts +- `packages/vue-components/src/` - Vue component implementations diff --git a/.opencode/skills/markbind-architecture/references/core-package.md b/.opencode/skills/markbind-architecture/references/core-package.md new file mode 100644 index 0000000000..a14e084b5a --- /dev/null +++ b/.opencode/skills/markbind-architecture/references/core-package.md @@ -0,0 +1,267 @@ +# Core Package Deep Dive + +## Directory Structure + +``` +packages/core/src/ +├── html/ # HTML processing +│ ├── NodeProcessor.ts # Main node traversal and processing +│ ├── vueServerRenderer/ # Server-side rendering with Vue +│ └── ... +├── variables/ # Nunjucks and variable handling +│ ├── VariableProcessor.ts +│ └── ... +├── Page/ # Page class and processing +├── Layout/ # Layout management +├── External/ # External file handling +├── Site/ # Site orchestration +├── plugins/ # Plugin system +│ ├── Plugin.ts # Plugin interface +│ └── default/ # Built-in plugins +├── lib/ # Helper libraries +│ ├── markdown-it/ # Markdown-it plugins and patches +│ └── ... +├── utils/ # Utility functions +├── patches/ # Patched external libraries +│ ├── htmlparser2.js +│ ├── nunjucks/ +│ └── ... +└── template/ # Init templates +``` + +## HTML Processing (packages/core/src/html/) + +### NodeProcessor.ts +The core processing engine that traverses the HTML DOM tree. + +**Key Methods**: +- `processNode(node)` - Main node processing, handles components and includes +- `preProcessNode(node)` - Called before main processing +- `postProcessNode(node)` - Called after main processing, handles CSS extraction + +**Processing Flow**: +1. Parse HTML into node tree (using htmlparser2) +2. Traverse tree depth-first +3. For each node: + - Check if it's a MarkBind component + - Apply transformations + - Process children recursively +4. Convert tree back to HTML (using cheerio) + +**Component Processing**: +- Nodes are matched by tag name (e.g., `` → panel component) +- Each component can have custom processing logic +- Plugins can hook into `processNode` and `postProcessNode` + +### vueServerRenderer/ +Server-side rendering logic for Vue components. + +**Key Files**: +- `PageVueServerRenderer.ts` - Main SSR entry point +- Compiles page content to Vue render function +- Executes render function with Vue's `renderToString` +- Returns pre-rendered HTML + +## Variable Processing (packages/core/src/variables/) + +### VariableProcessor.ts +Handles Nunjucks templating and variable resolution. + +**Key Responsibilities**: +- Compiles site and page variables +- Processes Nunjucks templates +- Manages variable scoping (global, page-level, frontmatter) +- Handles `{% set %}`, `{{ }}`, loops, conditionals + +**Variable Sources** (in order of precedence): +1. Inline frontmatter in page +2. Frontmatter overrides in `site.json` +3. Global variables in `site.json` +4. Built-in variables (e.g., `baseUrl`) + +## Page, Layout, External Classes + +### Page/index.ts +**Represents**: A single content page + +**Key Methods**: +- `generate()` - Main entry point for page generation +- `collectBaseUrl()` - Resolves base URLs for assets +- `collectPluginPageNunjucksAssets()` - Gathers plugin assets +- `generate()` - Runs through content processing flow + +**Properties**: +- `content` - Source content +- `frontmatter` - Page frontmatter +- `src` - Source file path +- `output` - Output file path + +### Layout/index.ts +**Represents**: A layout template + +**Key Difference from Page**: +- Does not generate output files directly +- Stores intermediate processed results +- Used by Pages during final rendering + +### External/index.ts +**Represents**: Dynamically-loaded content + +**Generated For**: +- `` with `preload=false` +- Other components with `src` attributes + +**Output Format**: `filename._include_.html` + +**Processing**: Same three-stage flow as Pages + +## Plugin System (packages/core/src/plugins/) + +### Plugin Interface +Plugins can implement: + +**Rendering Hooks**: +- `processNode(context, node)` - Process individual nodes +- `postRender(context, frontmatter, content)` - Process final HTML + +**Asset Injection**: +- `getLinks(context, frontmatter, content)` - Add `` tags +- `getScripts(context, frontmatter, content)` - Add ` +``` + +### Pattern 2: Self-Contained Components + +Implement custom behavior (e.g., Quiz component): + +```vue + + + +``` + +### Pattern 3: Hybrid Processing + +Some components use both node transformation and Vue: + +1. NodeProcessor transforms MarkBind syntax to HTML +2. HTML includes Vue component +3. Vue component adds interactivity + +## Debugging SSR Issues + +### Development Mode + +Use `-d` flag to enable SSR validation: +```bash +markbind serve -d +``` + +Shows hydration warnings in browser console. + +### Common Error Messages + +**"Hydration node mismatch"**: +- Server HTML differs from client virtual DOM +- Check for invalid HTML or state differences + +**"Hydration children mismatch"**: +- Different number of children +- Usually from conditional rendering issues + +**"Hydration attribute mismatch"**: +- Attribute values differ +- Check for dynamic attributes with server/client differences + +### Validation + +MarkBind validates HTML for common SSR issues in `packages/core/src/utils/htmlValidationUtils.ts`. + +Add validation rules when new hydration causes are discovered. + +## Best Practices + +### ✅ Do + +- Use client-only lifecycle hooks for DOM access +- Keep initial state consistent +- Use `v-show` for client-only visibility +- Test with SSR enabled (`-d` mode) +- Validate HTML structure + +### ❌ Don't + +- Access DOM in `setup()` or `created()` +- Use browser APIs on server +- Modify SSR HTML before hydration +- Create server/client state differences +- Use invalid HTML nesting + +### Testing for SSR Compatibility + +1. Serve with `-d` flag +2. Check browser console for hydration warnings +3. Verify no FOUC occurs +4. Test component interactivity after hydration +5. Check deployed preview (SSR warnings may differ) diff --git a/.opencode/skills/markbind-component-patterns/SKILL.md b/.opencode/skills/markbind-component-patterns/SKILL.md new file mode 100644 index 0000000000..4f97d3f187 --- /dev/null +++ b/.opencode/skills/markbind-component-patterns/SKILL.md @@ -0,0 +1,432 @@ +--- +name: markbind-component-patterns +description: Implementation patterns for creating MarkBind components and features including Vue components, node transformations, plugins, SSR compatibility, and TypeScript migration. Use when adding new MarkBind components, modifying existing components, implementing features, migrating JavaScript to TypeScript, or ensuring SSR compatibility. +--- + +# MarkBind Component Patterns + +## Component Implementation Approaches + +MarkBind provides three main ways to implement components. Choose based on complexity and requirements. + +### Approach 1: Node Transformation + +**When to Use**: +- Simple modifications to existing HTML +- Adding/modifying attributes +- No complex interactivity needed + +**How It Works**: +- Transform nodes directly during HTML processing +- Occurs in `processNode()`, `preProcessNode()`, or `postProcessNode()` +- Attributes automatically converted to HTML attributes + +**Example - Adding Line Numbers to Code Blocks**: +```javascript +// In NodeProcessor.ts +processNode(node) { + if (node.name === 'pre' && node.attribs['line-numbers']) { + cheerio(node).addClass('line-numbers'); + } +} +``` + +**Location**: Typically in `packages/core/src/html/NodeProcessor.ts` + +### Approach 2: Vue Component + +**When to Use**: +- Complex interactivity required +- State management needed +- Wrapping external libraries +- Dynamic content loading + +**How It Works**: +- Create `.vue` file in `packages/vue-components/src/` +- Register in `packages/vue-components/src/index.js` +- MarkBind attributes become Vue props (as strings) +- MarkBind slots become Vue named slots + +**Example - Panel Component**: +```vue + + + + +``` + +**Registration** (`packages/vue-components/src/index.js`): +```javascript +import Panel from './Panel.vue'; + +export default { + install(app) { + app.component('panel', Panel); + } +}; +``` + +### Approach 3: Plugin + +**When to Use**: +- Self-contained feature (e.g., diagram generation) +- Needs custom tag behavior +- Operates on full page content +- Adds external assets + +**How It Works**: +- Create plugin file in `packages/core/src/plugins/default/` +- Implement plugin interface methods +- Export plugin configuration + +**Example - Tree Component Plugin**: +```javascript +// packages/core/src/plugins/default/markbind-plugin-tree.ts +module.exports = { + processNode: (pluginContext, node) => { + if (node.name === 'tree') { + // Transform node to tree visualization + } + }, + + tagConfig: { + tree: { + isSpecial: true // Content treated as raw text + } + } +}; +``` + +**Location**: `packages/core/src/plugins/default/` + +For detailed plugin authoring, see MarkBind plugin documentation. + +## Vue Component Guidelines + +### Props from MarkBind Attributes + +All attributes are passed as **string props**: + +```html + + +``` + +```javascript +// Vue component receives +{ + header: "Title", // String + expanded: "", // Empty string (boolean attribute) + type: "info" // String +} +``` + +**Handling Boolean Attributes**: +```javascript +computed: { + isExpanded() { + return this.expanded !== undefined; // Check presence, not value + } +} +``` + +### Slots from MarkBind + +Named slots work identically to Vue: + +```html + + +
Custom Header
+ Default content +
+``` + +```vue + + +``` + +### Attribute vs Slot Priority + +When both attribute and slot provided, **slot should override attribute**. + +```javascript +computed: { + hasHeaderSlot() { + return !!this.$slots.header; + } +} +``` + +```vue + +``` + +**Important**: Log a warning when both are provided: +```javascript +mounted() { + if (this.header && this.$slots.header) { + console.warn('Both header attribute and slot provided. Using slot.'); + } +} +``` + +### SSR Compatibility + +All Vue components **must be SSR-compatible**. See [references/ssr-patterns.md](references/ssr-patterns.md) for detailed rules. + +**Key Requirements**: +- No DOM access in `setup()` or `created()` +- Use `mounted()` for browser-only code +- Consistent initial state between server and client +- Valid HTML structure (no `
` in `

`) + +### Component Testing + +Add snapshot tests in `packages/vue-components/src/__tests__/`: + +```javascript +import { mount } from '@vue/test-utils'; +import Panel from '../Panel.vue'; + +test('renders panel with header', () => { + const wrapper = mount(Panel, { + props: { header: 'Test' }, + slots: { default: 'Content' } + }); + expect(wrapper.html()).toMatchSnapshot(); +}); +``` + +Run `npm run updatetest` after adding tests. + +## TypeScript Migration + +MarkBind is migrating backend code from JavaScript to TypeScript using a "Rename + Adapt" two-commit strategy. + +For complete TypeScript migration guidance, see the dedicated **markbind-typescript-migration** skill, which covers: +- Migration planning and preparation +- Creating the "Rename" commit (preserving git history) +- Creating the "Adapt" commit (fixing TypeScript errors) +- Import/export syntax conversion +- Troubleshooting common issues + +This skill focuses on component implementation patterns. Use **markbind-typescript-migration** for the migration process itself. + +## Dependency Management + +For detailed dependency strategies, see [references/dependency-management.md](references/dependency-management.md). + +### Adding a New Dependency + +1. Add to appropriate `package.json` in `packages/*` +2. Run `npm run setup` in root directory +3. Document why the dependency is needed + +### Updating a Dependency + +1. Check changelog for breaking changes +2. Update in `package.json` +3. Run `npm run setup` +4. Update ALL packages using the dependency +5. Run full test suite + +### Choosing: Install vs Fork vs Patch + +**Install** when: +- Library works as-is +- No custom behavior needed + +**Fork** when: +- Need significant changes +- Want to contribute upstream +- Changes benefit broader community + +**Patch** when: +- Small, focused changes +- Changes specific to MarkBind +- Want quick iteration + +**MarkBind prefers patching** for core dependencies. + +## Common Component Patterns + +### Pattern 1: Expandable/Collapsible + +```vue + + + +``` + +### Pattern 2: External Content Loading + +```vue + +``` + +### Pattern 3: Wrapping External Libraries + +```vue + + + +``` + +## Testing Patterns + +### Unit Tests +Located in `packages/*/test/unit/` + +Test individual functions and classes: +```javascript +describe('MyFunction', () => { + test('handles edge case', () => { + expect(myFunction(input)).toBe(expected); + }); +}); +``` + +### Functional Tests +Located in `packages/cli/test/functional/` + +Add test site content demonstrating the feature, then run `npm run updatetest`. + +### Snapshot Tests +Located in `packages/vue-components/src/__tests__/` + +Test Vue component rendering: +```javascript +test('renders correctly', () => { + const wrapper = mount(Component, { props }); + expect(wrapper.html()).toMatchSnapshot(); +}); +``` + +## Code Style Guidelines + +### JavaScript/TypeScript +- Use ES6+ features (arrow functions, destructuring, async/await) +- Prefer `const` over `let`, never `var` +- Use async/await over callbacks +- Follow ESLint rules + +### Vue Components +- Use composition API for new components (Vue 3) +- Single-file components (.vue) +- PascalCase for component names +- Props with types and defaults + +### Import/Export +- ES6 syntax for new code: `import X from 'Y'` +- TypeScript equivalent for compatibility: `import X = require('Y')` +- See TypeScript migration guide for details + +## Additional Considerations + +### Bundle Size +- Check bundlephobia.com for package sizes +- Avoid large dependencies for small features +- Code-split when possible + +### Browser Compatibility +- Test in major browsers (Chrome, Firefox, Safari, Edge) +- Avoid bleeding-edge JavaScript features +- Polyfills when necessary + +### Performance +- Lazy-load heavy components +- Minimize DOM operations +- Use virtual scrolling for long lists +- Optimize images and assets + +### Accessibility +- Use semantic HTML +- Add ARIA labels where needed +- Keyboard navigation support +- Color contrast compliance diff --git a/.opencode/skills/markbind-component-patterns/references/dependency-management.md b/.opencode/skills/markbind-component-patterns/references/dependency-management.md new file mode 100644 index 0000000000..e1a7d20b09 --- /dev/null +++ b/.opencode/skills/markbind-component-patterns/references/dependency-management.md @@ -0,0 +1,528 @@ +# Dependency Management in MarkBind + +## Three Approaches to Dependencies + +MarkBind uses three strategies for incorporating external code: **installing**, **forking**, and **patching**. + +## Approach 1: Installing (npm install) + +### When to Use + +- Package works as-is without modifications +- Package is actively maintained +- Standard functionality is sufficient +- Want easy upgrades + +### Pros + +- Easy to upgrade (just update version) +- Traceable in `package.json` +- Official releases with changelogs +- Community support + +### Cons + +- Cannot customize behavior +- Vulnerable if package becomes unmaintained +- Tied to package release schedule +- May include unnecessary features + +### Process + +1. **Choose package** based on: + - Active maintenance (recent commits, releases) + - Good documentation + - Reasonable bundle size + - Compatible license + - Few/manageable dependencies + +2. **Install in appropriate package**: + ```bash + cd packages/core # or cli, vue-components, core-web + npm install package-name + ``` + +3. **Delete package-lock.json**: + ```bash + rm package-lock.json + ``` + +4. **Update root lock file**: + ```bash + cd ../../ # back to root + npm run setup + ``` + +5. **Commit changes**: + ```bash + git add packages/*/package.json package-lock.json + git commit -m "Add package-name dependency" + ``` + +### Examples in MarkBind + +- `markdown-it` - Markdown parser +- `commander` - CLI framework +- `vue` - Frontend framework +- `cheerio` - DOM manipulation + +## Approach 2: Forking + +### When to Use + +- Need significant modifications +- Want to contribute changes upstream +- Changes benefit broader community +- Package is actively maintained upstream +- Changes are substantial enough to justify separate repo + +### Pros + +- Can modify as needed +- Leverage upstream testing +- Can contribute back via PRs +- Easy to pull upstream updates +- Benefit others with same use case + +### Cons + +- May become out-of-sync with upstream +- External maintenance burden +- Deployment complexity (publishing to npm) +- Need to maintain fork long-term + +### Process + +1. **Fork repository** on GitHub +2. **Clone fork locally** +3. **Make modifications** +4. **Publish to npm** (if needed): + ```bash + npm publish + ``` +5. **Update MarkBind** to use fork: + ```json + { + "dependencies": { + "package-name": "^1.0.0-markbind.1" + } + } + ``` + +### When to Contribute Upstream + +If changes are: +- Generally useful (not MarkBind-specific) +- Well-tested and documented +- Compatible with upstream goals + +Submit PR to original repository. + +### MarkBind Example + +Previously forked `vue-strap` from [yuche/vue-strap](https://github.com/yuche/vue-strap) into [MarkBind/vue-strap](https://github.com/MarkBind/vue-strap), later merged into main monorepo as `packages/vue-components`. + +## Approach 3: Patching + +### When to Use + +- Small, focused modifications needed +- Changes are MarkBind-specific +- Want changes to propagate to dependents +- Quick iteration is important +- Don't want to maintain external package + +### Pros + +- Quick - no external repo needed +- Changes propagate (e.g., cheerio uses htmlparser2 patches) +- Easy to maintain in monorepo +- Keep everything in one place +- Fast iteration + +### Cons + +- Harder to upgrade base library +- Changes not contributed upstream +- Must document rationale clearly +- Patches can break with version updates + +### Process + +1. **Create patch file** in `packages/core/src/patches/`: + ``` + packages/core/src/patches/ + ├── htmlparser2.js + ├── nunjucks/ + │ ├── file1.js + │ └── file2.js + ``` + +2. **Document rationale** at top of patch file: + ```javascript + /** + * Patched htmlparser2 for MarkBind-specific behavior + * + * Changes: + * 1. Modified tag parsing for custom components + * 2. Adjusted whitespace handling for Markdown compatibility + * 3. Added support for self-closing custom tags + * + * See: https://github.com/MarkBind/markbind/issues/123 + */ + ``` + +3. **Import patched version** instead of original: + ```javascript + // Instead of: const htmlparser2 = require('htmlparser2'); + const htmlparser2 = require('./patches/htmlparser2'); + ``` + +4. **Test thoroughly**: + - Unit tests for patched functionality + - Functional tests for integration + - Verify dependent packages work correctly + +### MarkBind Patched Libraries + +**packages/core/src/patches/htmlparser2.js**: +- Modify parsing for MarkBind components +- Handle whitespace per Markdown spec +- Support self-closing custom tags + +**packages/core/src/patches/nunjucks/**: +- Compatible with multi-stage processing +- MarkBind-specific filters +- Better error messages + +**packages/cli/src/lib/live-server/**: +- Custom file watching +- Fine-tuned reload behavior +- MarkBind build integration + +## Choosing the Right Approach + +### Decision Matrix + +| Factor | Install | Fork | Patch | +|---|---|---|---| +| **Modification size** | None | Large | Small | +| **Upstream contribution** | N/A | Yes | No | +| **Maintenance burden** | Low | High | Medium | +| **Upgrade difficulty** | Easy | Medium | Hard | +| **MarkBind-specific** | No | No | Yes | +| **External visibility** | N/A | High | None | + +### Decision Tree + +``` +Need modifications? +├─ No → Install +└─ Yes + ├─ Large/general changes? + │ └─ Yes → Fork (consider upstream contribution) + └─ No → Small/MarkBind-specific? + └─ Yes → Patch +``` + +### Questions to Ask + +1. **Does the package work as-is?** + - Yes → Install + - No → Continue + +2. **Are changes large and generally useful?** + - Yes → Fork + - No → Continue + +3. **Are changes small and MarkBind-specific?** + - Yes → Patch + - No → Reconsider if changes are actually needed + +4. **Will this benefit the broader community?** + - Yes → Fork and contribute upstream + - No → Patch + +5. **Is the package actively maintained?** + - Yes → Fork (easier to stay in sync) + - No → Patch or consider alternatives + +## Updating Dependencies + +### Updating Installed Packages + +```bash +# Check for outdated packages +npm outdated + +# Update package +cd packages/core +npm install package-name@latest + +# Update all packages +cd ../../ +npm run setup +``` + +**Important**: Check changelog for breaking changes! + +### Updating Forked Packages + +```bash +# In fork repository +git remote add upstream https://github.com/original/repo.git +git fetch upstream +git merge upstream/master + +# Fix conflicts +# Run tests +# Publish new version +npm version patch +npm publish +``` + +Then update MarkBind dependency version. + +### Updating Patched Packages + +**High risk** - patches may break! + +```bash +# Update base package +cd packages/core +npm install package-name@latest + +# Review changelog +# Update patch file to match new version +# Test extensively +``` + +Process: +1. Read upstream changelog +2. Check if patch changes are still needed +3. Update patch code for new version +4. Run all tests +5. Fix any breaking changes + +## Adding New Dependencies + +### Evaluation Checklist + +Before adding any dependency: + +- [ ] **Active maintenance?** Recent commits/releases +- [ ] **Bundle size acceptable?** Check bundlephobia.com +- [ ] **License compatible?** MIT, Apache, BSD preferred +- [ ] **Few dependencies?** Avoid dependency trees +- [ ] **Good documentation?** Essential for long-term maintenance +- [ ] **TypeScript support?** Types available or bundled +- [ ] **Browser compatible?** If used in frontend +- [ ] **SSR compatible?** If used with Vue components +- [ ] **Alternatives considered?** Is this the best option? + +### Bundle Size Considerations + +Check size impact at [bundlephobia.com](https://bundlephobia.com): + +``` +Small: < 10KB minified +Medium: 10-50KB +Large: 50-100KB +Too Large: > 100KB (reconsider or lazy-load) +``` + +### Adding to Monorepo + +1. **Identify target package**: + - Core processing → `packages/core` + - CLI functionality → `packages/cli` + - Vue components → `packages/vue-components` + - Client-side code → `packages/core-web` + - Dev tooling → root `package.json` + +2. **Add to package.json**: + ```json + { + "dependencies": { + "new-package": "^1.0.0" + } + } + ``` + +3. **Update lockfile**: + ```bash + npm run setup + ``` + +4. **Import and use**: + ```javascript + const newPackage = require('new-package'); + ``` + +5. **Add tests** for functionality using the new package + +6. **Document usage** if non-obvious + +## Removing Dependencies + +1. **Remove from package.json** +2. **Run setup**: + ```bash + npm run setup + ``` +3. **Verify nothing breaks**: + ```bash + npm run test + ``` +4. **Search for imports** to ensure complete removal: + ```bash + grep -r "package-name" packages/ + ``` + +## Dependency Version Strategy + +### Exact Versions for Internal Packages + +```json +{ + "dependencies": { + "@markbind/core": "5.0.0", // Exact, not ^5.0.0 + } +} +``` + +**Why**: Ensures consistency across all packages. + +**Managed by**: `lerna version --exact` + +### Semver Ranges for External Packages + +```json +{ + "dependencies": { + "markdown-it": "^13.0.0", // Caret range + "cheerio": "~1.0.0" // Tilde range + } +} +``` + +**Caret (^)**: Allow minor and patch updates +**Tilde (~)**: Allow only patch updates + +### Locking Versions + +Sometimes lock to exact version: + +```json +{ + "dependencies": { + "critical-package": "1.2.3" // Locked + } +} +``` + +**When to lock**: +- Known issues with newer versions +- Waiting for dependency to stabilize +- Temporary until proper fix + +**Document why** in comments or commit message. + +## Special Cases + +### Bootstrap and Bootswatch + +Must stay synchronized: + +```json +{ + "dependencies": { + "bootstrap": "5.1.3", + "bootswatch": "5.1.3" + } +} +``` + +**Why**: Bootswatch themes built for specific Bootstrap version. + +### PlantUML JAR + +Not an npm package, stored directly: +- Location: `packages/core/src/plugins/default/plantuml.jar` +- Update: Download new JAR, replace file +- Test: Generate diagrams in test sites + +### Vue Ecosystem + +Major version must match: + +```json +{ + "dependencies": { + "vue": "^3.3.11", + "@vue/compiler-sfc": "^3.3.11", + "@vue/server-renderer": "^3.3.11" + } +} +``` + +All Vue packages must use same major version (3.x). + +## Troubleshooting + +### "Cannot find module" + +```bash +# Clean and reinstall +npm run clean +npm run setup +``` + +### Version conflicts + +```bash +# Check for duplicates +npm ls package-name + +# Deduplicate +npm dedupe +``` + +### Broken after update + +1. Check changelog for breaking changes +2. Search issues for version number +3. Rollback if necessary: + ```bash + npm install package-name@previous-version + ``` + +### Peer dependency warnings + +Usually safe to ignore, but verify: +```bash +npm ls package-name +``` + +If multiple versions installed, may need to update dependents. + +## Best Practices + +### ✅ Do + +- Check maintenance status before adding +- Read changelogs before updating +- Test thoroughly after changes +- Document patch rationale +- Update all packages using a dependency +- Use exact versions for internal packages +- Check bundle size impact + +### ❌ Don't + +- Add dependencies without evaluation +- Update without reading changelog +- Patch without documenting why +- Mix dependency versions +- Ignore peer dependency warnings without investigation +- Commit package-lock.json changes alone (always with package.json) diff --git a/.opencode/skills/markbind-component-patterns/references/ssr-patterns.md b/.opencode/skills/markbind-component-patterns/references/ssr-patterns.md new file mode 100644 index 0000000000..6241dd38dd --- /dev/null +++ b/.opencode/skills/markbind-component-patterns/references/ssr-patterns.md @@ -0,0 +1,495 @@ +# SSR Compatibility Patterns + +## Server-Side Rendering Overview + +MarkBind uses Vue's SSR to pre-render components into static HTML during build. The browser then hydrates this HTML to make it interactive. + +## Critical SSR Rules + +### Rule 1: No DOM Access Before Mount + +**Problem**: DOM doesn't exist on server. + +```javascript +// ❌ BAD: Crashes on server +export default { + created() { + document.querySelector('.element'); // Error: document not defined + } +} + +// ✅ GOOD: Only access DOM in mounted() +export default { + mounted() { + // Safe - mounted() only runs in browser + document.querySelector('.element'); + } +} +``` + +### Rule 2: No Browser APIs on Server + +**Problem**: Browser APIs (`window`, `localStorage`, etc.) don't exist on server. + +```javascript +// ❌ BAD: window undefined on server +export default { + data() { + return { + width: window.innerWidth // Error on server + }; + } +} + +// ✅ GOOD: Initialize in mounted() +export default { + data() { + return { + width: 0 // Safe default + }; + }, + mounted() { + this.width = window.innerWidth; + } +} +``` + +### Rule 3: Consistent Initial State + +**Problem**: Server and client must start with identical state. + +```javascript +// ❌ BAD: Different on server vs client +export default { + data() { + return { + timestamp: Date.now(), // Different times + random: Math.random() // Different values + }; + } +} + +// ✅ GOOD: Same initial state, update on client +export default { + data() { + return { + timestamp: null, + random: null + }; + }, + mounted() { + this.timestamp = Date.now(); + this.random = Math.random(); + } +} +``` + +### Rule 4: Valid HTML Structure + +**Problem**: Browsers auto-correct invalid HTML, causing hydration mismatch. + +```html + +

+

Block content
+

+ + +
+

Paragraph text

+
Block content
+
+``` + +**Common Invalid Nesting**: +- `
` inside `

` +- `` inside `` +- Table elements outside `` + +**Reference**: [MDN HTML Element Reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) + +## Vue Lifecycle Hooks + +### Server + Client Hooks +These run on **both** server and client: +- `setup()` +- `created()` +- `beforeCreate()` + +**Rules**: +- No DOM access +- No browser APIs +- State must be identical on server and client + +### Client-Only Hooks +These run **only** in browser: +- `beforeMount()` +- `mounted()` +- `beforeUpdate()` +- `updated()` +- `beforeUnmount()` +- `unmounted()` + +**Safe for**: +- DOM operations +- Browser APIs +- Event listeners +- Timers + +## Conditional Rendering + +### v-if Must Match on Server and Client + +```javascript +// ❌ BAD: Always false on server +
+ Client-only content +
+ +// ✅ GOOD: Use data property set in mounted() + + + +``` + +### v-show for Client-Only Visibility + +`v-show` is better for client-only content (element exists in SSR HTML but hidden): + +```vue + + + +``` + +## Component Patterns + +### Pattern 1: Client-Only Component + +```vue + + + +``` + +### Pattern 2: Async Data Loading + +```vue + + + +``` + +### Pattern 3: Progressive Enhancement + +Provide fallback content that works without JavaScript: + +```vue + + + +``` + +## Common Hydration Issues + +### Issue 1: Text Whitespace Differences + +**Problem**: Server renders with different whitespace than client. + +```vue + + + + + +``` + +### Issue 2: Unknown HTML Elements + +**Problem**: Vue tries to resolve unknown tags as components. + +```html + + + + + +``` + +Or register as custom element in plugin config. + +### Issue 3: Async Component Loading + +**Problem**: Different timing on server vs client. + +```javascript +// ❌ BAD: May load at different times +export default { + components: { + AsyncComponent: () => import('./AsyncComponent.vue') + } +}; + +// ✅ GOOD: Synchronous on server, async on client +export default { + components: { + AsyncComponent: require('./AsyncComponent.vue').default + } +}; +``` + +### Issue 4: External Content Modification + +**Problem**: Scripts modify SSR HTML before hydration. + +```javascript +// ❌ BAD: Modifies SSR HTML +document.addEventListener('DOMContentLoaded', () => { + document.querySelector('.panel').classList.add('modified'); +}); + +// ✅ GOOD: Let Vue handle DOM +export default { + mounted() { + // Vue has already hydrated, safe to modify + this.$el.classList.add('modified'); + } +}; +``` + +## Debugging Hydration Issues + +### Enable SSR Warnings + +Use developer mode: +```bash +markbind serve -d +``` + +Check browser console for hydration warnings. + +### Common Warning Messages + +**"Hydration node mismatch"**: +- Cause: Server HTML differs from client virtual DOM +- Fix: Check for invalid HTML or state differences + +**"Hydration children mismatch"**: +- Cause: Different number of child elements +- Fix: Usually from `v-if` that evaluates differently + +**"Hydration attribute mismatch"**: +- Cause: Attribute values differ +- Fix: Check for dynamic attributes with server/client differences + +**"Hydration text mismatch"**: +- Cause: Text content differs +- Fix: Check for client-only state in templates + +### Isolate the Issue + +1. **Simplify the component**: Remove parts until hydration works +2. **Check HTML validity**: Use W3C validator +3. **Compare rendered HTML**: Check SSR output vs client render +4. **Add logging**: Log state in `created()` and `mounted()` + +### Validation Utility + +MarkBind validates HTML in `packages/core/src/utils/htmlValidationUtils.ts`. + +Add new validation rules when discovering hydration causes: + +```typescript +// Example validation rule +function validateNoBlockInParagraph(html: string): ValidationError[] { + const errors: ValidationError[] = []; + // Check for
,

, etc. inside

+ return errors; +} +``` + +## SSR Testing Checklist + +### Before Committing + +- [ ] Serve with `-d` flag and check console +- [ ] No hydration warnings +- [ ] Component is interactive after page load +- [ ] HTML structure is valid +- [ ] No FOUC (flash of unstyled content) + +### Test Scenarios + +1. **Initial render**: Component appears correctly +2. **Interaction**: Buttons/links work as expected +3. **State changes**: Reactivity works +4. **External content**: Loads properly +5. **Browser refresh**: No errors on hard reload + +### Cross-Browser Testing + +Test in: +- Chrome (latest) +- Firefox (latest) +- Safari (latest) +- Edge (latest) + +Some hydration issues are browser-specific. + +## Best Practices Summary + +### ✅ Do + +- Use `mounted()` for DOM access +- Initialize state consistently +- Use valid HTML structure +- Test with SSR enabled (`-d` mode) +- Use `v-show` for client-only visibility +- Provide progressive enhancement +- Document SSR considerations + +### ❌ Don't + +- Access DOM in `setup()` or `created()` +- Use browser APIs before `mounted()` +- Create server/client state differences +- Use invalid HTML nesting +- Modify SSR HTML before hydration +- Rely on `v-if` for client-only content +- Forget to test hydration + +## Advanced Topics + +### Custom SSR Context + +For advanced cases, access SSR context: + +```javascript +export default { + serverPrefetch() { + // Only runs on server + return this.fetchData(); + } +}; +``` + +### Handling Third-Party Libraries + +Many libraries aren't SSR-compatible. Wrap in client-only component: + +```vue + +``` + +### SSR Performance + +- Minimize server-side work +- Avoid heavy computations in SSR hooks +- Use caching when possible +- Consider using CSR for complex components + +## Reference + +See also: +- [Vue SSR Guide](https://vuejs.org/guide/scaling-up/ssr.html) +- [MDN HTML Element Reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element) +- MarkBind's `htmlValidationUtils.ts` for validation rules diff --git a/.opencode/skills/markbind-dev-workflow/SKILL.md b/.opencode/skills/markbind-dev-workflow/SKILL.md new file mode 100644 index 0000000000..d350ef062d --- /dev/null +++ b/.opencode/skills/markbind-dev-workflow/SKILL.md @@ -0,0 +1,214 @@ +--- +name: markbind-dev-workflow +description: Development workflows for the MarkBind project including environment setup, building backend/frontend, testing procedures, and git workflows. Use when setting up MarkBind for development, making code changes to MarkBind's backend (TypeScript/JavaScript) or frontend (Vue components), running tests, updating test sites, or preparing pull requests for MarkBind itself. +--- + +# MarkBind Development Workflow + +## Environment Setup + +### Prerequisites +- Node.js (v18 or higher) with npm +- Java 8+ (for PlantUML) +- Graphviz (optional on Windows, required for PlantUML diagrams) +- Python 3+ (for git hooks) + +### Initial Setup + +1. **Fork and clone** the repository +2. **Bind CLI to console** by navigating to `packages/cli` and running `npm link` +3. **Install dependencies** by running `npm run setup` in the root folder +4. **Install git hooks (optional but recommended)** by running: + ```bash + python3 ./pre-commit/pre-commit-2.20.0.pyz install + ``` + +### Project Structure +MarkBind uses a **monorepo** with 4 packages managed by lerna: +- `packages/cli` - Command-line interface +- `packages/core` - Core processing library (TypeScript + JavaScript) +- `packages/core-web` - Client-side bundles and Vue setup +- `packages/vue-components` - UI components library + +## Backend Development + +Backend code in `packages/core` includes TypeScript files that must be compiled to JavaScript. + +### Compilation Options + +**Option 1: Watch mode (Recommended for active development)** +```bash +npm run dev +``` +Starts file watcher that rebuilds on changes. + +**Option 2: Manual compilation** +```bash +npm run build:backend +``` +Compiles once. Re-run after each change. + +**Option 3: IDE automatic compilation** +Configure your IDE to compile TypeScript on save (see WebStorm/VS Code guides in workflow docs). + +### Important Notes +- Always compile before testing backend changes +- `packages/cli` depends on compiled output from `packages/core` +- Git hooks automatically build backend on commit/push + +## Frontend Development + +Frontend changes in `packages/core-web` or `packages/vue-components` require special handling since bundles are only updated during releases. + +### Development Options + +**Option 1: Developer mode (Recommended)** +```bash +markbind serve -d +``` +Adds webpack middlewares for live compilation and hot reloading of frontend sources. + +**Option 2: Manual bundle build** +```bash +npm run build:web +``` +Builds `markbind.min.js` and `markbind.min.css` bundles, then use normal `markbind` commands. + +### Bundle Files +- `markbind.min.js` - Main client-side bundle +- `markbind.min.css` - Minified styles +- `vuecommonappfactory.min.js` - Server-side rendering bundle +- `vuecommonappfactory.min.css` - SSR styles + +## Testing Workflow + +### Running Tests + +**All tests** (linting + unit tests + functional tests): +```bash +npm run test +``` + +**Package-specific tests**: +```bash +cd packages/cli && npm run test +cd packages/core && npm run test +``` + +### Test Components + +1. **Linting**: ESLint for code, StyleLint for CSS +2. **Unit tests**: Jest-based tests in `test/unit` directories +3. **Functional tests**: Builds test sites and compares output with expected files + +### Updating Test Files + +After making changes that affect test output: + +```bash +npm run updatetest +``` + +This updates: +- Expected HTML files in test sites' `expected/` folders +- Snapshot files in `__snapshots__/` folders + +**Critical**: Always verify generated output is correct before committing. Some binary files (images, fonts) may show as changed but should be discarded if not directly modified. + +### Expected Errors in Tests + +Some errors logged during tests are intentional. Check test logs for messages like: +``` +info: The following 2 errors are expected to be thrown during the test run: +info: 1: No such segment '#doesNotExist' in file +info: 2: Cyclic reference detected. +``` + +### Adding Test Site Content + +For detailed procedures on adding new test pages and configuring test sites, see [references/test-patterns.md](references/test-patterns.md). + +## Git Workflow + +### Keeping Fork Updated + +1. Sync fork on GitHub using "Sync fork" button +2. Update local master: `git checkout master && git pull` +3. Rebase or merge into feature branch: + - `git rebase master` (preferred for clean history) + - `git merge master` (alternative) + +### Commit Strategy + +- **Default**: Squash merge (maintainers will squash on merge) +- No need for elaborate commit messages or strict organization +- Exception: TypeScript migrations require separate "Rename" and "Adapt" commits + +### Git Hooks + +Three hooks are available (installed via pre-commit tool): +- `post-checkout`: Runs clean + build backend when switching branches +- `pre-commit`: Runs clean + build backend + lintfix +- `pre-push`: Runs clean + build backend + all tests + +Skip hooks with `--no-verify` flag if needed. + +For more details, see [references/git-hooks.md](references/git-hooks.md). + +## Debugging + +### WebStorm Configuration +1. Using docs as dev environment with `-o` (lazy reload) and `-d` (developer mode) +2. Debugging all tests +3. Debugging specific package tests + +### VS Code Configuration +Add to `.vscode/launch.json`: +```json +{ + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Dev Docs", + "cwd": "${workspaceFolder}/docs", + "program": "${workspaceFolder}/packages/cli/index.js", + "args": ["serve", "-o", "-d"] + } + ] +} +``` + +## Linting + +### Manual Linting +```bash +# Lint all files +npm run lint + +# Auto-fix issues +npm run lintfix + +# Lint specific file +eslint path/to/file.js +``` + +### IDE Integration +Install ESLint extensions in WebStorm or VS Code for real-time feedback and auto-fix on save. + +## Common Issues + +### Frontend components not working +Run `markbind serve -d` or `npm run build:web` to view latest frontend changes. + +### TypeScript compilation errors +Ensure automatic compilation is running or manually run `npm run build:backend`. + +### Test failures after pulling updates +Run `npm run setup` to ensure dependencies are up to date. + +### Merge conflicts in expected test files +1. Sync fork with upstream +2. Merge master into PR branch +3. Accept all changes to expected test files (they'll be regenerated) +4. Run `npm run updatetest` diff --git a/.opencode/skills/markbind-dev-workflow/references/git-hooks.md b/.opencode/skills/markbind-dev-workflow/references/git-hooks.md new file mode 100644 index 0000000000..f304060a2d --- /dev/null +++ b/.opencode/skills/markbind-dev-workflow/references/git-hooks.md @@ -0,0 +1,126 @@ +# Git Hooks in MarkBind + +MarkBind uses the [pre-commit](https://pre-commit.com/) framework to manage git hooks. + +## Available Hooks + +### post-checkout +**When**: After checking out a branch +**Actions**: +- Runs `npm run clean` +- Builds backend with `npm run build:backend` + +**Purpose**: Ensures compiled backend matches the checked-out code version. + +**Note**: Can slow down branch switching. Skip occasionally with `git checkout --no-verify` if needed. + +### pre-commit +**When**: Before creating a commit +**Actions**: +- Runs `npm run clean` +- Builds backend with `npm run build:backend` +- Runs `npm run lintfix` on staged files + +**Purpose**: Ensures commits include compiled backend and pass linting checks. + +**Skip with**: `git commit --no-verify` + +### pre-push +**When**: Before pushing to remote +**Actions**: +- Runs `npm run clean` +- Builds backend with `npm run build:backend` +- Runs full test suite with `npm run test` + +**Purpose**: Catches issues before they reach the remote repository. + +**Skip with**: `git push --no-verify` + +**Note**: This is the slowest hook. Consider skipping if you've already run tests manually. + +## Hook Configuration + +Hooks are configured in `.pre-commit-config.yaml` at the project root. + +Scripts are located in `./pre-commit/pre-commit-scripts/`. + +## Installation + +Install hooks during initial setup: +```bash +python3 ./pre-commit/pre-commit-2.20.0.pyz install +``` + +## Uninstallation + +Remove hooks if they become problematic: +```bash +python3 ./pre-commit/pre-commit-2.20.0.pyz uninstall +``` + +## When to Skip Hooks + +### Skip pre-commit +- Emergency hotfix commits +- WIP commits for backup purposes +- When lintfix has already been run manually + +### Skip pre-push +- Tests already passed locally +- Pushing to personal fork for backup +- Quick iteration during development + +### Skip post-checkout +- Rapidly switching between branches +- Checking out for quick reference +- When backend compilation isn't needed + +## Best Practices + +### Generally Keep Hooks Enabled +- Catches errors early +- Ensures code quality +- Prevents broken commits from reaching CI + +### Use --no-verify Judiciously +- Don't make it a habit +- Always run tests before creating PRs +- Fix issues rather than bypassing checks + +### Manual Equivalents + +Instead of skipping hooks, run commands manually: + +```bash +# Instead of skipping pre-commit +npm run lintfix +git commit + +# Instead of skipping pre-push +npm run test +git push --no-verify # Safe now since tests passed +``` + +## Troubleshooting + +### Hooks Not Running +- Verify hooks are installed: `ls -la .git/hooks/` +- Reinstall: `python3 ./pre-commit/pre-commit-2.20.0.pyz install` + +### Hooks Failing +- Check Python version: `python3 -V` (need 3.x) +- Check error messages for specific issues +- Try manual commands to isolate problems + +### Hooks Too Slow +- Skip post-checkout if branch switching is frequent +- Run tests manually before pushing, then skip pre-push hook +- Consider disabling hooks during rapid prototyping + +### Hook Conflicts with TypeScript Migration +For TypeScript migration "Rename" commits that don't compile: +```bash +git commit --no-verify -m "Rename core/src/html to TypeScript" +``` + +The "Adapt" commit should compile and can use normal commit. diff --git a/.opencode/skills/markbind-dev-workflow/references/test-patterns.md b/.opencode/skills/markbind-dev-workflow/references/test-patterns.md new file mode 100644 index 0000000000..85fbd3812d --- /dev/null +++ b/.opencode/skills/markbind-dev-workflow/references/test-patterns.md @@ -0,0 +1,211 @@ +# Test Patterns and Procedures + +## Functional Test Architecture + +Functional tests build test sites listed in `packages/cli/test/functional/testSites.js` and compare generated HTML with expected output in each test site's `expected/` directory. + +## Adding a Test Page to Existing Test Site + +Example: Adding `newTestPage.md` to `test_site` + +1. **Create the test page** in `packages/cli/test/functional/test_site/newTestPage.md` demonstrating the new feature + +2. **Update site.json** to include the new page: + ```json + "pages": [ + { + "src": "index.md", + "title": "Hello World" + }, + { + "src": "newTestPage.md", + "title": "New Feature Demo" + } + ] + ``` + +3. **Run updatetest** to generate expected output: + ```bash + npm run updatetest + ``` + +4. **Verify the output** by checking: + - Generated HTML in `test_site/expected/newTestPage.html` + - Only intended changes appear in git diff + - No unintended binary file changes (images, fonts) + +## Creating a New Test Site + +1. **Create test site directory** under `packages/cli/test/functional/` + +2. **Add minimum required files**: + - `index.md` - Main content + - `site.json` - Site configuration + +3. **Register test site** in `packages/cli/test/functional/testSites.js`: + ```javascript + const testSites = [ + 'test_site', + 'your_new_test_site', + // ... other test sites + ]; + ``` + +4. **Run updatetest** to generate expected output + +## Snapshot Tests for Vue Components + +Located in `packages/vue-components/src/__tests__/` + +### Adding Snapshot Tests + +1. **Create/modify test file** using Vue Test Utils: + ```javascript + import { mount } from '@vue/test-utils'; + import MyComponent from '../MyComponent.vue'; + + test('renders correctly', () => { + const wrapper = mount(MyComponent, { + props: { /* ... */ } + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + ``` + +2. **Run updatetest** to generate/update snapshots: + ```bash + npm run updatetest + ``` + +3. **Review snapshot changes** in `__snapshots__/` folder + +## Updating Test Files After Changes + +### When to Run updatetest + +Run after: +- Modifying component behavior +- Changing HTML output structure +- Updating frontend bundles +- Making any change that affects generated HTML + +### What updatetest Does + +1. Regenerates all expected HTML files in test sites +2. Updates snapshot files for Vue components +3. Rebuilds frontend bundles into test sites + +### Version-Only Changes During Release + +During releases, only version numbers should change in expected files: + +```diff +- ++ +``` + +If other lines change unexpectedly, functional tests weren't properly updated in earlier PRs. + +## PlantUML Test Images + +PlantUML generates images that are gitignored to avoid unnecessary file changes. + +### Maintained Ignore Lists + +- `packages/cli/test/functional/testSites.js` - Lists ignored PlantUML images +- `.gitignore` - Patterns for ignored generated content + +### Adding PlantUML Content + +Update both files if adding new PlantUML diagrams to test sites. + +## Binary Files in Tests + +### Common Binary Files +- Images: `.png`, `.jpg`, `.gif` +- Fonts: `.woff`, `.woff2`, `.ttf` +- Icons: `.ico` + +### Handling Binary Changes + +**If you didn't directly modify these files**, discard the changes: +```bash +git checkout -- path/to/binary/file +``` + +Binary files may appear changed due to: +- Different generation timestamps +- Non-deterministic compression +- Platform-specific rendering + +Only commit binary file changes if you intentionally modified them. + +## Expected Test Errors + +Some test cases intentionally trigger errors. These are documented in test logs: + +```javascript +// In test file +console.log('info: The following 2 errors are expected:'); +console.log('info: 1: No such segment \'#doesNotExist\' in file'); +console.log('info: 2: Cyclic reference detected.'); +``` + +If adding tests with expected errors, update the corresponding info messages. + +## Test Site Best Practices + +### Keep Test Sites Focused +- Each test site should demonstrate specific features +- Don't overload a single test site with unrelated features +- Create new test sites for distinctly different feature sets + +### Use Descriptive Filenames +- `pluginTest.md` - Tests plugin functionality +- `layoutsTest.md` - Tests layout features +- Clear names help locate tests for specific features + +### Document Test Purpose +Add comments in test files explaining what they test: +```markdown + + + +``` + +### Minimize Test Content +- Test the minimum needed to verify functionality +- Avoid large blocks of unnecessary content +- Focus on demonstrating the specific feature + +## Debugging Test Failures + +### Compare Expected vs Actual + +When tests fail: +1. Check diff between `expected/` and actual output +2. Identify which component/feature caused the difference +3. Determine if change is intended or a bug + +### Isolate the Issue + +1. Run single test site: + ```bash + cd packages/cli + npm run test -- --testPathPattern=functional/test_site + ``` + +2. Serve the test site locally: + ```bash + cd packages/cli/test/functional/test_site + markbind serve + ``` + +3. Inspect generated output in browser DevTools + +### Common Causes + +- Forgot to run `npm run build:backend` after TypeScript changes +- Forgot to run `npm run build:web` or `markbind serve -d` for frontend changes +- Stale node_modules (run `npm run setup`) +- Platform-specific line endings (configure git autocrlf) diff --git a/.opencode/skills/markbind-maintainer-tasks/SKILL.md b/.opencode/skills/markbind-maintainer-tasks/SKILL.md new file mode 100644 index 0000000000..7e8ad4f821 --- /dev/null +++ b/.opencode/skills/markbind-maintainer-tasks/SKILL.md @@ -0,0 +1,398 @@ +--- +name: markbind-maintainer-tasks +description: Maintainer-specific tasks for the MarkBind project including PR review, merging strategies, release procedures, and repository management. Use when reviewing pull requests, merging PRs, preparing releases, managing contributors, or performing other maintainer-only operations on the MarkBind repository. +--- + +# MarkBind Maintainer Tasks + +## PR Review and Approval + +### Approval Requirements + +**Simple PRs** (typo fixes, minor documentation): +- 1 approval sufficient for merging + +**Non-trivial PRs**: +- 2 approvals required +- Once 2nd approval from senior developer (or >= 3 approvals from anyone), can merge immediately +- If only 1 approval, wait 1 day before merging + +**Rationale**: Give other developers chance to review. + +### Review Checklist + +- [ ] Code follows style guides +- [ ] Tests added/updated appropriately +- [ ] Test sites updated if needed +- [ ] Documentation updated if needed +- [ ] No security concerns +- [ ] Backwards compatibility considered +- [ ] Bundle size impact acceptable (if frontend change) +- [ ] SSR compatibility maintained (if Vue component) +- [ ] TypeScript migration follows two-commit strategy (if applicable) + +### Code Coverage + +Check Codecov report in CI pipeline: +- Significant degradation requires attention +- Understand cause of coverage change +- May cause false-positive CI failure on master if coverage drops + +## Merging PRs + +### Before Merging + +**Critical**: Re-sync PR branch with master to trigger CI: +1. Ensure PR branch is up-to-date with master +2. Wait for CI to pass +3. Even if no conflicts, tests may fail when integrated with latest master + +### Merge Strategies + +**Default: Squash Merge** +- Use for most PRs +- Clean linear history +- Consolidates all commits into one + +**Merge Commit** +- Use when commits are well-organized +- Single task tackled with dependent changes +- Example: Multi-step feature with logical commit progression + +**Rebase Merge** +- Use when commits are independent tasks +- Rare - PRs should focus on related changes +- Example: [PR #1238](https://github.com/MarkBind/markbind/pull/1238) + +**TypeScript Migrations: Rebase Merge** +- Required to preserve "Rename" + "Adapt" commits +- Critical for maintaining file history + +### Merge Commit Format + +``` +PR_TITLE (#PR_NUMBER) + +Optional body with: +- Implementation details +- Breaking changes +- Migration instructions +``` + +Example: +``` +Add built-in support for light themes from bootswatch (#745) + +Adds support for all Bootswatch light themes. Users can now specify +theme in site.json without manually including CSS files. +``` + +### Merge Commit Guidelines + +**Title**: +- Follow [Git conventions](https://se-education.org/guides/conventions/git.html) +- Clear, descriptive, imperative mood +- Include PR number + +**Body** (for non-trivial PRs): +- Explain "why" not just "what" +- Breaking changes with migration instructions +- Implementation decisions +- Related issues + +### Critical: Check for Concurrent Merges + +Before confirming merge: +1. Refresh GitHub page +2. Verify no other PRs merged since you started drafting +3. If another PR was merged, GitHub may merge without squashing (glitch) + +Reference: [MarkBind#1160](https://github.com/MarkBind/markbind/pull/1160) + +## Post-Merge Tasks + +### 1. Add Version Label + +Tag PR with impact: +- `r.Major` - Breaking changes +- `r.Minor` - New features +- `r.Patch` - Bug fixes, minor improvements + +**Why**: Helps release manager determine next version. + +### 2. Draft Release Notes (if Breaking Changes) + +Work with PR author to add release note in PR description: + +```markdown +### Breaking Changes + +**Old behavior**: Description of what changed +**New behavior**: What it does now +**Migration**: Steps to update existing sites + +Example: +Old: `` +New: `` +Migration: Add `inline` attribute to all includes that should be inline. +``` + +### 3. Undo Accidental Merge + +If wrong merge strategy used: + +```bash +# 1. Switch to master locally +git checkout master + +# 2. Reset to before merge +git reset --hard HEAD~1 + +# 3. Force push (requires write access) +git push --force + +# 4. Create new PR from original branch +# 5. Merge with correct strategy +``` + +**Warning**: Force pushing rewrites history. Use with caution. Check no ongoing work conflicts with this. + +## Release Process + +For complete release procedures, see [references/release-procedures.md](references/release-procedures.md). + +### Quick Overview + +1. **Verify permissions** (GitHub releases, npm publish) +2. **Clean state**: `npx lerna clean && npm run setup` +3. **Increment version**: `npx lerna version --no-push --exact` +4. **Build frontend**: `npm run build:web` +5. **Update tests**: `npm run updatetest` +6. **Combine commits**: Amend version commit with bundle/test changes +7. **Push**: `git push upstream master && git push upstream vX.Y.Z` +8. **Publish**: `npx lerna publish from-git` +9. **Smoke test**: Install and test new version +10. **GitHub release**: Draft release notes +11. **Announce**: Post to Slack + +## Managing Contributors + +### All-Contributors System + +Uses [all-contributors](https://allcontributors.org/) specification. + +### Adding Contributors + +Use all-contributors bot in PR comments: + +``` +@all-contributors please add @username for code +``` + +**Contribution Types**: +- `code` - Code contributions +- `doc` - Documentation +- `test` - Tests +- `bug` - Bug reports +- `question` - Answering questions +- `mentoring` - Mentoring others + +**Multiple Types**: +``` +@all-contributors please add @username for code, doc +``` + +**Updating Later**: +``` +@all-contributors please add @username for test +``` +Result: Icons accumulate (code + test). + +### Workflow + +1. PR is merged +2. Comment with `@all-contributors` command +3. Bot creates automatic PR +4. Review and merge bot's PR +5. Delete bot's branch + +## Repository Management + +### GitHub Projects + +Use [GitHub Projects](https://github.com/MarkBind/markbind/projects) for: +- Version planning +- Feature roadmaps +- Issue triage +- Progress tracking + +### Creating Projects + +1. Identify goals (e.g., "Better Logging", "User Demand") +2. Create project with clear description +3. Add relevant issues +4. Track progress with project views + +### Project Tips + +- Don't treat as strict guide - tool for prioritization +- Help teams communicate and collaborate +- Triage issues into categories +- Use multiple views (Kanban, table, calendar) +- Custom fields for categorization + +### Managing Teams + +MarkBind has structured GitHub teams: + +**Maintainers** (Maintain Access): +- `maintainers` - Parent team +- `team-leads` - Mentors and leads +- `release-manager-admins` - Release managers + +**Developers** (Write Access): +- `developers` - Parent team +- `junior-developers` - Building familiarity +- `senior-developers` - Experienced developers + +**Contributors** (Triage Access): +- `active-contributors` - Regular contributors + +### Team Maintenance + +Review and update rosters regularly (e.g., start/mid/end of semester): + +1. **Review Activity**: Identify inactive members or those who progressed +2. **Promote Members**: Move eligible members up hierarchy +3. **Audit Membership**: No "floating" members in parent teams without child team + +### Contributor Journey + +``` +New Contributor + ↓ (consistent contribution) +active-contributors + ↓ +junior-developers + ↓ (demonstrated expertise) +senior-developers + ↓ (leadership/release management) +team-leads / release-manager-admins +``` + +## Issue Management + +### Labeling Issues + +**Type Labels**: +- `d.Feature` - New feature requests +- `d.Enhancement` - Improvements to existing features +- `d.Bug` - Bug reports +- `d.Documentation` - Documentation issues + +**Priority Labels**: +- `p.Critical` - Broken functionality, security issues +- `p.High` - Important but not critical +- `p.Medium` - Nice to have +- `p.Low` - Low priority + +**Difficulty Labels**: +- `good first issue` - Good for newcomers +- `d.Moderate` - Requires some experience +- `d.Difficult` - Complex, requires deep knowledge + +**Other Labels**: +- `s.Blocked` - Blocked by external factors +- `s.Duplicate` - Duplicate of another issue +- `s.Investigating` - Under investigation + +### Triaging New Issues + +1. **Verify issue**: Can you reproduce? Is description clear? +2. **Label appropriately**: Type, priority, difficulty +3. **Link related issues**: Reference related work +4. **Add to project** (if relevant): Include in roadmap +5. **Request information**: If issue unclear, ask for details +6. **Close if invalid**: Duplicates, not issues, out of scope + +### Closing Issues + +Issues can be closed when: +- Fixed and merged +- Duplicate of another issue +- Not a bug / working as intended +- Out of scope for MarkBind +- Stale with no response + +**Template responses** for common closures available in `.github/` (if created). + +## Security + +### Reporting Security Issues + +Direct to private channels (email to maintainers or security policy). + +### Handling Security Issues + +1. **Assess severity**: Critical vs low impact +2. **Create private fix**: Don't disclose publicly yet +3. **Coordinate disclosure**: With reporter and team +4. **Release patch**: Priority release if critical +5. **Public disclosure**: After patch available + +## Deployment + +### User Guide Deployment + +Automated via CI, but manual trigger if needed: + +```bash +npm run deploy:ug +``` + +### Developer Guide Deployment + +```bash +npm run deploy:dg +``` + +### Netlify Deployment + +Pull requests get preview deploys automatically. Check Netlify bot comment for preview URL. + +## Best Practices + +### Communication + +- Be respectful and welcoming +- Provide constructive feedback +- Explain decisions clearly +- Thank contributors for their work + +### Code Quality + +- Don't compromise quality for speed +- Request changes if necessary +- Provide guidance for improvements +- Pair complex changes with good tests + +### Maintenance + +- Keep dependencies updated +- Address security vulnerabilities promptly +- Monitor for issues after releases +- Be responsive to community + +### Documentation + +- Update docs with code changes +- Keep release notes accurate +- Document decisions in PRs/issues +- Maintain clear project guidelines + +## Resources + +- [SE-EDU PR Guidelines](https://se-education.org/guides/guidelines/PRs.html) +- [MarkBind Contributor Guidelines](https://github.com/MarkBind/markbind/blob/master/CONTRIBUTING.md) +- [Semantic Versioning](https://semver.org/) +- [All Contributors](https://allcontributors.org/) diff --git a/.opencode/skills/markbind-maintainer-tasks/references/release-procedures.md b/.opencode/skills/markbind-maintainer-tasks/references/release-procedures.md new file mode 100644 index 0000000000..58bb676fdb --- /dev/null +++ b/.opencode/skills/markbind-maintainer-tasks/references/release-procedures.md @@ -0,0 +1,472 @@ +# Release Procedures + +Complete step-by-step guide for releasing new MarkBind versions. + +## Prerequisites + +### Required Permissions + +**GitHub**: +- Push to master branch +- Create releases +- Check: "Draft a new release" button visible at [releases page](https://github.com/MarkBind/markbind/releases) + +**npm**: +- Member of [MarkBind organization](https://www.npmjs.com/org/markbind) +- Check: MarkBind listed under organizations in npm profile + +**Published Packages**: +- `markbind-cli` +- `@markbind/core` +- `@markbind/core-web` +- `@markbind/vue-components` (private, internal use) + +### Environment Setup + +1. **npm login**: + ```bash + npm login + ``` + +2. **Clean state**: + ```bash + npx lerna clean + npm run setup + ``` + +## Step 1: Version Increment + +### Determine Version Type + +Review merged PRs with version labels: +- `r.Major` - Breaking changes → Increment major (1.x.x → 2.0.0) +- `r.Minor` - New features → Increment minor (1.2.x → 1.3.0) +- `r.Patch` - Bug fixes → Increment patch (1.2.3 → 1.2.4) + +Follow [Semantic Versioning](https://semver.org/). + +### Increment Version + +```bash +npx lerna version --no-push --exact +``` + +**Flags**: +- `--no-push`: Don't push yet (we'll add more changes) +- `--exact`: Use exact versions (not semver ranges) + +**What it does**: +- Updates version in all `package.json` files +- Creates version commit +- Creates version tag (e.g., `v1.2.3`) + +**Important**: Don't push yet. + +## Step 2: Build Frontend Bundles + +```bash +npm run build:web +``` + +**What it does**: +- Builds `markbind.min.js` +- Builds `markbind.min.css` +- Builds `vuecommonappfactory.min.js` +- Builds `vuecommonappfactory.min.css` + +### Verify Bundle Changes + +Check git diff for bundles in `packages/core-web/dist/`: + +**Expected**: +- Version numbers updated +- New features reflected in code +- Reasonable size changes + +**Unexpected** (investigate): +- Massive size increase +- Strange code patterns +- Missing features + +**Note**: If no frontend changes since last release, bundles may be unchanged. This is normal. + +**Font files**: If fonts appear changed but you didn't modify them, discard changes: +```bash +git checkout -- packages/core-web/dist/fonts/ +``` + +## Step 3: Update Test Files + +```bash +npm run updatetest +``` + +### Verify Test Changes + +**Expected changes**: Only version numbers in HTML meta tags: + +```diff +- ++ +``` + +**Unexpected changes**: Other lines changed +- **Problem**: Functional tests weren't updated in earlier PRs +- **Solution**: Revert everything, fix tests in separate PR first + +### Verify Bundle Copies + +If frontend bundles were updated, check they're copied to test sites: + +```bash +git status packages/cli/test/functional/*/expected/markbind/ +``` + +Should show updated bundles in all test site `expected/` directories. + +## Step 4: Combine Commits + +Amend the version commit to include bundle and test updates: + +```bash +# Stage all changes +git add . + +# Amend version commit (replace vX.Y.Z with actual version) +git commit --amend --reuse-message=vX.Y.Z + +# Update tag to point to amended commit +git tag --force vX.Y.Z +``` + +**Result**: Single commit with version bump + bundles + tests. + +## Step 5: Push + +```bash +# Push commit (replace 'upstream' with your remote name if different) +git push upstream master + +# Push tag (replace vX.Y.Z with actual version) +git push upstream vX.Y.Z +``` + +## Step 6: Publish to npm + +```bash +npx lerna publish from-git +``` + +**What it does**: +- Detects tag from git +- Publishes all packages with new version +- Only publishes public packages (not vue-components) + +**Verify success**: Check npm output for success messages. + +## Step 7: Smoke Test + +Test the published version: + +```bash +# Install globally +npm i -g markbind-cli@X.Y.Z + +# Test commands +markbind init test-site +cd test-site +markbind serve +markbind build +``` + +**Test checklist**: +- [ ] Init creates site correctly +- [ ] Serve works without errors +- [ ] Build generates output +- [ ] Frontend components work +- [ ] No console errors +- [ ] Styles load correctly + +**Important**: Test on clean environment (not using `npm link`). + +**Recommendation**: Test on different platform/computer to ensure it works for end users. + +## Step 8: Verify Website Deployment + +Check [markbind.org](https://markbind.org): + +1. Open in **incognito window** (avoid cache) +2. Check footer shows new version number +3. Verify pages load correctly +4. Test search functionality + +**Note**: May take a few minutes to update. + +### Manual Deployment (if automated fails) + +```bash +npm run deploy:ug +``` + +Check CI/CD logs if deployment issues persist. + +## Step 9: GitHub Release + +### Create Release + +1. Go to [releases page](https://github.com/MarkBind/markbind/releases) +2. Click "Draft a new release" +3. Enter tag: `vX.Y.Z` (should show "Existing tag") +4. Leave title blank (GitHub uses tag) +5. Click "Generate release notes" for auto-generated PR list + +### Format Release Notes + +Use this template: + +```markdown +# markbind-cli + +## User Facing Changes + +### Breaking Changes + +> **Change**: Description of what changed +> +> **Migration**: Steps to update existing sites +> +> Example: +> ```markdown +> +> +> +> +> +> ``` + +### Features + +- Add feature X (#123) +- Support for Y (#456) + +### Enhancements + +- Improve Z performance (#789) +- Better error messages for W (#012) + +### Fixes + +- Fix A not working (#345) +- Resolve B issue (#678) + +### Documentation + +- Update C documentation (#901) +- Add examples for D (#234) + +## Developer Facing Changes + +### Code Quality + +- Refactor E module (#567) +- Extract F utility (#890) + +### DevOps Changes + +- Update CI pipeline (#123) +- Improve build process (#456) + +### Dependencies + +- Update G to v2.0 (#789) +- Bump H from 1.0 to 1.1 (#012) + +### Miscellaneous + +- Other changes (#345) +``` + +### Categorize PRs + +Review each PR in generated list: +1. Move to appropriate section +2. Summarize clearly +3. Include PR number +4. Add author credit +5. Remove empty sections + +### Breaking Changes Detail + +For breaking changes, provide: +- What changed and why +- Old vs new behavior +- Migration steps with examples +- Links to relevant docs + +**Example** (from v1.18.0): + +```markdown +### Breaking Changes + +#653 Disable decamelize for anchor ID generation (#667) + +> Headings with PascalCase wordings now generate different anchor IDs +> to match GitHub Flavored Markdown. +> +> **Example**: +> ```markdown +> # MarkBind docs +> ``` +> +> Old anchor: `mark-bind-docs` +> **New anchor**: `markbind-docs` +> +> **Migration**: Update internal links to use new anchor format. +``` + +### Review and Publish + +1. Preview release notes +2. Verify all PRs categorized +3. Check breaking changes have migration guides +4. Click "Publish release" + +## Step 10: Announce + +Post to Slack channel: + +``` +Published: npm i -g markbind-cli@X.Y.Z + +Release notes: https://github.com/MarkBind/markbind/releases/tag/vX.Y.Z +``` + +## Troubleshooting + +### npm Publish Fails + +**Check permissions**: +```bash +npm whoami +npm org ls markbind +``` + +**Verify version not already published**: +```bash +npm info markbind-cli versions +``` + +**Re-publish if needed**: +```bash +npx lerna publish from-git +``` + +### Website Doesn't Update + +1. Wait 5-10 minutes +2. Check in incognito window +3. Check CI/CD logs +4. Manually deploy: `npm run deploy:ug` +5. Check for errors in build logs + +### Smoke Test Fails + +**If critical issue**: +1. Don't announce release +2. Fix issue quickly +3. Publish patch version +4. Test again +5. Announce when working + +**If minor issue**: +1. File issue +2. Fix in next release +3. Document known issue in release notes + +### Version Tag Conflict + +If tag already exists: +```bash +# Delete local tag +git tag -d vX.Y.Z + +# Delete remote tag (careful!) +git push origin :refs/tags/vX.Y.Z + +# Recreate correctly +git tag vX.Y.Z +git push upstream vX.Y.Z +``` + +### Wrong Version Published + +**Can't unpublish** recent versions (npm policy). + +**Solution**: Publish hotfix patch version immediately: +1. Increment patch: `npx lerna version patch --no-push --exact` +2. Fix issue +3. Follow release process +4. Deprecate bad version: `npm deprecate markbind-cli@X.Y.Z "Use X.Y.Z+1 instead"` + +## Post-Release Checklist + +- [ ] Version published to npm +- [ ] GitHub release created with notes +- [ ] Website updated with new version +- [ ] Smoke test passed +- [ ] Announcement posted +- [ ] No critical issues reported +- [ ] Documentation up to date + +## Release Schedule + +No fixed schedule, but considerations: + +**Good times to release**: +- After significant features merged +- Security fixes (as soon as possible) +- Accumulated bug fixes (every 2-4 weeks) + +**Avoid releasing**: +- Right before holidays +- Friday afternoons (less time to fix issues) +- During exams (if maintainers are students) + +## Version Numbering Examples + +| Current | Change Type | New Version | +|---|---|---| +| 1.2.3 | Bug fix | 1.2.4 | +| 1.2.3 | New feature | 1.3.0 | +| 1.2.3 | Breaking change | 2.0.0 | +| 2.0.0 | Bug fix | 2.0.1 | +| 2.0.0 | New feature | 2.1.0 | + +## Common Mistakes to Avoid + +- [ ] Forgetting to build frontend bundles +- [ ] Not updating test files +- [ ] Pushing before combining commits +- [ ] Not testing before announcing +- [ ] Incomplete release notes +- [ ] Missing migration guides for breaking changes +- [ ] Not checking website deployment +- [ ] Announcing before smoke test + +## Timeline Estimate + +- **Preparation**: 10 minutes +- **Version + build + test**: 15 minutes +- **Push + publish**: 5 minutes +- **Smoke test**: 10 minutes +- **Release notes**: 20-30 minutes +- **Verification**: 5 minutes +- **Total**: ~1 hour (more if many PRs to categorize) + +## Getting Help + +If stuck: +- Check previous releases for examples +- Ask in maintainers channel +- Review this guide carefully +- Don't rush - better to take time than release broken version diff --git a/.opencode/skills/markbind-typescript-migration/SKILL.md b/.opencode/skills/markbind-typescript-migration/SKILL.md new file mode 100644 index 0000000000..119292611e --- /dev/null +++ b/.opencode/skills/markbind-typescript-migration/SKILL.md @@ -0,0 +1,257 @@ +--- +name: markbind-typescript-migration +description: Complete guide for migrating JavaScript files to TypeScript in the MarkBind project, including the two-commit strategy, import/export syntax conversion, and best practices. Use when migrating .js files to .ts in MarkBind's core package, preparing TypeScript migration pull requests, or understanding the Rename+Adapt commit workflow required for preserving git history. +--- + +# TypeScript Migration for MarkBind + +MarkBind is progressively migrating backend code from JavaScript to TypeScript. This skill provides a complete guide for performing migrations that preserve git file history. + +## Critical Requirements + +**Two-Commit Strategy Required**: TypeScript migrations MUST use two separate commits: +1. **"Rename" commit** - Rename `.js` to `.ts` only +2. **"Adapt" commit** - Fix TypeScript errors and convert syntax + +**Why**: Preserves git file history by keeping similarity index above 50%. + +**PR Merge Strategy**: Use **rebase-merge** (not squash-merge) to keep both commits in target branch. + +## Quick Start Checklist + +Before starting migration: +- [ ] Identify files to migrate (directory or specific files) +- [ ] Stop auto-compilation (`npm run dev` or IDE) +- [ ] Install required `@types/*` packages +- [ ] Understand the two-commit workflow + +## Migration Workflow Overview + +### Phase 1: Preparation +1. Install type definitions for external dependencies +2. Stash package.json changes +3. Stop auto-compilation + +### Phase 2: "Rename" Commit +1. Add compiled `.js` paths to `.gitignore` and `.eslintignore` +2. Rename `.js` files to `.ts` +3. Verify files show as "renamed" in git status +4. Commit with `--no-verify` (code doesn't compile yet) + +### Phase 3: "Adapt" Commit +1. Start auto-compilation (`npm run dev`) +2. Convert import/export syntax +3. Fix TypeScript errors +4. Update dependent files +5. Run tests +6. Commit normally + +## When to Migrate + +### Good Candidates +- Files with complex logic that benefit from types +- Files with many internal dependencies +- Core processing files (NodeProcessor, VariableProcessor, etc.) +- Files that are actively maintained + +### Avoid Migrating +- Files scheduled for deletion/major refactor +- External patches (keep matching upstream) +- Files with minimal logic +- Test files (can migrate later) + +## Detailed Step-by-Step Guide + +For complete instructions with examples and troubleshooting, see: + +### Planning and Preparation +- [references/preparation.md](references/preparation.md) - Installing types, planning migration scope + +### Rename Commit +- [references/rename-commit.md](references/rename-commit.md) - Creating the first commit with file renames + +### Adapt Commit +- [references/adapt-commit.md](references/adapt-commit.md) - Fixing TypeScript errors and converting syntax + +### Import/Export Syntax +- [references/import-export-syntax.md](references/import-export-syntax.md) - Converting CommonJS to TypeScript/ES6 + +### Troubleshooting +- [references/troubleshooting.md](references/troubleshooting.md) - Common issues and solutions + +## Import/Export Quick Reference + +### Exports + +| CommonJS | TypeScript Equivalent | ES6 | +|---|---|---| +| `module.exports = X` | `export = X` | ❌ `export default` (don't use) | +| `module.exports = { a, b }` | `export = { a, b }` | ✅ `export { a, b }` | + +### Imports + +| CommonJS | TypeScript Equivalent | ES6 | +|---|---|---| +| `const X = require('a')` | `import X = require('a')` | `import X from 'a'` | +| `const { a, b } = require('x')` | Import whole object | `import { a, b } from 'x'` | + +**Rule**: Match import syntax with export syntax. + +## Common Mistakes to Avoid + +### ❌ Don't +- Combine rename and adapt in one commit +- Use `export default` during migration +- Skip type definitions installation +- Forget to add compiled files to ignore lists +- Use `any` type without justification +- Push "Rename" commit without `--no-verify` + +### ✅ Do +- Separate rename from adapt commits +- Install `@types/*` packages before starting +- Verify files show as "renamed" in git status +- Document stand-in types with TODOs +- Test thoroughly before committing +- Use rebase-merge for PR + +## Testing Your Migration + +### Before Committing "Adapt" + +```bash +# Full test suite +npm run test + +# Core package only +cd packages/core && npm run test + +# CLI package (uses compiled core) +cd packages/cli && npm run test +``` + +**Both must pass**: Core tests verify `.ts` compilation, CLI tests verify compiled `.js` output. + +### What to Verify +- [ ] No TypeScript compilation errors +- [ ] All tests pass (core and cli) +- [ ] No `any` types (or justified with comments) +- [ ] Import/export syntax is consistent +- [ ] Dependent files are updated +- [ ] Stand-in types documented with TODOs + +## PR Submission + +### PR Title +``` +Migrate [component/directory] to TypeScript +``` + +### PR Description Template +```markdown +## What +Migrates `packages/core/src/[component]` from JavaScript to TypeScript. + +## Changes +- Renamed X files from .js to .ts +- Added type annotations +- Installed @types/[library] (if applicable) +- Updated dependent files: [list files] + +## Testing +- [ ] Core tests pass +- [ ] CLI tests pass +- [ ] No new `any` types + +## Notes +[Any stand-in types or temporary solutions] +``` + +### Important +- Ensure PR has exactly 2 commits: "Rename" and "Adapt" +- Request rebase-merge in PR description +- Link to this migration guide if needed + +## Example Migrations + +Reference these PRs for migration patterns: +- [#1877: Adopt TypeScript for core package](https://github.com/MarkBind/markbind/pull/1877) + +## Post-Migration + +After your migration is merged: + +### Update Stand-in Types +If you created stand-in types for dependencies still in JavaScript: +- File issues to migrate those dependencies +- Update types when dependencies are migrated +- Remove TODOs + +### Update Documentation +If you migrated a major component: +- Update architecture docs if needed +- Note TypeScript-specific patterns +- Document any type utilities created + +## Quick Tips + +### Finding Files to Migrate +```bash +# Find all .js files in core package +find packages/core/src -name "*.js" -type f + +# Count .js vs .ts files +find packages/core/src -name "*.js" | wc -l +find packages/core/src -name "*.ts" | wc -l +``` + +### Checking Git Similarity +```bash +# After rename, before commit +git diff --cached --stat + +# Should show "renamed" not "deleted/added" +``` + +### Batch Renaming +```bash +# Rename all .js files in a directory +find packages/core/src/html -name "*.js" -exec bash -c 'mv "$0" "${0%.js}.ts"' {} \; +``` + +## Getting Help + +### If Stuck +1. Read the detailed guides in `references/` +2. Check example PR #1877 +3. Search for similar migrations in git history +4. Ask in PR comments with specific error messages + +### Common Questions +- "Do I need to migrate tests?" - No, focus on source files +- "What about external patches?" - Keep as JavaScript to match upstream +- "Can I migrate multiple directories?" - Yes, but keep PRs focused +- "Should I fix bugs while migrating?" - No, separate concerns + +## Configuration + +TypeScript configuration is in root `tsconfig.json`. Don't modify without team discussion. + +Current settings: +- Target: ES2020 +- Module: CommonJS +- Strict: true +- Output: In-place compilation + +## Success Criteria + +A successful migration: +- ✅ Two commits: "Rename" and "Adapt" +- ✅ Files show as renamed in first commit +- ✅ All tests pass +- ✅ No `any` types (or justified) +- ✅ Import/export syntax consistent +- ✅ Dependent files updated +- ✅ Stand-in types documented + +Your migration is ready when you can confidently say: "This TypeScript code provides better type safety without changing runtime behavior." diff --git a/.opencode/skills/markbind-typescript-migration/references/adapt-commit.md b/.opencode/skills/markbind-typescript-migration/references/adapt-commit.md new file mode 100644 index 0000000000..8feeaae42a --- /dev/null +++ b/.opencode/skills/markbind-typescript-migration/references/adapt-commit.md @@ -0,0 +1,480 @@ +# Creating the "Adapt" Commit + +This commit fixes TypeScript errors and converts syntax. The files are already renamed, now make them valid TypeScript. + +## Step 1: Start Auto-Compilation + +Enable TypeScript compilation to see errors in real-time: + +```bash +# In project root +npm run dev +``` + +This starts the TypeScript compiler in watch mode. You'll see errors immediately: + +``` +packages/core/src/html/NodeProcessor.ts(5,10): error TS2304: Cannot find name 'require'. +packages/core/src/Page/index.ts(15,1): error TS2304: Cannot find name 'module'. +``` + +**Keep this running** in a terminal while you work. + +## Step 2: Convert Import/Export Syntax + +The main work of adaptation. See [import-export-syntax.md](import-export-syntax.md) for complete guide. + +### Quick Syntax Conversion + +**Exports**: +```javascript +// Before (CommonJS) +module.exports = MyClass; +module.exports = { a, b, c }; + +// After (TypeScript equivalent - for single export) +export = MyClass; + +// After (ES6 - for multiple exports) +export { a, b, c }; +``` + +**Imports**: +```javascript +// Before (CommonJS) +const MyClass = require('./MyClass'); +const { a, b } = require('./utils'); + +// After (match the export style) +import MyClass = require('./MyClass'); // If using export = +import { a, b } from './utils'; // If using export { } +``` + +### Conversion Process + +1. **Start with exports** in each file +2. **Convert imports** to match export style +3. **Update files that import these files** +4. **Verify compilation** after each file + +### Common Patterns + +**Pattern 1: Single class export** +```typescript +// MyClass.ts +class MyClass { + constructor() { } + method() { } +} + +export = MyClass; +``` + +**Pattern 2: Multiple exports** +```typescript +// utils.ts +export function helperA() { } +export function helperB() { } +export const CONSTANT = 'value'; +``` + +**Pattern 3: Type exports** +```typescript +// types.ts +export interface MyType { + id: string; + name: string; +} + +export type Status = 'pending' | 'complete'; +``` + +## Step 3: Fix TypeScript Errors + +### Add Type Annotations + +**Function parameters**: +```typescript +// Before +function process(data, options) { + return data.map(item => item.value); +} + +// After +function process(data: DataItem[], options: ProcessOptions): string[] { + return data.map(item => item.value); +} +``` + +**Variables**: +```typescript +// Explicit type (when needed) +const config: Config = { port: 3000 }; + +// Type inference (preferred when obvious) +const port = 3000; // inferred as number +``` + +**Class properties**: +```typescript +class MyClass { + // Before (JavaScript) + constructor() { + this.name = ''; + this.count = 0; + } + + // After (TypeScript) + name: string; + count: number; + + constructor() { + this.name = ''; + this.count = 0; + } +} +``` + +### Handle Missing Types + +**For internal dependencies still in .js**: +```typescript +// Stand-in type until dependency is migrated +interface TemporaryPageType { + content: string; + title: string; + // TODO: Replace when Page is migrated to TypeScript +} + +// Use the stand-in +const page = require('./Page') as TemporaryPageType; +``` + +**For properties on objects**: +```typescript +// Before (JavaScript) +const obj = {}; +obj.newProp = 'value'; + +// After (TypeScript) - Option 1: Interface +interface MyObject { + newProp?: string; +} +const obj: MyObject = {}; +obj.newProp = 'value'; + +// Option 2: Index signature +const obj: { [key: string]: string } = {}; +obj.newProp = 'value'; +``` + +### Avoid `any` Type + +**❌ Bad**: +```typescript +function process(data: any): any { + return data.value; +} +``` + +**✅ Better**: +```typescript +function process(data: { value: string }): string { + return data.value; +} +``` + +**✅ Best** (with proper interface): +```typescript +interface DataItem { + value: string; + id: number; +} + +function process(data: DataItem): string { + return data.value; +} +``` + +**When `any` is acceptable**: +- External library with no types +- Complex dynamic behavior +- Temporary during migration + +**If using `any`, document why**: +```typescript +// TODO: Type this properly after cheerio types are fixed +function parseHtml(html: string): any { + return cheerio.load(html); +} +``` + +## Step 4: Update Dependent Files + +Files that import your migrated files need updates. + +### Find Dependent Files + +```bash +# Find files importing yours +grep -r "require('./NodeProcessor')" packages/core/src/ +grep -r "from './NodeProcessor'" packages/core/src/ +``` + +### Update Imports + +**If dependent is TypeScript**: +```typescript +// Update to match your export style +import NodeProcessor = require('./NodeProcessor'); +// or +import { NodeProcessor } from './NodeProcessor'; +``` + +**If dependent is still JavaScript**: +- No changes needed (compiled .js will work) +- Add to your TODO list for future migration + +### Update Type References + +**If you created better types**: +```typescript +// Before (in dependent file) +interface TempNodeType { + name: string; + // ... stand-in definition +} + +// After (now that Node is migrated) +import { Node } from './Node'; // Use real type +``` + +## Step 5: Restore Stashed Changes + +Remember the package.json changes from preparation? + +```bash +# Check what's stashed +git stash list + +# Restore the stash +git stash pop +``` + +This brings back: +- `packages/core/package.json` (with new @types/* packages) +- Root `package-lock.json` (with updated dependencies) + +## Step 6: Run Tests + +### Full Test Suite + +```bash +# From project root +npm run test +``` + +This runs: +- ESLint (should pass now) +- Jest unit tests for core (tests .ts files directly) +- Jest unit tests for cli (tests compiled .js files) +- Functional tests (builds test sites) + +**All must pass.** + +### If Tests Fail + +**Compilation errors**: +```bash +# Check TypeScript errors +npm run build:backend +``` +Fix errors and retry. + +**Unit test failures**: +```bash +# Run specific package tests +cd packages/core && npm run test +cd packages/cli && npm run test +``` + +**Common causes**: +- Import/export mismatch +- Missing type definitions +- Changed behavior (unintended) + +### Verify Compilation Output + +```bash +# Check compiled JavaScript exists +ls packages/core/src/html/NodeProcessor.js + +# Verify it works +node packages/core/src/html/NodeProcessor.js +``` + +## Step 7: Final Review + +### Check for Mistakes + +**No `any` types** (or justified): +```bash +grep -n "any" packages/core/src/html/*.ts +``` + +**Import/export consistency**: +- All imports match their module's export style +- No mixing of `require` and `import from` + +**Stand-in types documented**: +```bash +grep -n "TODO.*TypeScript" packages/core/src/html/*.ts +``` + +**Files compile**: +```bash +npm run build:backend +# Should succeed with no errors +``` + +### Compare Git Diff + +```bash +git diff packages/core/src/html/NodeProcessor.ts +``` + +**Expected changes**: +- `require` → `import` +- `module.exports` → `export` +- Type annotations added +- Interfaces defined + +**Unexpected changes** (investigate): +- Logic changes +- Behavior modifications +- Deleted code + +## Step 8: Commit + +### Stage Changes + +```bash +git add packages/core/src/ +git add packages/core/package.json +git add package-lock.json +``` + +### Commit Message + +```bash +git commit -m "Adapt [component/directory] to TypeScript" +``` + +**Examples**: +```bash +git commit -m "Adapt core/src/html to TypeScript" +git commit -m "Adapt NodeProcessor to TypeScript" +git commit -m "Adapt core/src/Page to TypeScript" +``` + +### Verify Commit + +```bash +git log -1 --stat +``` + +**Should show**: +- Modified `.ts` files (with line changes) +- Modified `package.json` (if types added) +- Modified `package-lock.json` (if types added) + +## Adapt Commit Checklist + +- [ ] Auto-compilation started +- [ ] Import/export syntax converted +- [ ] TypeScript errors fixed +- [ ] No (or justified) `any` types +- [ ] Dependent files updated +- [ ] Stashed changes restored +- [ ] All tests pass (core and cli) +- [ ] Code review completed +- [ ] Changes committed + +## What's in This Commit? + +**Included**: +- ✅ Modified `.ts` files (syntax and types) +- ✅ Modified `package.json` (if types added) +- ✅ Modified `package-lock.json` (if types added) +- ✅ Type annotations and interfaces +- ✅ Import/export syntax changes + +**Not included**: +- ❌ File renames (in previous commit) +- ❌ Ignore list changes (in previous commit) +- ❌ Logic changes (separate PR if needed) + +## Next Steps + +Your migration is complete! You now have: +1. ✅ "Rename" commit preserving history +2. ✅ "Adapt" commit with TypeScript + +**Next**: Create PR with rebase-merge request. + +## Common Adapt Issues + +### Issue 1: Import/Export Mismatch + +**Symptom**: `Cannot find module` errors + +**Solution**: Ensure import syntax matches export syntax: +```typescript +// If file exports with: export = X +import X = require('./file'); // Not: import X from './file' +``` + +### Issue 2: Tests Pass Locally, Fail in CI + +**Symptom**: CI fails but local passes + +**Solution**: +```bash +# Clean and rebuild +npm run clean +npm run setup +npm run test +``` + +### Issue 3: Compiled JS Not Generated + +**Symptom**: CLI tests fail, can't find compiled files + +**Solution**: +```bash +npm run build:backend +# Check output +ls packages/core/src/html/NodeProcessor.js +``` + +### Issue 4: Type Errors in Test Files + +**Symptom**: Test files show type errors + +**Solution**: Test files can remain JavaScript (migrate later), but if type errors prevent compilation: +```typescript +// In test file +const NodeProcessor = require('../NodeProcessor'); // Keep as require +``` + +## Quick Reference + +```bash +# Full adapt workflow +npm run dev # Terminal 1: watch compilation +# ... edit files, fix errors ... +git stash pop # Restore package.json +npm run test # Verify everything works +git add packages/core/src/ packages/core/package.json package-lock.json +git commit -m "Adapt core/src/html to TypeScript" +``` diff --git a/.opencode/skills/markbind-typescript-migration/references/import-export-syntax.md b/.opencode/skills/markbind-typescript-migration/references/import-export-syntax.md new file mode 100644 index 0000000000..74f47bb333 --- /dev/null +++ b/.opencode/skills/markbind-typescript-migration/references/import-export-syntax.md @@ -0,0 +1,437 @@ +# Import/Export Syntax Conversion + +Converting from CommonJS (`require`/`module.exports`) to TypeScript/ES6 syntax. + +## Core Principle + +**Match import syntax with export syntax**. TypeScript supports two styles: + +1. **TypeScript equivalent** (`export =` / `import = require()`) +2. **ES6** (`export { }` / `import { } from`) + +**Never mix styles** - causes compilation errors. + +## Export Syntax + +### Exporting Single Thing + +**CommonJS**: +```javascript +class MyClass { } +module.exports = MyClass; +``` + +**TypeScript Equivalent** (Recommended for compatibility): +```typescript +class MyClass { } +export = MyClass; +``` + +**ES6** (❌ Don't use during migration): +```typescript +class MyClass { } +export default MyClass; // AVOID - breaks JS imports +``` + +**Why avoid `export default`**: Compiled differently, breaks imports from files still in JavaScript. + +### Exporting Multiple Things + +**CommonJS**: +```javascript +function foo() { } +function bar() { } +module.exports = { foo, bar }; +``` + +**TypeScript Equivalent**: +```typescript +function foo() { } +function bar() { } +export = { foo, bar }; +``` + +**ES6** (Preferred): +```typescript +export function foo() { } +export function bar() { } +// or +function foo() { } +function bar() { } +export { foo, bar }; +``` + +**When to use ES6**: When exporting multiple things that shouldn't be wrapped in an object. + +## Import Syntax + +### Importing Single Thing + +**Match the export style**: + +**If module uses `export =`**: +```typescript +// MyClass.ts uses: export = MyClass +import MyClass = require('./MyClass'); +``` + +**If module uses `export default`**: +```typescript +// MyClass.ts uses: export default MyClass +import MyClass from './MyClass'; +``` + +**Common mistake**: +```typescript +// MyClass.ts uses: export = MyClass +import MyClass from './MyClass'; // ❌ WRONG - won't work +``` + +### Importing Multiple Things + +**If module uses `export = { a, b }`**: +```typescript +// utils.ts uses: export = { foo, bar } +import utils = require('./utils'); +const { foo, bar } = utils; + +// or import whole object +import utils = require('./utils'); +utils.foo(); +``` + +**If module uses `export { a, b }`**: +```typescript +// utils.ts uses: export { foo, bar } +import { foo, bar } from './utils'; + +// or import everything +import * as utils from './utils'; +utils.foo(); +``` + +## Decision Tree + +### Choosing Export Style for Your File + +``` +How many things to export? +├─ One thing only +│ └─ Use: export = X +│ (TypeScript equivalent) +│ +└─ Multiple things + ├─ Related utilities/functions? + │ └─ Use: export { a, b, c } + │ (ES6) + │ + └─ Should be wrapped in object? + └─ Use: export = { a, b, c } + (TypeScript equivalent) +``` + +### Updating Existing Imports + +When you migrate a file, other TypeScript files that import it may need updates: + +**Before migration** (dependency was .js): +```typescript +// YourFile.ts importing from dependency.js +const Dep = require('./dependency'); +``` + +**After migration** (dependency is now .ts with `export =`): +```typescript +// YourFile.ts importing from dependency.ts +import Dep = require('./dependency'); // Update to match export style +``` + +## Complete Examples + +### Example 1: Class Export + +**File: NodeProcessor.ts** +```typescript +class NodeProcessor { + constructor(config: Config) { } + process(node: Node): void { } +} + +// Export single class +export = NodeProcessor; +``` + +**File: Site.ts** (imports NodeProcessor) +```typescript +// Import matches export style +import NodeProcessor = require('./html/NodeProcessor'); + +class Site { + processor: NodeProcessor; + + constructor() { + this.processor = new NodeProcessor(config); + } +} + +export = Site; +``` + +### Example 2: Utility Functions + +**File: utils.ts** +```typescript +// Export multiple functions +export function slugify(text: string): string { + return text.toLowerCase().replace(/\s+/g, '-'); +} + +export function capitalize(text: string): string { + return text.charAt(0).toUpperCase() + text.slice(1); +} + +export const CONSTANT = 'value'; +``` + +**File: Page.ts** (imports utils) +```typescript +// Import specific functions +import { slugify, capitalize } from './utils'; + +class Page { + generateSlug(title: string): string { + return slugify(title); + } +} + +export = Page; +``` + +### Example 3: Mixed Exports + +**File: Plugin.ts** +```typescript +// Main class +class Plugin { + name: string; + constructor(name: string) { + this.name = name; + } +} + +// Helper functions +function loadPlugins(): Plugin[] { + return []; +} + +// Export main as default, helpers as named +export = Plugin; +export { loadPlugins }; // ❌ Can't do this - choose one style! +``` + +**Correct approach**: +```typescript +class Plugin { + name: string; + constructor(name: string) { + this.name = name; + } +} + +function loadPlugins(): Plugin[] { + return []; +} + +// Option 1: ES6 style +export { Plugin, loadPlugins }; +export { Plugin as default }; // If you need default + +// Option 2: TypeScript equivalent style +export = { + Plugin, + loadPlugins +}; +``` + +## Type-Only Imports + +Import types without importing values: + +```typescript +// Import only the type (zero runtime cost) +import type { MyType } from './types'; + +// Use in type annotation +const data: MyType = { }; + +// Can't use as value +const instance = new MyType(); // ❌ Error +``` + +**When to use**: +- Importing only types/interfaces +- Avoiding circular dependencies +- Reducing bundle size + +## External Library Imports + +### Libraries with Types + +**Built-in types**: +```typescript +import cheerio from 'cheerio'; // Types bundled +``` + +**Separate @types package**: +```typescript +import lodash from 'lodash'; // Uses @types/lodash +``` + +### Libraries without Types + +**Create declaration file** (`declarations.d.ts` in same directory): +```typescript +declare module 'old-library' { + export function doSomething(x: string): void; +} +``` + +**Then import**: +```typescript +import { doSomething } from 'old-library'; +``` + +## Common Patterns in MarkBind + +### Pattern 1: Core Class + +```typescript +// NodeProcessor.ts +class NodeProcessor { + // ... implementation +} +export = NodeProcessor; + +// Import in other files +import NodeProcessor = require('./NodeProcessor'); +``` + +### Pattern 2: Utilities + +```typescript +// urlUtils.ts +export function resolveUrl(base: string, rel: string): string { } +export function isAbsolute(url: string): boolean { } + +// Import in other files +import { resolveUrl, isAbsolute } from './urlUtils'; +``` + +### Pattern 3: Constants and Types + +```typescript +// constants.ts +export const DEFAULT_PORT = 8080; +export const TEMPLATE_DIR = '_markbind/layouts'; + +export interface Config { + port: number; + baseUrl: string; +} + +// Import in other files +import { DEFAULT_PORT, Config } from './constants'; +``` + +### Pattern 4: Plugin Interface + +```typescript +// Plugin.ts +export interface PluginContext { + config: any; +} + +export interface PluginInterface { + processNode?(context: PluginContext, node: Node): void; + postRender?(context: PluginContext, content: string): string; +} + +// Import in other files +import { PluginInterface, PluginContext } from './Plugin'; +``` + +## Syntax Conversion Cheat Sheet + +| Scenario | CommonJS | TypeScript Equivalent | ES6 | +|---|---|---|---| +| **Export single class** | `module.exports = C` | `export = C` | ~~`export default C`~~ | +| **Export multiple** | `module.exports = {a,b}` | `export = {a,b}` | `export {a,b}` | +| **Import single** | `const C = require('x')` | `import C = require('x')` | `import C from 'x'` | +| **Import multiple** | `const {a,b} = require('x')` | Import whole object | `import {a,b} from 'x'` | +| **Import all** | `const x = require('x')` | `import x = require('x')` | `import * as x from 'x'` | + +## Checking Your Syntax + +### Verify Compilation + +```bash +npm run build:backend +``` + +**If success**: Syntax is correct. +**If errors**: Check import/export mismatch. + +### Common Errors + +**Error: `Cannot find module`** +```typescript +// File uses: export = X +import X from './file'; // ❌ Wrong +import X = require('./file'); // ✅ Correct +``` + +**Error: `X is not a function`** +```typescript +// File uses: export { foo } +import utils = require('./utils'); +utils.foo(); // ❌ Wrong - utils is not an object + +import { foo } from './utils'; // ✅ Correct +foo(); +``` + +**Error: `Module has no default export`** +```typescript +// File uses: export { a, b } +import utils from './utils'; // ❌ Wrong - no default + +import { a, b } from './utils'; // ✅ Correct +``` + +## Best Practices + +### ✅ Do + +- Match import syntax with export syntax +- Use TypeScript equivalent (`export =`) for single exports +- Use ES6 (`export { }`) for multiple exports +- Be consistent within a file +- Update all imports when changing export style + +### ❌ Don't + +- Use `export default` during migration +- Mix `export =` and `export { }` in same file +- Leave mismatched imports after changing exports +- Use different styles for similar files + +## Quick Reference + +**When migrating a file**: +1. Decide export style (one thing = `export =`, multiple = `export { }`) +2. Convert exports +3. Update imports in this file to match external exports +4. Update imports in other files that import this file +5. Verify compilation + +**If unsure**: Use TypeScript equivalent style (`export =` / `import = require()`) - more compatible during migration. diff --git a/.opencode/skills/markbind-typescript-migration/references/preparation.md b/.opencode/skills/markbind-typescript-migration/references/preparation.md new file mode 100644 index 0000000000..dfcbbbddd7 --- /dev/null +++ b/.opencode/skills/markbind-typescript-migration/references/preparation.md @@ -0,0 +1,321 @@ +# Preparation for TypeScript Migration + +## Planning Your Migration + +### Scope Selection + +**Directory-based migration** (Recommended): +- Migrate entire logical units (e.g., `packages/core/src/html/`) +- Easier to track and review +- Maintains module cohesion + +**File-based migration**: +- Cherry-pick specific files +- Useful for high-value targets +- May require more stand-in types + +### Analyzing Dependencies + +Before migrating, understand the dependency graph: + +```bash +# Find what imports your target files +grep -r "require('./YourFile')" packages/core/src/ +grep -r "from './YourFile'" packages/core/src/ + +# Find what your target files import +grep "require\|import" packages/core/src/path/YourFile.js +``` + +**Key questions**: +- What internal files will need stand-in types? +- Which TypeScript files already import this? +- Will this migration unblock other migrations? + +### Migration Priority + +**High Priority**: +1. Core processing logic (NodeProcessor, VariableProcessor) +2. Frequently modified files +3. Files that unblock other migrations +4. Complex logic that benefits from types + +**Lower Priority**: +1. External library patches (keep matching upstream) +2. Files scheduled for refactoring +3. Simple utility files +4. Test files + +## Installing Type Definitions + +### Check if Types Needed + +Your migration may need types for external dependencies: + +```javascript +// If file imports external libraries: +const cheerio = require('cheerio'); +const lodash = require('lodash'); +``` + +### Finding Type Packages + +1. **Search TypeScript DefinitelyTyped**: + - Visit [https://www.typescriptlang.org/dt/search](https://www.typescriptlang.org/dt/search) + - Search for library name + +2. **Check package documentation**: + - Some packages bundle types (look for `types` or `typings` in package.json) + - Others have separate `@types/*` packages + +### Installing Types + +For packages needing `@types/*`: + +```bash +# Navigate to core package +cd packages/core + +# Install type definitions (example) +npm install -D @types/cheerio +npm install -D @types/lodash + +# Delete the local package-lock.json +rm package-lock.json + +# Return to root and update lockfile +cd ../.. +npm run setup +``` + +### Version Matching + +Match `@types/*` version with base library: + +```json +{ + "dependencies": { + "cheerio": "1.0.0" + }, + "devDependencies": { + "@types/cheerio": "1.0.0" // Match major.minor + } +} +``` + +**If exact match unavailable**: Use closest version (prioritize matching major version). + +### Verifying Installation + +```bash +# Check types are installed +ls node_modules/@types/ + +# Verify in package.json +cat packages/core/package.json | grep "@types" +``` + +## Preparing Your Environment + +### Stop Auto-Compilation + +If running, stop these processes: + +**`npm run dev` in root**: +```bash +# Find and kill the process +ps aux | grep "npm run dev" +kill [PID] + +# Or just Ctrl+C in the terminal +``` + +**IDE auto-compilation**: +- WebStorm: Settings → TypeScript → Disable "Recompile on changes" +- VS Code: Disable auto-build tasks + +**Why**: Prevent compilation errors during rename phase. + +### Stash Package Changes + +After installing types, stash the changes: + +```bash +git status +# Should show: +# modified: packages/core/package.json +# modified: package-lock.json + +git stash push -m "TypeScript migration types" +``` + +**Why**: These belong in "Adapt" commit, not "Rename" commit. + +### Clean Working Directory + +```bash +git status +# Should show: "nothing to commit, working tree clean" +``` + +## Understanding Two-Commit Strategy + +### The Problem + +Git tracks file history using **similarity index**: +- Above 50% similar → Git shows as "renamed" +- Below 50% similar → Git shows as "deleted" + "added" +- Lost history: blame, log, etc. don't follow renames + +**If you rename AND adapt in one commit**: +```javascript +// Before: myFile.js (100 lines) +module.exports = function() { /* ... */ }; + +// After: myFile.ts (100 lines, much changed) +export function myFunction(): void { /* ... */ } +``` +Similarity drops below 50% → History lost. + +### The Solution + +**Commit 1: Rename Only** +```bash +# Just rename, no content changes +mv myFile.js myFile.ts +git add myFile.ts +# Similarity: 100% → Git tracks rename +``` + +**Commit 2: Adapt Content** +```typescript +// Now fix TypeScript errors +export function myFunction(): void { /* ... */ } +``` + +### Why Rebase-Merge? + +**Normal PR flow**: Squash merge (single commit in master) +- Great for feature PRs +- Loses the two-commit structure +- History is lost + +**TypeScript migration**: Rebase-merge (preserves both commits) +- "Rename" commit enters master +- "Adapt" commit enters master +- History is preserved + +**Request in PR**: "Please use rebase-merge for this PR" + +## Pre-Migration Checklist + +Before starting, verify: + +- [ ] **Scope identified**: Know which files to migrate +- [ ] **Dependencies analyzed**: Understand what's imported/exported +- [ ] **Types installed**: All `@types/*` packages added +- [ ] **Changes stashed**: package.json changes saved +- [ ] **Compilation stopped**: No auto-compile running +- [ ] **Clean working directory**: `git status` is clean +- [ ] **Understanding confirmed**: Read two-commit strategy + +## Setting Up IDE (Optional but Recommended) + +### VS Code + +Create `.vscode/settings.json` (if not exists): +```json +{ + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} +``` + +### WebStorm + +1. Settings → Languages & Frameworks → TypeScript +2. TypeScript version: Use project's TypeScript +3. Check "Enable TypeScript Compiler" +4. Options: `--noEmitOnError` + +## Estimating Migration Time + +**Small file** (< 100 lines): +- Preparation: 5 minutes +- Rename commit: 2 minutes +- Adapt commit: 10-20 minutes +- Testing: 5 minutes +- **Total**: ~30 minutes + +**Medium directory** (5-10 files, 500 lines): +- Preparation: 10 minutes +- Rename commit: 5 minutes +- Adapt commit: 30-60 minutes +- Testing: 10 minutes +- **Total**: 1-2 hours + +**Large directory** (20+ files, 2000+ lines): +- Preparation: 20 minutes +- Rename commit: 10 minutes +- Adapt commit: 2-4 hours +- Testing: 20 minutes +- **Total**: 3-5 hours + +**First migration**: Add 50% more time for learning curve. + +## Common Preparation Mistakes + +### ❌ Mistake 1: Skipping Type Installation +```bash +# Migrate without installing @types/cheerio +# Result: TypeScript errors for cheerio usage +``` +**Fix**: Install types before starting. + +### ❌ Mistake 2: Not Stashing Package Changes +```bash +# Include package.json in rename commit +# Result: Confusing commit history +``` +**Fix**: Stash and include in adapt commit. + +### ❌ Mistake 3: Forgetting to Stop Compilation +```bash +# npm run dev still running +# Result: Compiler errors during rename phase +``` +**Fix**: Stop all compilation processes. + +### ❌ Mistake 4: Dirty Working Directory +```bash +# Unrelated changes present +# Result: Messy commits, hard to review +``` +**Fix**: Commit or stash unrelated work first. + +## Ready to Proceed? + +Once preparation is complete: +1. ✅ Types are installed and stashed +2. ✅ Working directory is clean +3. ✅ Compilation is stopped +4. ✅ You understand the two-commit strategy + +**Next step**: Proceed to [rename-commit.md](rename-commit.md) for creating the "Rename" commit. + +## Getting Help + +**If unsure about scope**: +- Start with a single small file +- Gain confidence before tackling directories +- Ask in PR for scope review + +**If type packages unclear**: +- Check library's npm page for type info +- Search DefinitelyTyped +- Ask in issue/discussion + +**If preparation seems complex**: +- It gets easier with practice +- First migration is always slowest +- Follow checklist step-by-step diff --git a/.opencode/skills/markbind-typescript-migration/references/rename-commit.md b/.opencode/skills/markbind-typescript-migration/references/rename-commit.md new file mode 100644 index 0000000000..1decc93bef --- /dev/null +++ b/.opencode/skills/markbind-typescript-migration/references/rename-commit.md @@ -0,0 +1,313 @@ +# Creating the "Rename" Commit + +This commit renames `.js` files to `.ts` without changing content. Goal: Preserve git file history. + +## Step 1: Update Ignore Lists + +Add compiled JavaScript paths to both `.gitignore` and `.eslintignore`. + +### Understanding Why + +When you rename `File.ts`, TypeScript compiles to `File.js`. We need git and ESLint to ignore the compiled output. + +### Adding to .gitignore + +Edit `.gitignore` in project root: + +```gitignore +# Before migration (example) +packages/core/dist/ +*.log + +# Add your files +packages/core/src/html/NodeProcessor.js +packages/core/src/html/HtmlProcessor.js +packages/core/src/Page/index.js + +# Or use patterns for directories +packages/core/src/html/*.js +packages/core/src/Page/*.js +``` + +**Pattern tips**: +- Specific paths for few files: `packages/core/src/File.js` +- Patterns for directories: `packages/core/src/html/*.js` +- Avoid too broad patterns: Don't use `**/*.js` (would ignore everything) + +### Adding to .eslintignore + +Edit `.eslintignore` in project root: + +``` +# Add same paths as .gitignore +packages/core/src/html/NodeProcessor.js +packages/core/src/html/HtmlProcessor.js +packages/core/src/Page/index.js +``` + +**Important**: Keep in sync with `.gitignore`. + +### Verify Ignore Lists + +```bash +# Check what will be ignored +git status --ignored + +# Should show your paths in ignored section +``` + +## Step 2: Rename Files + +### Single File + +```bash +mv packages/core/src/html/NodeProcessor.js packages/core/src/html/NodeProcessor.ts +``` + +### Multiple Files in Directory + +```bash +# All .js files in a directory +cd packages/core/src/html +for file in *.js; do + mv "$file" "${file%.js}.ts" +done +cd ../../../.. +``` + +### Using find (for nested directories) + +```bash +# Rename all .js files recursively in html/ directory +find packages/core/src/html -name "*.js" -type f -exec bash -c 'mv "$0" "${0%.js}.ts"' {} \; +``` + +### Verify Renames + +```bash +# Check that .ts files exist +ls packages/core/src/html/*.ts + +# Check that .js files are gone (might show compiled ones, that's OK) +ls packages/core/src/html/*.js +``` + +## Step 3: Stage Changes + +### Add Renamed Files + +```bash +# Stage all changes +git add .gitignore .eslintignore +git add packages/core/src/ +``` + +### Critical: Verify Git Shows "Renamed" + +```bash +git status +``` + +**You must see**: +``` +Changes to be committed: + modified: .eslintignore + modified: .gitignore + renamed: packages/core/src/html/NodeProcessor.js -> packages/core/src/html/NodeProcessor.ts + renamed: packages/core/src/html/HtmlProcessor.js -> packages/core/src/html/HtmlProcessor.ts +``` + +**If you see this instead** (❌ WRONG): +``` + deleted: packages/core/src/html/NodeProcessor.js + new file: packages/core/src/html/NodeProcessor.ts +``` + +**Problem**: Files changed too much, or git is confused. + +**Solution**: +```bash +# Reset and try again +git reset +git add packages/core/src/html/NodeProcessor.ts +git status +# Should now show "renamed" +``` + +## Step 4: Commit with --no-verify + +### Commit Message Format + +```bash +git commit --no-verify -m "Rename [component/directory] to TypeScript" +``` + +**Examples**: +```bash +git commit --no-verify -m "Rename core/src/html to TypeScript" +git commit --no-verify -m "Rename NodeProcessor to TypeScript" +git commit --no-verify -m "Rename core/src/Page to TypeScript" +``` + +### Why --no-verify? + +**Git hooks will fail** because: +- TypeScript files don't compile yet (wrong import/export syntax) +- Pre-commit hook tries to build and lint +- Build fails → Commit blocked + +`--no-verify` skips hooks for this commit only. + +### Verify Commit + +```bash +# Check commit was created +git log -1 --stat + +# Should show renames: +# packages/core/src/html/NodeProcessor.js => packages/core/src/html/NodeProcessor.ts | 0 +``` + +The `| 0` means zero lines changed (perfect for rename commit). + +## Step 5: Verify Git History Is Preserved + +```bash +# Check file history follows the rename +git log --follow packages/core/src/html/NodeProcessor.ts + +# Should show history from when it was .js +``` + +**What you should see**: +- Commits from before the rename +- Full file history +- Blame information intact + +## Common Rename Issues + +### Issue 1: Git Shows Deleted + Added + +**Symptom**: +``` +deleted: File.js +new file: File.ts +``` + +**Cause**: Files too different, or content was modified during rename. + +**Solution**: +```bash +# Reset +git reset + +# Check if file was accidentally modified +diff packages/core/src/File.js packages/core/src/File.ts + +# If different, undo changes and rename again +git checkout packages/core/src/File.js +mv packages/core/src/File.js packages/core/src/File.ts +``` + +### Issue 2: Pre-commit Hook Runs Despite --no-verify + +**Symptom**: Hook still runs and fails. + +**Cause**: Using `git commit --no-verify` in wrong order. + +**Solution**: +```bash +# Correct order +git commit --no-verify -m "Message" + +# Not this +git commit -m "Message" --no-verify +``` + +### Issue 3: Forgot to Update Ignore Lists + +**Symptom**: Git wants to stage compiled `.js` files. + +**Cause**: Didn't add to `.gitignore` first. + +**Solution**: +```bash +# Reset commit +git reset HEAD~1 + +# Update ignore lists +echo "packages/core/src/html/*.js" >> .gitignore +echo "packages/core/src/html/*.js" >> .eslintignore + +# Stage changes again +git add . +git commit --no-verify -m "Rename core/src/html to TypeScript" +``` + +### Issue 4: Mixed Renamed and Modified + +**Symptom**: +``` +renamed: File.js -> File.ts +modified: File.ts +``` + +**Cause**: Accidentally edited content after rename. + +**Solution**: +```bash +# Reset +git reset + +# Restore original +git checkout packages/core/src/File.js +mv packages/core/src/File.js packages/core/src/File.ts + +# Stage only the rename +git add packages/core/src/File.ts +``` + +## Rename Commit Checklist + +Before proceeding to adapt commit: + +- [ ] `.gitignore` includes compiled `.js` paths +- [ ] `.eslintignore` includes compiled `.js` paths +- [ ] All `.js` files renamed to `.ts` +- [ ] `git status` shows files as "renamed" +- [ ] Commit created with `--no-verify` +- [ ] Commit message follows format +- [ ] `git log --follow` shows preserved history +- [ ] Commit shows `| 0` (zero lines changed) + +## What's in This Commit? + +**Included**: +- ✅ Modified `.gitignore` +- ✅ Modified `.eslintignore` +- ✅ Renamed files (.js → .ts) + +**Not included**: +- ❌ Content changes +- ❌ Import/export syntax changes +- ❌ Type annotations +- ❌ Package.json changes + +## Next Steps + +Your "Rename" commit is complete. The files are now `.ts` but with JavaScript syntax. + +**Next**: Proceed to [adapt-commit.md](adapt-commit.md) to fix TypeScript errors and convert syntax. + +## Quick Reference + +```bash +# Full rename workflow +echo "path/to/file.js" >> .gitignore +echo "path/to/file.js" >> .eslintignore +mv packages/core/src/File.js packages/core/src/File.ts +git add . +git status # Verify shows "renamed" +git commit --no-verify -m "Rename File to TypeScript" +git log -1 --stat # Verify | 0 lines changed +``` diff --git a/.opencode/skills/markbind-typescript-migration/references/troubleshooting.md b/.opencode/skills/markbind-typescript-migration/references/troubleshooting.md new file mode 100644 index 0000000000..d1a7703144 --- /dev/null +++ b/.opencode/skills/markbind-typescript-migration/references/troubleshooting.md @@ -0,0 +1,403 @@ +# TypeScript Migration Troubleshooting + +Common issues and solutions when migrating JavaScript files to TypeScript in MarkBind. + +## Git Similarity Index Issues + +### Problem: Git doesn't detect file renames +If `git diff --staged --stat` shows deletions and additions instead of renames: + +``` +src/old.js | 200 ------------------------- +src/old.ts | 200 +++++++++++++++++++++++++ +``` + +### Solutions + +1. **Check similarity threshold**: Git requires >50% similarity by default + ```bash + # Lower threshold temporarily + git config diff.renames true + git config diff.renameLimit 999999 + ``` + +2. **Verify file is unchanged**: Make sure you ONLY renamed, no content changes + ```bash + # Compare contents (ignore extension) + diff src/old.js src/old.ts + ``` + +3. **Split into smaller commits**: If file is too large, Git may fail detection + ```bash + # Commit smaller batches of renames + git add src/component1.ts + git commit -m "Rename component1.js to .ts" + git add src/component2.ts + git commit -m "Rename component2.js to .ts" + ``` + +4. **Use git mv**: Explicitly tell Git about the rename + ```bash + git mv src/old.js src/old.ts + # Make no changes to content yet! + git commit -m "Rename old.js to .ts" + ``` + +## Import/Export Mismatch Errors + +### Problem: "Cannot find module" or "has no default export" + +```typescript +// Error: Module '"./foo"' has no default export +import foo from './foo'; +``` + +### Solutions + +1. **Check what the module actually exports**: + ```bash + # Look at the source file + grep -E "module.exports|export" src/foo.ts + ``` + +2. **Match import style to export style**: + ```typescript + // If source has: module.exports = { foo, bar } + import * as fooModule from './foo'; + const { foo, bar } = fooModule; + + // If source has: module.exports.foo = ... + import * as fooModule from './foo'; + const foo = fooModule.foo; + + // If source has: export default foo + import foo from './foo'; + + // If source has: export { foo, bar } + import { foo, bar } from './foo'; + ``` + +3. **Add type assertion for problematic imports**: + ```typescript + // Temporary workaround + const foo = require('./foo') as any; + ``` + +## TypeScript Compilation Errors + +### Problem: "Property does not exist on type" + +```typescript +// Error: Property 'customField' does not exist on type 'Node' +node.customField = value; +``` + +### Solutions + +1. **Add type assertion**: + ```typescript + (node as any).customField = value; + ``` + +2. **Define interface extension** (better, but more work): + ```typescript + interface ExtendedNode extends Node { + customField?: string; + } + const extNode = node as ExtendedNode; + extNode.customField = value; + ``` + +3. **Use index signature**: + ```typescript + interface NodeWithDynamicProps extends Node { + [key: string]: any; + } + ``` + +### Problem: "Type 'X' is not assignable to type 'Y'" + +```typescript +// Error: Type 'string | undefined' is not assignable to type 'string' +const name: string = obj.name; +``` + +### Solutions + +1. **Use optional chaining and nullish coalescing**: + ```typescript + const name: string = obj.name ?? 'default'; + ``` + +2. **Add type guard**: + ```typescript + if (obj.name !== undefined) { + const name: string = obj.name; + } + ``` + +3. **Use non-null assertion** (only if you're certain): + ```typescript + const name: string = obj.name!; + ``` + +### Problem: "Cannot redeclare block-scoped variable" + +```typescript +// Error in test files +const foo = require('./foo'); +const foo = require('./foo'); // Different test +``` + +### Solution: Use different variable names or scoping + +```typescript +// Option 1: Different names +const fooModule1 = require('./foo'); +const fooModule2 = require('./foo'); + +// Option 2: Block scope +{ + const foo = require('./foo'); + // use foo +} +{ + const foo = require('./foo'); + // use foo again +} +``` + +## Test Failures After Migration + +### Problem: Functional tests fail with import errors + +``` +Error: Cannot use import statement outside a module +``` + +### Solutions + +1. **Check jest.config.js transform settings**: + ```javascript + module.exports = { + transform: { + '^.+\\.ts$': 'ts-jest', + '^.+\\.js$': 'babel-jest', + }, + }; + ``` + +2. **Verify tsconfig.json includes test files**: + ```json + { + "include": ["src/**/*", "test/**/*"] + } + ``` + +3. **Check for mixed import styles in tests**: + ```typescript + // Don't mix CommonJS and ES6 in same file + const foo = require('./foo'); // CommonJS + import bar from './bar'; // ES6 + + // Pick one style per file + ``` + +### Problem: Snapshot tests fail after migration + +### Solutions + +1. **Update snapshots if only formatting changed**: + ```bash + npm run test -- -u + ``` + +2. **Review snapshot diffs carefully**: + ```bash + git diff test/__snapshots__/ + ``` + +3. **If snapshots show real behavior changes, investigate**: + - Check if TypeScript strict mode caught real bugs + - Verify import/export conversions are correct + - Look for accidental changes during rename/adapt + +## Pre-commit Hook Failures + +### Problem: Linting fails on migrated files + +``` +Error: Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser +``` + +### Solutions + +1. **Check .eslintrc.js includes TS files**: + ```javascript + module.exports = { + overrides: [{ + files: ['*.ts'], + parserOptions: { + project: './tsconfig.json', + }, + }], + }; + ``` + +2. **Verify tsconfig.json includes the file**: + ```json + { + "include": ["src/**/*", "packages/**/*"] + } + ``` + +3. **Temporarily disable for problematic files** (last resort): + ```typescript + /* eslint-disable */ + // problematic code + /* eslint-enable */ + ``` + +### Problem: Prettier reformats migrated files + +### Solution: This is expected, let it happen + +```bash +# Prettier will auto-format on pre-commit +# Just re-stage the changes +git add . +git commit -m "Adapt foo.ts to TypeScript" +``` + +## CI/Build Failures + +### Problem: GitHub Actions fails with "Cannot find module" + +### Solutions + +1. **Check package.json has all type definitions**: + ```bash + npm run build + # If it fails locally, CI will fail too + ``` + +2. **Verify TypeScript version matches CI**: + ```bash + # Check .github/workflows/*.yml + # Ensure Node version and npm version match + ``` + +3. **Check for missing dependencies**: + ```bash + npm ci + npm run build + ``` + +## Debugging Strategy + +When migration issues occur, follow this systematic approach: + +1. **Isolate the problem**: + ```bash + # Does it compile? + npx tsc --noEmit + + # Does it lint? + npm run lint + + # Do tests pass? + npm run test + ``` + +2. **Check the commit history**: + ```bash + # Did you truly only rename in the Rename commit? + git show + + # Are there unexpected changes? + git diff ^.. --stat + ``` + +3. **Compare with original**: + ```bash + # Check out the original file + git show HEAD^:src/old.js > /tmp/original.js + + # Compare logic (ignoring TS types) + diff -u /tmp/original.js src/old.ts + ``` + +4. **Test in isolation**: + ```bash + # Create minimal reproduction + # Test just the migrated file + npm run test -- --testPathPattern=migrated-file + ``` + +5. **Revert if necessary**: + ```bash + # If you're stuck, revert and try again + git revert HEAD + # Or reset if not pushed yet + git reset --hard HEAD^ + ``` + +## Common Anti-patterns + +### ❌ Don't: Mix changes in Rename commit + +```bash +# BAD: Changed import style during rename +git show +-const foo = require('./bar'); ++import foo from './bar'; +``` + +**Fix**: Revert, rename only, then adapt imports in separate commit. + +### ❌ Don't: Use `any` everywhere to silence errors + +```typescript +// BAD: Defeats purpose of TypeScript +const result: any = await someFunction(); +const data: any = result.data; +return data as any; +``` + +**Fix**: Add proper types incrementally, use `unknown` if truly uncertain. + +### ❌ Don't: Change behavior during migration + +```typescript +// BAD: "Fixed" a bug while migrating +-if (value == null) { // Original used == ++if (value === null) { // Changed to strict equality +``` + +**Fix**: Migrate syntax only. File bugs separately, fix in later commits. + +### ❌ Don't: Commit without running tests + +```bash +# BAD: Skipping verification +git commit -m "Migrate to TypeScript" --no-verify +``` + +**Fix**: Always run full test suite before committing. + +## Getting Help + +If you're stuck after trying these solutions: + +1. **Check similar migrations**: Look at other TypeScript migration commits in the repo + ```bash + git log --all --grep="TypeScript" --grep="Migrate" --oneline + ``` + +2. **Review the dev guide**: Check `docs/devGuide/development/techStack.md` for TypeScript configuration + +3. **Ask maintainers**: File an issue with: + - File being migrated + - Error message (full stack trace) + - What you've tried already + - Minimal reproduction if possible