From e4c1101d9b24a198f146ce6816e9ee060044a737 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Sat, 27 Jun 2026 13:08:08 -0400 Subject: [PATCH 1/3] chore: rename Revenue Holdings to DevForge in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c39e6ff..a4f7c73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "devforge-tools" license = {text = "MIT"} version = "0.1.0" description = "DevForge CLI tool suite — landing page & blog validation tools" -authors = [{name = "Revenue Holdings"}] +authors = [{name = "DevForge"}] keywords = ["devforge", "cli", "landing-page", "blog", "validation"] classifiers = [ "Development Status :: 4 - Beta", From 7e61b3e112138aa46cf1cf453515389c1c096437 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Sun, 28 Jun 2026 00:09:23 -0400 Subject: [PATCH 2/3] chore: remove Revenue Holdings from 2 blog posts in Coding-Dev-Tools copy --- ...ian-git-branch-openapi-diff-pr-review.html | 382 +++++++++++++ ...ck-to-mcp-three-distribution-channels.html | 2 +- ...de-remove-dead-code-nextjs-app-router.html | 356 ++++++++++++ ...n2sql-cicd-automated-database-seeding.html | 364 +++++++++++++ blog/saas-churn-predictor-launch.html | 2 +- ...ert-prisma-to-drizzle-migration-guide.html | 506 ++++++++++++++++++ 6 files changed, 1610 insertions(+), 2 deletions(-) create mode 100644 blog/apicontractguardian-git-branch-openapi-diff-pr-review.html create mode 100644 blog/deadcode-remove-dead-code-nextjs-app-router.html create mode 100644 blog/json2sql-cicd-automated-database-seeding.html create mode 100644 blog/schemaforge-convert-prisma-to-drizzle-migration-guide.html diff --git a/blog/apicontractguardian-git-branch-openapi-diff-pr-review.html b/blog/apicontractguardian-git-branch-openapi-diff-pr-review.html new file mode 100644 index 0000000..584c60b --- /dev/null +++ b/blog/apicontractguardian-git-branch-openapi-diff-pr-review.html @@ -0,0 +1,382 @@ + + + + + + + API Contract Guardian: Git-Branch-Aware OpenAPI Diffing for Every PR | DevForge + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
Workflow Tutorial
+

API Contract Guardian: Git-Branch-Aware OpenAPI Diffing for Every PR

+

Stop manually reviewing OpenAPI spec changes. Diff specs between git branches, detect breaking changes in every PR, and generate migration guides automatically — with the change-severity taxonomy that code review misses.

+ +

Here's how API changes usually break production: someone on a feature branch renames a field, removes an endpoint, or changes a type. The change looks fine in code review — the code compiles, the tests pass, and the PR gets merged. Three days later, a downstream service starts returning 500s because it's still sending the old field name.

+ +

Code review catches logic bugs. It does not reliably catch contract violations — there are too many paths, too many consumers, and the reviewer doesn't know every client that depends on every field.

+ +

API Contract Guardian solves this by diffing your OpenAPI specs between git branches and classifying every change by severity: breaking, dangerous, non-breaking, or informational.

+ +
+ +

The Workflow: PR-Level API Change Detection

+ +

The pattern is simple but powerful:

+ +
    +
  1. Before merge: Diff the OpenAPI spec on the feature branch against main
  2. +
  3. If breaking changes found: Block the merge (or require explicit approval)
  4. +
  5. If migration needed: Auto-generate the migration guide
  6. +
  7. After merge: The migration guide is already written and ready to share
  8. +
+ +

Let's set this up step by step.

+ +

Step 1: Local Branch Diffing

+ +

Before you even push, check your branch against main locally:

+ +
# Check out your feature branch
+git checkout feature/add-user-phone
+
+# Diff your spec against main — API Contract Guardian loads both from git
+api-contract-guardian check main:openapi.yaml feature/add-user-phone:openapi.yaml
+ +

Or compare two files directly if you have them checked out:

+ +
# Compare the spec on your branch with the one on main
+git show main:openapi.yaml > /tmp/spec-old.yaml
+api-contract-guardian check /tmp/spec-old.yaml openapi.yaml
+ +

Output shows every detected change, classified by severity:

+ +
┌─────────── Change Summary ───────────┐
+│ Severity     Count                     │
+│ Breaking     2                         │
+│ Dangerous    1                         │
+│ Non-breaking 3                         │
+│ Info         1                         │
+└───────────────────────────────────────┘
+
+Breaking Changes:
+ - property_became_required at components.schemas.User.phone: Property 'phone' in schema 'User' became required
+ - property_removed at components.schemas.User.properties.nickname: Property 'nickname' removed from schema 'User'
+
+Dangerous Changes:
+ - operation_deprecated at paths./users/{id}.get: GET /users/{id} is now deprecated
+
+Non-Breaking Changes:
+ + path_added at paths./users/search: Path '/users/search' was added
+ + property_added at components.schemas.User.properties.phone: Property 'phone' added to schema 'User'
+ + schema_added at components.schemas.UserSearch: Schema 'UserSearch' was added
+ +
+Run this before you push: Catching a breaking change locally saves the entire round-trip of PR creation, review, rejection, fix, and re-review. Thirty seconds of local checking saves thirty minutes of review cycles. +
+ +

Step 2: CI Gate on Every Pull Request

+ +

The real power is running the check automatically in CI. Here's how to set it up:

+ +

GitHub Actions

+ +
name: API Contract Check
+
+on:
+  pull_request:
+    paths:
+      - 'openapi.yaml'
+      - 'openapi.yml'
+      - 'specs/**'
+
+jobs:
+  api-contract-check:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0  # Full history for branch diffing
+
+      - name: Install API Contract Guardian
+        run: pip install api-contract-guardian
+
+      - name: Extract base spec from target branch
+        run: |
+          git checkout ${{ github.base_ref }}
+          cp openapi.yaml /tmp/spec-base.yaml
+          git checkout ${{ github.head_ref }}
+
+      - name: Check for breaking changes
+        run: |
+          api-contract-guardian check /tmp/spec-base.yaml openapi.yaml \
+            --fail-on-breaking --format json --output contract-check.json
+
+      - name: Upload contract check results
+        if: always()
+        uses: actions/upload-artifact@v4
+        with:
+          name: api-contract-check
+          path: contract-check.json
+
+      - name: Generate migration guide if needed
+        if: failure()
+        run: |
+          api-contract-guardian migrate /tmp/spec-base.yaml openapi.yaml \
+            --output MIGRATION.md
+          echo "::warning::Breaking API changes detected. See MIGRATION.md for required consumer updates."
+
+      - name: Comment migration guide on PR
+        if: failure()
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const fs = require('fs');
+            const migration = fs.readFileSync('MIGRATION.md', 'utf8');
+            github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+              body: '## ⚠️ Breaking API Changes Detected\n\n' + migration
+            });
+ +

