snapshot of https://github.com/ueberdosis/tiptap.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:11:02 +05:00
commit 083a46e93b
2115 changed files with 245958 additions and 0 deletions

33
codemods/tiptap-v4-migration/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build artifacts
target/
dist/
build/
# Temporary files
*.tmp
*.temp
.cache/
# Environment files
.env
.env.local
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Package bundles
*.tar.gz
*.tgz

View File

@@ -0,0 +1,38 @@
# @tiptap/codemod-v3-to-v4
Codemod workflows for migrating Tiptap projects from v3 to v4.
## Current migrations
- Renames `@tiptap/vue-3` to `@tiptap/vue`.
- Renames `@tiptap/extension-drag-handle-vue-3` to `@tiptap/extension-drag-handle-vue`.
## Local development
```bash
pnpm install
pnpm validate
pnpm test
pnpm exec codemod workflow run -w . -t /path/to/project --param vue_target_path=/path/to/project
```
## Publish
Run these commands from this directory:
```bash
pnpm exec codemod login
pnpm validate
pnpm test
pnpm exec codemod publish
```
## Verify the published workflow
Run the published workflow against a disposable project before documenting it in a release:
```bash
npx codemod@latest @tiptap/codemod-v3-to-v4 \
--target /path/to/project \
--param vue_target_path=/path/to/project
```

View File

@@ -0,0 +1,21 @@
schema_version: '1.0'
name: '@tiptap/codemod-v3-to-v4'
version: '0.1.0'
description: 'Migrate Tiptap projects from v3 to v4'
author: 'Tiptap'
license: 'MIT'
workflows:
- name: main
default: true
path: workflow.yaml
targets:
languages: ['javascript', 'typescript', 'tsx', 'json', 'vue']
keywords: ['migration', 'tiptap', 'v3-to-v4']
registry:
access: 'public'
visibility: 'public'

View File

@@ -0,0 +1,24 @@
{
"name": "@tiptap/codemod-v3-to-v4",
"version": "0.1.0",
"private": true,
"description": "Migrate Tiptap Vue package imports for v4",
"type": "module",
"scripts": {
"build": "pnpm validate",
"check-types": "tsc --noEmit",
"test": "pnpm test:javascript && pnpm test:typescript && pnpm test:tsx && pnpm test:json && pnpm test:vue",
"test:javascript": "codemod jssg test -l javascript scripts/vue-package-renames.ts tests/javascript",
"test:json": "codemod jssg test -l json scripts/vue-package-renames.ts tests/json",
"test:tsx": "codemod jssg test -l tsx scripts/vue-package-renames.ts tests/tsx",
"test:typescript": "codemod jssg test -l typescript scripts/vue-package-renames.ts tests/typescript",
"test:vue": "node --test tests/vue-sfc-imports.test.mjs",
"validate": "codemod workflow validate -w workflow.yaml"
},
"devDependencies": {
"@codemod.com/jssg-types": "^1.6.3",
"codemod": "^1.15.2",
"typescript": "^5.7.3"
},
"packageManager": "pnpm@11.2.2"
}

View File

@@ -0,0 +1,33 @@
import type { Codemod, Edit } from 'codemod:ast-grep'
import type JSON from 'codemod:ast-grep/langs/json'
import type JS from 'codemod:ast-grep/langs/javascript'
import type TS from 'codemod:ast-grep/langs/typescript'
import type TSX from 'codemod:ast-grep/langs/tsx'
const packageSpecifiers = new Map([
["'@tiptap/vue-3'", "'@tiptap/vue'"],
['"@tiptap/vue-3"', '"@tiptap/vue"'],
["'@tiptap/extension-drag-handle-vue-3'", "'@tiptap/extension-drag-handle-vue'"],
['"@tiptap/extension-drag-handle-vue-3"', '"@tiptap/extension-drag-handle-vue"'],
])
type Language = JS | TS | TSX | JSON
const codemod: Codemod<Language> = async root => {
const edits: Edit[] = []
root
.root()
.findAll({ rule: { kind: 'string' } })
.forEach(node => {
const replacement = packageSpecifiers.get(node.text())
if (replacement) {
edits.push(node.replace(replacement))
}
})
return edits.length > 0 ? root.root().commitEdits(edits) : null
}
export default codemod

View File

