diff --git a/.eslintrc.json b/.eslintrc.json index b509fe7..31073c4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,23 +1,23 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "env": { - "node": true, - "es2021": true - }, - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "rules": { - "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], - "@typescript-eslint/no-explicit-any": "warn", - "no-console": "off" - }, - "ignorePatterns": ["out/", "node_modules/", ".vscode-test/"] -} +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "env": { + "node": true, + "es2021": true + }, + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/no-explicit-any": "warn", + "no-console": "off" + }, + "ignorePatterns": ["out/", "node_modules/", ".vscode-test/"] +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 782a0ad..5662b43 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,10 @@ -version: 2 -updates: - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f475141..d4a9f7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,33 +1,68 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - -permissions: - contents: read - -jobs: - build-and-lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Compile - run: npm run compile - - - name: Lint - run: npm run lint +name: CI + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + matrix: + node-version: [18, 20, 22] + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Compile + run: npm run compile + + - name: Test + run: npm test + + package: + runs-on: ubuntu-latest + needs: build + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Package VSIX + run: npx @vscode/vsce package --no-dependencies + + - name: Upload VSIX artifact + uses: actions/upload-artifact@v4 + with: + name: vscode-schemaforge + path: "*.vsix" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..b113dba --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,60 @@ +name: Publish to VS Code Marketplace + +on: + release: + types: [published] + workflow_dispatch: + inputs: + target: + description: 'Publish target (marketplace or open-vsx)' + default: 'marketplace' + type: choice + options: + - marketplace + - open-vsx + - both + +permissions: + contents: read + +jobs: + publish-marketplace: + runs-on: ubuntu-latest + if: ${{ inputs.target == 'marketplace' || inputs.target == 'both' || github.event_name == 'release' }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Publish to VS Code Marketplace + run: npx @vscode/vsce publish -p ${{ secrets.VSCE_PAT }} + if: ${{ inputs.target != 'open-vsx' }} + + publish-open-vsx: + runs-on: ubuntu-latest + if: ${{ inputs.target == 'open-vsx' || inputs.target == 'both' }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Publish to Open VSX Registry + run: npx ovsx publish -p ${{ secrets.OVSX_PAT }} diff --git a/.gitignore b/.gitignore index 8ac0960..d96e6c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,21 @@ -# Dependencies -node_modules/ - -# Build output -out/ -*.vsix - -# IDE -.vscode-test/ -.vscode/*.log - -# Temp files -.temp/ - -# OS files -.DS_Store -Thumbs.db - -# Local opencode config -AGENTS.md -.agents/ - +# Dependencies +node_modules/ + +# Build output +out/ +*.vsix + +# IDE +.vscode-test/ +.vscode/*.log + +# Temp files +.temp/ + +# OS files +.DS_Store +Thumbs.db + +# Local opencode config +.agents/ + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e0b21fc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# vscode-schemaforge + +## Purpose +Bidirectional schema converter extension for VS Code — convert between SQL DDL, Prisma, Django, TypeORM, SQLAlchemy, Alembic, JSON Schema, GraphQL, EF Core, and Scala case classes. + +## Build & Test Commands +- Install: `npm install` +- Test: `npm test` (runs vscode-test) +- Lint: `npm run lint` (eslint src --ext ts) +- Build: `npm run compile` (tsc -p ./) +- Pre-publish: `npm run vscode:prepublish` + +## Architecture +Key directories: +- `src/` — Main extension source + - `extension.ts` — Extension entry point + activation + - `cli.ts` — SchemaForge CLI wrapper + - `commands/` — Convert, detect, diff command handlers + - `panels/` — Live preview WebView panel + - `providers/` — Custom editor provider +- `.github/workflows/` — CI/CD (2 workflows) +- `syntaxes/` — TextMate grammars +- `media/` — Icons/assets + +## Conventions +- Language: TypeScript +- Test framework: @vscode/test-electron (vscode-test) +- CI: GitHub Actions (2 workflows) +- Linting: eslint with @typescript-eslint +- Compilation: TypeScript 5.3+ +- VS Code Engine: ^1.85.0 +- Publisher: revenue-holdings \ No newline at end of file diff --git a/LICENSE b/LICENSE index a575db2..6052a2d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2026 Revenue Holdings - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2026 Revenue Holdings + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e52af10..01487cc 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,84 @@ -# SchemaForge VS Code Extension - -[](https://github.com/Coding-Dev-Tools/vscode-schemaforge/actions/workflows/ci.yml) -[](LICENSE) - -Bidirectional schema converter extension for VS Code — convert between SQL DDL, Prisma, Django, TypeORM, SQLAlchemy, Alembic, JSON Schema, GraphQL, EF Core, and Scala case classes. - -Part of the [DevForge](https://devforge.revenueholdings.dev) open-source CLI tool suite by [Revenue Holdings](https://revenueholdings.dev). - -## Features - -- **Live Preview** — side panel showing your active schema file converted to all 11 formats -- **Quick Convert** — `Ctrl+Alt+S` to convert the active editor's schema to your target format -- **Format Detection** — `Ctrl+Alt+D` to detect schema format -- **Schema Diff** — diff two schema files in VS Code's native diff editor -- **Right-Click** — convert or detect from the file explorer context menu -- **Custom Editor** — rich conversion preview for `.schemaforge` files - -## Prerequisites - -Requires [SchemaForge](https://github.com/Coding-Dev-Tools/schemaforge) CLI installed: - -```bash -pip install schemaforge -``` - -The CLI must be available on your PATH. - -## Extension Settings - -| Setting | Default | Description | -|---------|---------|-------------| -| `schemaforge.cliPath` | `schemaforge` | Path to the schemaforge CLI executable | -| `schemaforge.defaultTargetFormat` | `sql` | Default conversion target (`sql`, `prisma`, `django`, `sqlalchemy`, `alembic`, `json_schema`, `graphql`, `ef`, `scala`) | -| `schemaforge.livePreview.enabled` | `true` | Enable live preview when editing schema files | - -## Commands - -| Command | Keybinding | Description | -|---------|------------|-------------| -| SchemaForge: Convert Schema File... | — | Select a schema file and target format to convert | -| SchemaForge: Quick Convert (Active Editor) | `Ctrl+Alt+S` (`Cmd+Alt+S` on Mac) | Convert the active editor content to default target format | -| SchemaForge: Diff Two Schema Files | — | Select two files and view differences | -| SchemaForge: Detect Format | `Ctrl+Alt+D` (`Cmd+Alt+D` on Mac) | Detect the format of the active editor's content | - -## Development - -```bash -# Clone -git clone https://github.com/Coding-Dev-Tools/vscode-schemaforge.git -cd vscode-schemaforge - -# Install dependencies -npm install - -# Compile -npm run compile - -# Lint -npm run lint - -# Launch Extension Development Host -# Press F5 in VS Code -``` - -### Project Structure - -``` -src/ -├── extension.ts # Extension entry point + activation -├── cli.ts # SchemaForge CLI wrapper -├── commands/ -│ ├── convert.ts # Convert command handler -│ ├── detect.ts # Format detection handler -│ └── diff.ts # Schema diff handler -├── panels/ -│ └── previewPanel.ts # Live preview WebView panel -└── providers/ - └── schemaEditorProvider.ts # Custom editor provider -``` - -## License - -MIT — see [LICENSE](LICENSE) for details. +# SchemaForge VS Code Extension + +[](https://github.com/Coding-Dev-Tools/vscode-schemaforge/actions/workflows/ci.yml) +[](LICENSE) + +Bidirectional schema converter extension for VS Code — convert between SQL DDL, Prisma, Django, TypeORM, SQLAlchemy, Alembic, JSON Schema, GraphQL, EF Core, and Scala case classes. + +Part of the [DevForge](https://devforge.revenueholdings.dev) open-source CLI tool suite by [Revenue Holdings](https://revenueholdings.dev). + +## Features + +- **Live Preview** — side panel showing your active schema file converted to all 11 formats +- **Quick Convert** — `Ctrl+Alt+S` to convert the active editor's schema to your target format +- **Format Detection** — `Ctrl+Alt+D` to detect schema format +- **Schema Diff** — diff two schema files in VS Code's native diff editor +- **Right-Click** — convert or detect from the file explorer context menu +- **Custom Editor** — rich conversion preview for `.schemaforge` files + +## Prerequisites + +Requires [SchemaForge](https://github.com/Coding-Dev-Tools/schemaforge) CLI installed: + +```bash +pip install schemaforge +``` + +The CLI must be available on your PATH. + +## Extension Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `schemaforge.cliPath` | `schemaforge` | Path to the schemaforge CLI executable | +| `schemaforge.defaultTargetFormat` | `sql` | Default conversion target (`sql`, `prisma`, `django`, `sqlalchemy`, `alembic`, `json_schema`, `graphql`, `ef`, `scala`) | +| `schemaforge.livePreview.enabled` | `true` | Enable live preview when editing schema files | + +## Commands + +| Command | Keybinding | Description | +|---------|------------|-------------| +| SchemaForge: Convert Schema File... | — | Select a schema file and target format to convert | +| SchemaForge: Quick Convert (Active Editor) | `Ctrl+Alt+S` (`Cmd+Alt+S` on Mac) | Convert the active editor content to default target format | +| SchemaForge: Diff Two Schema Files | — | Select two files and view differences | +| SchemaForge: Detect Format | `Ctrl+Alt+D` (`Cmd+Alt+D` on Mac) | Detect the format of the active editor's content | + +## Development + +```bash +# Clone +git clone https://github.com/Coding-Dev-Tools/vscode-schemaforge.git +cd vscode-schemaforge + +# Install dependencies +npm install + +# Compile +npm run compile + +# Lint +npm run lint + +# Launch Extension Development Host +# Press F5 in VS Code +``` + +### Project Structure + +``` +src/ +├── extension.ts # Extension entry point + activation +├── cli.ts # SchemaForge CLI wrapper +├── commands/ +│ ├── convert.ts # Convert command handler +│ ├── detect.ts # Format detection handler +│ └── diff.ts # Schema diff handler +├── panels/ +│ └── previewPanel.ts # Live preview WebView panel +└── providers/ + └── schemaEditorProvider.ts # Custom editor provider +``` + +## License + +MIT — see [LICENSE](LICENSE) for details. diff --git a/src/panels/previewPanel.ts b/src/panels/previewPanel.ts index 42847b2..bcef99c 100644 --- a/src/panels/previewPanel.ts +++ b/src/panels/previewPanel.ts @@ -1,217 +1,217 @@ -import * as vscode from 'vscode'; -import { execSchemaForge, getAvailableFormats } from '../cli'; - -/** - * WebView panel for live schema preview. - * Shows conversions of the active schema file to all other formats. - */ -export class SchemaPreviewPanel { - private panel: vscode.WebviewPanel | undefined; - private context: vscode.ExtensionContext; - private currentFile: string = ''; - - constructor(context: vscode.ExtensionContext) { - this.context = context; - } - - show() { - if (this.panel) { - this.panel.reveal(vscode.ViewColumn.Beside); - return; - } - - this.panel = vscode.window.createWebviewPanel( - 'schemaforgePreview', - 'SchemaForge Live Preview', - vscode.ViewColumn.Beside, - { - enableScripts: true, - retainContextWhenHidden: true, - } - ); - - this.panel.onDidDispose(() => { - this.panel = undefined; - }); - - // Initialize with current editor - const editor = vscode.window.activeTextEditor; - if (editor) { - this.currentFile = editor.document.fileName; - } - - this.render(); - - // Listen for configuration changes - this.context.subscriptions.push( - vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('schemaforge')) { - this.render(); - } - }) - ); - } - - refresh(filePath?: string) { - if (!this.panel) return; - - if (filePath) { - this.currentFile = filePath; - } - - this.render(); - } - - private async render() { - if (!this.panel || !this.currentFile) { - return; - } - - this.panel.title = `SchemaForge: ${this.currentFile.split(/[/\\]/).pop()}`; - this.panel.webview.html = '
Loading...
'; - - try { - // Detect format - const detectResult = await execSchemaForge(['detect', this.currentFile]); - const sourceFormat = detectResult.trim(); - - // Get all formats - const allFormats = ['sql', 'prisma', 'drizzle', 'typeorm', 'django', 'sqlalchemy', 'alembic', 'json_schema', 'graphql', 'ef', 'scala']; - const targetFormats = allFormats.filter(f => f !== sourceFormat); - - // Convert to all other formats (limit to 5 most relevant to keep it fast) - const previewFormats = targetFormats.slice(0, 5); - const conversions: Array<{ format: string; result: string; error?: string }> = []; - - for (const fmt of previewFormats) { - try { - const result = await execSchemaForge(['convert', this.currentFile, '--from', sourceFormat, '--to', fmt]); - conversions.push({ format: fmt, result: result || '(empty result)' }); - } catch (e: any) { - conversions.push({ format: fmt, result: '', error: e.message }); - } - } - - // Also run detect --verbose for details - let detectDetails = ''; - try { - detectDetails = await execSchemaForge(['detect', '--verbose', this.currentFile]); - } catch { /* detect --verbose is best-effort */ } - - this.panel.webview.html = this.getPreviewHtml( - this.currentFile, - sourceFormat, - conversions, - detectDetails - ); - - } catch (e: any) { - this.panel.webview.html = this.getErrorHtml(e.message); - } - } - - private getPreviewHtml( - filePath: string, - sourceFormat: string, - conversions: Array<{ format: string; result: string; error?: string }>, - detectDetails: string - ): string { - const fileName = filePath.split(/[/\\]/).pop(); - - const conversionTabs = conversions.map((c, i) => { - const active = i === 0 ? 'active' : ''; - const content = c.error - ? `${this.escapeHtml(c.result)}`;
- return `
- Open a schema file to see live conversion previews
-`; - } - - private getErrorHtml(message: string): string { - return ` - -Error:
-${this.escapeHtml(message)}
- Loading...
'; + + try { + // Detect format + const detectResult = await execSchemaForge(['detect', this.currentFile]); + const sourceFormat = detectResult.trim(); + + // Get all formats + const allFormats = ['sql', 'prisma', 'drizzle', 'typeorm', 'django', 'sqlalchemy', 'alembic', 'json_schema', 'graphql', 'ef', 'scala']; + const targetFormats = allFormats.filter(f => f !== sourceFormat); + + // Convert to all other formats (limit to 5 most relevant to keep it fast) + const previewFormats = targetFormats.slice(0, 5); + const conversions: Array<{ format: string; result: string; error?: string }> = []; + + for (const fmt of previewFormats) { + try { + const result = await execSchemaForge(['convert', this.currentFile, '--from', sourceFormat, '--to', fmt]); + conversions.push({ format: fmt, result: result || '(empty result)' }); + } catch (e: any) { + conversions.push({ format: fmt, result: '', error: e.message }); + } + } + + // Also run detect --verbose for details + let detectDetails = ''; + try { + detectDetails = await execSchemaForge(['detect', '--verbose', this.currentFile]); + } catch { /* detect --verbose is best-effort */ } + + this.panel.webview.html = this.getPreviewHtml( + this.currentFile, + sourceFormat, + conversions, + detectDetails + ); + + } catch (e: any) { + this.panel.webview.html = this.getErrorHtml(e.message); + } + } + + private getPreviewHtml( + filePath: string, + sourceFormat: string, + conversions: Array<{ format: string; result: string; error?: string }>, + detectDetails: string + ): string { + const fileName = filePath.split(/[/\\]/).pop(); + + const conversionTabs = conversions.map((c, i) => { + const active = i === 0 ? 'active' : ''; + const content = c.error + ? `${this.escapeHtml(c.result)}`;
+ return `
+ Open a schema file to see live conversion previews
+`; + } + + private getErrorHtml(message: string): string { + return ` + +Error:
+${this.escapeHtml(message)}
+ Loading SchemaForge preview...
`; - } - - private getEmptyHtml(): string { - return ` - -Empty schema file. Add content to see format conversions.
-`; - } - - private getErrorHtml(message: string): string { - const escaped = message.replace(/&/g, '&').replace(//g, '>'); - return ` - -${escaped}${c.result.replace(/&/g, '&').replace(//g, '>')}`;
- return `Loading SchemaForge preview...
`; + } + + private getEmptyHtml(): string { + return ` + +Empty schema file. Add content to see format conversions.
+`; + } + + private getErrorHtml(message: string): string { + const escaped = message.replace(/&/g, '&').replace(//g, '>'); + return ` + +${escaped}${c.result.replace(/&/g, '&').replace(//g, '>')}`;
+ return `