@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.
Files changed (161) hide show
  1. package/.jest/ckeditorMock.js +67 -0
  2. package/.jest/esToolkitMock.js +23 -0
  3. package/.jest/setupTests.js +33 -0
  4. package/.jest/styleMock.js +1 -0
  5. package/.jest/svgTransform.js +10 -0
  6. package/.jest/translationFileMock.js +4 -0
  7. package/.jest/translationMock.js +12 -0
  8. package/LICENSE +84 -0
  9. package/README.md +107 -0
  10. package/codemod/__fixtures__/hammer.config.mjs +15 -0
  11. package/codemod/__fixtures__/jest.config.js +6 -0
  12. package/codemod/__fixtures__/procore.config.js +12 -0
  13. package/codemod/__fixtures__/src/components/ComplexEditor.tsx +21 -0
  14. package/codemod/__fixtures__/src/components/FormWithRichText.tsx +10 -0
  15. package/codemod/__fixtures__/src/components/MultilineFormRichText.tsx +14 -0
  16. package/codemod/__fixtures__/src/components/NoTextEditor.tsx +11 -0
  17. package/codemod/__fixtures__/src/components/ReadOnlyRichText.tsx +10 -0
  18. package/codemod/__fixtures__/src/components/SimpleEditor.tsx +11 -0
  19. package/codemod/__fixtures__/src/components/TypeImportEditor.tsx +17 -0
  20. package/codemod/text-editor-migrate.js +509 -0
  21. package/codemod/text-editor-migrate.test.js +225 -0
  22. package/dist/TextEditor/EditorError.js +9 -0
  23. package/dist/TextEditor/EditorError.js.map +1 -0
  24. package/dist/TextEditor/StickyToolbar/index.js +2 -0
  25. package/dist/TextEditor/StickyToolbar/index.js.map +1 -0
  26. package/dist/TextEditor/StickyToolbar/useStickyToolbar.js +59 -0
  27. package/dist/TextEditor/StickyToolbar/useStickyToolbar.js.map +1 -0
  28. package/dist/TextEditor/StickyToolbar/useStickyToolbar.types.js +2 -0
  29. package/dist/TextEditor/StickyToolbar/useStickyToolbar.types.js.map +1 -0
  30. package/dist/TextEditor/TextEditor.js +226 -0
  31. package/dist/TextEditor/TextEditor.js.map +1 -0
  32. package/dist/TextEditor/TextEditor.styles.js +26 -0
  33. package/dist/TextEditor/TextEditor.styles.js.map +1 -0
  34. package/dist/TextEditor/TextEditor.types.js +2 -0
  35. package/dist/TextEditor/TextEditor.types.js.map +1 -0
  36. package/dist/TextEditor/TextEditorProvider.js +17 -0
  37. package/dist/TextEditor/TextEditorProvider.js.map +1 -0
  38. package/dist/TextEditor/TextEditorProvider.types.js +2 -0
  39. package/dist/TextEditor/TextEditorProvider.types.js.map +1 -0
  40. package/dist/TextEditor/index.js +4 -0
  41. package/dist/TextEditor/index.js.map +1 -0
  42. package/dist/TextEditor/license_key.js +3 -0
  43. package/dist/TextEditor/license_key.js.map +1 -0
  44. package/dist/TextEditor/plugins/CutPlugin/CutCommand.js +99 -0
  45. package/dist/TextEditor/plugins/CutPlugin/CutCommand.js.map +1 -0
  46. package/dist/TextEditor/plugins/CutPlugin/CutPlugin.js +56 -0
  47. package/dist/TextEditor/plugins/CutPlugin/CutPlugin.js.map +1 -0
  48. package/dist/TextEditor/plugins/CutPlugin/index.js +2 -0
  49. package/dist/TextEditor/plugins/CutPlugin/index.js.map +1 -0
  50. package/dist/TextEditor/plugins/IndentPaddingToMarginPlugin/IndentPaddingToMarginPlugin.js +40 -0
  51. package/dist/TextEditor/plugins/IndentPaddingToMarginPlugin/IndentPaddingToMarginPlugin.js.map +1 -0
  52. package/dist/TextEditor/plugins/IndentPaddingToMarginPlugin/index.js +2 -0
  53. package/dist/TextEditor/plugins/IndentPaddingToMarginPlugin/index.js.map +1 -0
  54. package/dist/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextCommand.js +86 -0
  55. package/dist/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextCommand.js.map +1 -0
  56. package/dist/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextPlugin.js +56 -0
  57. package/dist/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextPlugin.js.map +1 -0
  58. package/dist/TextEditor/plugins/PasteAsTextPlugin/index.js +2 -0
  59. package/dist/TextEditor/plugins/PasteAsTextPlugin/index.js.map +1 -0
  60. package/dist/TextEditor/plugins/PastePlugin/PasteCommand.js +149 -0
  61. package/dist/TextEditor/plugins/PastePlugin/PasteCommand.js.map +1 -0
  62. package/dist/TextEditor/plugins/PastePlugin/PastePlugin.js +56 -0
  63. package/dist/TextEditor/plugins/PastePlugin/PastePlugin.js.map +1 -0
  64. package/dist/TextEditor/plugins/PastePlugin/index.js +2 -0
  65. package/dist/TextEditor/plugins/PastePlugin/index.js.map +1 -0
  66. package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js +87 -0
  67. package/dist/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.js.map +1 -0
  68. package/dist/TextEditor/plugins/TabSpacesPlugin/index.js +2 -0
  69. package/dist/TextEditor/plugins/TabSpacesPlugin/index.js.map +1 -0
  70. package/dist/TextEditor/textEditorTheming/icons.js +24 -0
  71. package/dist/TextEditor/textEditorTheming/icons.js.map +1 -0
  72. package/dist/TextEditor/textEditorTheming/index.js +2 -0
  73. package/dist/TextEditor/textEditorTheming/index.js.map +1 -0
  74. package/dist/TextEditor/textEditorTheming/textEditorTheming.styles.js +10 -0
  75. package/dist/TextEditor/textEditorTheming/textEditorTheming.styles.js.map +1 -0
  76. package/dist/TextEditor/useCKEditorCss.js +36 -0
  77. package/dist/TextEditor/useCKEditorCss.js.map +1 -0
  78. package/dist/TextEditor/useTabAsNavigation.js +29 -0
  79. package/dist/TextEditor/useTabAsNavigation.js.map +1 -0
  80. package/dist/TextEditor/utils/config.js +179 -0
  81. package/dist/TextEditor/utils/config.js.map +1 -0
  82. package/dist/TextEditor/utils/index.js +3 -0
  83. package/dist/TextEditor/utils/index.js.map +1 -0
  84. package/dist/TextEditor/utils/locale.js +102 -0
  85. package/dist/TextEditor/utils/locale.js.map +1 -0
  86. package/dist/TextEditor/utils/plugins.js +184 -0
  87. package/dist/TextEditor/utils/plugins.js.map +1 -0
  88. package/dist/TextEditorOutput/TextEditorOutput.js +29 -0
  89. package/dist/TextEditorOutput/TextEditorOutput.js.map +1 -0
  90. package/dist/TextEditorOutput/TextEditorOutput.styles.js +6 -0
  91. package/dist/TextEditorOutput/TextEditorOutput.styles.js.map +1 -0
  92. package/dist/TextEditorOutput/TextEditorOutput.types.js +2 -0
  93. package/dist/TextEditorOutput/TextEditorOutput.types.js.map +1 -0
  94. package/dist/TextEditorOutput/TextEditorOutput.utils.js +59 -0
  95. package/dist/TextEditorOutput/TextEditorOutput.utils.js.map +1 -0
  96. package/dist/TextEditorOutput/index.js +2 -0
  97. package/dist/TextEditorOutput/index.js.map +1 -0
  98. package/dist/_storyHelpers/constants.js +48 -0
  99. package/dist/_storyHelpers/constants.js.map +1 -0
  100. package/dist/_typedoc/TextEditor/TextEditor.types.json +227 -0
  101. package/dist/_typedoc/TextEditor/TextEditorProvider.types.json +28 -0
  102. package/dist/_typedoc/TextEditorOutput/TextEditorOutput.types.json +38 -0
  103. package/dist/_typedoc/deprecations.json +1 -0
  104. package/dist/_utils/propsTypedoc.js +4 -0
  105. package/dist/_utils/propsTypedoc.js.map +1 -0
  106. package/dist/codemod/__fixtures__/src/components/ComplexEditor.d.ts +2 -0
  107. package/dist/codemod/__fixtures__/src/components/FormWithRichText.d.ts +2 -0
  108. package/dist/codemod/__fixtures__/src/components/MultilineFormRichText.d.ts +2 -0
  109. package/dist/codemod/__fixtures__/src/components/NoTextEditor.d.ts +2 -0
  110. package/dist/codemod/__fixtures__/src/components/ReadOnlyRichText.d.ts +2 -0
  111. package/dist/codemod/__fixtures__/src/components/SimpleEditor.d.ts +2 -0
  112. package/dist/codemod/__fixtures__/src/components/TypeImportEditor.d.ts +2 -0
  113. package/dist/index.js +3 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/src/TextEditor/EditorError.d.ts +2 -0
  116. package/dist/src/TextEditor/StickyToolbar/index.d.ts +2 -0
  117. package/dist/src/TextEditor/StickyToolbar/useStickyToolbar.d.ts +2 -0
  118. package/dist/src/TextEditor/StickyToolbar/useStickyToolbar.types.d.ts +8 -0
  119. package/dist/src/TextEditor/TextEditor.d.ts +44 -0
  120. package/dist/src/TextEditor/TextEditor.styles.d.ts +7 -0
  121. package/dist/src/TextEditor/TextEditor.types.d.ts +155 -0
  122. package/dist/src/TextEditor/TextEditorProvider.d.ts +4 -0
  123. package/dist/src/TextEditor/TextEditorProvider.types.d.ts +14 -0
  124. package/dist/src/TextEditor/index.d.ts +4 -0
  125. package/dist/src/TextEditor/license_key.d.ts +2 -0
  126. package/dist/src/TextEditor/plugins/CutPlugin/CutCommand.d.ts +5 -0
  127. package/dist/src/TextEditor/plugins/CutPlugin/CutPlugin.d.ts +5 -0
  128. package/dist/src/TextEditor/plugins/CutPlugin/index.d.ts +1 -0
  129. package/dist/src/TextEditor/plugins/IndentPaddingToMarginPlugin/IndentPaddingToMarginPlugin.d.ts +5 -0
  130. package/dist/src/TextEditor/plugins/IndentPaddingToMarginPlugin/index.d.ts +1 -0
  131. package/dist/src/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextCommand.d.ts +5 -0
  132. package/dist/src/TextEditor/plugins/PasteAsTextPlugin/PasteAsTextPlugin.d.ts +5 -0
  133. package/dist/src/TextEditor/plugins/PasteAsTextPlugin/index.d.ts +1 -0
  134. package/dist/src/TextEditor/plugins/PastePlugin/PasteCommand.d.ts +5 -0
  135. package/dist/src/TextEditor/plugins/PastePlugin/PastePlugin.d.ts +5 -0
  136. package/dist/src/TextEditor/plugins/PastePlugin/index.d.ts +1 -0
  137. package/dist/src/TextEditor/plugins/TabSpacesPlugin/TabSpacesPlugin.d.ts +6 -0
  138. package/dist/src/TextEditor/plugins/TabSpacesPlugin/index.d.ts +1 -0
  139. package/dist/src/TextEditor/textEditorTheming/icons.d.ts +23 -0
  140. package/dist/src/TextEditor/textEditorTheming/index.d.ts +1 -0
  141. package/dist/src/TextEditor/textEditorTheming/textEditorTheming.styles.d.ts +2 -0
  142. package/dist/src/TextEditor/useCKEditorCss.d.ts +3 -0
  143. package/dist/src/TextEditor/useTabAsNavigation.d.ts +11 -0
  144. package/dist/src/TextEditor/utils/config.d.ts +3 -0
  145. package/dist/src/TextEditor/utils/index.d.ts +2 -0
  146. package/dist/src/TextEditor/utils/locale.d.ts +3 -0
  147. package/dist/src/TextEditor/utils/plugins.d.ts +7 -0
  148. package/dist/src/TextEditorOutput/TextEditorOutput.d.ts +8 -0
  149. package/dist/src/TextEditorOutput/TextEditorOutput.styles.d.ts +2 -0
  150. package/dist/src/TextEditorOutput/TextEditorOutput.types.d.ts +21 -0
  151. package/dist/src/TextEditorOutput/TextEditorOutput.utils.d.ts +2 -0
  152. package/dist/src/TextEditorOutput/index.d.ts +2 -0
  153. package/dist/src/_storyHelpers/constants.d.ts +14 -0
  154. package/dist/src/_utils/propsTypedoc.d.ts +3 -0
  155. package/dist/src/index.d.ts +2 -0
  156. package/dist/src/stories/util.d.ts +21 -0
  157. package/dist/stories/util.js +86 -0
  158. package/dist/stories/util.js.map +1 -0
  159. package/jestConfig.d.ts +1 -0
  160. package/jestConfig.js +66 -0
  161. 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()