@procore/text-editor 0.0.1
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/.jest/ckeditorMock.js +67 -0
- package/.jest/esToolkitMock.js +23 -0
- package/.jest/setupTests.js +33 -0
- package/.jest/styleMock.js +1 -0
- package/.jest/svgTransform.js +10 -0
- package/.jest/translationFileMock.js +4 -0
- package/.jest/translationMock.js +12 -0
- package/LICENSE +84 -0
- package/README.md +107 -0
- package/codemod/__fixtures__/hammer.config.mjs +15 -0
- package/codemod/__fixtures__/jest.config.js +6 -0
- package/codemod/__fixtures__/procore.config.js +12 -0
- package/codemod/__fixtures__/src/components/ComplexEditor.tsx +21 -0
- package/codemod/__fixtures__/src/components/FormWithRichText.tsx +10 -0
- package/codemod/__fixtures__/src/components/MultilineFormRichText.tsx +14 -0
- package/codemod/__fixtures__/src/components/NoTextEditor.tsx +11 -0
- package/codemod/__fixtures__/src/components/ReadOnlyRichText.tsx +10 -0
- package/codemod/__fixtures__/src/components/SimpleEditor.tsx +11 -0
- package/codemod/__fixtures__/src/components/TypeImportEditor.tsx +17 -0
- package/codemod/text-editor-migrate.js +509 -0
- package/codemod/text-editor-migrate.test.js +225 -0
- package/dist/TextEditor/EditorError.js +9 -0
- package/dist/TextEditor/EditorError.js.map +1 -0
- package/dist/TextEditor/StickyToolbar/index.js +2 -0
- package/dist/TextEditor/StickyToolbar/index.js.map +1 -0
- package/dist/TextEditor/StickyToolbar/useStickyToolbar.js +59 -0
- package/dist/TextEditor/StickyToolbar/useStickyToolbar.js.map +1 -0
- package/dist/TextEditor/StickyToolbar/useStickyToolbar.types.js +2 -0
- package/dist/TextEditor/StickyToolbar/useStickyToolbar.types.js.map +1 -0
- package/dist/TextEditor/TextEditor.js +226 -0
- package/dist/TextEditor/TextEditor.js.map +1 -0
- package/dist/TextEditor/TextEditor.styles.js +26 -0
- package/dist/TextEditor/TextEditor.styles.js.map +1 -0
- package/dist/TextEditor/TextEditor.types.js +2 -0
- package/dist/TextEditor/TextEditor.types.js.map +1 -0
- package/dist/TextEditor/TextEditorProvider.js +17 -0
- package/dist/TextEditor/TextEditorProvider.js.map +1 -0
- package/dist/TextEditor/TextEditorProvider.types.js +2 -0
- package/dist/TextEditor/TextEditorProvider.types.js.map +1 -0
- package/dist/TextEditor/index.js +4 -0
- package/dist/TextEditor/index.js.map +1 -0
- package/dist/TextEditor/license_key.js +3 -0
- package/dist/TextEditor/license_key.js.map +1 -0
- package/dist/TextEditor/plugins/CutPlugin/CutCommand.js +99 -0
- package/dist/TextEditor/plugins/CutPlugin/CutCommand.js.map +1 -0
- package/dist/TextEditor/plugins/CutPlugin/CutPlugin.js +56 -0
- package/dist/TextEditor/plugins/CutPlugin/CutPlugin.js.map +1 -0
- package/dist/TextEditor/plugins/CutPlugin/index.js +2 -0
- package/dist/TextEditor/plugins/CutPlugin/index.js.map +1 -0
- package/dist/TextEditor/plugins/IndentPaddingToMarginPlugin/IndentPaddingToMarginPlugin.js +40 -0
- package/dist/TextEditor/plugins/IndentPaddingToMarginPlugin/IndentPaddingToMarginPlugin.js.map +1 -0
- package/dist/TextEditor/plugins/IndentPaddingToMarginPlugin/index.js +2 -0
- package/dist/TextEditor/plugins/IndentPaddingToMarginPlugin/index.js.map +1 -0
- package/dist/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextCommand.js +86 -0
- package/dist/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextCommand.js.map +1 -0
- package/dist/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextPlugin.js +56 -0
- package/dist/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextPlugin.js.map +1 -0
- package/dist/TextEditor/plugins/PasteAsTextPlugin/index.js +2 -0
- package/dist/TextEditor/plugins/PasteAsTextPlugin/index.js.map +1 -0
- package/dist/TextEditor/plugins/PastePlugin/PasteCommand.js +149 -0
- package/dist/TextEditor/plugins/PastePlugin/PasteCommand.js.map +1 -0
- package/dist/TextEditor/plugins/PastePlugin/PastePlugin.js +56 -0
- package/dist/TextEditor/plugins/PastePlugin/PastePlugin.js.map +1 -0
- package/dist/TextEditor/plugins/PastePlugin/index.js +2 -0
- package/dist/TextEditor/plugins/PastePlugin/index.js.map +1 -0
- package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js +87 -0
- package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js.map +1 -0
- package/dist/TextEditor/plugins/TabSpacesPlugin/index.js +2 -0
- package/dist/TextEditor/plugins/TabSpacesPlugin/index.js.map +1 -0
- package/dist/TextEditor/textEditorTheming/icons.js +24 -0
- package/dist/TextEditor/textEditorTheming/icons.js.map +1 -0
- package/dist/TextEditor/textEditorTheming/index.js +2 -0
- package/dist/TextEditor/textEditorTheming/index.js.map +1 -0
- package/dist/TextEditor/textEditorTheming/textEditorTheming.styles.js +10 -0
- package/dist/TextEditor/textEditorTheming/textEditorTheming.styles.js.map +1 -0
- package/dist/TextEditor/useCKEditorCss.js +36 -0
- package/dist/TextEditor/useCKEditorCss.js.map +1 -0
- package/dist/TextEditor/useTabAsNavigation.js +29 -0
- package/dist/TextEditor/useTabAsNavigation.js.map +1 -0
- package/dist/TextEditor/utils/config.js +179 -0
- package/dist/TextEditor/utils/config.js.map +1 -0
- package/dist/TextEditor/utils/index.js +3 -0
- package/dist/TextEditor/utils/index.js.map +1 -0
- package/dist/TextEditor/utils/locale.js +102 -0
- package/dist/TextEditor/utils/locale.js.map +1 -0
- package/dist/TextEditor/utils/plugins.js +184 -0
- package/dist/TextEditor/utils/plugins.js.map +1 -0
- package/dist/TextEditorOutput/TextEditorOutput.js +29 -0
- package/dist/TextEditorOutput/TextEditorOutput.js.map +1 -0
- package/dist/TextEditorOutput/TextEditorOutput.styles.js +6 -0
- package/dist/TextEditorOutput/TextEditorOutput.styles.js.map +1 -0
- package/dist/TextEditorOutput/TextEditorOutput.types.js +2 -0
- package/dist/TextEditorOutput/TextEditorOutput.types.js.map +1 -0
- package/dist/TextEditorOutput/TextEditorOutput.utils.js +59 -0
- package/dist/TextEditorOutput/TextEditorOutput.utils.js.map +1 -0
- package/dist/TextEditorOutput/index.js +2 -0
- package/dist/TextEditorOutput/index.js.map +1 -0
- package/dist/_storyHelpers/constants.js +48 -0
- package/dist/_storyHelpers/constants.js.map +1 -0
- package/dist/_typedoc/TextEditor/TextEditor.types.json +227 -0
- package/dist/_typedoc/TextEditor/TextEditorProvider.types.json +28 -0
- package/dist/_typedoc/TextEditorOutput/TextEditorOutput.types.json +38 -0
- package/dist/_typedoc/deprecations.json +1 -0
- package/dist/_utils/propsTypedoc.js +4 -0
- package/dist/_utils/propsTypedoc.js.map +1 -0
- package/dist/codemod/__fixtures__/src/components/ComplexEditor.d.ts +2 -0
- package/dist/codemod/__fixtures__/src/components/FormWithRichText.d.ts +2 -0
- package/dist/codemod/__fixtures__/src/components/MultilineFormRichText.d.ts +2 -0
- package/dist/codemod/__fixtures__/src/components/NoTextEditor.d.ts +2 -0
- package/dist/codemod/__fixtures__/src/components/ReadOnlyRichText.d.ts +2 -0
- package/dist/codemod/__fixtures__/src/components/SimpleEditor.d.ts +2 -0
- package/dist/codemod/__fixtures__/src/components/TypeImportEditor.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/src/TextEditor/EditorError.d.ts +2 -0
- package/dist/src/TextEditor/StickyToolbar/index.d.ts +2 -0
- package/dist/src/TextEditor/StickyToolbar/useStickyToolbar.d.ts +2 -0
- package/dist/src/TextEditor/StickyToolbar/useStickyToolbar.types.d.ts +8 -0
- package/dist/src/TextEditor/TextEditor.d.ts +44 -0
- package/dist/src/TextEditor/TextEditor.styles.d.ts +7 -0
- package/dist/src/TextEditor/TextEditor.types.d.ts +155 -0
- package/dist/src/TextEditor/TextEditorProvider.d.ts +4 -0
- package/dist/src/TextEditor/TextEditorProvider.types.d.ts +14 -0
- package/dist/src/TextEditor/index.d.ts +4 -0
- package/dist/src/TextEditor/license_key.d.ts +2 -0
- package/dist/src/TextEditor/plugins/CutPlugin/CutCommand.d.ts +5 -0
- package/dist/src/TextEditor/plugins/CutPlugin/CutPlugin.d.ts +5 -0
- package/dist/src/TextEditor/plugins/CutPlugin/index.d.ts +1 -0
- package/dist/src/TextEditor/plugins/IndentPaddingToMarginPlugin/IndentPaddingToMarginPlugin.d.ts +5 -0
- package/dist/src/TextEditor/plugins/IndentPaddingToMarginPlugin/index.d.ts +1 -0
- package/dist/src/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextCommand.d.ts +5 -0
- package/dist/src/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextPlugin.d.ts +5 -0
- package/dist/src/TextEditor/plugins/PasteAsTextPlugin/index.d.ts +1 -0
- package/dist/src/TextEditor/plugins/PastePlugin/PasteCommand.d.ts +5 -0
- package/dist/src/TextEditor/plugins/PastePlugin/PastePlugin.d.ts +5 -0
- package/dist/src/TextEditor/plugins/PastePlugin/index.d.ts +1 -0
- package/dist/src/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.d.ts +6 -0
- package/dist/src/TextEditor/plugins/TabSpacesPlugin/index.d.ts +1 -0
- package/dist/src/TextEditor/textEditorTheming/icons.d.ts +23 -0
- package/dist/src/TextEditor/textEditorTheming/index.d.ts +1 -0
- package/dist/src/TextEditor/textEditorTheming/textEditorTheming.styles.d.ts +2 -0
- package/dist/src/TextEditor/useCKEditorCss.d.ts +3 -0
- package/dist/src/TextEditor/useTabAsNavigation.d.ts +11 -0
- package/dist/src/TextEditor/utils/config.d.ts +3 -0
- package/dist/src/TextEditor/utils/index.d.ts +2 -0
- package/dist/src/TextEditor/utils/locale.d.ts +3 -0
- package/dist/src/TextEditor/utils/plugins.d.ts +7 -0
- package/dist/src/TextEditorOutput/TextEditorOutput.d.ts +8 -0
- package/dist/src/TextEditorOutput/TextEditorOutput.styles.d.ts +2 -0
- package/dist/src/TextEditorOutput/TextEditorOutput.types.d.ts +21 -0
- package/dist/src/TextEditorOutput/TextEditorOutput.utils.d.ts +2 -0
- package/dist/src/TextEditorOutput/index.d.ts +2 -0
- package/dist/src/_storyHelpers/constants.d.ts +14 -0
- package/dist/src/_utils/propsTypedoc.d.ts +3 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/stories/util.d.ts +21 -0
- package/dist/stories/util.js +86 -0
- package/dist/stories/util.js.map +1 -0
- package/jestConfig.d.ts +1 -0
- package/jestConfig.js +66 -0
- package/package.json +146 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Codemod to migrate from @procore/core-react TextEditor to @procore/text-editor package
|
|
5
|
+
*
|
|
6
|
+
* This codemod performs the following transformations:
|
|
7
|
+
*
|
|
8
|
+
* 1. Updates imports from @procore/core-react to @procore/text-editor for TextEditor components
|
|
9
|
+
* 2. Updates Form.RichText usage to include textEditorComponent prop
|
|
10
|
+
* 3. Updates Jest configuration to use textEditorJestConfig
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs')
|
|
14
|
+
const path = require('path')
|
|
15
|
+
|
|
16
|
+
// ANSI color codes for console output
|
|
17
|
+
const colors = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
bold: '\x1b[1m',
|
|
20
|
+
success: '\x1b[32m',
|
|
21
|
+
error: '\x1b[31m',
|
|
22
|
+
info: '\x1b[36m',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Track statistics
|
|
26
|
+
const stats = {
|
|
27
|
+
filesProcessed: 0,
|
|
28
|
+
importsUpdated: 0,
|
|
29
|
+
formRichTextUpdated: 0,
|
|
30
|
+
jestConfigUpdated: 0,
|
|
31
|
+
errors: [],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// TextEditor-related exports that should be migrated
|
|
35
|
+
const TEXT_EDITOR_EXPORTS = [
|
|
36
|
+
'TextEditor',
|
|
37
|
+
'TextEditorOutput',
|
|
38
|
+
'TextEditorProvider',
|
|
39
|
+
'TextEditorProps',
|
|
40
|
+
'TextEditorOutputProps',
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Transform TypeScript/JavaScript imports
|
|
45
|
+
*/
|
|
46
|
+
function transformImports(fileContent) {
|
|
47
|
+
let modified = false
|
|
48
|
+
let newContent = fileContent
|
|
49
|
+
let needsTextEditorImport = false
|
|
50
|
+
|
|
51
|
+
// Pattern to match import statements from @procore/core-react
|
|
52
|
+
const importRegex =
|
|
53
|
+
/import\s*(?:type\s+)?{([^}]+)}\s*from\s*['"]@procore\/core-react['"]/gs
|
|
54
|
+
|
|
55
|
+
newContent = newContent.replace(importRegex, (match, imports) => {
|
|
56
|
+
// Check if this is a type import
|
|
57
|
+
const isTypeImport = match.includes('import type')
|
|
58
|
+
|
|
59
|
+
// Check if this is a multiline import
|
|
60
|
+
const isMultiline = imports.includes('\n')
|
|
61
|
+
|
|
62
|
+
// Split by comma and filter out empty entries
|
|
63
|
+
const importList = imports
|
|
64
|
+
.split(',')
|
|
65
|
+
.map((i) => i.trim())
|
|
66
|
+
.filter((i) => i)
|
|
67
|
+
const textEditorImports = []
|
|
68
|
+
const coreReactImports = []
|
|
69
|
+
|
|
70
|
+
importList.forEach((imp) => {
|
|
71
|
+
// Skip empty imports
|
|
72
|
+
if (!imp) return
|
|
73
|
+
|
|
74
|
+
// Remove type prefix if present
|
|
75
|
+
const cleanImport = imp.replace(/^type\s+/, '')
|
|
76
|
+
const importName = cleanImport.split(/\s+as\s+/)[0].trim()
|
|
77
|
+
|
|
78
|
+
if (TEXT_EDITOR_EXPORTS.includes(importName)) {
|
|
79
|
+
textEditorImports.push(imp)
|
|
80
|
+
modified = true
|
|
81
|
+
} else {
|
|
82
|
+
coreReactImports.push(imp)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
let result = ''
|
|
87
|
+
|
|
88
|
+
// Keep core-react import if there are remaining imports
|
|
89
|
+
if (coreReactImports.length > 0) {
|
|
90
|
+
// Filter out any empty strings from the import list
|
|
91
|
+
const cleanedImports = coreReactImports.filter((imp) => imp.trim())
|
|
92
|
+
const importKeyword = isTypeImport ? 'import type' : 'import'
|
|
93
|
+
|
|
94
|
+
if (isMultiline && cleanedImports.length > 0) {
|
|
95
|
+
result = `${importKeyword} {\n ${cleanedImports.join(
|
|
96
|
+
',\n '
|
|
97
|
+
)},\n} from '@procore/core-react'`
|
|
98
|
+
} else {
|
|
99
|
+
result = `${importKeyword} { ${cleanedImports.join(
|
|
100
|
+
', '
|
|
101
|
+
)} } from '@procore/core-react'`
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add text-editor import if needed
|
|
106
|
+
if (textEditorImports.length > 0) {
|
|
107
|
+
if (result) result += '\n'
|
|
108
|
+
const importKeyword = isTypeImport ? 'import type' : 'import'
|
|
109
|
+
|
|
110
|
+
if (isMultiline && textEditorImports.length > 0) {
|
|
111
|
+
result += `${importKeyword} {\n ${textEditorImports.join(
|
|
112
|
+
',\n '
|
|
113
|
+
)},\n} from '@procore/text-editor'`
|
|
114
|
+
} else {
|
|
115
|
+
result += `${importKeyword} { ${textEditorImports.join(
|
|
116
|
+
', '
|
|
117
|
+
)} } from '@procore/text-editor'`
|
|
118
|
+
}
|
|
119
|
+
stats.importsUpdated++
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return result
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Check if file uses Form.RichText without TextEditor import
|
|
126
|
+
if (
|
|
127
|
+
newContent.includes('Form.RichText') &&
|
|
128
|
+
!newContent.includes('@procore/text-editor')
|
|
129
|
+
) {
|
|
130
|
+
needsTextEditorImport = true
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { content: newContent, modified, needsTextEditorImport }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Transform Form.RichText usage
|
|
138
|
+
*/
|
|
139
|
+
function transformFormRichText(fileContent) {
|
|
140
|
+
let modified = false
|
|
141
|
+
let newContent = fileContent
|
|
142
|
+
let needsTextEditorImport = false
|
|
143
|
+
|
|
144
|
+
// Pattern to match Form.RichText components
|
|
145
|
+
const formRichTextRegex = /<Form\.(RichText(?:Field)?)([\s\S]*?)(\/>|>)/g
|
|
146
|
+
|
|
147
|
+
newContent = newContent.replace(
|
|
148
|
+
formRichTextRegex,
|
|
149
|
+
(match, componentName, propsAndWhitespace, closing) => {
|
|
150
|
+
// Check if textEditorComponent prop already exists
|
|
151
|
+
if (propsAndWhitespace.includes('textEditorComponent')) {
|
|
152
|
+
return match // Already migrated
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Skip Form.RichText.Read (TextEditorOutput) components
|
|
156
|
+
if (match.includes('Form.RichText.Read')) {
|
|
157
|
+
return match
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
modified = true
|
|
161
|
+
needsTextEditorImport = true
|
|
162
|
+
stats.formRichTextUpdated++
|
|
163
|
+
|
|
164
|
+
// Check if props span multiple lines by looking for newlines after the opening tag
|
|
165
|
+
const hasNewlines = propsAndWhitespace.includes('\n')
|
|
166
|
+
|
|
167
|
+
if (hasNewlines) {
|
|
168
|
+
// For multiline props, add the new prop on its own line with proper indentation
|
|
169
|
+
// Find the last prop line to get the indentation
|
|
170
|
+
const lines = propsAndWhitespace.split('\n')
|
|
171
|
+
|
|
172
|
+
// Find the last non-empty line that contains a prop
|
|
173
|
+
let lastPropLineIndex = -1
|
|
174
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
175
|
+
if (lines[i].trim() && lines[i].includes('=')) {
|
|
176
|
+
lastPropLineIndex = i
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (lastPropLineIndex >= 0) {
|
|
182
|
+
// Get indentation from the last prop line
|
|
183
|
+
const indentMatch = lines[lastPropLineIndex].match(/^(\s*)/)
|
|
184
|
+
const indent = indentMatch ? indentMatch[1] : ' '
|
|
185
|
+
|
|
186
|
+
// Insert the new prop after the last prop line
|
|
187
|
+
lines.splice(
|
|
188
|
+
lastPropLineIndex + 1,
|
|
189
|
+
0,
|
|
190
|
+
`${indent}textEditorComponent={TextEditor}`
|
|
191
|
+
)
|
|
192
|
+
const newPropsAndWhitespace = lines.join('\n')
|
|
193
|
+
|
|
194
|
+
return `<Form.${componentName}${newPropsAndWhitespace}${closing}`
|
|
195
|
+
} else {
|
|
196
|
+
// No props found, detect indentation from the first line
|
|
197
|
+
const firstLineIndent = lines[0]
|
|
198
|
+
? lines[0].match(/^(\s*)/)?.[1] || ' '
|
|
199
|
+
: ' '
|
|
200
|
+
return `<Form.${componentName}\n${firstLineIndent}textEditorComponent={TextEditor}${propsAndWhitespace}${closing}`
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
// For single-line or no props
|
|
204
|
+
const trimmed = propsAndWhitespace.trim()
|
|
205
|
+
if (trimmed) {
|
|
206
|
+
// Has props on same line
|
|
207
|
+
return `<Form.${componentName} ${trimmed} textEditorComponent={TextEditor}${closing}`
|
|
208
|
+
} else {
|
|
209
|
+
// No props
|
|
210
|
+
return `<Form.${componentName} textEditorComponent={TextEditor}${closing}`
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
// If we need TextEditor import and it's not already there, add it
|
|
217
|
+
if (
|
|
218
|
+
needsTextEditorImport &&
|
|
219
|
+
!newContent.includes("from '@procore/text-editor'")
|
|
220
|
+
) {
|
|
221
|
+
// Check if there's already an import from @procore/core-react
|
|
222
|
+
const coreReactImportMatch = newContent.match(
|
|
223
|
+
/import\s*{([^}]+)}\s*from\s*['"]@procore\/core-react['"]/
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if (coreReactImportMatch) {
|
|
227
|
+
// Add the text-editor import after the core-react import
|
|
228
|
+
const insertPosition =
|
|
229
|
+
coreReactImportMatch.index + coreReactImportMatch[0].length
|
|
230
|
+
newContent =
|
|
231
|
+
newContent.slice(0, insertPosition) +
|
|
232
|
+
"\nimport { TextEditor } from '@procore/text-editor'" +
|
|
233
|
+
newContent.slice(insertPosition)
|
|
234
|
+
} else {
|
|
235
|
+
// Find the last import statement and add after it
|
|
236
|
+
const lastImportMatch = [
|
|
237
|
+
...newContent.matchAll(/import\s+.*?from\s+['"].*?['"]\s*;?\s*\n/g),
|
|
238
|
+
].pop()
|
|
239
|
+
|
|
240
|
+
if (lastImportMatch) {
|
|
241
|
+
const insertPosition = lastImportMatch.index + lastImportMatch[0].length
|
|
242
|
+
newContent =
|
|
243
|
+
newContent.slice(0, insertPosition) +
|
|
244
|
+
"import { TextEditor } from '@procore/text-editor'\n" +
|
|
245
|
+
newContent.slice(insertPosition)
|
|
246
|
+
} else {
|
|
247
|
+
// No imports found, add at the beginning
|
|
248
|
+
newContent =
|
|
249
|
+
"import { TextEditor } from '@procore/text-editor'\n\n" + newContent
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { content: newContent, modified }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Transform Jest configuration files
|
|
259
|
+
*/
|
|
260
|
+
function transformJestConfig(fileContent) {
|
|
261
|
+
let modified = false
|
|
262
|
+
let newContent = fileContent
|
|
263
|
+
|
|
264
|
+
// Check if file uses coreReactJestConfig
|
|
265
|
+
if (
|
|
266
|
+
newContent.includes('coreReactJestConfig') &&
|
|
267
|
+
newContent.includes('@procore/core-react/jestConfig')
|
|
268
|
+
) {
|
|
269
|
+
// Replace CommonJS require statements
|
|
270
|
+
newContent = newContent.replace(
|
|
271
|
+
/const\s*{\s*coreReactJestConfig\s*}\s*=\s*require\s*\(\s*['"]@procore\/core-react\/jestConfig['"]\s*\)/g,
|
|
272
|
+
"const { textEditorJestConfig } = require('@procore/text-editor/jestConfig')"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
// Replace ES6 import statements
|
|
276
|
+
newContent = newContent.replace(
|
|
277
|
+
/import\s*{\s*coreReactJestConfig\s*}\s*from\s*['"]@procore\/core-react\/jestConfig['"]/g,
|
|
278
|
+
"import { textEditorJestConfig } from '@procore/text-editor/jestConfig'"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
// Replace destructured imports (e.g., import { coreReactJestConfig as something })
|
|
282
|
+
newContent = newContent.replace(
|
|
283
|
+
/import\s*{\s*coreReactJestConfig\s+as\s+(\w+)\s*}\s*from\s*['"]@procore\/core-react\/jestConfig['"]/g,
|
|
284
|
+
"import { textEditorJestConfig as $1 } from '@procore/text-editor/jestConfig'"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
// Replace the function calls
|
|
288
|
+
newContent = newContent.replace(
|
|
289
|
+
/coreReactJestConfig\s*\(/g,
|
|
290
|
+
'textEditorJestConfig('
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if (newContent !== fileContent) {
|
|
294
|
+
modified = true
|
|
295
|
+
stats.jestConfigUpdated++
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { content: newContent, modified }
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Process a single file
|
|
304
|
+
*/
|
|
305
|
+
function processFile(filePath) {
|
|
306
|
+
try {
|
|
307
|
+
const content = fs.readFileSync(filePath, 'utf8')
|
|
308
|
+
let newContent = content
|
|
309
|
+
let wasModified = false
|
|
310
|
+
|
|
311
|
+
// Determine file type
|
|
312
|
+
const ext = path.extname(filePath)
|
|
313
|
+
const fileName = path.basename(filePath)
|
|
314
|
+
|
|
315
|
+
// Config file detection
|
|
316
|
+
const isConfigFile =
|
|
317
|
+
/^jest\.(config|setup)\.(js|ts|cjs|mjs)$/.test(fileName) ||
|
|
318
|
+
/\.config\.(js|ts|cjs|mjs)$/.test(fileName)
|
|
319
|
+
|
|
320
|
+
// Process Jest configuration
|
|
321
|
+
if (isConfigFile && content.includes('coreReactJestConfig')) {
|
|
322
|
+
const result = transformJestConfig(content)
|
|
323
|
+
if (result.modified) {
|
|
324
|
+
newContent = result.content
|
|
325
|
+
wasModified = true
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// For regular JS/TS files, process imports and Form.RichText
|
|
330
|
+
if (['.js', '.jsx', '.ts', '.tsx'].includes(ext) && !isConfigFile) {
|
|
331
|
+
// Transform imports
|
|
332
|
+
const importResult = transformImports(content)
|
|
333
|
+
if (importResult.modified) {
|
|
334
|
+
newContent = importResult.content
|
|
335
|
+
wasModified = true
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Transform Form.RichText
|
|
339
|
+
const formResult = transformFormRichText(newContent)
|
|
340
|
+
if (formResult.modified) {
|
|
341
|
+
newContent = formResult.content
|
|
342
|
+
wasModified = true
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Write back if modified
|
|
347
|
+
if (wasModified) {
|
|
348
|
+
fs.writeFileSync(filePath, newContent, 'utf8')
|
|
349
|
+
console.log(`${colors.success}✓${colors.reset} Updated: ${filePath}`)
|
|
350
|
+
stats.filesProcessed++
|
|
351
|
+
}
|
|
352
|
+
} catch (error) {
|
|
353
|
+
stats.errors.push({ file: filePath, error: error.message })
|
|
354
|
+
console.error(
|
|
355
|
+
`${colors.error}✗${colors.reset} Error processing ${filePath}: ${error.message}`
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Find all relevant files in a directory
|
|
362
|
+
*/
|
|
363
|
+
function findFiles(dir, pattern) {
|
|
364
|
+
const files = []
|
|
365
|
+
|
|
366
|
+
function walk(currentDir) {
|
|
367
|
+
try {
|
|
368
|
+
const items = fs.readdirSync(currentDir)
|
|
369
|
+
|
|
370
|
+
for (const item of items) {
|
|
371
|
+
const fullPath = path.join(currentDir, item)
|
|
372
|
+
const stat = fs.statSync(fullPath)
|
|
373
|
+
|
|
374
|
+
// Skip node_modules and common build directories
|
|
375
|
+
if (
|
|
376
|
+
item === 'node_modules' ||
|
|
377
|
+
item === 'dist' ||
|
|
378
|
+
item === 'build' ||
|
|
379
|
+
item === '.git'
|
|
380
|
+
) {
|
|
381
|
+
continue
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (stat.isDirectory()) {
|
|
385
|
+
walk(fullPath)
|
|
386
|
+
} else if (pattern.test(item)) {
|
|
387
|
+
files.push(fullPath)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error(`Error reading directory ${currentDir}: ${error.message}`)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
walk(dir)
|
|
396
|
+
return files
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Main function
|
|
401
|
+
*/
|
|
402
|
+
function main() {
|
|
403
|
+
const args = process.argv.slice(2)
|
|
404
|
+
|
|
405
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
406
|
+
console.log(`
|
|
407
|
+
${colors.bold}TextEditor Migration Codemod${colors.reset}
|
|
408
|
+
|
|
409
|
+
${colors.bold}Usage:${colors.reset}
|
|
410
|
+
|
|
411
|
+
${colors.info}npx text-editor-migrate [path]${colors.reset}
|
|
412
|
+
|
|
413
|
+
${colors.bold}Description:${colors.reset}
|
|
414
|
+
|
|
415
|
+
Migrates code from using TextEditor components in @procore/core-react
|
|
416
|
+
to the new @procore/text-editor package.
|
|
417
|
+
|
|
418
|
+
${colors.bold}Transformations:${colors.reset}
|
|
419
|
+
|
|
420
|
+
• Updates imports from @procore/core-react to @procore/text-editor
|
|
421
|
+
• Adds textEditorComponent prop to Form.RichText
|
|
422
|
+
• Updates Jest configuration to use textEditorJestConfig
|
|
423
|
+
|
|
424
|
+
${colors.bold}Examples:${colors.reset}
|
|
425
|
+
|
|
426
|
+
${colors.info}npx text-editor-migrate${colors.reset} # Run on current directory
|
|
427
|
+
${colors.info}npx text-editor-migrate ./src${colors.reset} # Run on src directory
|
|
428
|
+
${colors.info}npx text-editor-migrate ./src/Component.tsx${colors.reset} # Run on single file
|
|
429
|
+
`)
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Default to current directory if no path provided
|
|
434
|
+
const targetPath = path.resolve(args[0] || '.')
|
|
435
|
+
|
|
436
|
+
console.log(
|
|
437
|
+
`\n${colors.bold}Starting TextEditor migration...${colors.reset}\n`
|
|
438
|
+
)
|
|
439
|
+
console.log(`Target: ${targetPath}\n`)
|
|
440
|
+
|
|
441
|
+
// Check if target exists
|
|
442
|
+
if (!fs.existsSync(targetPath)) {
|
|
443
|
+
console.error(
|
|
444
|
+
`${colors.error}Error: Path does not exist: ${targetPath}${colors.reset}`
|
|
445
|
+
)
|
|
446
|
+
process.exit(1)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const stat = fs.statSync(targetPath)
|
|
450
|
+
let filesToProcess = []
|
|
451
|
+
|
|
452
|
+
if (stat.isDirectory()) {
|
|
453
|
+
// Find all JavaScript/TypeScript files
|
|
454
|
+
filesToProcess = findFiles(targetPath, /\.(jsx?|tsx?|cjs|mjs)$/)
|
|
455
|
+
|
|
456
|
+
// Also include config files (they might be missed if they're .cjs or .mjs)
|
|
457
|
+
const configFiles = findFiles(targetPath, /\.config\.(js|ts|cjs|mjs)$/)
|
|
458
|
+
|
|
459
|
+
// Deduplicate files (in case config files were already included)
|
|
460
|
+
const uniqueFiles = new Set([...filesToProcess, ...configFiles])
|
|
461
|
+
filesToProcess = Array.from(uniqueFiles)
|
|
462
|
+
} else {
|
|
463
|
+
filesToProcess = [targetPath]
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
console.log(`Found ${filesToProcess.length} files to process\n`)
|
|
467
|
+
|
|
468
|
+
// Process all files
|
|
469
|
+
filesToProcess.forEach((file) => processFile(file))
|
|
470
|
+
|
|
471
|
+
// Check if any changes were made
|
|
472
|
+
if (stats.filesProcessed === 0 && stats.errors.length === 0) {
|
|
473
|
+
console.log(`${colors.bold}No changes${colors.reset}\n`)
|
|
474
|
+
return
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Print summary
|
|
478
|
+
console.log(`\n${colors.bold}Migration Summary:${colors.reset}\n`)
|
|
479
|
+
console.log(
|
|
480
|
+
`${colors.success}✓${colors.reset} Files processed: ${stats.filesProcessed}`
|
|
481
|
+
)
|
|
482
|
+
console.log(
|
|
483
|
+
`${colors.success}✓${colors.reset} Imports updated: ${stats.importsUpdated}`
|
|
484
|
+
)
|
|
485
|
+
console.log(
|
|
486
|
+
`${colors.success}✓${colors.reset} Form.RichText updated: ${stats.formRichTextUpdated}`
|
|
487
|
+
)
|
|
488
|
+
console.log(
|
|
489
|
+
`${colors.success}✓${colors.reset} Jest configs updated: ${stats.jestConfigUpdated}`
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
if (stats.errors.length > 0) {
|
|
493
|
+
console.log(`\n${colors.error}Errors encountered:${colors.reset}`)
|
|
494
|
+
stats.errors.forEach(({ file, error }) => {
|
|
495
|
+
console.log(` ${colors.error}✗${colors.reset} ${file}: ${error}`)
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
console.log(`\n${colors.bold}Migration complete!${colors.reset}\n`)
|
|
500
|
+
|
|
501
|
+
if (stats.filesProcessed > 0) {
|
|
502
|
+
console.log(`${colors.bold}Next steps:${colors.reset}\n`)
|
|
503
|
+
console.log('1. Review the changes made by the codemod')
|
|
504
|
+
console.log('2. Run your tests to ensure everything works correctly\n')
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Run the codemod
|
|
509
|
+
main()
|