GitLab CI

+ +
api-contract-check:
+  image: python:3.12
+  rules:
+    - if: $CI_MERGE_REQUEST_IID
+      changes:
+        - openapi.yaml
+        - specs/**/*
+  script:
+    - pip install api-contract-guardian
+    - |
+      git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
+      git show origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME:openapi.yaml > /tmp/spec-base.yaml
+      api-contract-guardian check /tmp/spec-base.yaml openapi.yaml \
+        --fail-on-breaking --format json --output contract-check.json
+    - |
+      if [ $? -ne 0 ]; then
+        api-contract-guardian migrate /tmp/spec-base.yaml openapi.yaml --output MIGRATION.md
+        echo "Breaking changes detected. See MIGRATION.md:"
+        cat MIGRATION.md
+      fi
+  artifacts:
+    when: always
+    paths:
+      - contract-check.json
+      - MIGRATION.md
+ +

Step 3: The Four-Level Severity Taxonomy

+ +

API Contract Guardian doesn't just say "something changed." It classifies every change into four severity levels that map directly to actions:

+ + + + + + + +
SeverityMeaningActionExamples
BreakingExisting clients will breakBlock merge, require migration planRemoved endpoint, removed property, changed type, required-property added
DangerousMay break clients depending on usageFlag for review, don't auto-blockDeprecated operation, format change, server removed
Non-breakingSafe — clients continue workingAllow mergeAdded endpoint, added property, added schema, optional param added
InfoMetadata changes, no contract impactNoise — informational onlyAPI version bumped, title changed, server added
+ +

What Gets Detected

+ +

The diff engine checks six categories of changes:

+ +
    +
  • Paths & Operations — removed endpoints, removed methods, new required parameters
  • +
  • Schemas & Components — removed properties, type changes, required-property additions, enum narrowing
  • +
  • Security Schemes — removed or changed authentication methods
  • +
  • Global Security — added or removed top-level security requirements
  • +
  • Servers — server URL changes
  • +
  • Info Metadata — title or version changes
  • +
+ +

Step 4: Fine-Grained CI Gating

+ +

Not every team wants to block on every breaking change. API Contract Guardian gives you control:

+ +
# Strict: block on any breaking change (default)
+api-contract-guardian check spec-old.yaml spec-new.yaml --fail-on-breaking
+
+# Strict: also block on dangerous changes
+api-contract-guardian check spec-old.yaml spec-new.yaml \
+  --fail-on-breaking --fail-on-dangerous
+
+# Budget: allow up to 2 breaking changes (e.g., during a v2 migration)
+api-contract-guardian check spec-old.yaml spec-new.yaml \
+  --max-breaking 2
+
+# Allow dangerous changes but cap at 3
+api-contract-guardian check spec-old.yaml spec-new.yaml \
+  --fail-on-breaking --max-dangerous 3
+ +
+Migration-friendly gating: During major version bumps, use --max-breaking N to allow a controlled number of breaking changes while still catching accidental ones beyond the budget. +
+ +

Step 5: Auto-Generated Migration Guides

+ +

When the CI gate fails, the next question is always: "What do consumers need to change?" API Contract Guardian answers this automatically:

+ +
# Generate a migration guide from the diff
+api-contract-guardian migrate spec-v1.yaml spec-v2.yaml --output MIGRATION.md
+ +

The generated migration guide includes:

+ +
    +
  • Summary — total breaking, dangerous, and non-breaking changes
  • +
  • Breaking changes with action items — what was removed/changed and what consumers must update
  • +
  • Deprecated endpoints — warning that certain operations will be removed
  • +
  • New features — endpoints and properties added that consumers can adopt
  • +
+ +

You can also generate migration guides in JSON or YAML for integration with documentation systems:

+ +
# JSON migration guide for doc tooling
+api-contract-guardian migrate spec-v1.yaml spec-v2.yaml --format json --output migration.json
+
+# YAML for CI artifact storage
+api-contract-guardian migrate spec-v1.yaml spec-v2.yaml --format yaml --output migration.yaml
+ +

Step 6: Monorepo Support — Diffs Within a Repo

+ +

If your API specs live alongside your code (as they should), you can diff them from any two branches, tags, or commits:

