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 + + + + +``` + +### 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-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-typescript-migration/SKILL.md b/.opencode/skills/markbind-typescript-migration/SKILL.md new file mode 100644 index 0000000000..94ecb7642f --- /dev/null +++ b/.opencode/skills/markbind-typescript-migration/SKILL.md @@ -0,0 +1,263 @@ +--- +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 + +**Update files incrementally**: Migrate one component/directory in each commit to keep PRs focused and reviewable. + +**PR Merge Strategy**: Use **rebase-merge** (not squash-merge) to keep both commits in target branch. + +**Strict TypeScript Settings**: Migrations must adhere to existing strict TypeScript settings (no `any` types/disable linting without justification, etc.). + +## 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 + +if `@types/*` are not available, assess if stand-in types should be created, or explore alternative packages that are actively maintained as replacements. + +## Migration Workflow Overview + +### Phase 0: Planning + +1. Identify files to migrate +1. Determine work scope and order +1. Seek approval from team before starting + +### 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. Update package.json to add tsc compilation step if needed +6. Run tests (npm run setup && npm run test && npm run lintfix) +7. 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 + +## 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