@planningcenter/tapestry-migration-cli 3.1.0-rc.6 → 3.1.0-rc.7

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 (52) hide show
  1. package/package.json +3 -3
  2. package/src/components/button/transforms/convertStyleProps.test.ts +97 -0
  3. package/src/components/button/transforms/removeTypeButton.test.ts +0 -1
  4. package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +3 -0
  5. package/src/components/input/index.ts +66 -0
  6. package/src/components/input/transformableInput.ts +8 -0
  7. package/src/components/input/transforms/auditSpreadProps.test.ts +192 -0
  8. package/src/components/input/transforms/auditSpreadProps.ts +26 -0
  9. package/src/components/input/transforms/autoWidthTransform.test.ts +172 -0
  10. package/src/components/input/transforms/autoWidthTransform.ts +41 -0
  11. package/src/components/input/transforms/convertStyleProps.test.ts +128 -0
  12. package/src/components/input/transforms/convertStyleProps.ts +12 -0
  13. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.test.ts +186 -0
  14. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.ts +27 -0
  15. package/src/components/input/transforms/inputLabelToLabelProp.test.ts +319 -0
  16. package/src/components/input/transforms/inputLabelToLabelProp.ts +203 -0
  17. package/src/components/input/transforms/mergeFieldIntoInput.test.ts +391 -0
  18. package/src/components/input/transforms/mergeFieldIntoInput.ts +213 -0
  19. package/src/components/input/transforms/mergeInputLabel.test.ts +458 -0
  20. package/src/components/input/transforms/mergeInputLabel.ts +204 -0
  21. package/src/components/input/transforms/moveInputImport.test.ts +166 -0
  22. package/src/components/input/transforms/moveInputImport.ts +14 -0
  23. package/src/components/input/transforms/numberFieldAddTypeNumber.test.ts +92 -0
  24. package/src/components/input/transforms/numberFieldAddTypeNumber.ts +14 -0
  25. package/src/components/input/transforms/numberFieldRenameToInput.test.ts +126 -0
  26. package/src/components/input/transforms/numberFieldRenameToInput.ts +9 -0
  27. package/src/components/input/transforms/removeAsInput.test.ts +139 -0
  28. package/src/components/input/transforms/removeAsInput.ts +20 -0
  29. package/src/components/input/transforms/removeDuplicateKeys.test.ts +302 -0
  30. package/src/components/input/transforms/removeDuplicateKeys.ts +10 -0
  31. package/src/components/input/transforms/removeInputBox.test.ts +352 -0
  32. package/src/components/input/transforms/removeInputBox.ts +109 -0
  33. package/src/components/input/transforms/removeRedundantAriaLabel.test.ts +128 -0
  34. package/src/components/input/transforms/removeRedundantAriaLabel.ts +21 -0
  35. package/src/components/input/transforms/removeTypeText.test.ts +160 -0
  36. package/src/components/input/transforms/removeTypeText.ts +18 -0
  37. package/src/components/input/transforms/sizeMapping.test.ts +198 -0
  38. package/src/components/input/transforms/sizeMapping.ts +17 -0
  39. package/src/components/input/transforms/skipRenderSideProps.test.ts +236 -0
  40. package/src/components/input/transforms/skipRenderSideProps.ts +27 -0
  41. package/src/components/input/transforms/stateToInvalid.test.ts +208 -0
  42. package/src/components/input/transforms/stateToInvalid.ts +59 -0
  43. package/src/components/input/transforms/stateToInvalidTernary.test.ts +159 -0
  44. package/src/components/input/transforms/stateToInvalidTernary.ts +13 -0
  45. package/src/components/input/transforms/unsupportedProps.test.ts +566 -0
  46. package/src/components/input/transforms/unsupportedProps.ts +84 -0
  47. package/src/components/link/transforms/reviewStyles.test.ts +0 -1
  48. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +52 -0
  49. package/src/components/shared/transformFactories/helpers/manageImports.ts +14 -12
  50. package/src/components/shared/transformFactories/sizeMappingFactory.ts +9 -2
  51. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +54 -16
  52. package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +65 -0