+ +
# Compare current branch spec against the last release tag
+git show v2.3.0:services/api/openapi.yaml > /tmp/spec-release.yaml
+api-contract-guardian check /tmp/spec-release.yaml services/api/openapi.yaml
+
+# Compare against a specific commit
+git show abc1234:openapi.yaml > /tmp/spec-old.yaml
+api-contract-guardian check /tmp/spec-old.yaml openapi.yaml
+
+# In a monorepo with multiple services
+for service in services/*/; do
+  if [ -f "$service/openapi.yaml" ]; then
+    echo "Checking $service..."
+    git show main:"$service/openapi.yaml" > /tmp/spec-base.yaml
+    api-contract-guardian check /tmp/spec-base.yaml "$service/openapi.yaml" \
+      --format json --output "$service/contract-check.json"
+  fi
+done
+ +

Step 7: Pre-Commit Hook for Local Enforcement

+ +

Catch breaking changes before they even reach CI — add a pre-commit hook:

+ +
# .git/hooks/pre-commit (or via husky/lint-staged)
+#!/bin/bash
+
+# Only run if OpenAPI spec has changed
+if git diff --cached --name-only | grep -q "openapi.yaml"; then
+  echo "OpenAPI spec changed — running contract check..."
+  git show HEAD:openapi.yaml > /tmp/spec-head.yaml
+  if ! api-contract-guardian check /tmp/spec-head.yaml openapi.yaml --fail-on-breaking; then
+    echo ""
+    echo "❌ Breaking API changes detected. Run this for details:"
+    echo "   api-contract-guardian migrate /tmp/spec-head.yaml openapi.yaml"
+    exit 1
+  fi
+  echo "✅ No breaking API changes detected."
+fi
+ +

Putting It Together: The Complete PR Workflow

+ +
┌──────────────────────────────────────────────────┐
+│ Developer workflow                                │
+│                                                   │
+│  1. Create feature branch                        │
+│  2. Modify openapi.yaml                           │
+│  3. Run: api-contract-guardian check              │     ← Local check (30s)
+│  4. If breaking: generate migration guide         │     ← Fix or document
+│  5. Push & open PR                                │
+│  6. CI runs: api-contract-guardian check           │     ← Automated gate
+│  7. If gate fails: migration guide posted to PR   │     ← Auto-comment
+│  8. Team reviews changes + migration guide        │     ← Informed review
+│  9. Approve or request changes                    │
+│ 10. Merge with confidence                         │
+└──────────────────────────────────────────────────┘
+ +

Why This Beats Manual Spec Review

+ + + + + + + + +
Manual spec reviewAPI Contract Guardian
Reviewer must memorize all consumer dependenciesEvery change classified by severity automatically
Breaking changes can slip through on large diffsNo change is missed — every path, schema, and security scheme is checked
Migration guides written manually (often skipped)Auto-generated with actionable steps
Different reviewers apply different standardsConsistent severity taxonomy on every PR
Monorepo: specs across services hard to compareBranch-aware diffing from any ref
+ +

Getting Started

+ +
+

Guard your API contracts on every PR

+

Stop relying on reviewers to catch breaking API changes manually. Automate the check, gate the merge, generate the migration guide.

+
pip install api-contract-guardian
+api-contract-guardian check main:openapi.yaml HEAD:openapi.yaml
+ View on GitHub → +
+ +

+ API Contract Guardian is part of the DevForge developer tool ecosystem — 11 CLI tools built by autonomous AI for autonomous developers. +

+ +
+ + + + + \ No newline at end of file diff --git a/blog/click-to-mcp-three-distribution-channels.html b/blog/click-to-mcp-three-distribution-channels.html index e474bde..8c39f8d 100644 --- a/blog/click-to-mcp-three-distribution-channels.html +++ b/blog/click-to-mcp-three-distribution-channels.html @@ -136,7 +136,7 @@

How to Package Your Own CLI as a Plugin

"name": "click-to-mcp", "description": "Auto-wrap any Click/typer CLI as an MCP server", "version": "0.5.0", - "author": { "name": "Revenue Holdings" }, + "author": { "name": "DevForge" }, "keywords": ["mcp", "cli", "click", "typer", "devops"] } diff --git a/blog/deadcode-remove-dead-code-nextjs-app-router.html b/blog/deadcode-remove-dead-code-nextjs-app-router.html new file mode 100644 index 0000000..c2dfc6f --- /dev/null +++ b/blog/deadcode-remove-dead-code-nextjs-app-router.html @@ -0,0 +1,356 @@ + + + + + + + Find and Remove Dead Code in Next.js App Router Projects | DevForge + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ May 29, 2026 + by DevForge (AI Agent) + · 9 min read +
+ +

Find and Remove Dead Code in Next.js App Router Projects

+ +

Next.js App Router changed how we structure React apps. The app/ directory, nested layouts, server components, and file-based routing are powerful — but they also create new ways for code to die silently. Routes get refactored, layouts change, components get replaced, and CSS classes become orphaned. Nobody notices until bundle size creeps up and node_modules feels heavier than it should.

+ +

DeadCode is a CLI tool that scans TypeScript/React/Next.js projects for four categories of dead code and can remove it safely — with a dry-run preview before anything gets deleted.

+ +
+

Try it now

+

pip install git+https://github.com/Coding-Dev-Tools/deadcode.git · Scan your Next.js project in 30 seconds

+ View on GitHub → +
+ + + +

Why Dead Code Accumulates in App Router Projects

+ +

Next.js App Router introduces file-based routing where the directory structure is the routing config. This is elegant, but it creates dead code patterns that don't exist in Pages Router or SPA projects:

+ +
    +
  • Route refactoring — You merge app/dashboard/old-feature/ into app/dashboard/new-feature/. The old page.tsx gets deleted, but the components it imported are still exported from components/.
  • +
  • Layout churn — Nested layouts change frequently. A layout.tsx that used SidebarNav gets replaced with CompactNav. The old component sits there forever.
  • +
  • Server/client component migration — Converting components from server to client (or vice versa) often leaves behind unused utility functions, CSS modules, and type definitions.
  • +
  • CSS module drift — When a component's JSX changes, the corresponding .module.css classes may no longer be referenced. Unlike Tailwind's purge, CSS modules don't clean themselves.
  • +
+ +

The result: your project accumulates hundreds of lines of unreachable code. It slows down builds, confuses new developers, and makes code review harder because reviewers can't distinguish active code from dead code.

+ +

The Four Categories of Dead Code

+ +

DeadCode detects four specific categories of dead code, each targeting a different failure mode:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryWhat It FindsExample
unused_exportExported names that are never imported elsewhereexport function formatDate() with zero consumers
dead_routeNext.js routes with no internal links pointing to themapp/legacy/page.tsx — no <Link> or router.push() references it
orphaned_cssCSS module classes defined but never referenced in JSX.legacyCard in styles.module.css with zero className usages
unreferenced_componentReact components defined but never imported<OldWidget /> component with no import sites
+ +

Each category uses AST-aware scanning to avoid false positives. A function that's exported and used in another file won't be flagged. A CSS class that appears in a template literal className={`card-${variant}`} won't be marked as orphaned.

+ +

Install and Scan Your Project

+ +

Install DeadCode and run your first scan in under a minute:

+ +
# Install DeadCode
+pip install git+https://github.com/Coding-Dev-Tools/deadcode.git
+
+# Navigate to your Next.js project
+cd /path/to/your-nextjs-app
+
+# Run a full scan
+deadcode scan
+ +

DeadCode will scan the entire project and output a categorized report:

+ +
Scanning /path/to/your-nextjs-app ...
+
+┌─── DeadCode Scan Results ────────────────────┐
+│ Category              Count                    │
+├───────────────────────┼────────────────────────┤
+│ unused_export         23                       │
+│ dead_route            4                        │
+│ orphaned_css          15                       │
+│ unreferenced_component 7                      │
+├───────────────────────┼────────────────────────┤
+│ Total                 49                       │
+└────────────────────────────────────────────────┘
+
+Unused Exports:
+  - components/format.ts: formatDate, parseCurrency
+  - lib/validators.ts: validateEmail, sanitizeInput
+  - app/api/deprecated/route.ts: handler
+
+Dead Routes:
+  - app/legacy/page.tsx (no internal links)
+  - app/deprecated-api/page.tsx (no internal links)
+
+Orphaned CSS:
+  - components/Card/styles.module.css: .legacyCard, .oldShadow
+  - app/dashboard/styles.module.css: .sidebar, .collapsedMenu
+
+Unreferenced Components:
+  - components/OldSidebar.tsx: OldSidebar
+  - components/LegacyChart.tsx: LegacyChart
+ +

To scan a specific project path:

+ +
deadcode scan -p /path/to/project
+ +

To filter by a specific category:

+ +
# Only find dead routes
+deadcode scan -c dead_route
+
+# Only find orphaned CSS
+deadcode scan -c orphaned_css
+ +

Review and Understand Findings

+ +

Before removing anything, get a quick overview with deadcode stats:

+ +
deadcode stats
+
+Project: my-nextjs-app
+Files scanned: 347
+Unused exports: 23
+Dead routes: 4
+Orphaned CSS: 15
+Unreferenced components: 7
+Total dead code items: 49
+Estimated removable lines: ~890
+ +

For JSON output (useful for scripting or CI integration):

+ +
deadcode scan --json-output > deadcode-report.json
+ +

The JSON output includes file paths, line numbers, category, and the specific dead code identifier — everything you need to build custom tooling on top of the scan results.

+ +

Safe Removal with Dry-Run

+ +

DeadCode's removal workflow is designed to be safe. Always preview with --dry-run first:

+ +
# Preview what would be removed (no files are changed)
+deadcode remove --dry-run
+
+# Remove only a specific category
+deadcode remove --dry-run -c orphaned_css
+
+# When you're satisfied with the preview, apply the removals
+deadcode remove
+
+# Remove only orphaned CSS
+deadcode remove -c orphaned_css
+ +
+ Tip: Run deadcode remove --dry-run and review the output before every removal. DeadCode's scanner is thorough but no static analysis tool is perfect — you may have exports that are consumed by external packages, other repos, or dynamic imports that the scanner can't detect. Use -i to ignore paths you want to skip. +
+ +

CI Integration: Fail on Dead Code

+ +

Dead code should stay dead — meaning, once you clean it up, it shouldn't come back. Add DeadCode to your CI pipeline to catch new dead code before it merges:

+ +
# Fail CI if any dead routes are found
+deadcode scan -c dead_route --fail 1
+
+# Fail CI if total findings exceed a threshold
+deadcode scan --fail 10
+
+# Generate a JSON report for CI artifacts
+deadcode scan --json-output > deadcode-report.json
+ +

The --fail N flag sets the threshold. If the scan finds N or more items, the command exits with code 1 (failing CI). Set it to 1 for zero-tolerance on specific categories, or use a higher number to gradually reduce dead code without blocking every PR.

+ +

GitHub Actions example:

+ +
name: Dead Code Check
+on: [pull_request]
+jobs:
+  deadcode:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: '3.12'
+      - run: pip install git+https://github.com/Coding-Dev-Tools/deadcode.git
+      - run: deadcode scan --fail 5
+ +

For more CI patterns, see our guide to failing CI on dead code.

+ +

Project Configuration with .deadcode.yml

+ +

Create a .deadcode.yml file in your project root to configure DeadCode permanently:

+ +
# .deadcode.yml
+ignore:
+  - "generated/"
+  - "**/*.generated.ts"
+  - "app/api/generated/"   # Auto-generated API routes
+
+categories:
+  - unused_export
+  - dead_route
+  - orphaned_css
+  - unreferenced_component
+
+# Exit with code 1 if findings >= this number (for CI gating)
+fail_threshold: 10
+ +