@@ -0,0 +1,243 @@
import { readdir, readFile, writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const packageSpecifiers = new Map([
['@tiptap/vue-3', '@tiptap/vue'],
['@tiptap/extension-drag-handle-vue-3', '@tiptap/extension-drag-handle-vue'],
])
const scriptPattern = /<script\b[^>]*>([\s\S]*?)<\/script>/gi
const skippedDirectories = new Set(['.git', 'build', 'dist', 'node_modules'])
function skipTrivia(source, start) {
let index = start
while (index < source.length) {
if (/\s/.test(source[index])) {
index += 1
continue
}
if (source.startsWith('//', index)) {
const lineEnd = source.indexOf('\n', index + 2)
index = lineEnd === -1 ? source.length : lineEnd + 1
continue
}
if (source.startsWith('/*', index)) {
const commentEnd = source.indexOf('*/', index + 2)
index = commentEnd === -1 ? source.length : commentEnd + 2
continue
}
break
}
return index
}
function readStringEnd(source, start) {
const quote = source[start]
for (let index = start + 1; index < source.length; index += 1) {
if (source[index] === '\\') {
index += 1
continue
}
if (source[index] === quote) {
return index
}
}
return source.length
}
function readIdentifierEnd(source, start) {
let index = start
while (index < source.length && /[\w$]/.test(source[index])) {
index += 1
}
return index
}
function isIdentifierStart(character) {
return /[A-Za-z_$]/.test(character)
}
function getSpecifierEdit(source, start) {
const quote = source[start]
if (quote !== "'" && quote !== '"') {
return null
}
const end = readStringEnd(source, start)
const specifier = source.slice(start + 1, end)
const replacement = packageSpecifiers.get(specifier)
if (!replacement || end === source.length) {
return null
}
return {
end: end + 1,
start,
text: `${quote}${replacement}${quote}`,
}
}
function findSpecifier(source, start, requiresCall) {
let index = skipTrivia(source, start)
if (requiresCall) {
if (source[index] !== '(') {
return null
}
index = skipTrivia(source, index + 1)
}
return getSpecifierEdit(source, index)
}
function findImportedSpecifier(source, start, limit = source.length) {
const end = limit === -1 ? source.length : limit
for (let index = start; index < end; index += 1) {
if (source.startsWith('//', index)) {
const lineEnd = source.indexOf('\n', index + 2)
index = lineEnd === -1 ? source.length : lineEnd
continue
}
if (source.startsWith('/*', index)) {
const commentEnd = source.indexOf('*/', index + 2)
index = commentEnd === -1 ? source.length : commentEnd + 1
continue
}
if (source[index] === "'" || source[index] === '"' || source[index] === '`') {
index = readStringEnd(source, index)
continue
}
if (isIdentifierStart(source[index])) {
const end = readIdentifierEnd(source, index)
if (source.slice(index, end) === 'from') {
return findSpecifier(source, end, false)
}
index = end - 1
}
}
return null
}
function replacePackageSpecifiers(source) {
const edits = []
for (let index = 0; index < source.length;) {
if (source.startsWith('//', index)) {
const lineEnd = source.indexOf('\n', index + 2)
index = lineEnd === -1 ? source.length : lineEnd + 1
continue
}
if (source.startsWith('/*', index)) {
const commentEnd = source.indexOf('*/', index + 2)
index = commentEnd === -1 ? source.length : commentEnd + 2
continue
}
if (source[index] === "'" || source[index] === '"' || source[index] === '`') {
index = readStringEnd(source, index) + 1
continue
}
if (!isIdentifierStart(source[index])) {
index += 1
continue
}
const end = readIdentifierEnd(source, index)
const identifier = source.slice(index, end)
const previous = source.slice(0, index).trimEnd().at(-1)
if (previous === '.') {
index = end
continue
}
const edit =
identifier === 'import'
? (findSpecifier(source, end, source[skipTrivia(source, end)] === '(') ??
findImportedSpecifier(source, end))
: identifier === 'export'
? findImportedSpecifier(source, end, source.indexOf('\n', end))
: identifier === 'require'
? findSpecifier(source, end, true)
: null
if (edit) {
edits.push(edit)
}
index = end
}
return edits.reduceRight(
(result, edit) => `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`,
source,
)
}
/** Rewrites known Tiptap package specifiers inside Vue script blocks. */
export function transformVueSource(source) {
return source.replace(scriptPattern, (script, content) =>
script.replace(content, replacePackageSpecifiers(content)),
)
}
async function findVueFiles(directory) {
const entries = await readdir(directory, { withFileTypes: true })
const files = []
for (const entry of entries) {
if (entry.isDirectory()) {
if (!skippedDirectories.has(entry.name)) {
files.push(...(await findVueFiles(resolve(directory, entry.name))))
}
continue
}
if (entry.isFile() && entry.name.endsWith('.vue')) {
files.push(resolve(directory, entry.name))
}
}
return files
}
async function migrateVueFiles(directory) {
const files = await findVueFiles(directory)
for (const file of files) {
const source = await readFile(file, 'utf8')
const transformedSource = transformVueSource(source)
if (transformedSource !== source) {
await writeFile(file, transformedSource)
}
}
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
await migrateVueFiles(resolve(process.argv[2] ?? process.cwd()))
}

View File

@@ -0,0 +1,6 @@
import { Editor } from '@tiptap/vue'
const DragHandle = require('@tiptap/extension-drag-handle-vue')
const legacyPackage = '@tiptap/vue-2'
export { Editor, DragHandle, legacyPackage }

View File

@@ -0,0 +1,6 @@
import { Editor } from '@tiptap/vue-3'
const DragHandle = require('@tiptap/extension-drag-handle-vue-3')
const legacyPackage = '@tiptap/vue-2'
export { Editor, DragHandle, legacyPackage }

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"@tiptap/vue": "^3.30.3",
"@tiptap/extension-drag-handle-vue": "^3.30.3",
"@tiptap/vue-2": "^3.30.3"
}
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"@tiptap/vue-3": "^3.30.3",
"@tiptap/extension-drag-handle-vue-3": "^3.30.3",
"@tiptap/vue-2": "^3.30.3"
}
}