@@ -0,0 +1,204 @@
1
+ import { JSXElement, Transform } from "jscodeshift"
2
+
3
+ import { addAttribute } from "../../shared/actions/addAttribute"
4
+ import { addComment } from "../../shared/actions/addComment"
5
+ import { getAttribute } from "../../shared/actions/getAttribute"
6
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
7
+ import { orConditions } from "../../shared/conditions/orConditions"
8
+ import { extractTextContent } from "../../shared/helpers/childrenToLabelHelpers"
9
+ import { getImportName } from "../../shared/transformFactories/helpers/manageImports"
10
+
11
+ const transform: Transform = (fileInfo, api) => {
12
+ const j = api.jscodeshift
13
+ const source = j(fileInfo.source)
14
+
15
+ const inputLocalName = getImportName(
16
+ "Input",
17
+ "@planningcenter/tapestry-react",
18
+ {
19
+ j,
20
+ source,
21
+ }
22
+ )
23
+ if (!inputLocalName) return null
24
+
25
+ const boxLocalName = getImportName("Box", "@planningcenter/tapestry-react", {
26
+ j,
27
+ source,
28
+ })
29
+ const stackViewLocalName = getImportName(
30
+ "StackView",
31
+ "@planningcenter/tapestry-react",
32
+ {
33
+ j,
34
+ source,
35
+ }
36
+ )
37
+
38
+ if (!boxLocalName && !stackViewLocalName) return null
39
+
40
+ let hasChanges = false
41
+
42
+ source.find(j.JSXElement).forEach((path) => {
43
+ const parentEl = path.value
44
+ const openingElement = parentEl.openingElement
45
+
46
+ if (openingElement.name.type !== "JSXIdentifier") return
47
+ const parentName = openingElement.name.name
48
+ const isBox = boxLocalName && parentName === boxLocalName
49
+ const isStackView = stackViewLocalName && parentName === stackViewLocalName
50
+ if (!isBox && !isStackView) return
51
+
52
+ if (isStackView) {
53
+ const axisAttr = getAttribute({ element: parentEl, name: "axis" })
54
+ if (axisAttr) {
55
+ const axisValue = axisAttr.value
56
+ if (
57
+ axisValue &&
58
+ ((axisValue.type === "StringLiteral" &&
59
+ axisValue.value === "horizontal") ||
60
+ (axisValue.type === "JSXExpressionContainer" &&
61
+ axisValue.expression.type === "StringLiteral" &&
62
+ axisValue.expression.value === "horizontal"))
63
+ ) {
64
+ return
65
+ }
66
+ }
67
+ }
68
+
69
+ const children = parentEl.children || []
70
+ let i = 0
71
+
72
+ while (i < children.length) {
73
+ const child = children[i]
74
+
75
+ if (child.type !== "JSXElement") {
76
+ i++
77
+ continue
78
+ }
79
+
80
+ const childOpening = child.openingElement
81
+ const childName = childOpening.name
82
+
83
+ if (childName.type !== "JSXMemberExpression") {
84
+ i++
85
+ continue
86
+ }
87
+ const objectPart = childName.object
88
+ if (
89
+ objectPart.type !== "JSXIdentifier" ||
90
+ objectPart.name !== inputLocalName
91
+ ) {
92
+ i++
93
+ continue
94
+ }
95
+ if (childName.property.name !== "InputLabel") {
96
+ i++
97
+ continue
98
+ }
99
+
100
+ // Find the next JSXElement sibling (skip JSXText whitespace nodes)
101
+ let nextIdx = i + 1
102
+ while (
103
+ nextIdx < children.length &&
104
+ children[nextIdx].type === "JSXText"
105
+ ) {
106
+ nextIdx++
107
+ }
108
+
109
+ if (
110
+ nextIdx >= children.length ||
111
+ children[nextIdx].type !== "JSXElement"
112
+ ) {
113
+ i++
114
+ continue
115
+ }
116
+
117
+ const inputEl = children[nextIdx] as JSXElement
118
+ const inputOpening = inputEl.openingElement
119
+
120
+ if (
121
+ inputOpening.name.type !== "JSXIdentifier" ||
122
+ inputOpening.name.name !== inputLocalName
123
+ ) {
124
+ i++
125
+ continue
126
+ }
127
+
128
+ // Skip inputs with renderLeft/renderRight — they are not transformable
129
+ const hasRenderSide = orConditions(
130
+ hasAttribute("renderLeft"),
131
+ hasAttribute("renderRight")
132
+ )
133
+ if (hasRenderSide(inputEl)) {
134
+ i++
135
+ continue
136
+ }
137
+
138
+ const { isSimpleText, textContent } = extractTextContent(
139
+ child.children || []
140
+ )
141
+
142
+ // Collect non-controls attrs from InputLabel to warn about
143
+ const inputLabelAttrs = childOpening.attributes || []
144
+ const removedProps: string[] = []
145
+ for (const attr of inputLabelAttrs) {
146
+ if (
147
+ attr.type === "JSXAttribute" &&
148
+ attr.name.type === "JSXIdentifier" &&
149
+ attr.name.name !== "controls"
150
+ ) {
151
+ removedProps.push(attr.name.name)
152
+ }
153
+ }
154
+
155
+ children.splice(i, nextIdx - i)
156
+
157
+ hasChanges = true
158
+
159
+ if (!isSimpleText) {
160
+ addComment({
161
+ element: inputEl,
162
+ j,
163
+ scope: "mergeInputLabel",
164
+ source,
165
+ text: "InputLabel children are complex and cannot be auto-converted to a label prop. Please migrate manually.",
166
+ })
167
+ } else {
168
+ const existingLabel = getAttribute({ element: inputEl, name: "label" })
169
+ if (existingLabel) {
170
+ addComment({
171
+ element: inputEl,
172
+ j,
173
+ scope: "mergeInputLabel",
174
+ source,
175
+ text: `Input already has a label prop. The InputLabel had text: "${textContent}". Please review manually.`,
176
+ })
177
+ } else {
178
+ addAttribute({
179
+ element: inputEl,
180
+ j,
181
+ name: "label",
182
+ value: textContent,
183
+ })
184
+ }
185
+ }
186
+
187
+ if (removedProps.length > 0) {
188
+ addComment({
189
+ element: inputEl,
190
+ j,
191
+ scope: "mergeInputLabel",
192
+ source,
193
+ text: `The following props from Input.InputLabel were removed: ${removedProps.join(", ")}. Please review manually.`,
194
+ })
195
+ }
196
+
197
+ i++
198
+ }
199
+ })
200
+
201
+ return hasChanges ? source.toSource() : null
202
+ }
203
+
204
+ export default transform
@@ -0,0 +1,166 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./moveInputImport"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ const result = transform(
11
+ fileInfo,
12
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
13
+ {}
14
+ ) as string | null
15
+ return result || source
16
+ }
17
+
18
+ describe("moveInputImport transform", () => {
19
+ describe("import migration", () => {
20
+ it("should change import from tapestry-react to tapestry", () => {
21
+ const input = `
22
+ import { Input } from "@planningcenter/tapestry-react"
23
+
24
+ function Test() {
25
+ return <Input label="Name" />
26
+ }
27
+ `.trim()
28
+
29
+ const result = applyTransform(input)
30
+ expect(result).toContain(
31
+ 'import { Input } from "@planningcenter/tapestry"'
32
+ )
33
+ expect(result).not.toContain("@planningcenter/tapestry-react")
34
+ })
35
+
36
+ it("should only move Input, leaving other imports in place", () => {
37
+ const input = `
38
+ import { Button, Input } from "@planningcenter/tapestry-react"
39
+
40
+ function Test() {
41
+ return (
42
+ <div>
43
+ <Button>Click</Button>
44
+ <Input label="Name" />
45
+ </div>
46
+ )
47
+ }
48
+ `.trim()
49
+
50
+ const result = applyTransform(input)
51
+ expect(result).toContain(
52
+ 'import { Button } from "@planningcenter/tapestry-react"'
53
+ )
54
+ expect(result).toContain(
55
+ 'import { Input } from "@planningcenter/tapestry"'
56
+ )
57
+ })
58
+
59
+ it("should handle Input as sole import in declaration", () => {
60
+ const input = `
61
+ import { Button } from "@planningcenter/tapestry-react"
62
+ import { Input } from "@planningcenter/tapestry-react"
63
+
64
+ function Test() {
65
+ return (
66
+ <div>
67
+ <Button>Click</Button>
68
+ <Input label="Name" />
69
+ </div>
70
+ )
71
+ }
72
+ `.trim()
73
+
74
+ const result = applyTransform(input)
75
+ expect(result).toContain(
76
+ 'import { Button } from "@planningcenter/tapestry-react"'
77
+ )
78
+ expect(result).toContain(
79
+ 'import { Input } from "@planningcenter/tapestry"'
80
+ )
81
+ })
82
+
83
+ it("should not affect other components", () => {
84
+ const input = `
85
+ import { Button } from "@planningcenter/tapestry-react"
86
+
87
+ function Test() {
88
+ return <Button>Click me</Button>
89
+ }
90
+ `.trim()
91
+
92
+ const result = applyTransform(input)
93
+ expect(result).toContain(
94
+ 'import { Button } from "@planningcenter/tapestry-react"'
95
+ )
96
+ })
97
+ })
98
+
99
+ describe("edge cases", () => {
100
+ it("should handle already migrated imports", () => {
101
+ const input = `
102
+ import { Input } from "@planningcenter/tapestry"
103
+
104
+ function Test() {
105
+ return <Input label="Name" />
106
+ }
107
+ `.trim()
108
+
109
+ const result = applyTransform(input)
110
+ expect(result).toContain(
111
+ 'import { Input } from "@planningcenter/tapestry"'
112
+ )
113
+ })
114
+
115
+ it("should handle no imports", () => {
116
+ const input = `
117
+ function Test() {
118
+ return <div>No imports</div>
119
+ }
120
+ `.trim()
121
+
122
+ const result = applyTransform(input)
123
+ expect(result).toBe(input)
124
+ })
125
+
126
+ it("should preserve all attributes", () => {
127
+ const input = `
128
+ import { Input } from "@planningcenter/tapestry-react"
129
+
130
+ function Test() {
131
+ return (
132
+ <Input
133
+ label="Name"
134
+ placeholder="Enter name"
135
+ disabled
136
+ onChange={() => {}}
137
+ />
138
+ )
139
+ }
140
+ `.trim()
141
+
142
+ const result = applyTransform(input)
143
+ expect(result).toContain(
144
+ 'import { Input } from "@planningcenter/tapestry"'
145
+ )
146
+ expect(result).toContain('label="Name"')
147
+ expect(result).toContain('placeholder="Enter name"')
148
+ expect(result).toContain("disabled")
149
+ expect(result).toContain("onChange={() => {}}")
150
+ })
151
+
152
+ it("should handle alias import", () => {
153
+ const input = `
154
+ import { Input as TapestryInput } from "@planningcenter/tapestry-react"
155
+
156
+ function Test() {
157
+ return <TapestryInput label="Name" />
158
+ }
159
+ `.trim()
160
+
161
+ const result = applyTransform(input)
162
+ expect(result).toContain("@planningcenter/tapestry")
163
+ expect(result).not.toContain("@planningcenter/tapestry-react")
164
+ })
165
+ })
166
+ })
@@ -0,0 +1,14 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { componentTransformFactory } from "../../shared/transformFactories/componentTransformFactory"
4
+ import { transformableInput } from "../transformableInput"
5
+
6
+ const transform: Transform = componentTransformFactory({
7
+ condition: transformableInput,
8
+ fromComponent: "Input",
9
+ fromPackage: "@planningcenter/tapestry-react",
10
+ toComponent: "Input",
11
+ toPackage: "@planningcenter/tapestry",
12
+ })
13
+
14
+ export default transform
@@ -0,0 +1,92 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./numberFieldAddTypeNumber"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string | null {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ return transform(
11
+ fileInfo,
12
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
13
+ {}
14
+ ) as string | null
15
+ }
16
+
17
+ describe("numberFieldAddTypeNumber transform", () => {
18
+ it("adds type='number' to NumberField", () => {
19
+ const input = `
20
+ import { NumberField } from "@planningcenter/tapestry-react"
21
+
22
+ function Test() {
23
+ return <NumberField label="Amount" />
24
+ }
25
+ `.trim()
26
+
27
+ const result = applyTransform(input)
28
+ expect(result).not.toBeNull()
29
+ expect(result).toContain('type="number"')
30
+ })
31
+
32
+ it("preserves other props when adding type", () => {
33
+ const input = `
34
+ import { NumberField } from "@planningcenter/tapestry-react"
35
+
36
+ function Test() {
37
+ return <NumberField label="Amount" value={42} onChange={handleChange} />
38
+ }
39
+ `.trim()
40
+
41
+ const result = applyTransform(input)
42
+ expect(result).toContain('type="number"')
43
+ expect(result).toContain('label="Amount"')
44
+ expect(result).toContain("value={42}")
45
+ expect(result).toContain("onChange={handleChange}")
46
+ })
47
+
48
+ it("does not add type if already present", () => {
49
+ const input = `
50
+ import { NumberField } from "@planningcenter/tapestry-react"
51
+
52
+ function Test() {
53
+ return <NumberField label="Amount" type="number" />
54
+ }
55
+ `.trim()
56
+
57
+ const result = applyTransform(input)
58
+ expect(result).toBeNull()
59
+ })
60
+
61
+ it("returns null when not imported from @planningcenter/tapestry-react", () => {
62
+ const input = `
63
+ import { NumberField } from "other-library"
64
+
65
+ function Test() {
66
+ return <NumberField label="Amount" />
67
+ }
68
+ `.trim()
69
+
70
+ expect(applyTransform(input)).toBeNull()
71
+ })
72
+
73
+ it("handles multiple NumberField elements", () => {
74
+ const input = `
75
+ import { NumberField } from "@planningcenter/tapestry-react"
76
+
77
+ function Test() {
78
+ return (
79
+ <div>
80
+ <NumberField label="Qty" />
81
+ <NumberField label="Price" />
82
+ </div>
83
+ )
84
+ }
85
+ `.trim()
86
+
87
+ const result = applyTransform(input)
88
+ expect(result).not.toBeNull()
89
+ const matches = result!.match(/type="number"/g)
90
+ expect(matches).toHaveLength(2)
91
+ })
92
+ })
@@ -0,0 +1,14 @@
1
+ import { addAttribute } from "../../shared/actions/addAttribute"
2
+ import { getAttribute } from "../../shared/actions/getAttribute"
3
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
4
+
5
+ export default attributeTransformFactory({
6
+ targetComponent: "NumberField",
7
+ targetPackage: "@planningcenter/tapestry-react",
8
+ transform: (element, { j }) => {
9
+ if (getAttribute({ element, name: "type" })) return false
10
+
11
+ addAttribute({ element, j, name: "type", value: "number" })
12
+ return true
13
+ },
14
+ })
@@ -0,0 +1,126 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./numberFieldRenameToInput"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string | null {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ return transform(
11
+ fileInfo,
12
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
13
+ {}
14
+ ) as string | null
15
+ }
16
+
17
+ describe("numberFieldRenameToInput transform", () => {
18
+ it("renames NumberField to Input", () => {
19
+ const input = `
20
+ import { NumberField } from "@planningcenter/tapestry-react"
21
+
22
+ function Test() {
23
+ return <NumberField label="Amount" type="number" />
24
+ }
25
+ `.trim()
26
+
27
+ const result = applyTransform(input)
28
+ expect(result).not.toBeNull()
29
+ expect(result).toContain("<Input")
30
+ expect(result).not.toContain("NumberField")
31
+ })
32
+
33
+ it("updates import from tapestry-react to tapestry", () => {
34
+ const input = `
35
+ import { NumberField } from "@planningcenter/tapestry-react"
36
+
37
+ function Test() {
38
+ return <NumberField label="Amount" type="number" />
39
+ }
40
+ `.trim()
41
+
42
+ const result = applyTransform(input)
43
+ expect(result).toContain('@planningcenter/tapestry"')
44
+ expect(result).not.toContain("@planningcenter/tapestry-react")
45
+ })
46
+
47
+ it("returns null when not imported from @planningcenter/tapestry-react", () => {
48
+ const input = `
49
+ import { NumberField } from "other-library"
50
+
51
+ function Test() {
52
+ return <NumberField label="Amount" />
53
+ }
54
+ `.trim()
55
+
56
+ expect(applyTransform(input)).toBeNull()
57
+ })
58
+
59
+ it("handles multiple NumberField elements", () => {
60
+ const input = `
61
+ import { NumberField } from "@planningcenter/tapestry-react"
62
+
63
+ function Test() {
64
+ return (
65
+ <div>
66
+ <NumberField label="Qty" type="number" />
67
+ <NumberField label="Price" type="number" />
68
+ </div>
69
+ )
70
+ }
71
+ `.trim()
72
+
73
+ const result = applyTransform(input)
74
+ expect(result).not.toBeNull()
75
+ expect(result).not.toContain("NumberField")
76
+ const inputMatches = result!.match(/<Input/g)
77
+ expect(inputMatches).toHaveLength(2)
78
+ })
79
+
80
+ it("does not create duplicate Input identifier when file already imports Input from tapestry-react", () => {
81
+ const input = `
82
+ import { Input, NumberField } from "@planningcenter/tapestry-react"
83
+
84
+ function Test() {
85
+ return (
86
+ <div>
87
+ <Input label="Name" />
88
+ <NumberField label="Amount" type="number" />
89
+ </div>
90
+ )
91
+ }
92
+ `.trim()
93
+
94
+ const result = applyTransform(input)
95
+ expect(result).not.toBeNull()
96
+ expect(result).not.toContain("NumberField")
97
+ // Should not produce two separate Input declarations (would cause parse error)
98
+ const importDeclarationMatches = result!.match(/import.*Input/g)
99
+ expect(importDeclarationMatches).toHaveLength(1)
100
+ })
101
+
102
+ it("merges Input import when file already imports Input from tapestry", () => {
103
+ const input = `
104
+ import { Input } from "@planningcenter/tapestry"
105
+ import { NumberField } from "@planningcenter/tapestry-react"
106
+
107
+ function Test() {
108
+ return (
109
+ <div>
110
+ <Input label="Name" />
111
+ <NumberField label="Amount" type="number" />
112
+ </div>
113
+ )
114
+ }
115
+ `.trim()
116
+
117
+ const result = applyTransform(input)
118
+ expect(result).not.toBeNull()
119
+ expect(result).not.toContain("NumberField")
120
+ // Should not produce two separate Input imports
121
+ const importMatches = result!.match(
122
+ /import.*Input.*from.*@planningcenter\/tapestry/g
123
+ )
124
+ expect(importMatches).toHaveLength(1)
125
+ })
126
+ })
@@ -0,0 +1,9 @@
1
+ import { componentTransformFactory } from "../../shared/transformFactories/componentTransformFactory"
2
+
3
+ export default componentTransformFactory({
4
+ condition: () => true,
5
+ fromComponent: "NumberField",
6
+ fromPackage: "@planningcenter/tapestry-react",
7
+ toComponent: "Input",
8
+ toPackage: "@planningcenter/tapestry",
9
+ })