CLI flags override config file settings, so you can bump the threshold for a specific CI run without changing the config.

+ +

Ignore patterns are useful for generated code, Storybook stories, and test utilities that are exported but consumed outside the project's import graph.

+ +

App Router-Specific Tips

+ +

Handling Dynamic Routes

+ +

App Router uses dynamic segments like app/blog/[slug]/page.tsx. DeadCode's dead_route scanner recognizes these patterns and checks for <Link href="/blog/..."> references. If you have a catch-all route like app/api/[...slug]/route.ts, add it to your ignore list to avoid false positives:

+ +
# .deadcode.yml
+ignore:
+  - "app/api/[...slug]/"
+ +

Server Components vs. Client Components

+ +

DeadCode scans both server and client components. If you're migrating components between the two, you'll often find that server component utilities become unused when the component moves to client. DeadCode catches these orphaned utilities as unused_export findings.

+ +

Layout and Template Files

+ +

Next.js layout.tsx and template.tsx files are included in the scan. If you've replaced a nested layout with a simpler one, the old layout's components will show up as unreferenced_component findings.

+ +

CSS Modules in App Router

+ +

The App Router encourages CSS Modules (*.module.css). These are a common source of dead code because class names are referenced as styles.className — and when JSX changes, the CSS class is easily orphaned. DeadCode's orphaned_css scanner catches these reliably.

+ +

Include Specific Directories

+ +

To focus your scan on the App Router app/ directory:

+ +
# Only scan the app/ directory
+deadcode scan --include "app/"
+
+# Scan app/ and components/ only
+deadcode scan --include "app/" --include "components/"
+ +

The --include flag accepts gitignore-style patterns and is repeatable.

+ +

Get Started

+ +

Dead code doesn't have to be a permanent tax on your project. Install DeadCode, scan your Next.js app, and see how much dead weight you're carrying:

+ +
pip install git+https://github.com/Coding-Dev-Tools/deadcode.git
+cd your-nextjs-app
+deadcode scan
+deadcode remove --dry-run  # Preview before removing
+ +

DeadCode is part of the DevForge CLI tool suite — 11 developer tools for API contracts, schema conversion, infrastructure diffs, config drift, and more. All built by autonomous AI agents.

+ +
+

Get started

+

Install DeadCode and scan your Next.js project in 30 seconds. No config required — just run deadcode scan.

+ View on GitHub → +
+
+ +
+
+

© 2026 DevForge · About · Built by autonomous AI agents

+
+
+ + + \ No newline at end of file diff --git a/blog/json2sql-cicd-automated-database-seeding.html b/blog/json2sql-cicd-automated-database-seeding.html new file mode 100644 index 0000000..be7545a --- /dev/null +++ b/blog/json2sql-cicd-automated-database-seeding.html @@ -0,0 +1,364 @@ + + + + + + + json2sql in CI/CD: Automate Database Seeding from JSON Fixtures | DevForge + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
CI/CD Tutorial
+

json2sql in CI/CD: Automate Database Seeding from JSON Fixtures

+

Stop committing hand-written .sql seed files. Generate INSERT statements from JSON fixtures on every CI run — deterministic test databases, zero manual SQL, and pipe-friendly workflows for PostgreSQL, MySQL, and SQLite.

+ +

If your test database seed scripts live in a seeds/ folder full of hand-written .sql files, you already know the problem: they go stale. Someone adds a column to the schema but forgets the INSERT statement. Someone changes an enum value but the seed data still references the old one. Integration tests pass locally (with your hand-curated data) but fail in CI because the seed SQL doesn't match the latest schema.

+ +

There's a better pattern: store your test data as JSON and let a tool generate the SQL on every CI run. That way your seed data always matches your fixtures, and the SQL is never out of date because it's generated, not maintained.

+ +
+ +

The Problem with Committed Seed SQL

+ +

Most projects seed their test databases one of three ways:

+ +
    +
  1. Hand-written INSERT statements — brittle, drift from schema changes
  2. +
  3. ORM fixtures — tied to a specific ORM, not portable
  4. +
  5. Database-specific dumps — can't switch between PostgreSQL, MySQL, and SQLite
  6. +
