@planningcenter/tapestry-migration-cli 3.1.0-rc.9 → 3.1.0

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 (48) hide show
  1. package/dist/tapestry-react-shim.cjs +7 -1
  2. package/package.json +3 -3
  3. package/src/components/input/transformableInput.ts +47 -6
  4. package/src/components/input/transforms/mergeFieldIntoInput.test.ts +78 -0
  5. package/src/components/input/transforms/mergeFieldIntoInput.ts +6 -212
  6. package/src/components/input/transforms/removeDuplicateKeys.test.ts +3 -3
  7. package/src/components/input/transforms/removeTypeInput.ts +3 -3
  8. package/src/components/input/transforms/removeTypeText.ts +2 -3
  9. package/src/components/input/transforms/unsupportedProps.test.ts +20 -20
  10. package/src/components/select/index.ts +58 -0
  11. package/src/components/select/transformableSelect.ts +7 -0
  12. package/src/components/select/transforms/auditSpreadProps.test.ts +103 -0
  13. package/src/components/select/transforms/auditSpreadProps.ts +26 -0
  14. package/src/components/select/transforms/childrenToOptions.test.ts +367 -0
  15. package/src/components/select/transforms/childrenToOptions.ts +295 -0
  16. package/src/components/select/transforms/convertLegacyOptions.test.ts +150 -0
  17. package/src/components/select/transforms/convertLegacyOptions.ts +105 -0
  18. package/src/components/select/transforms/convertStyleProps.test.ts +73 -0
  19. package/src/components/select/transforms/convertStyleProps.ts +12 -0
  20. package/src/components/select/transforms/emptyValueToPlaceholder.test.ts +122 -0
  21. package/src/components/select/transforms/emptyValueToPlaceholder.ts +22 -0
  22. package/src/components/select/transforms/innerRefToRef.test.ts +89 -0
  23. package/src/components/select/transforms/innerRefToRef.ts +18 -0
  24. package/src/components/select/transforms/mapChildrenToOptions.test.ts +521 -0
  25. package/src/components/select/transforms/mapChildrenToOptions.ts +312 -0
  26. package/src/components/select/transforms/mergeFieldIntoSelect.test.ts +506 -0
  27. package/src/components/select/transforms/mergeFieldIntoSelect.ts +7 -0
  28. package/src/components/select/transforms/mergeSelectLabel.test.ts +458 -0
  29. package/src/components/select/transforms/mergeSelectLabel.ts +225 -0
  30. package/src/components/select/transforms/moveSelectImport.test.ts +148 -0
  31. package/src/components/select/transforms/moveSelectImport.ts +14 -0
  32. package/src/components/select/transforms/removeDefaultProps.test.ts +249 -0
  33. package/src/components/select/transforms/removeDefaultProps.ts +112 -0
  34. package/src/components/select/transforms/sizeMapping.test.ts +188 -0
  35. package/src/components/select/transforms/sizeMapping.ts +17 -0
  36. package/src/components/select/transforms/skipMultipleSelect.test.ts +148 -0
  37. package/src/components/select/transforms/skipMultipleSelect.ts +23 -0
  38. package/src/components/select/transforms/stateToInvalid.test.ts +217 -0
  39. package/src/components/select/transforms/stateToInvalid.ts +59 -0
  40. package/src/components/select/transforms/stateToInvalidTernary.test.ts +146 -0
  41. package/src/components/select/transforms/stateToInvalidTernary.ts +13 -0
  42. package/src/components/select/transforms/unsupportedProps.test.ts +252 -0
  43. package/src/components/select/transforms/unsupportedProps.ts +44 -0
  44. package/src/components/shared/helpers/getAttributeExpression.ts +26 -0
  45. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +19 -2
  46. package/src/components/shared/transformFactories/mergeFieldFactory.ts +244 -0
  47. package/src/components/text-area/transforms/mergeFieldIntoTextArea.ts +4 -226
  48. package/src/index.ts +2 -1
@@ -1,227 +1,5 @@
1
- import { JSXElement, JSXText, Transform } from "jscodeshift"
1
+ import { mergeFieldFactory } from "../../shared/transformFactories/mergeFieldFactory"
2
2
 
