@planningcenter/tapestry-migration-cli 2.1.1-rc.0 → 2.1.1-rc.2

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.
@@ -0,0 +1,35 @@
1
+ import { Collection, JSCodeshift } from "jscodeshift"
2
+
3
+ /**
4
+ * Gets the local name of Button imported from tapestry-react
5
+ * Returns null if Button is not imported from tapestry-react
6
+ * Handles both normal and renamed imports:
7
+ * - import { Button } from "@planningcenter/tapestry-react" -> "Button"
8
+ * - import { Button as TapestryButton } from "@planningcenter/tapestry-react" -> "TapestryButton"
9
+ */
10
+ export function getTapestryReactImportName(
11
+ importName: string,
12
+ { source, j }: { j: JSCodeshift; source: Collection }
13
+ ): string | null {
14
+ let localName: string | null = null
15
+
16
+ source
17
+ .find(j.ImportDeclaration, {
18
+ source: { value: "@planningcenter/tapestry-react" },
19
+ })
20
+ .forEach((path) => {
21
+ const specifiers = path.value.specifiers || []
22
+
23
+ specifiers.forEach((spec) => {
24
+ if (
25
+ spec.type === "ImportSpecifier" &&
26
+ spec.imported?.name === importName
27
+ ) {
28
+ // spec.local.name is the local name (after "as" if renamed)
29
+ localName = (spec.local?.name as string) || importName
30
+ }
31
+ })
32
+ })
33
+
34
+ return localName
35
+ }
@@ -0,0 +1,288 @@
1
+ import jscodeshift, { JSXElement } from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import {
5
+ andConditions,
6
+ hasAttribute,
7
+ hasAttributeValue,
8
+ orConditions,
9
+ } from "./transformConfig"
10
+
11
+ const j = jscodeshift.withParser("tsx")
12
+
13
+ // Helper function to create a JSX element for testing
14
+ function createJSXElement(attributes: string): JSXElement {
15
+ const code = `<Button${attributes}>Content</Button>`
16
+ const ast = j(code)
17
+ return ast.find(j.JSXElement).get().value as JSXElement
18
+ }
19
+
20
+ describe("transformConfig", () => {
21
+ describe("hasAttribute", () => {
22
+ it("should return true when attribute exists", () => {
23
+ const condition = hasAttribute("href")
24
+ const element = createJSXElement(' href="/test"')
25
+
26
+ expect(condition(element)).toBe(true)
27
+ })
28
+
29
+ it("should return false when attribute does not exist", () => {
30
+ const condition = hasAttribute("href")
31
+ const element = createJSXElement(' className="test"')
32
+
33
+ expect(condition(element)).toBe(false)
34
+ })
35
+
36
+ it("should return true for attribute without value", () => {
37
+ const condition = hasAttribute("disabled")
38
+ const element = createJSXElement(" disabled")
39
+
40
+ expect(condition(element)).toBe(true)
41
+ })
42
+
43
+ it("should return false for empty element", () => {
44
+ const condition = hasAttribute("href")
45
+ const element = createJSXElement("")
46
+
47
+ expect(condition(element)).toBe(false)
48
+ })
49
+
50
+ it("should handle multiple attributes", () => {
51
+ const condition = hasAttribute("onClick")
52
+ const element = createJSXElement(
53
+ ' className="test" onClick={handleClick} disabled'
54
+ )
55
+
56
+ expect(condition(element)).toBe(true)
57
+ })
58
+ })
59
+
60
+ describe("hasAttributeValue", () => {
61
+ it("should return true when attribute has exact string value", () => {
62
+ const condition = hasAttributeValue("as", "a")
63
+ const element = createJSXElement(' as="a"')
64
+
65
+ expect(condition(element)).toBe(true)
66
+ })
67
+
68
+ it("should return false when attribute has different value", () => {
69
+ const condition = hasAttributeValue("as", "a")
70
+ const element = createJSXElement(' as="button"')
71
+
72
+ expect(condition(element)).toBe(false)
73
+ })
74
+
75
+ it("should return false when attribute does not exist", () => {
76
+ const condition = hasAttributeValue("as", "a")
77
+ const element = createJSXElement(' className="test"')
78
+
79
+ expect(condition(element)).toBe(false)
80
+ })
81
+
82
+ it("should return false when attribute exists but has no value", () => {
83
+ const condition = hasAttributeValue("disabled", "true")
84
+ const element = createJSXElement(" disabled")
85
+
86
+ expect(condition(element)).toBe(false)
87
+ })
88
+
89
+ it("should return false for expression values", () => {
90
+ const condition = hasAttributeValue("onClick", "handleClick")
91
+ const element = createJSXElement(" onClick={handleClick}")
92
+
93
+ expect(condition(element)).toBe(false)
94
+ })
95
+
96
+ it("should handle multiple attributes with different values", () => {
97
+ const condition = hasAttributeValue("type", "submit")
98
+ const element = createJSXElement(
99
+ ' className="test" type="submit" disabled'
100
+ )
101
+
102
+ expect(condition(element)).toBe(true)
103
+ })
104
+ })
105
+
106
+ describe("andConditions", () => {
107
+ it("should return true when all conditions are met", () => {
108
+ const condition = andConditions(
109
+ hasAttribute("href"),
110
+ hasAttributeValue("target", "_blank")
111
+ )
112
+ const element = createJSXElement(' href="/test" target="_blank"')
113
+
114
+ expect(condition(element)).toBe(true)
115
+ })
116
+
117
+ it("should return false when one condition fails", () => {
118
+ const condition = andConditions(
119
+ hasAttribute("href"),
120
+ hasAttributeValue("target", "_blank")
121
+ )
122
+ const element = createJSXElement(' href="/test" target="_self"')
123
+
124
+ expect(condition(element)).toBe(false)
125
+ })
126
+
127
+ it("should return false when all conditions fail", () => {
128
+ const condition = andConditions(
129
+ hasAttribute("href"),
130
+ hasAttributeValue("target", "_blank")
131
+ )
132
+ const element = createJSXElement(' className="test"')
133
+
134
+ expect(condition(element)).toBe(false)
135
+ })
136
+
137
+ it("should handle single condition", () => {
138
+ const condition = andConditions(hasAttribute("disabled"))
139
+ const element = createJSXElement(" disabled")
140
+
141
+ expect(condition(element)).toBe(true)
142
+ })
143
+
144
+ it("should handle empty conditions (return true)", () => {
145
+ const condition = andConditions()
146
+ const element = createJSXElement("")
147
+
148
+ expect(condition(element)).toBe(true)
149
+ })
150
+
151
+ it("should handle complex combinations", () => {
152
+ const condition = andConditions(
153
+ hasAttribute("href"),
154
+ hasAttribute("className"),
155
+ hasAttributeValue("role", "button")
156
+ )
157
+ const element = createJSXElement(
158
+ ' href="/test" className="btn" role="button"'
159
+ )
160
+
161
+ expect(condition(element)).toBe(true)
162
+ })
163
+ })
164
+
165
+ describe("orConditions", () => {
166
+ it("should return true when at least one condition is met", () => {
167
+ const condition = orConditions(
168
+ hasAttribute("href"),
169
+ hasAttribute("onClick")
170
+ )
171
+ const element = createJSXElement(' href="/test"')
172
+
173
+ expect(condition(element)).toBe(true)
174
+ })
175
+
176
+ it("should return true when all conditions are met", () => {
177
+ const condition = orConditions(
178
+ hasAttribute("href"),
179
+ hasAttribute("onClick")
180
+ )
181
+ const element = createJSXElement(' href="/test" onClick={handleClick}')
182
+
183
+ expect(condition(element)).toBe(true)
184
+ })
185
+
186
+ it("should return false when no conditions are met", () => {
187
+ const condition = orConditions(
188
+ hasAttribute("href"),
189
+ hasAttribute("onClick")
190
+ )
191
+ const element = createJSXElement(' className="test"')
192
+
193
+ expect(condition(element)).toBe(false)
194
+ })
195
+
196
+ it("should handle single condition", () => {
197
+ const condition = orConditions(hasAttribute("disabled"))
198
+ const element = createJSXElement(" disabled")
199
+
200
+ expect(condition(element)).toBe(true)
201
+ })
202
+
203
+ it("should handle empty conditions (return false)", () => {
204
+ const condition = orConditions()
205
+ const element = createJSXElement("")
206
+
207
+ expect(condition(element)).toBe(false)
208
+ })
209
+
210
+ it("should handle mixed attribute and value conditions", () => {
211
+ const condition = orConditions(
212
+ hasAttributeValue("type", "submit"),
213
+ hasAttribute("href"),
214
+ hasAttributeValue("role", "button")
215
+ )
216
+
217
+ // Should match type="submit"
218
+ const element1 = createJSXElement(' type="submit"')
219
+ expect(condition(element1)).toBe(true)
220
+
221
+ // Should match href
222
+ const element2 = createJSXElement(' href="/test"')
223
+ expect(condition(element2)).toBe(true)
224
+
225
+ // Should match role="button"
226
+ const element3 = createJSXElement(' role="button"')
227
+ expect(condition(element3)).toBe(true)
228
+
229
+ // Should not match
230
+ const element4 = createJSXElement(' className="test" type="button"')
231
+ expect(condition(element4)).toBe(false)
232
+ })
233
+ })
234
+
235
+ describe("complex condition combinations", () => {
236
+ it("should handle nested and/or conditions", () => {
237
+ const condition = andConditions(
238
+ hasAttribute("className"),
239
+ orConditions(hasAttribute("href"), hasAttribute("onClick"))
240
+ )
241
+
242
+ // Should match className + href
243
+ const element1 = createJSXElement(' className="btn" href="/test"')
244
+ expect(condition(element1)).toBe(true)
245
+
246
+ // Should match className + onClick
247
+ const element2 = createJSXElement(' className="btn" onClick={handle}')
248
+ expect(condition(element2)).toBe(true)
249
+
250
+ // Should not match (missing className)
251
+ const element3 = createJSXElement(' href="/test"')
252
+ expect(condition(element3)).toBe(false)
253
+
254
+ // Should not match (missing href/onClick)
255
+ const element4 = createJSXElement(' className="btn"')
256
+ expect(condition(element4)).toBe(false)
257
+ })
258
+
259
+ it("should handle or of and conditions", () => {
260
+ const condition = orConditions(
261
+ andConditions(
262
+ hasAttribute("href"),
263
+ hasAttributeValue("target", "_blank")
264
+ ),
265
+ andConditions(
266
+ hasAttribute("onClick"),
267
+ hasAttributeValue("type", "button")
268
+ )
269
+ )
270
+
271
+ // Should match first AND condition
272
+ const element1 = createJSXElement(' href="/test" target="_blank"')
273
+ expect(condition(element1)).toBe(true)
274
+
275
+ // Should match second AND condition
276
+ const element2 = createJSXElement(' onClick={handle} type="button"')
277
+ expect(condition(element2)).toBe(true)
278
+
279
+ // Should not match (partial first condition)
280
+ const element3 = createJSXElement(' href="/test" target="_self"')
281
+ expect(condition(element3)).toBe(false)
282
+
283
+ // Should not match (partial second condition)
284
+ const element4 = createJSXElement(' onClick={handle} type="submit"')
285
+ expect(condition(element4)).toBe(false)
286
+ })
287
+ })
288
+ })
@@ -0,0 +1,79 @@
1
+ import { JSXElement } from "jscodeshift"
2
+
3
+ /**
4
+ * Condition function that determines whether a JSX element should be transformed
5
+ */
6
+ export type TransformCondition = (element: JSXElement) => boolean
7
+
8
+ /**
9
+ * Configuration for a component transformation
10
+ */
11
+ export interface ComponentTransformConfig {
12
+ /** Condition that must be met for the transform to occur */
13
+ condition: TransformCondition
14
+ /** Optional alias to use if target component conflicts with existing imports */
15
+ conflictAlias?: string
16
+ /** The source component name to transform from */
17
+ fromComponent: string
18
+ /** The package to import the source component from */
19
+ fromPackage: string
20
+ /** The target component name to transform to */
21
+ toComponent: string
22
+ /** The package to import the target component from */
23
+ toPackage: string
24
+ }
25
+
26
+ /**
27
+ * Helper function to create a condition that checks for the presence of an attribute
28
+ */
29
+ export function hasAttribute(attributeName: string): TransformCondition {
30
+ return (element: JSXElement) => {
31
+ const attributes = element.openingElement.attributes || []
32
+ return attributes.some(
33
+ (attr) =>
34
+ attr.type === "JSXAttribute" &&
35
+ attr.name?.type === "JSXIdentifier" &&
36
+ attr.name.name === attributeName
37
+ )
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Helper function to create a condition that checks for an attribute with a specific value
43
+ */
44
+ export function hasAttributeValue(
45
+ attributeName: string,
46
+ value: string
47
+ ): TransformCondition {
48
+ return (element: JSXElement) => {
49
+ const attributes = element.openingElement.attributes || []
50
+ return attributes.some(
51
+ (attr) =>
52
+ attr.type === "JSXAttribute" &&
53
+ attr.name?.type === "JSXIdentifier" &&
54
+ attr.name.name === attributeName &&
55
+ attr.value?.type === "StringLiteral" &&
56
+ attr.value.value === value
57
+ )
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Helper function to combine multiple conditions with AND logic
63
+ */
64
+ export function andConditions(
65
+ ...conditions: TransformCondition[]
66
+ ): TransformCondition {
67
+ return (element: JSXElement) =>
68
+ conditions.every((condition) => condition(element))
69
+ }
70
+
71
+ /**
72
+ * Helper function to combine multiple conditions with OR logic
73
+ */
74
+ export function orConditions(
75
+ ...conditions: TransformCondition[]
76
+ ): TransformCondition {
77
+ return (element: JSXElement) =>
78
+ conditions.some((condition) => condition(element))
79
+ }