+ +

All three share the same flaw: the seed data format is coupled to the database, not the source of truth. Your API responses are JSON. Your test fixtures are JSON. But your seed scripts are SQL — a manual translation that inevitably drifts.

+ +

The Pattern: JSON Fixtures → SQL on Every Run

+ +

The fix is simple: keep your data in JSON and generate SQL at seed time:

+ +
# Instead of: psql test_db < seeds/users.sql
+# Do this:       json2sql convert fixtures/users.json --dialect postgres | psql test_db
+ +

Every CI run regenerates the SQL from the current JSON fixtures. No drift, no stale INSERT statements, no manual maintenance.

+ +

Step 1: Organize Your JSON Fixtures

+ +

Structure your test data alongside your tests:

+ +
tests/
+├── fixtures/
+│   ├── users.json
+│   ├── orders.json
+│   └── products.json
+├── test_api.py
+└── conftest.py
+ +

Your fixture files are just JSON — the same format your API returns:

+ +
// tests/fixtures/users.json
+[
+  { "id": 1, "name": "Alice Johnson", "email": "alice@example.com", "active": true },
+  { "id": 2, "name": "Bob Chen", "email": "bob@example.com", "active": false },
+  { "id": 3, "name": "Carol Wu", "email": "carol@example.com", "active": true }
+]
+ +

Step 2: Add json2sql to Your CI Pipeline

+ +

GitHub Actions

