@jackuait/blok 0.3.1-beta.0 → 0.3.1-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/codemod/README.md +177 -0
- package/codemod/migrate-editorjs-to-blok.js +496 -0
- package/codemod/package.json +32 -0
- package/codemod/test.js +299 -0
- package/dist/{blok-e-cML09O.mjs → blok-DVGmVJt8.mjs} +4480 -4339
- package/dist/blok.mjs +1 -1
- package/dist/blok.umd.js +35 -27
- package/dist/{index-BeLqsXna.mjs → index-6boN1DB4.mjs} +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -44,10 +44,10 @@ Run the codemod to automatically update your codebase:
|
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
# Preview changes (recommended first)
|
|
47
|
-
npx
|
|
47
|
+
npx blok-codemod ./src --dry-run
|
|
48
48
|
|
|
49
49
|
# Apply changes
|
|
50
|
-
npx
|
|
50
|
+
npx blok-codemod ./src
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
The codemod handles:
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# EditorJS to Blok Codemod
|
|
2
|
+
|
|
3
|
+
Automatically migrate your codebase from EditorJS to Blok.
|
|
4
|
+
|
|
5
|
+
## Installation & Usage
|
|
6
|
+
|
|
7
|
+
### Using npx (recommended)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Dry run (preview changes without modifying files)
|
|
11
|
+
npx blok-codemod ./src --dry-run
|
|
12
|
+
|
|
13
|
+
# Apply changes
|
|
14
|
+
npx blok-codemod ./src
|
|
15
|
+
|
|
16
|
+
# Process entire project
|
|
17
|
+
npx blok-codemod .
|
|
18
|
+
|
|
19
|
+
# Verbose output
|
|
20
|
+
npx blok-codemod ./src --verbose
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What It Does
|
|
24
|
+
|
|
25
|
+
### Import Transformations
|
|
26
|
+
|
|
27
|
+
```diff
|
|
28
|
+
- import EditorJS from '@editorjs/editorjs';
|
|
29
|
+
+ import Blok from '@jackuait/blok';
|
|
30
|
+
|
|
31
|
+
- import Header from '@editorjs/header';
|
|
32
|
+
- import Paragraph from '@editorjs/paragraph';
|
|
33
|
+
+ // Header is now bundled with Blok: use Blok.Header
|
|
34
|
+
+ // Paragraph is now bundled with Blok: use Blok.Paragraph
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Type Transformations
|
|
38
|
+
|
|
39
|
+
```diff
|
|
40
|
+
- import type { EditorConfig } from '@editorjs/editorjs';
|
|
41
|
+
+ import type { BlokConfig } from '@jackuait/blok';
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Class Name Transformations
|
|
45
|
+
|
|
46
|
+
```diff
|
|
47
|
+
- const editor = new EditorJS({ ... });
|
|
48
|
+
+ const editor = new Blok({ ... });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### CSS Selector Transformations
|
|
52
|
+
|
|
53
|
+
```diff
|
|
54
|
+
- .codex-editor { }
|
|
55
|
+
+ .blok-editor { }
|
|
56
|
+
|
|
57
|
+
- .ce-block { }
|
|
58
|
+
+ [data-blok-testid="block-wrapper"] { }
|
|
59
|
+
|
|
60
|
+
- .ce-block--selected { }
|
|
61
|
+
+ [data-blok-selected="true"] { }
|
|
62
|
+
|
|
63
|
+
- .ce-toolbar { }
|
|
64
|
+
+ [data-blok-testid="toolbar"] { }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Data Attribute Transformations
|
|
68
|
+
|
|
69
|
+
```diff
|
|
70
|
+
- document.querySelector('[data-id="abc123"]');
|
|
71
|
+
+ document.querySelector('[data-blok-id="abc123"]');
|
|
72
|
+
|
|
73
|
+
- document.querySelector('[data-item-name="bold"]');
|
|
74
|
+
+ document.querySelector('[data-blok-item-name="bold"]');
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Default Holder Transformation
|
|
78
|
+
|
|
79
|
+
```diff
|
|
80
|
+
- <div id="editorjs"></div>
|
|
81
|
+
+ <div id="blok"></div>
|
|
82
|
+
|
|
83
|
+
- holder: 'editorjs'
|
|
84
|
+
+ holder: 'blok'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Tool Configuration Transformations
|
|
88
|
+
|
|
89
|
+
```diff
|
|
90
|
+
tools: {
|
|
91
|
+
- header: Header,
|
|
92
|
+
- paragraph: Paragraph,
|
|
93
|
+
+ header: Blok.Header,
|
|
94
|
+
+ paragraph: Blok.Paragraph,
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### package.json Updates
|
|
99
|
+
|
|
100
|
+
```diff
|
|
101
|
+
{
|
|
102
|
+
"dependencies": {
|
|
103
|
+
- "@editorjs/editorjs": "^2.28.0",
|
|
104
|
+
- "@editorjs/header": "^2.8.0",
|
|
105
|
+
- "@editorjs/paragraph": "^2.11.0",
|
|
106
|
+
+ "@jackuait/blok": "latest"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Options
|
|
112
|
+
|
|
113
|
+
| Option | Description |
|
|
114
|
+
|--------|-------------|
|
|
115
|
+
| `--dry-run` | Preview changes without modifying files |
|
|
116
|
+
| `--verbose` | Show detailed output for each file |
|
|
117
|
+
| `--help` | Show help message |
|
|
118
|
+
|
|
119
|
+
## Supported File Types
|
|
120
|
+
|
|
121
|
+
- JavaScript: `.js`, `.jsx`
|
|
122
|
+
- TypeScript: `.ts`, `.tsx`
|
|
123
|
+
- Vue: `.vue`
|
|
124
|
+
- Svelte: `.svelte`
|
|
125
|
+
- HTML: `.html`
|
|
126
|
+
- CSS: `.css`, `.scss`, `.less`
|
|
127
|
+
|
|
128
|
+
## After Migration
|
|
129
|
+
|
|
130
|
+
1. **Install dependencies**: Run `npm install` or `yarn` to update your dependencies
|
|
131
|
+
|
|
132
|
+
2. **Review changes**: The codemod handles most common patterns, but review the changes for:
|
|
133
|
+
- Custom tool implementations
|
|
134
|
+
- Complex selector patterns
|
|
135
|
+
- Dynamic string construction
|
|
136
|
+
|
|
137
|
+
3. **Update custom tools**: If you have custom tools, ensure they follow Blok's API:
|
|
138
|
+
- Lifecycle hooks: `rendered()`, `updated()`, `removed()`, `moved()`
|
|
139
|
+
- Use `data-blok-*` attributes
|
|
140
|
+
|
|
141
|
+
4. **Test thoroughly**: Run your test suite and manually verify the editor works correctly
|
|
142
|
+
|
|
143
|
+
5. **Check MIGRATION.md**: See the full [migration guide](../MIGRATION.md) for manual updates
|
|
144
|
+
|
|
145
|
+
## Programmatic Usage
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
// If you've cloned the blok repository
|
|
149
|
+
const {
|
|
150
|
+
transformFile,
|
|
151
|
+
updatePackageJson,
|
|
152
|
+
applyTransforms,
|
|
153
|
+
} = require('./codemod/migrate-editorjs-to-blok');
|
|
154
|
+
|
|
155
|
+
// Transform a single file
|
|
156
|
+
const result = transformFile('/path/to/file.ts', false);
|
|
157
|
+
console.log(result.changes);
|
|
158
|
+
|
|
159
|
+
// Update package.json
|
|
160
|
+
const pkgResult = updatePackageJson('/path/to/package.json', false);
|
|
161
|
+
console.log(pkgResult.changes);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Known Limitations
|
|
165
|
+
|
|
166
|
+
- Does not handle dynamic imports with variable paths
|
|
167
|
+
- Complex nested selectors may need manual adjustment
|
|
168
|
+
- Custom EditorJS plugins need manual migration
|
|
169
|
+
- String templates with EditorJS references need manual review
|
|
170
|
+
|
|
171
|
+
## Contributing
|
|
172
|
+
|
|
173
|
+
Found a pattern that should be transformed? Open an issue or PR on the [Blok repository](https://github.com/jackuait/blok).
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
Apache-2.0
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EditorJS to Blok Codemod
|
|
5
|
+
*
|
|
6
|
+
* This script automates the migration from EditorJS to Blok.
|
|
7
|
+
* It transforms imports, class names, selectors, data attributes, and text references.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx blok-codemod [path] [options]
|
|
11
|
+
*
|
|
12
|
+
* Options:
|
|
13
|
+
* --dry-run Show changes without modifying files
|
|
14
|
+
* --verbose Show detailed output
|
|
15
|
+
* --help Show help
|
|
16
|
+
*
|
|
17
|
+
* Examples:
|
|
18
|
+
* npx blok-codemod ./src
|
|
19
|
+
* npx blok-codemod ./src --dry-run
|
|
20
|
+
* npx blok-codemod .
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Configuration
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
const FILE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte', '.html', '.css', '.scss', '.less'];
|
|
31
|
+
|
|
32
|
+
// Import transformations
|
|
33
|
+
const IMPORT_TRANSFORMS = [
|
|
34
|
+
// Main EditorJS import
|
|
35
|
+
{
|
|
36
|
+
pattern: /from\s+['"]@editorjs\/editorjs['"]/g,
|
|
37
|
+
replacement: "from '@jackuait/blok'",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
pattern: /require\s*\(\s*['"]@editorjs\/editorjs['"]\s*\)/g,
|
|
41
|
+
replacement: "require('@jackuait/blok')",
|
|
42
|
+
},
|
|
43
|
+
// Header tool (now bundled)
|
|
44
|
+
{
|
|
45
|
+
pattern: /import\s+(\w+)\s+from\s+['"]@editorjs\/header['"];?\n?/g,
|
|
46
|
+
replacement: '// Header is now bundled with Blok: use Blok.Header\n',
|
|
47
|
+
note: 'Header tool is now bundled',
|
|
48
|
+
},
|
|
49
|
+
// Paragraph tool (now bundled)
|
|
50
|
+
{
|
|
51
|
+
pattern: /import\s+(\w+)\s+from\s+['"]@editorjs\/paragraph['"];?\n?/g,
|
|
52
|
+
replacement: '// Paragraph is now bundled with Blok: use Blok.Paragraph\n',
|
|
53
|
+
note: 'Paragraph tool is now bundled',
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// Type import transformations (order matters - more specific patterns first)
|
|
58
|
+
const TYPE_TRANSFORMS = [
|
|
59
|
+
{ pattern: /EditorJS\.EditorConfig/g, replacement: 'BlokConfig' },
|
|
60
|
+
{ pattern: /EditorConfig/g, replacement: 'BlokConfig' },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// Class name transformations
|
|
64
|
+
const CLASS_NAME_TRANSFORMS = [
|
|
65
|
+
// Constructor
|
|
66
|
+
{ pattern: /new\s+EditorJS\s*\(/g, replacement: 'new Blok(' },
|
|
67
|
+
// Type annotations
|
|
68
|
+
{ pattern: /:\s*EditorJS(?![a-zA-Z])/g, replacement: ': Blok' },
|
|
69
|
+
// Generic type parameters
|
|
70
|
+
{ pattern: /<EditorJS>/g, replacement: '<Blok>' },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
// CSS class transformations
|
|
74
|
+
const CSS_CLASS_TRANSFORMS = [
|
|
75
|
+
// Editor wrapper classes
|
|
76
|
+
{ pattern: /\.codex-editor(?![\w-])/g, replacement: '.blok-editor' },
|
|
77
|
+
{ pattern: /\.codex-editor--narrow/g, replacement: '.blok-editor--narrow' },
|
|
78
|
+
{ pattern: /\.codex-editor--rtl/g, replacement: '.blok-editor--rtl' },
|
|
79
|
+
// CE prefix classes (commonly used)
|
|
80
|
+
{ pattern: /\.ce-block(?![\w-])/g, replacement: '[data-blok-testid="block-wrapper"]' },
|
|
81
|
+
{ pattern: /\.ce-block--selected/g, replacement: '[data-blok-selected="true"]' },
|
|
82
|
+
{ pattern: /\.ce-block--stretched/g, replacement: '[data-blok-stretched="true"]' },
|
|
83
|
+
{ pattern: /\.ce-block__content/g, replacement: '[data-blok-testid="block-content"]' },
|
|
84
|
+
{ pattern: /\.ce-toolbar(?![\w-])/g, replacement: '[data-blok-testid="toolbar"]' },
|
|
85
|
+
{ pattern: /\.ce-toolbar__plus/g, replacement: '[data-blok-testid="plus-button"]' },
|
|
86
|
+
{ pattern: /\.ce-toolbar__settings-btn/g, replacement: '[data-blok-testid="settings-toggler"]' },
|
|
87
|
+
{ pattern: /\.ce-toolbar__actions/g, replacement: '[data-blok-testid="toolbar-actions"]' },
|
|
88
|
+
{ pattern: /\.ce-inline-toolbar/g, replacement: '[data-blok-testid="inline-toolbar"]' },
|
|
89
|
+
{ pattern: /\.ce-popover(?![\w-])/g, replacement: '[data-blok-testid="popover-container"]' },
|
|
90
|
+
{ pattern: /\.ce-popover-item/g, replacement: '[data-blok-testid="popover-item"]' },
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
// Data attribute transformations
|
|
94
|
+
const DATA_ATTRIBUTE_TRANSFORMS = [
|
|
95
|
+
// Core data attributes
|
|
96
|
+
{ pattern: /data-id(?=["'\]=\s\]])/g, replacement: 'data-blok-id' },
|
|
97
|
+
{ pattern: /data-item-name/g, replacement: 'data-blok-item-name' },
|
|
98
|
+
{ pattern: /data-empty/g, replacement: 'data-blok-empty' },
|
|
99
|
+
// Cypress/test selectors
|
|
100
|
+
{ pattern: /\[data-cy=["']?editorjs["']?\]/g, replacement: '[data-blok-testid="blok-editor"]' },
|
|
101
|
+
{ pattern: /data-cy=["']?editorjs["']?/g, replacement: 'data-blok-testid="blok-editor"' },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
// Selector transformations for JavaScript/TypeScript query strings
|
|
105
|
+
const SELECTOR_TRANSFORMS = [
|
|
106
|
+
// querySelector patterns
|
|
107
|
+
{ pattern: /\[data-id=/g, replacement: '[data-blok-id=' },
|
|
108
|
+
{ pattern: /\[data-item-name=/g, replacement: '[data-blok-item-name=' },
|
|
109
|
+
{ pattern: /\[data-empty\]/g, replacement: '[data-blok-empty]' },
|
|
110
|
+
{ pattern: /\[data-empty=/g, replacement: '[data-blok-empty=' },
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
// Default holder transformation
|
|
114
|
+
const HOLDER_TRANSFORMS = [
|
|
115
|
+
// HTML id attribute
|
|
116
|
+
{ pattern: /id=["']editorjs["']/g, replacement: 'id="blok"' },
|
|
117
|
+
// JavaScript string literals for holder
|
|
118
|
+
{ pattern: /holder:\s*['"]editorjs['"]/g, replacement: "holder: 'blok'" },
|
|
119
|
+
{ pattern: /getElementById\s*\(\s*['"]editorjs['"]\s*\)/g, replacement: "getElementById('blok')" },
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
// Tool configuration transformations
|
|
123
|
+
const TOOL_CONFIG_TRANSFORMS = [
|
|
124
|
+
// Handle class property syntax
|
|
125
|
+
{ pattern: /class:\s*Header(?!Config)/g, replacement: 'class: Blok.Header' },
|
|
126
|
+
{ pattern: /class:\s*Paragraph(?!Config)/g, replacement: 'class: Blok.Paragraph' },
|
|
127
|
+
// Handle standalone tool references in tools config (e.g., `paragraph: Paragraph`)
|
|
128
|
+
{ pattern: /(\bheader\s*:\s*)Header(?!Config)(?=\s*[,}\n])/g, replacement: '$1Blok.Header' },
|
|
129
|
+
{ pattern: /(\bparagraph\s*:\s*)Paragraph(?!Config)(?=\s*[,}\n])/g, replacement: '$1Blok.Paragraph' },
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// Text transformations for "EditorJS" string references
|
|
133
|
+
const TEXT_TRANSFORMS = [
|
|
134
|
+
// Replace exact "EditorJS" text (preserves case-sensitive matching)
|
|
135
|
+
{ pattern: /EditorJS(?![a-zA-Z])/g, replacement: 'Blok' },
|
|
136
|
+
// Replace #editorjs with #blok (e.g., in CSS ID selectors or anchor links)
|
|
137
|
+
{ pattern: /#editorjs(?![a-zA-Z0-9_-])/g, replacement: '#blok' },
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Utility Functions
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
function log(message, verbose = false) {
|
|
145
|
+
if (verbose || !global.isVerbose) {
|
|
146
|
+
console.log(message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function logVerbose(message) {
|
|
151
|
+
if (global.isVerbose) {
|
|
152
|
+
console.log(` ${message}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
157
|
+
const files = fs.readdirSync(dirPath);
|
|
158
|
+
|
|
159
|
+
files.forEach((file) => {
|
|
160
|
+
const fullPath = path.join(dirPath, file);
|
|
161
|
+
|
|
162
|
+
// Skip node_modules, dist, and hidden directories
|
|
163
|
+
if (file === 'node_modules' || file === 'dist' || file === 'build' || file.startsWith('.')) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
168
|
+
getAllFiles(fullPath, arrayOfFiles);
|
|
169
|
+
} else {
|
|
170
|
+
const ext = path.extname(file).toLowerCase();
|
|
171
|
+
if (FILE_EXTENSIONS.includes(ext)) {
|
|
172
|
+
arrayOfFiles.push(fullPath);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return arrayOfFiles;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function applyTransforms(content, transforms, fileName) {
|
|
181
|
+
let result = content;
|
|
182
|
+
const changes = [];
|
|
183
|
+
|
|
184
|
+
transforms.forEach(({ pattern, replacement, note }) => {
|
|
185
|
+
const matches = result.match(pattern);
|
|
186
|
+
if (matches) {
|
|
187
|
+
changes.push({
|
|
188
|
+
pattern: pattern.toString(),
|
|
189
|
+
count: matches.length,
|
|
190
|
+
note,
|
|
191
|
+
});
|
|
192
|
+
result = result.replace(pattern, replacement);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return { result, changes };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function transformFile(filePath, dryRun = false) {
|
|
200
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
201
|
+
let transformed = content;
|
|
202
|
+
const allChanges = [];
|
|
203
|
+
|
|
204
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
205
|
+
const isStyleFile = ['.css', '.scss', '.less'].includes(ext);
|
|
206
|
+
const isHtmlFile = ext === '.html';
|
|
207
|
+
const isJsFile = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte'].includes(ext);
|
|
208
|
+
|
|
209
|
+
// Apply import transforms (JS/TS only)
|
|
210
|
+
if (isJsFile) {
|
|
211
|
+
const { result, changes } = applyTransforms(transformed, IMPORT_TRANSFORMS, filePath);
|
|
212
|
+
transformed = result;
|
|
213
|
+
allChanges.push(...changes.map((c) => ({ ...c, category: 'imports' })));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Apply type transforms (JS/TS only)
|
|
217
|
+
if (isJsFile) {
|
|
218
|
+
const { result, changes } = applyTransforms(transformed, TYPE_TRANSFORMS, filePath);
|
|
219
|
+
transformed = result;
|
|
220
|
+
allChanges.push(...changes.map((c) => ({ ...c, category: 'types' })));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Apply class name transforms (JS/TS only)
|
|
224
|
+
if (isJsFile) {
|
|
225
|
+
const { result, changes } = applyTransforms(transformed, CLASS_NAME_TRANSFORMS, filePath);
|
|
226
|
+
transformed = result;
|
|
227
|
+
allChanges.push(...changes.map((c) => ({ ...c, category: 'class-names' })));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Apply CSS class transforms (all files)
|
|
231
|
+
{
|
|
232
|
+
const { result, changes } = applyTransforms(transformed, CSS_CLASS_TRANSFORMS, filePath);
|
|
233
|
+
transformed = result;
|
|
234
|
+
allChanges.push(...changes.map((c) => ({ ...c, category: 'css-classes' })));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Apply data attribute transforms (JS/TS/HTML)
|
|
238
|
+
if (isJsFile || isHtmlFile) {
|
|
239
|
+
const { result, changes } = applyTransforms(transformed, DATA_ATTRIBUTE_TRANSFORMS, filePath);
|
|
240
|
+
transformed = result;
|
|
241
|
+
allChanges.push(...changes.map((c) => ({ ...c, category: 'data-attributes' })));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Apply selector transforms (JS/TS)
|
|
245
|
+
if (isJsFile) {
|
|
246
|
+
const { result, changes } = applyTransforms(transformed, SELECTOR_TRANSFORMS, filePath);
|
|
247
|
+
transformed = result;
|
|
248
|
+
allChanges.push(...changes.map((c) => ({ ...c, category: 'selectors' })));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Apply holder transforms (JS/TS/HTML)
|
|
252
|
+
if (isJsFile || isHtmlFile) {
|
|
253
|
+
const { result, changes } = applyTransforms(transformed, HOLDER_TRANSFORMS, filePath);
|
|
254
|
+
transformed = result;
|
|
255
|
+
allChanges.push(...changes.map((c) => ({ ...c, category: 'holder' })));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Apply tool config transforms (JS/TS only)
|
|
259
|
+
if (isJsFile) {
|
|
260
|
+
const { result, changes } = applyTransforms(transformed, TOOL_CONFIG_TRANSFORMS, filePath);
|
|
261
|
+
transformed = result;
|
|
262
|
+
allChanges.push(...changes.map((c) => ({ ...c, category: 'tool-config' })));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Apply text transforms (JS/TS/HTML) - replace "EditorJS" with "Blok"
|
|
266
|
+
if (isJsFile || isHtmlFile) {
|
|
267
|
+
const { result, changes } = applyTransforms(transformed, TEXT_TRANSFORMS, filePath);
|
|
268
|
+
transformed = result;
|
|
269
|
+
allChanges.push(...changes.map((c) => ({ ...c, category: 'text' })));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const hasChanges = transformed !== content;
|
|
273
|
+
|
|
274
|
+
if (hasChanges && !dryRun) {
|
|
275
|
+
fs.writeFileSync(filePath, transformed, 'utf8');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
filePath,
|
|
280
|
+
hasChanges,
|
|
281
|
+
changes: allChanges,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function updatePackageJson(packageJsonPath, dryRun = false) {
|
|
286
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
287
|
+
return { hasChanges: false, changes: [] };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const content = fs.readFileSync(packageJsonPath, 'utf8');
|
|
291
|
+
const pkg = JSON.parse(content);
|
|
292
|
+
const changes = [];
|
|
293
|
+
|
|
294
|
+
// Track dependencies to remove
|
|
295
|
+
const depsToRemove = ['@editorjs/editorjs', '@editorjs/header', '@editorjs/paragraph'];
|
|
296
|
+
const devDepsToRemove = [...depsToRemove];
|
|
297
|
+
|
|
298
|
+
// Check and update dependencies
|
|
299
|
+
if (pkg.dependencies) {
|
|
300
|
+
depsToRemove.forEach((dep) => {
|
|
301
|
+
if (pkg.dependencies[dep]) {
|
|
302
|
+
changes.push({ action: 'remove', type: 'dependencies', package: dep });
|
|
303
|
+
delete pkg.dependencies[dep];
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Add @jackuait/blok if not present
|
|
308
|
+
if (!pkg.dependencies['@jackuait/blok']) {
|
|
309
|
+
changes.push({ action: 'add', type: 'dependencies', package: '@jackuait/blok' });
|
|
310
|
+
pkg.dependencies['@jackuait/blok'] = 'latest';
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check and update devDependencies
|
|
315
|
+
if (pkg.devDependencies) {
|
|
316
|
+
devDepsToRemove.forEach((dep) => {
|
|
317
|
+
if (pkg.devDependencies[dep]) {
|
|
318
|
+
changes.push({ action: 'remove', type: 'devDependencies', package: dep });
|
|
319
|
+
delete pkg.devDependencies[dep];
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const hasChanges = changes.length > 0;
|
|
325
|
+
|
|
326
|
+
if (hasChanges && !dryRun) {
|
|
327
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { hasChanges, changes };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ============================================================================
|
|
334
|
+
// Main Execution
|
|
335
|
+
// ============================================================================
|
|
336
|
+
|
|
337
|
+
function printHelp() {
|
|
338
|
+
console.log(`
|
|
339
|
+
EditorJS to Blok Codemod
|
|
340
|
+
|
|
341
|
+
Usage:
|
|
342
|
+
npx blok-codemod [path] [options]
|
|
343
|
+
|
|
344
|
+
Arguments:
|
|
345
|
+
path Directory or file to transform (default: current directory)
|
|
346
|
+
|
|
347
|
+
Options:
|
|
348
|
+
--dry-run Show changes without modifying files
|
|
349
|
+
--verbose Show detailed output for each file
|
|
350
|
+
--help Show this help message
|
|
351
|
+
|
|
352
|
+
Examples:
|
|
353
|
+
npx blok-codemod ./src
|
|
354
|
+
npx blok-codemod ./src --dry-run
|
|
355
|
+
npx blok-codemod . --verbose
|
|
356
|
+
|
|
357
|
+
What this codemod does:
|
|
358
|
+
• Transforms EditorJS imports to Blok imports
|
|
359
|
+
• Updates type names (EditorConfig → BlokConfig)
|
|
360
|
+
• Replaces 'new EditorJS()' with 'new Blok()'
|
|
361
|
+
• Converts CSS selectors (.ce-* → [data-blok-*])
|
|
362
|
+
• Updates data attributes (data-id → data-blok-id)
|
|
363
|
+
• Changes default holder from 'editorjs' to 'blok'
|
|
364
|
+
• Updates package.json dependencies
|
|
365
|
+
• Converts bundled tool imports (Header, Paragraph)
|
|
366
|
+
|
|
367
|
+
Note: After running, you may need to manually:
|
|
368
|
+
• Update any custom tool implementations
|
|
369
|
+
• Review and test the changes
|
|
370
|
+
• Run 'npm install' or 'yarn' to update dependencies
|
|
371
|
+
`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function main() {
|
|
375
|
+
const args = process.argv.slice(2);
|
|
376
|
+
|
|
377
|
+
// Parse arguments
|
|
378
|
+
const dryRun = args.includes('--dry-run');
|
|
379
|
+
const verbose = args.includes('--verbose');
|
|
380
|
+
const help = args.includes('--help') || args.includes('-h');
|
|
381
|
+
|
|
382
|
+
global.isVerbose = verbose;
|
|
383
|
+
|
|
384
|
+
if (help) {
|
|
385
|
+
printHelp();
|
|
386
|
+
process.exit(0);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Get target path
|
|
390
|
+
const targetPath = args.find((arg) => !arg.startsWith('--')) || '.';
|
|
391
|
+
const absolutePath = path.resolve(targetPath);
|
|
392
|
+
|
|
393
|
+
if (!fs.existsSync(absolutePath)) {
|
|
394
|
+
console.error(`Error: Path does not exist: ${absolutePath}`);
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
console.log('\n🔄 EditorJS to Blok Migration\n');
|
|
399
|
+
console.log(`📁 Target: ${absolutePath}`);
|
|
400
|
+
console.log(`🔍 Mode: ${dryRun ? 'Dry run (no changes will be made)' : 'Live'}\n`);
|
|
401
|
+
|
|
402
|
+
const stats = {
|
|
403
|
+
filesScanned: 0,
|
|
404
|
+
filesModified: 0,
|
|
405
|
+
totalChanges: 0,
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// Get all files to process
|
|
409
|
+
let files = [];
|
|
410
|
+
if (fs.statSync(absolutePath).isDirectory()) {
|
|
411
|
+
files = getAllFiles(absolutePath);
|
|
412
|
+
} else {
|
|
413
|
+
files = [absolutePath];
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
stats.filesScanned = files.length;
|
|
417
|
+
|
|
418
|
+
console.log(`📝 Scanning ${files.length} files...\n`);
|
|
419
|
+
|
|
420
|
+
// Process each file
|
|
421
|
+
files.forEach((filePath) => {
|
|
422
|
+
const result = transformFile(filePath, dryRun);
|
|
423
|
+
|
|
424
|
+
if (result.hasChanges) {
|
|
425
|
+
stats.filesModified++;
|
|
426
|
+
stats.totalChanges += result.changes.length;
|
|
427
|
+
|
|
428
|
+
const relativePath = path.relative(absolutePath, filePath);
|
|
429
|
+
console.log(`✏️ ${relativePath}`);
|
|
430
|
+
|
|
431
|
+
if (verbose) {
|
|
432
|
+
result.changes.forEach((change) => {
|
|
433
|
+
console.log(` - [${change.category}] ${change.count} occurrence(s)`);
|
|
434
|
+
if (change.note) {
|
|
435
|
+
console.log(` Note: ${change.note}`);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Process package.json
|
|
443
|
+
const packageJsonPath = path.join(
|
|
444
|
+
fs.statSync(absolutePath).isDirectory() ? absolutePath : path.dirname(absolutePath),
|
|
445
|
+
'package.json'
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
const pkgResult = updatePackageJson(packageJsonPath, dryRun);
|
|
449
|
+
if (pkgResult.hasChanges) {
|
|
450
|
+
console.log(`\n📦 package.json updates:`);
|
|
451
|
+
pkgResult.changes.forEach((change) => {
|
|
452
|
+
const symbol = change.action === 'add' ? '+' : '-';
|
|
453
|
+
console.log(` ${symbol} ${change.package} (${change.type})`);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Print summary
|
|
458
|
+
console.log('\n' + '─'.repeat(50));
|
|
459
|
+
console.log('\n📊 Summary:\n');
|
|
460
|
+
console.log(` Files scanned: ${stats.filesScanned}`);
|
|
461
|
+
console.log(` Files modified: ${stats.filesModified}`);
|
|
462
|
+
console.log(` Total changes: ${stats.totalChanges}`);
|
|
463
|
+
|
|
464
|
+
if (dryRun) {
|
|
465
|
+
console.log('\n⚠️ Dry run complete. No files were modified.');
|
|
466
|
+
console.log(' Run without --dry-run to apply changes.\n');
|
|
467
|
+
} else {
|
|
468
|
+
console.log('\n✅ Migration complete!\n');
|
|
469
|
+
console.log('📋 Next steps:');
|
|
470
|
+
console.log(' 1. Run `npm install` or `yarn` to update dependencies');
|
|
471
|
+
console.log(' 2. Review the changes in your codebase');
|
|
472
|
+
console.log(' 3. Test your application thoroughly');
|
|
473
|
+
console.log(' 4. Check MIGRATION.md for any manual updates needed\n');
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Run if called directly
|
|
478
|
+
if (require.main === module) {
|
|
479
|
+
main();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Export for testing
|
|
483
|
+
module.exports = {
|
|
484
|
+
transformFile,
|
|
485
|
+
updatePackageJson,
|
|
486
|
+
applyTransforms,
|
|
487
|
+
IMPORT_TRANSFORMS,
|
|
488
|
+
TYPE_TRANSFORMS,
|
|
489
|
+
CLASS_NAME_TRANSFORMS,
|
|
490
|
+
CSS_CLASS_TRANSFORMS,
|
|
491
|
+
DATA_ATTRIBUTE_TRANSFORMS,
|
|
492
|
+
SELECTOR_TRANSFORMS,
|
|
493
|
+
HOLDER_TRANSFORMS,
|
|
494
|
+
TOOL_CONFIG_TRANSFORMS,
|
|
495
|
+
TEXT_TRANSFORMS,
|
|
496
|
+
};
|