3
- import { addComment } from "../../shared/actions/addComment"
4
- import {
5
- getImportName,
6
- removeImportFromDeclaration,
7
- } from "../../shared/transformFactories/helpers/manageImports"
8
-
9
- const SCOPE = "mergeFieldIntoTextArea"
10
-
11
- const transform: Transform = (fileInfo, api) => {
12
- const j = api.jscodeshift
13
- const source = j(fileInfo.source)
14
-
15
- const fieldLocalName = getImportName(
16
- "Field",
17
- "@planningcenter/tapestry-react",
18
- { j, source }
19
- )
20
- if (!fieldLocalName) return null
21
-
22
- const textAreaLocalName = getImportName(
23
- "TextArea",
24
- "@planningcenter/tapestry-react",
25
- { j, source }
26
- )
27
- if (!textAreaLocalName) return null
28
-
29
- let hasChanges = false
30
- let anyFieldRemoved = false
31
-
32
- source.find(j.JSXElement).forEach((path) => {
33
- const el = path.value
34
- const opening = el.openingElement
35
-
36
- if (opening.name.type !== "JSXIdentifier") return
37
- if (opening.name.name !== fieldLocalName) return
38
-
39
- const elementChildren = (el.children || []).filter(
40
- (child) =>
41
- child.type !== "JSXText" || (child as JSXText).value.trim() !== ""
42
- )
43
-
44
- const textAreaChildren = elementChildren.filter((child) => {
45
- if (child.type !== "JSXElement") return false
46
- const childOpening = (child as JSXElement).openingElement
47
- return (
48
- childOpening.name.type === "JSXIdentifier" &&
49
- childOpening.name.name === textAreaLocalName
50
- )
51
- }) as JSXElement[]
52
-
53
- // Case: exactly 1 child and it is a TextArea — merge props and unwrap
54
- if (elementChildren.length === 1 && textAreaChildren.length === 1) {
55
- const textAreaEl = textAreaChildren[0]
56
- const fieldAttrs = opening.attributes || []
57
-
58
- // Bail out if Field has spread props — we can't know what they contain
59
- const hasFieldSpreads = fieldAttrs.some(
60
- (attr) => attr.type === "JSXSpreadAttribute"
61
- )
62
- if (hasFieldSpreads) {
63
- addComment({
64
- element: textAreaEl,
65
- j,
66
- scope: SCOPE,
67
- source,
68
- text: "Field has spread props that cannot be auto-merged into TextArea. Please migrate manually.",
69
- })
70
- hasChanges = true
71
- return
72
- }
73
-
74
- for (const attr of fieldAttrs) {
75
- if (attr.type !== "JSXAttribute") continue
76
- if (attr.name.type !== "JSXIdentifier") continue
77
-
78
- const attrName = attr.name.name
79
- const textAreaAttrs = textAreaEl.openingElement.attributes || []
80
-
81
- if (attrName === "label") {
82
- const hasLabel = textAreaAttrs.some(
83
- (a) =>
84
- a.type === "JSXAttribute" &&
85
- a.name?.type === "JSXIdentifier" &&
86
- a.name.name === "label"
87
- )
88
- if (hasLabel) {
89
- addComment({
90
- element: textAreaEl,
91
- j,
92
- scope: SCOPE,
93
- source,
94
- text: "Field had label prop but TextArea already has label. Please migrate manually.",
95
- })
96
- } else {
97
- textAreaEl.openingElement.attributes.push(attr)
98
- }
99
- } else if (attrName === "feedbackText") {
100
- const hasDescription = textAreaAttrs.some(
101
- (a) =>
102
- a.type === "JSXAttribute" &&
103
- a.name?.type === "JSXIdentifier" &&
104
- a.name.name === "description"
105
- )
106
- if (hasDescription) {
107
- addComment({
108
- element: textAreaEl,
109
- j,
110
- scope: SCOPE,
111
- source,
112
- text: "Field had feedbackText prop but TextArea already has description. Please migrate manually.",
113
- })
114
- } else {
115
- const newAttr = j.jsxAttribute(
116
- j.jsxIdentifier("description"),
117
- attr.value
118
- )
119
- textAreaEl.openingElement.attributes.push(newAttr)
120
- }
121
- } else if (attrName === "state") {
122
- const hasState = textAreaAttrs.some(
123
- (a) =>
124
- a.type === "JSXAttribute" &&
125
- a.name?.type === "JSXIdentifier" &&
126
- a.name.name === "state"
127
- )
128
- if (hasState) {
129
- addComment({
130
- element: textAreaEl,
131
- j,
132
- scope: SCOPE,
133
- source,
134
- text: "Field had state prop but TextArea already has state. Please migrate manually.",
135
- })
136
- } else {
137
- textAreaEl.openingElement.attributes.push(attr)
138
- }
139
- } else if (attrName === "key") {
140
- const hasKey = textAreaAttrs.some(
141
- (a) =>
142
- a.type === "JSXAttribute" &&
143
- a.name?.type === "JSXIdentifier" &&
144
- a.name.name === "key"
145
- )
146
- if (!hasKey) {
147
- textAreaEl.openingElement.attributes.push(attr)
148
- }
149
- } else {
150
- // Unsupported prop — add comment to TextArea
151
- addComment({
152
- element: textAreaEl,
153
- j,
154
- scope: SCOPE,
155
- source,
156
- text: `Field prop '${attrName}' is not supported by TextArea. Please migrate manually.`,
157
- })
158
- }
159
- }
160
-
161
- const parent = path.parent?.value
162
- if (parent?.children) {
163
- const idx = parent.children.indexOf(el)
164
- if (idx === -1) return
165
- parent.children.splice(idx, 1, ...(el.children || []))
166
- } else {
167
- // Root JSX (e.g. directly inside return parens) — use path.replace
168
- const nonWsChildren = (el.children || []).filter(
169
- (child) =>
170
- child.type !== "JSXText" || (child as JSXText).value.trim() !== ""
171
- )
172
- if (nonWsChildren.length === 1) {
173
- path.replace(nonWsChildren[0])
174
- } else {
175
- path.replace(
176
- j.jsxFragment(
177
- j.jsxOpeningFragment(),
178
- j.jsxClosingFragment(),
179
- el.children || []
180
- )
181
- )
182
- }
183
- }
184
- hasChanges = true
185
- anyFieldRemoved = true
186
- return
187
- }
188
-
189
- // Case: more than 1 non-whitespace child — comment each TextArea child, leave Field
190
- if (elementChildren.length > 1) {
191
- for (const child of textAreaChildren) {
192
- addComment({
193
- element: child,
194
- j,
195
- scope: SCOPE,
196
- source,
197
- text: "Field has multiple children and cannot be auto-merged into TextArea. Please migrate manually.",
198
- })
199
- hasChanges = true
200
- }
201
- return
202
- }
203
-
204
- // Case: exactly 1 child but not a TextArea — skip without comment
205
- })
206
-
207
- // Remove Field from imports only if all Field usages were converted
208
- if (anyFieldRemoved) {
209
- const stillUsesField =
210
- source.find(j.JSXOpeningElement, {
211
- name: { name: fieldLocalName },
212
- }).length > 0
213
-
214
- if (!stillUsesField) {
215
- const fieldImports = source.find(j.ImportDeclaration, {
216
- source: { value: "@planningcenter/tapestry-react" },
217
- })
218
- for (let i = 0; i < fieldImports.length; i++) {
219
- removeImportFromDeclaration(fieldImports.at(i), "Field")
220
- }
221
- }
222
- }
223
-
224
- return hasChanges ? source.toSource() : null
225
- }
226
-
227
- export default transform
3
+ export default mergeFieldFactory({
4
+ targetComponent: "TextArea",
5
+ })
package/src/index.ts CHANGED
@@ -17,6 +17,7 @@ const COMPONENTS_SET = new Set([
17
17
  "input",
18
18
  "link",
19
19
  "radio",
20
+ "select",
20
21
  "text-area",
21
22
  "toggle-switch",
22
23
  ])
@@ -26,7 +27,7 @@ program
26
27
  .description("Run a migration of a component from Tapestry React to Tapestry")
27
28
  .argument(
28
29
  "<component-name>",
29
- "The name of the component to migrate (button, checkbox, input, link, radio, text-area, toggle-switch)"
30
+ "The name of the component to migrate (button, checkbox, input, link, radio, select, text-area, toggle-switch)"
30
31
  )
31
32
  .requiredOption(
32
33
  "-p, --path <path>",