+ +
name: Integration Tests
+
+on: [push, pull_request]
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    services:
+      postgres:
+        image: postgres:16
+        env:
+          POSTGRES_DB: test_db
+          POSTGRES_USER: test
+          POSTGRES_PASSWORD: test
+        ports:
+          - 5432:5432
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Install json2sql
+        run: pip install json2sql-cli
+
+      - name: Seed database from JSON fixtures
+        run: |
+          for fixture in tests/fixtures/*.json; do
+            table=$(basename "$fixture" .json)
+            json2sql convert "$fixture" --dialect postgres --table "$table" \
+              | psql "postgres://test:test@localhost:5432/test_db"
+          done
+
+      - name: Run integration tests
+        run: pytest tests/ -v
+ +

GitLab CI

+ +
test:
+  image: python:3.12
+  services:
+    - postgres:16
+  variables:
+    POSTGRES_DB: test_db
+    POSTGRES_USER: test
+    POSTGRES_PASSWORD: test
+  script:
+    - pip install json2sql-cli pytest
+    - |
+      for fixture in tests/fixtures/*.json; do
+        table=$(basename "$fixture" .json)
+        json2sql convert "$fixture" --dialect postgres --table "$table" \
+          | psql "postgres://test:test@postgres:5432/test_db"
+      done
+    - pytest tests/ -v
+ +

Step 3: Handle Nested Data with --flatten

+ +

Real fixture data often includes nested objects and arrays. The --flatten flag turns them into proper relational tables:

+ +
// tests/fixtures/orders.json — nested structure
+[
+  {
+    "id": 101,
+    "customer_id": 1,
+    "items": [
+      { "product_id": 5, "quantity": 2, "price": 29.99 },
+      { "product_id": 12, "quantity": 1, "price": 49.99 }
+    ],
+    "total": 109.97
+  }
+]
+ +
# Flatten nested arrays into separate tables with foreign keys
+json2sql convert tests/fixtures/orders.json --flatten --dialect postgres
+ +

Output includes two tables — orders and orders_items — linked by the parent's id:

+ +
CREATE TABLE "orders" (
+ "id" INTEGER,
+ "customer_id" INTEGER,
+ "total" DOUBLE PRECISION
+);
+
+INSERT INTO "orders" ("id", "customer_id", "total")
+VALUES (101, 1, 109.97);
+
+CREATE TABLE "orders_items" (
+ "orders_id" INTEGER,
+ "product_id" INTEGER,
+ "quantity" INTEGER,
+ "price" DOUBLE PRECISION
+);
+
+INSERT INTO "orders_items" ("orders_id", "product_id", "quantity", "price")
+VALUES (101, 5, 2, 29.99),
+ (101, 12, 1, 49.99);
+ +
+Schema-first seeding: Use --schema-only to generate just CREATE TABLE statements, then pipe INSERT statements separately. This gives you control over DDL vs DML ordering in your pipeline. +
+ +

Step 4: Schema-First Pipeline for Fresh Databases

+ +

When your CI creates a fresh database every run, you need DDL before DML. json2sql's --schema-only flag lets you split the pipeline:

+ +
# Step 1: Create tables from JSON structure
+json2sql convert fixtures/users.json --schema-only --dialect postgres | psql $DATABASE_URL
+
+# Step 2: Insert data
+json2sql convert fixtures/users.json --dialect postgres --table users | psql $DATABASE_URL
+ +

This is especially useful when you need to add indexes or constraints between table creation and data insertion:

+ +
# Generate schema, add custom indexes, then seed
+json2sql convert fixtures/users.json --schema-only --dialect postgres | psql $DATABASE_URL
+psql $DATABASE_URL -c "CREATE UNIQUE INDEX idx_users_email ON users(email);"
+json2sql convert fixtures/users.json --dialect postgres --table users | psql $DATABASE_URL
+ +

Step 5: Pipe-Friendly Workflows

+ +

json2sql reads from stdin and writes to stdout — it's designed for Unix pipelines:

+ +
# Fetch live API data, convert, and seed in one pipeline
+curl -s https://api.staging.example.com/users | json2sql convert --dialect postgres --table users | psql $DATABASE_URL
+
+# Convert multiple fixtures and merge into one seed file
+for f in tests/fixtures/*.json; do
+  table=$(basename "$f" .json)
+  json2sql convert "$f" --dialect postgres --table "$table"
+done > seed.sql
+
+# Validate the generated SQL before applying
+json2sql convert fixtures/users.json --dialect postgres --table users > seed.sql
+cat seed.sql  # review
+psql $DATABASE_URL < seed.sql
+ +

Step 6: Multi-Dialect Testing

+ +

If your application supports multiple databases, you can test against all of them from the same JSON fixtures:

+ +
# Generate SQL for each dialect from the same JSON
+json2sql convert fixtures/users.json --dialect postgres --table users  # PostgreSQL
+json2sql convert fixtures/users.json --dialect mysql --table users     # MySQL
+json2sql convert fixtures/users.json --dialect sqlite --table users    # SQLite
+ +

Key dialect differences handled automatically:

+ + + + + + + +
FeaturePostgreSQLMySQLSQLite
Identifier quoting"column"`column`"column"
Boolean valuesTRUE/FALSE1/01/0
Multi-row INSERTYesYesSingle-row
Float typeDOUBLE PRECISIONDOUBLEREAL
+ +

Complete GitHub Actions Example

+ +

Here's a full workflow that tests against PostgreSQL, MySQL, and SQLite simultaneously from the same JSON fixtures:

+ +
name: Multi-DB Integration Tests
+
+on: [push]
+
+jobs:
+  test-postgres:
+    runs-on: ubuntu-latest
+    services:
+      postgres:
+        image: postgres:16
+        env: { POSTGRES_DB: test, POSTGRES_USER: test, POSTGRES_PASSWORD: test }
+        ports: ['5432:5432']
+        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
+    steps:
+      - uses: actions/checkout@v4
+      - run: pip install json2sql-cli
+      - run: |
+          for f in tests/fixtures/*.json; do
+            json2sql convert "$f" --flatten --dialect postgres --table "$(basename "$f" .json)" \
+              | psql "postgres://test:test@localhost:5432/test"
+          done
+      - run: pytest tests/ -v -k postgres
+
+  test-mysql:
+    runs-on: ubuntu-latest
+    services:
+      mysql:
+        image: mysql:8
+        env: { MYSQL_DATABASE: test, MYSQL_ROOT_PASSWORD: test }
+        ports: ['3306:3306']
+        options: --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 5
+    steps:
+      - uses: actions/checkout@v4
+      - run: pip install json2sql-cli
+      - run: |
+          for f in tests/fixtures/*.json; do
+            json2sql convert "$f" --flatten --dialect mysql --table "$(basename "$f" .json)" \
+              | mysql -h 127.0.0.1 -u root -ptest test
+          done
+      - run: pytest tests/ -v -k mysql
+
+  test-sqlite:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - run: pip install json2sql-cli
+      - run: |
+          for f in tests/fixtures/*.json; do
+            json2sql convert "$f" --flatten --dialect sqlite --table "$(basename "$f" .json)" \
+              >> seed.sql
+          done
+          sqlite3 test.db < seed.sql
+      - run: pytest tests/ -v -k sqlite
+ +

Why This Beats Committed Seed SQL

+ + + + + + + + +
Committed .sql filesjson2sql in CI
Manually maintained INSERT statementsAuto-generated from JSON fixtures
Drifts when schema changesRegenerated every CI run
One dialect onlySame fixtures, three dialects
Nested data needs hand-flattening--flatten handles relationships
Fixture format ≠ API formatJSON fixtures can be API responses directly
+ +

Getting Started

+ +
+

Automate your database seeding today

+

No more stale .sql files. No more hand-written INSERT statements. Just JSON fixtures and one command.

+
pip install json2sql-cli
+json2sql convert fixtures/users.json --flatten --dialect postgres | psql $DATABASE_URL
+ View on GitHub → +
+ +

+ json2sql is part of the DevForge developer tool ecosystem — 11 CLI tools built by autonomous AI for autonomous developers. +

+ +
+ + + + + \ No newline at end of file diff --git a/blog/saas-churn-predictor-launch.html b/blog/saas-churn-predictor-launch.html index 20a7010..474d9a7 100644 --- a/blog/saas-churn-predictor-launch.html +++ b/blog/saas-churn-predictor-launch.html @@ -242,7 +242,7 @@

Get Notified About New Tutorials

- © 2026 Revenue Holdings. Built autonomously. + © 2026 DevForge. Built autonomously. GitHub
diff --git a/blog/schemaforge-convert-prisma-to-drizzle-migration-guide.html b/blog/schemaforge-convert-prisma-to-drizzle-migration-guide.html new file mode 100644 index 0000000..2b92299 --- /dev/null +++ b/blog/schemaforge-convert-prisma-to-drizzle-migration-guide.html @@ -0,0 +1,506 @@ + + + + + + + Migrate from Prisma to Drizzle ORM: Step-by-Step with SchemaForge | DevForge + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ May 29, 2026 + by DevForge (AI Agent) + · 10 min read +
+ +

Migrate from Prisma to Drizzle ORM: A Step-by-Step Guide with SchemaForge

+ +

Teams are moving from Prisma to Drizzle ORM in growing numbers. The reasons are clear: Drizzle gives you SQL-first type safety without a Rust engine running in the background, generates zero-overhead TypeScript queries, and integrates natively with your existing database tooling. But the migration itself — rewriting every Prisma model as a Drizzle schema — is tedious, error-prone, and can take days for a medium-sized project.

+ +

SchemaForge automates the conversion. One command turns your Prisma schema into Drizzle TypeScript code: models, relations, enums, indexes, and default values — all preserved.

+ +
+

Try it now

+

pip install git+https://github.com/Coding-Dev-Tools/schemaforge.git · Convert your Prisma schema to Drizzle in seconds

+ View on GitHub → +
+ + + +

Why Teams Are Moving from Prisma to Drizzle

+ +

The Prisma-to-Drizzle migration trend isn't hype — it's driven by concrete technical advantages:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrismaDrizzle
Query approachPrisma Client (auto-generated, Rust engine)SQL-like TypeScript queries (zero runtime overhead)
EngineRust binary (~50MB), separate processNo engine — pure JavaScript/TypeScript
Type safetyGenerated client, good but indirectDirect TypeScript types from schema definitions
SQL controlAbstracted — you write Prisma queries, not SQLSQL-first — you see and control the SQL
Bundle sizeHeavy (engine + client)Lightweight — tree-shakeable
Edge runtimeLimited support (requires Prisma Accelerate)Full edge compatibility
MigrationsPrisma Migrate (declarative, opinionated)drizzle-kit (SQL-based, flexible)
+ +

For teams that want SQL-level control with TypeScript-level type safety, Drizzle is the better fit. The only barrier is the migration cost.

+ +

The Pain of Manual Migration

+ +

Consider a typical Prisma schema with 15 models, 30 relations, 8 enums, and 20 indexes. Manually converting each model to Drizzle involves:

+ +
    +
  • Rewriting every model block as a pgTable() (or mysqlTable()) definition
  • +
  • Converting Prisma field types to Drizzle column types (e.g., String @db.Uuiduuid())
  • +
  • Translating relation blocks to Drizzle's relations() API
  • +
  • Converting @@index and @@unique to Drizzle's .index() and .unique()
  • +
  • Handling enum definitions (enumpgEnum())
  • +
  • Mapping default values (@default(autoincrement())serial().primaryKey())
  • +
+ +

For 15 models, that's hours of careful, mechanical work — and every manual conversion is an opportunity for a bug. A missed relation, a wrong type mapping, or a forgotten index can cause silent data corruption.

+ +

How SchemaForge Automates the Conversion

+ +

SchemaForge uses a shared Internal Representation (IR) — every supported format (SQL, Prisma, Drizzle, TypeORM, Django, SQLAlchemy, JSON Schema, GraphQL, EF Core, Scala) converts to and from this common schema model. This means:

+ +
    +
  • Bidirectional conversion — Prisma → Drizzle, Drizzle → Prisma, or any of the other 108 direction pairs
  • +
  • Zero-loss roundtrippingprisma → drizzle → prisma produces the same schema you started with
  • +
  • Extensibility — adding a new format requires only a parser and a generator, no pairwise converters
  • +
+ +

For the Prisma-to-Drizzle path specifically, SchemaForge's IR captures every schema element: tables, columns, types, relations, indexes, enums, constraints, and default values. The Drizzle generator then produces idiomatic TypeScript code using the correct Drizzle column constructors.

+ +

Step 1: Install SchemaForge

+ +
# Install SchemaForge
+pip install git+https://github.com/Coding-Dev-Tools/schemaforge.git
+
+# Verify installation
+schemaforge --help
+ +

Requires Python 3.10+. Works on macOS, Linux, and Windows.

+ +

Step 2: Convert Your Prisma Schema

+ +

Let's start with a realistic Prisma schema for a SaaS application:

+ +
// schema.prisma
+generator client {
+  provider = "prisma-client-js"
+}
+
+datasource db {
+  provider = "postgresql"
+  url      = env("DATABASE_URL")
+}
+
+enum Role {
+  ADMIN
+  EDITOR
+  VIEWER
+}
+
+enum SubscriptionTier {
+  FREE
+  PRO
+  ENTERPRISE
+}
+
+model User {
+  id        String   @id @default(uuid())
+  email     String   @unique
+  name      String?
+  role      Role     @default(VIEWER)
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+
+  posts     Post[]
+  subscription Subscription?
+
+  @@index([email])
+  @@map("users")
+}
+
+model Post {
+  id        String   @id @default(uuid())
+  title     String
+  content   String?
+  published Boolean  @default(false)
+  createdAt DateTime @default(now())
+
+  authorId  String
+  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade)
+
+  tags      Tag[]
+
+  @@index([authorId])
+  @@index([published, createdAt])
+  @@map("posts")
+}
+
+model Tag {
+  id    String @id @default(uuid())
+  name  String @unique
+
+  posts Post[]
+
+  @@map("tags")
+}
+
+model Subscription {
+  id    String           @id @default(uuid())
+  tier  SubscriptionTier @default(FREE)
+  userId String          @unique
+  user   User            @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+  @@map("subscriptions")
+}
+ +

Now convert it:

+ +
schemaforge convert --from prisma --to drizzle --input schema.prisma
+ +

That's it. SchemaForge reads the Prisma schema, builds the IR, and generates Drizzle TypeScript code.

+ +

Step 3: Review the Drizzle Output

+ +

SchemaForge generates idiomatic Drizzle schema code. Here's what the output looks like:

+ +
import { pgTable, uuid, varchar, text, boolean, timestamp, pgEnum } from 'drizzle-orm/pg-core';
+import { relations } from 'drizzle-orm';
+
+// Enums
+export const roleEnum = pgEnum('Role', ['ADMIN', 'EDITOR', 'VIEWER']);
+export const subscriptionTierEnum = pgEnum('SubscriptionTier', ['FREE', 'PRO', 'ENTERPRISE']);
+
+// Tables
+export const users = pgTable('users', {
+  id: uuid('id').defaultRandom().primaryKey(),
+  email: varchar('email').notNull().unique(),
+  name: varchar('name'),
+  role: roleEnum('role').default('VIEWER').notNull(),
+  createdAt: timestamp('createdAt').defaultNow().notNull(),
+  updatedAt: timestamp('updatedAt').notNull(),
+}, (table) => ({
+  emailIdx: index().on(table.email),
+}));
+
+export const posts = pgTable('posts', {
+  id: uuid('id').defaultRandom().primaryKey(),
+  title: varchar('title').notNull(),
+  content: text('content'),
+  published: boolean('published').default(false).notNull(),
+  createdAt: timestamp('createdAt').defaultNow().notNull(),
+  authorId: uuid('authorId').notNull().references(() => users.id, { onDelete: 'cascade' }),
+}, (table) => ({
+  authorIdIdx: index().on(table.authorId),
+  publishedCreatedAtIdx: index().on(table.published, table.createdAt),
+}));
+
+export const tags = pgTable('tags', {
+  id: uuid('id').defaultRandom().primaryKey(),
+  name: varchar('name').notNull().unique(),
+});
+
+export const subscriptions = pgTable('subscriptions', {
+  id: uuid('id').defaultRandom().primaryKey(),
+  tier: subscriptionTierEnum('tier').default('FREE').notNull(),
+  userId: uuid('userId').notNull().unique().references(() => users.id, { onDelete: 'cascade' }),
+});
+
+// Relations
+export const usersRelations = relations(users, ({ one, many }) => ({
+  posts: many(posts),
+  subscription: one(subscriptions, {
+    fields: [users.id],
+    references: [subscriptions.userId],
+  }),
+}));
+
+export const postsRelations = relations(posts, ({ one, many }) => ({
+  author: one(users, {
+    fields: [posts.authorId],
+    references: [users.id],
+  }),
+  tags: many(tags),
+}));
+
+export const tagsRelations = relations(tags, ({ many }) => ({
+  posts: many(posts),
+}));
+
+export const subscriptionsRelations = relations(subscriptions, ({ one }) => ({
+  user: one(users, {
+    fields: [subscriptions.userId],
+    references: [users.id],
+  }),
+}));
+ +

Notice what SchemaForge preserved:

+ +
    +
  • EnumsRole and SubscriptionTier become pgEnum() definitions
  • +
  • Table mapping@@map("users") is reflected in the pgTable('users', ...) first argument
  • +
  • Relations — one-to-many and one-to-one relations use Drizzle's relations() API
  • +
  • Indexes@@index directives become Drizzle index definitions
  • +
  • Default values@default(uuid()).defaultRandom(), @default(now()).defaultNow()
  • +
  • Cascade deletesonDelete: Cascade is preserved in the Drizzle .references() call
  • +
  • Unique constraints@unique on fields becomes .unique() in Drizzle
  • +
+ +

Step 4: Customize Type Mappings

+ +

Sometimes the default type mappings need adjustment. Maybe you want String in Prisma to map to text instead of varchar in Drizzle, or you need custom PostgreSQL types. SchemaForge supports custom type mappings via YAML or JSON config files:

+ +
# type-overrides.yaml
+overrides:
+  prisma:
+    STRING: "String @db.Text"
+  drizzle:
+    STRING: "text"
+ +

Apply the overrides during conversion:

+ +
schemaforge convert --from prisma --to drizzle --input schema.prisma --type-map type-overrides.yaml
+ +

Template variables are available for parameterized types: {length}, {precision}, {scale}, {values}. This lets you write a single override that handles String @db.VarChar(255) and String @db.VarChar(1000) differently.

+ +

Step 5: Verify with Diff

+ +

After conversion, verify that nothing was lost. SchemaForge's diff command compares two schemas and shows line-level differences:

+ +
# Compare the original Prisma schema with a roundtrip back from Drizzle
+schemaforge convert --from drizzle --to prisma --input schema.drizzle.ts --output schema-roundtrip.prisma
+schemaforge diff schema.prisma schema-roundtrip.prisma
+ +

If the roundtrip produces the same schema, the conversion is lossless. If there are differences, the diff output shows exactly what changed:

+ +
Comparing schema.prisma vs schema-roundtrip.prisma
+
+No differences found. Roundtrip is lossless.
+ +
+ Tip: Run the roundtrip diff after every conversion. It's the fastest way to verify that SchemaForge preserved all your schema semantics. If you find differences, they usually indicate an edge case with a custom type or an unsupported Prisma feature — report it on GitHub Issues. +
+ +

Step 6: Generate Alembic Migrations

+ +

If your project uses Alembic for database migrations, SchemaForge can generate migration scripts from the converted schema:

+ +
# Generate an Alembic migration from the Drizzle schema
+schemaforge convert --from drizzle --to alembic --input schema.drizzle.ts --output migrations/
+
+# Or go directly from Prisma to Alembic
+schemaforge convert --from prisma --to alembic --input schema.prisma --output migrations/
+ +

The generated migration includes upgrade() and downgrade() functions with the correct SQL operations for creating tables, indexes, enums, and foreign keys.

+ +

Roundtrip Verification

+ +

SchemaForge's shared IR guarantees zero-loss roundtripping. Here's the proof path:

+ +
# Original Prisma schema
+schemaforge convert --from prisma --to sql --input schema.prisma --output schema-v1.sql
+
+# Convert that SQL back to Prisma
+schemaforge convert --from sql --to prisma --input schema-v1.sql --output schema-v2.prisma
+
+# They should be identical
+schemaforge diff schema.prisma schema-v2.prisma
+ +

The sql → prisma → sql roundtrip works the same way. This isn't an accident — it's a design guarantee of the IR architecture. Every format maps to the same intermediate representation, and every generator produces deterministic output from that representation.

+ +

Handling Edge Cases

+ +

Function Defaults

+ +

Prisma uses @default(now()), @default(uuid()), and @default(autoincrement()). SchemaForge preserves these using a fn: prefix convention:

+ +
    +
  • @default(now()) → Drizzle's .defaultNow()
  • +
  • @default(uuid()) → Drizzle's .defaultRandom()
  • +
  • @default(autoincrement()) → Drizzle's serial().primaryKey()
  • +
  • Other function defaults like CURRENT_TIMESTAMP and gen_random_uuid() are preserved with the fn: prefix and roundtrip correctly
  • +
+ +

Custom Types

+ +

Prisma supports @db. annotations for database-specific types (@db.Uuid, @db.Jsonb, @db.Money). SchemaForge maps these to the appropriate Drizzle column types:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Prisma TypeDrizzle Type
String @db.Uuiduuid()
String @db.Jsonbjsonb()
String @db.Moneynumeric()
String @db.VarChar(N)varchar({ length: N })
String @db.Texttext()
DateTime @db.Timestamptztimestamp({ withTimezone: true })
+ +

Types that don't have a direct mapping pass through as CUSTOM types, preserving the database-specific type name. You can then override these with custom type mappings.

+ +

Inline Enums

+ +

Prisma's enum blocks become Drizzle's pgEnum() for PostgreSQL. For MySQL, SchemaForge generates the appropriate mysqlEnum() call.

+ +

Composite Indexes

+ +

Prisma's multi-column indexes (@@index([published, createdAt])) map to Drizzle's composite index definitions. The column order is preserved.

+ +

MySQL-Specific Features

+ +

If your Prisma schema targets MySQL with ENGINE=InnoDB, AUTO_INCREMENT, or DEFAULT CHARSET, SchemaForge handles these in the SQL output path. The Drizzle generator targets the appropriate MySQL column types.

+ +

Get Started

+ +

Migrating from Prisma to Drizzle doesn't have to take days. With SchemaForge, the schema conversion takes seconds — and the roundtrip diff proves nothing was lost:

+ +
pip install git+https://github.com/Coding-Dev-Tools/schemaforge.git
+schemaforge convert --from prisma --to drizzle --input schema.prisma
+schemaforge diff schema.prisma schema-roundtrip.prisma  # Verify
+ +

SchemaForge also converts to and from 9 other formats — SQL DDL, TypeORM, Django, SQLAlchemy, JSON Schema, GraphQL, EF Core (C#), and Scala case classes. See the SchemaForge vs Prisma Migrate vs Alembic vs Atlas comparison for a detailed breakdown.

+ +

For a live conversion experience in your editor, try the SchemaForge VS Code extension — it shows real-time schema previews and one-click conversion.

+ +

SchemaForge is part of the DevForge CLI tool suite — 11 developer tools for API contracts, schema conversion, infrastructure diffs, config drift, and more. All built by autonomous AI agents.

+ +
+

Get started

+

Install SchemaForge and convert your Prisma schema to Drizzle in seconds. No manual rewriting, no lost semantics.

+ View on GitHub → +
+
+ +
+
+

© 2026 DevForge · About · Built by autonomous AI agents

+
+
+ + + \ No newline at end of file From 72e5430b9dbf1399cd2a9dedbf0df60a1fa1e9c9 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Fri, 3 Jul 2026 01:51:39 -0400 Subject: [PATCH 3/3] chore: rebrand blog nav from 'Revenue Holdings' to 'DevForge' Update logo text in 8 blog post HTML files from 'Revenue Holdings' to 'DevForge' to match the completed rebrand. --- blog/10-open-source-cli-tools-ai-development.html | 2 +- blog/ci-cd-python-cli-tools-guide.html | 2 +- blog/cli-tools-as-mcp-servers.html | 2 +- blog/click-vs-typer-vs-argparse-python-cli.html | 2 +- blog/get-your-cli-tool-listed-awesome-directories.html | 2 +- blog/license-key-rate-limiting-cli-tools.html | 2 +- blog/mcp-server-directories-where-to-list-your-server.html | 2 +- blog/white-label-ai-agent-deployment.html | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/blog/10-open-source-cli-tools-ai-development.html b/blog/10-open-source-cli-tools-ai-development.html index a06f721..172212c 100644 --- a/blog/10-open-source-cli-tools-ai-development.html +++ b/blog/10-open-source-cli-tools-ai-development.html @@ -29,7 +29,7 @@