View File

@@ -0,0 +1,3 @@
import { EditorContent } from '@tiptap/vue'
export const Content = () => <EditorContent />

View File

@@ -0,0 +1,3 @@
import { EditorContent } from '@tiptap/vue-3'
export const Content = () => <EditorContent />

View File

@@ -0,0 +1,3 @@
export { EditorContent } from '@tiptap/vue'
export const loadDragHandle = () => import('@tiptap/extension-drag-handle-vue')

View File

@@ -0,0 +1,3 @@
export { EditorContent } from '@tiptap/vue-3'
export const loadDragHandle = () => import('@tiptap/extension-drag-handle-vue-3')

View File

@@ -0,0 +1,48 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { transformVueSource } from '../scripts/vue-sfc-imports.mjs'
test('migrates imports inside Vue script blocks', () => {
const source = `<template>{{ '@tiptap/vue-3' }}</template>
<script setup lang="ts">
import { EditorContent } from '@tiptap/vue-3'
export { EditorContent } from '@tiptap/vue-3'
export * from '@tiptap/extension-drag-handle-vue-3'
import '@tiptap/vue-3'
import '@tiptap/extension-drag-handle-vue-3'
const DragHandle = require('@tiptap/extension-drag-handle-vue-3')
const example = "import('@tiptap/vue-3')"
// require('@tiptap/extension-drag-handle-vue-3')
const legacyPackage = '@tiptap/vue-2'
</script>`
const expected = `<template>{{ '@tiptap/vue-3' }}</template>
<script setup lang="ts">
import { EditorContent } from '@tiptap/vue'
export { EditorContent } from '@tiptap/vue'
export * from '@tiptap/extension-drag-handle-vue'
import '@tiptap/vue'
import '@tiptap/extension-drag-handle-vue'
const DragHandle = require('@tiptap/extension-drag-handle-vue')
const example = "import('@tiptap/vue-3')"
// require('@tiptap/extension-drag-handle-vue-3')
const legacyPackage = '@tiptap/vue-2'
</script>`
assert.equal(transformVueSource(source), expected)
})
test('does not scan past local exports', () => {
const source = `<script setup>
export { Local }
import { EditorContent } from '@tiptap/vue-3'
</script>`
const expected = `<script setup>
export { Local }
import { EditorContent } from '@tiptap/vue'
</script>`
assert.equal(transformVueSource(source), expected)
})

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"types": ["@codemod.com/jssg-types"],
"allowImportingTsExtensions": true,
"noEmit": true,
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"strict": true,
"strictNullChecks": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true
},
"exclude": ["tests"]
}

View File

@@ -0,0 +1,51 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod/codemod/refs/heads/main/schemas/workflow.json
version: '1'
params:
schema:
vue_target_path:
name: Vue target path
description: Directory containing Vue SFC files
type: string
default: '.'
nodes:
- id: migrate-package-specifiers
name: Migrate Vue package specifiers
type: automatic
steps:
- name: Migrate JavaScript imports
js-ast-grep:
js_file: scripts/vue-package-renames.ts
base_path: '.'
language: 'javascript'
include: ['**/*.{js,mjs,cjs}']
exclude: ['**/node_modules/**', '**/dist/**', '**/build/**']
- name: Migrate TypeScript imports
js-ast-grep:
js_file: scripts/vue-package-renames.ts
base_path: '.'
language: 'typescript'
include: ['**/*.{ts,mts,cts}']
exclude: ['**/node_modules/**', '**/dist/**', '**/build/**']
- name: Migrate TSX imports
js-ast-grep:
js_file: scripts/vue-package-renames.ts
base_path: '.'
language: 'tsx'
include: ['**/*.{tsx,jsx}']
exclude: ['**/node_modules/**', '**/dist/**', '**/build/**']
- name: Migrate package manifests
js-ast-grep:
js_file: scripts/vue-package-renames.ts
base_path: '.'
language: 'json'
include: ['**/package.json']
exclude: ['**/node_modules/**']
- name: Migrate Vue SFC imports
run: node scripts/vue-sfc-imports.mjs "${{ params.vue_target_path }}"