@planningcenter/tapestry-migration-cli 2.2.1-qa-362.0 → 2.3.0-rc.10

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 (64) hide show
  1. package/dist/tapestry-react-shim.cjs +5065 -0
  2. package/package.json +9 -5
  3. package/src/components/button/index.ts +53 -3
  4. package/src/components/button/transforms/auditSpreadProps.test.ts +352 -0
  5. package/src/components/button/transforms/auditSpreadProps.ts +24 -0
  6. package/src/components/button/transforms/childrenToLabel.test.ts +363 -0
  7. package/src/components/button/transforms/childrenToLabel.ts +84 -0
  8. package/src/components/button/transforms/commentOnVisualKindDifference.ts +24 -0
  9. package/src/components/button/transforms/convertStyleProps.test.ts +464 -0
  10. package/src/components/button/transforms/convertStyleProps.ts +16 -0
  11. package/src/components/button/transforms/iconLeftToPrefix.test.ts +432 -0
  12. package/src/components/button/transforms/iconLeftToPrefix.ts +33 -0
  13. package/src/components/button/transforms/iconRightToSuffix.test.ts +407 -0
  14. package/src/components/button/transforms/iconRightToSuffix.ts +33 -0
  15. package/src/components/button/transforms/iconToIconButton.test.ts +377 -0
  16. package/src/components/button/transforms/iconToIconButton.ts +53 -0
  17. package/src/components/button/transforms/removeAsButton.ts +15 -0
  18. package/src/components/button/transforms/removeDuplicateKeys.test.ts +302 -0
  19. package/src/components/button/transforms/removeDuplicateKeys.ts +8 -0
  20. package/src/components/button/transforms/reviewStyles.ts +17 -0
  21. package/src/components/button/transforms/spinnerToLoadingButton.test.ts +165 -0
  22. package/src/components/button/transforms/spinnerToLoadingButton.ts +14 -0
  23. package/src/components/button/transforms/themeVariantToKind.test.ts +401 -0
  24. package/src/components/button/transforms/themeVariantToKind.ts +90 -0
  25. package/src/components/button/transforms/tooltipToWrapper.test.ts +392 -0
  26. package/src/components/button/transforms/tooltipToWrapper.ts +35 -0
  27. package/src/components/button/transforms/unsupportedProps.ts +73 -0
  28. package/src/components/shared/actions/addAttribute.test.ts +300 -0
  29. package/src/components/shared/actions/addAttribute.ts +65 -0
  30. package/src/components/shared/actions/addComment.test.ts +1 -1
  31. package/src/components/shared/actions/addCommentToAttribute.test.ts +45 -0
  32. package/src/components/shared/actions/addCommentToAttribute.ts +28 -0
  33. package/src/components/shared/actions/addCommentToUnsupportedProps.ts +29 -0
  34. package/src/components/shared/actions/convertAttributeFromObjectToJSXElement.test.ts +139 -0
  35. package/src/components/shared/actions/convertAttributeFromObjectToJSXElement.ts +81 -0
  36. package/src/components/shared/actions/createWrapper.test.ts +642 -0
  37. package/src/components/shared/actions/createWrapper.ts +70 -0
  38. package/src/components/shared/actions/getAttribute.ts +18 -0
  39. package/src/components/shared/actions/getAttributeValue.test.ts +261 -0
  40. package/src/components/shared/actions/getAttributeValue.ts +15 -0
  41. package/src/components/shared/actions/getAttributeValueAsProps.ts +57 -0
  42. package/src/components/shared/actions/getSpreadProps.ts +7 -0
  43. package/src/components/shared/actions/hasSpreadProps.ts +7 -0
  44. package/src/components/shared/actions/removeChildren.ts +7 -0
  45. package/src/components/shared/actions/removeDuplicateKeys.test.ts +280 -0
  46. package/src/components/shared/actions/removeDuplicateKeys.ts +45 -0
  47. package/src/components/shared/actions/removeUnusedImport.test.ts +302 -0
  48. package/src/components/shared/actions/removeUnusedImport.ts +81 -0
  49. package/src/components/shared/actions/transformElementName.test.ts +9 -9
  50. package/src/components/shared/actions/transformElementName.ts +13 -16
  51. package/src/components/shared/conditions/hasChildren.ts +5 -0
  52. package/src/components/shared/getJavaScriptTheme.ts +68 -0
  53. package/src/components/shared/jsThemeLoader.ts +85 -0
  54. package/src/components/shared/transformFactories/attributeCombineFactory.test.ts +374 -0
  55. package/src/components/shared/transformFactories/attributeCombineFactory.ts +300 -0
  56. package/src/components/shared/transformFactories/attributeTransformFactory.ts +14 -6
  57. package/src/components/shared/transformFactories/componentTransformFactory.ts +1 -1
  58. package/src/components/shared/transformFactories/helpers/addImport.test.ts +278 -0
  59. package/src/components/shared/transformFactories/helpers/manageImports.ts +53 -20
  60. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +362 -0
  61. package/src/index.ts +4 -0
  62. package/src/stubs/stackViewPlugin.ts +33 -0
  63. package/src/stubs/tapestry-stub.ts +16 -0
  64. package/src/tapestry-react-shim.ts +7 -0
@@ -1,4 +1,10 @@
1
- import { Collection, JSCodeshift, JSXElement, Transform } from "jscodeshift"
1
+ import {
2
+ Collection,
3
+ JSCodeshift,
4
+ JSXElement,
5
+ Options,
6
+ Transform,
7
+ } from "jscodeshift"
2
8
 
3
9
  import { TransformCondition } from "../types"
4
10
  import { getImportName } from "./helpers/manageImports"
@@ -8,7 +14,7 @@ import { getImportName } from "./helpers/manageImports"
8
14
  */
9
15
  export function attributeTransformFactory(config: {
10
16
  /** Condition that must be met for the transform to occur */
11
- condition: TransformCondition
17
+ condition?: TransformCondition
12
18
  /** Component to target for attribute transformation */
13
19
  targetComponent: string
14
20
  /** Package the target component is imported from */
@@ -16,10 +22,10 @@ export function attributeTransformFactory(config: {
16
22
  /** Function that performs the actual attribute transformation */
17
23
  transform: (
18
24
  element: JSXElement,
19
- resources: { j: JSCodeshift; source: Collection }
25
+ resources: { j: JSCodeshift; options: Options; source: Collection }
20
26
  ) => boolean
21
27
  }): Transform {
22
- return (fileInfo, api) => {
28
+ return (fileInfo, api, options) => {
23
29
  const j = api.jscodeshift
24
30
  const source = j(fileInfo.source)
25
31
  let hasChanges = false
@@ -36,14 +42,16 @@ export function attributeTransformFactory(config: {
36
42
  return null
37
43
  }
38
44
 
45
+ const { condition = () => true, transform } = config
46
+
39
47
  // Find and transform matching JSX elements
40
48
  source
41
49
  .find(j.JSXOpeningElement, { name: { name: targetComponentName } })
42
50
  .forEach((path) => {
43
51
  const element = path.parent.value
44
52
 
45
- if (config.condition(element)) {
46
- if (config.transform(element, { j, source })) {
53
+ if (condition(element)) {
54
+ if (transform(element, { j, options, source })) {
47
55
  hasChanges = true
48
56
  }
49
57
  }
@@ -60,7 +60,7 @@ export function componentTransformFactory(config: {
60
60
  const element = path.parent.value
61
61
 
62
62
  if (config.condition(element)) {
63
- if (transformElementName(path, targetComponentName)) {
63
+ if (transformElementName({ element, name: targetComponentName })) {
64
64
  hasChanges = true
65
65
  }
66
66
  }
@@ -0,0 +1,278 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import { addImport } from "./manageImports"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function createSource(code: string) {
9
+ return j(code)
10
+ }
11
+
12
+ describe("addImport", () => {
13
+ describe("when component already imported from target package", () => {
14
+ it("should return existing import name", () => {
15
+ const source = createSource(
16
+ 'import { Button } from "@planningcenter/tapestry"'
17
+ )
18
+ const result = addImport({
19
+ component: "Button",
20
+ conflictAlias: "TButton",
21
+ j,
22
+ pkg: "@planningcenter/tapestry",
23
+ source,
24
+ })
25
+
26
+ expect(result).toBe("Button")
27
+ // Should not modify the source
28
+ const imports = source.find(j.ImportDeclaration)
29
+ expect(imports).toHaveLength(1)
30
+ expect(imports.at(0).get().value.source.value).toBe(
31
+ "@planningcenter/tapestry"
32
+ )
33
+ expect(imports.at(0).get().value.specifiers).toHaveLength(1)
34
+ })
35
+
36
+ it("should return existing aliased import name", () => {
37
+ const source = createSource(
38
+ 'import { Button as TapestryButton } from "@planningcenter/tapestry"'
39
+ )
40
+ const result = addImport({
41
+ component: "Button",
42
+ conflictAlias: "TButton",
43
+ j,
44
+ pkg: "@planningcenter/tapestry",
45
+ source,
46
+ })
47
+
48
+ expect(result).toBe("TapestryButton")
49
+ })
50
+ })
51
+
52
+ describe("when adding to existing import declaration", () => {
53
+ it("should add component to existing import without conflict", () => {
54
+ const source = createSource(
55
+ 'import { Link } from "@planningcenter/tapestry"'
56
+ )
57
+ const result = addImport({
58
+ component: "Button",
59
+ conflictAlias: "TButton",
60
+ j,
61
+ pkg: "@planningcenter/tapestry",
62
+ source,
63
+ })
64
+
65
+ expect(result).toBe("Button")
66
+
67
+ const imports = source.find(j.ImportDeclaration)
68
+ expect(imports).toHaveLength(1)
69
+ expect(imports.at(0).get().value.specifiers).toHaveLength(2)
70
+
71
+ const specifierNames = imports
72
+ .at(0)
73
+ .get()
74
+ .value.specifiers?.map(
75
+ (spec: { imported: { name: string }; type: string }) =>
76
+ spec.type === "ImportSpecifier" ? spec.imported?.name : null
77
+ )
78
+ expect(specifierNames).toContain("Link")
79
+ expect(specifierNames).toContain("Button")
80
+ })
81
+
82
+ it("should add component with auto-generated alias when conflict exists", () => {
83
+ const source = createSource(`
84
+ import { Button } from "@some/other-package"
85
+ import { Link } from "@planningcenter/tapestry"
86
+ `)
87
+
88
+ const result = addImport({
89
+ component: "Button",
90
+ conflictAlias: "TButton",
91
+ j,
92
+ pkg: "@planningcenter/tapestry",
93
+ source,
94
+ })
95
+
96
+ expect(result).toBe("TButton")
97
+
98
+ const tapestryImport = source.find(j.ImportDeclaration, {
99
+ source: { value: "@planningcenter/tapestry" },
100
+ })
101
+ expect(tapestryImport).toHaveLength(1)
102
+
103
+ const specifiers = tapestryImport.at(0).get().value.specifiers
104
+ const buttonSpec = specifiers?.find(
105
+ (spec: { imported: { name: string }; type: string }) =>
106
+ spec.type === "ImportSpecifier" && spec.imported?.name === "Button"
107
+ )
108
+ expect(buttonSpec?.local?.name).toBe("TButton")
109
+ })
110
+
111
+ it("should add component with custom alias when conflict exists", () => {
112
+ const source = createSource(`
113
+ import { Button } from "@some/other-package"
114
+ import { Link } from "@planningcenter/tapestry"
115
+ `)
116
+
117
+ const result = addImport({
118
+ component: "Button",
119
+ conflictAlias: "TapestryButton",
120
+ j,
121
+ pkg: "@planningcenter/tapestry",
122
+ source,
123
+ })
124
+
125
+ expect(result).toBe("TapestryButton")
126
+
127
+ const tapestryImport = source.find(j.ImportDeclaration, {
128
+ source: { value: "@planningcenter/tapestry" },
129
+ })
130
+ expect(tapestryImport).toHaveLength(1)
131
+
132
+ const specifiers = tapestryImport.at(0).get().value.specifiers
133
+ const buttonSpec = specifiers?.find(
134
+ (spec: { imported: { name: string }; type: string }) =>
135
+ spec.type === "ImportSpecifier" && spec.imported?.name === "Button"
136
+ )
137
+ expect(buttonSpec?.local?.name).toBe("TapestryButton")
138
+ })
139
+ })
140
+
141
+ describe("when creating new import declaration", () => {
142
+ it("should create new import without conflict", () => {
143
+ const source = createSource('import React from "react"')
144
+ const result = addImport({
145
+ component: "Button",
146
+ conflictAlias: "TButton",
147
+ j,
148
+ pkg: "@planningcenter/tapestry",
149
+ source,
150
+ })
151
+
152
+ expect(result).toBe("Button")
153
+
154
+ const imports = source.find(j.ImportDeclaration)
155
+ expect(imports).toHaveLength(2)
156
+
157
+ const tapestryImport = source.find(j.ImportDeclaration, {
158
+ source: { value: "@planningcenter/tapestry" },
159
+ })
160
+ expect(tapestryImport).toHaveLength(1)
161
+ expect(tapestryImport.at(0).get().value.specifiers).toHaveLength(1)
162
+ expect(
163
+ tapestryImport.at(0).get().value.specifiers?.[0].imported?.name
164
+ ).toBe("Button")
165
+ })
166
+
167
+ it("should create new import with auto-generated alias when conflict exists", () => {
168
+ const source = createSource(`
169
+ import React from "react"
170
+ import { Button } from "@some/other-package"
171
+ `)
172
+
173
+ const result = addImport({
174
+ component: "Button",
175
+ conflictAlias: "TButton",
176
+ j,
177
+ pkg: "@planningcenter/tapestry",
178
+ source,
179
+ })
180
+
181
+ expect(result).toBe("TButton")
182
+
183
+ const imports = source.find(j.ImportDeclaration)
184
+ expect(imports).toHaveLength(3)
185
+
186
+ const tapestryImport = source.find(j.ImportDeclaration, {
187
+ source: { value: "@planningcenter/tapestry" },
188
+ })
189
+ expect(tapestryImport).toHaveLength(1)
190
+
191
+ const buttonSpec = tapestryImport.at(0).get().value.specifiers?.[0]
192
+ expect(buttonSpec?.imported?.name).toBe("Button")
193
+ expect(buttonSpec?.local?.name).toBe("TButton")
194
+ })
195
+
196
+ it("should create new import with custom alias when conflict exists", () => {
197
+ const source = createSource(`
198
+ import React from "react"
199
+ import { Button } from "@some/other-package"
200
+ `)
201
+
202
+ const result = addImport({
203
+ component: "Button",
204
+ conflictAlias: "TapestryButton",
205
+ j,
206
+ pkg: "@planningcenter/tapestry",
207
+ source,
208
+ })
209
+
210
+ expect(result).toBe("TapestryButton")
211
+
212
+ const tapestryImport = source.find(j.ImportDeclaration, {
213
+ source: { value: "@planningcenter/tapestry" },
214
+ })
215
+ expect(tapestryImport).toHaveLength(1)
216
+
217
+ const buttonSpec = tapestryImport.at(0).get().value.specifiers?.[0]
218
+ expect(buttonSpec?.imported?.name).toBe("Button")
219
+ expect(buttonSpec?.local?.name).toBe("TapestryButton")
220
+ })
221
+ })
222
+
223
+ describe("edge cases", () => {
224
+ it("should handle duplicate add calls", () => {
225
+ const source = createSource('import React from "react"')
226
+
227
+ const result1 = addImport({
228
+ component: "Button",
229
+ conflictAlias: "TButton",
230
+ j,
231
+ pkg: "@planningcenter/tapestry",
232
+ source,
233
+ })
234
+
235
+ const result2 = addImport({
236
+ component: "Button",
237
+ conflictAlias: "TButton",
238
+ j,
239
+ pkg: "@planningcenter/tapestry",
240
+ source,
241
+ })
242
+
243
+ expect(result1).toBe("Button")
244
+ expect(result2).toBe("Button")
245
+
246
+ // Should still only have 2 imports (React + Button)
247
+ const imports = source.find(j.ImportDeclaration)
248
+ expect(imports).toHaveLength(2)
249
+
250
+ // Button should only appear once in the tapestry import
251
+ const tapestryImport = source.find(j.ImportDeclaration, {
252
+ source: { value: "@planningcenter/tapestry" },
253
+ })
254
+ expect(tapestryImport.at(0).get().value.specifiers).toHaveLength(1)
255
+ })
256
+
257
+ it("should not use conflictAlias when no conflict exists", () => {
258
+ const source = createSource('import React from "react"')
259
+
260
+ const result = addImport({
261
+ component: "Button",
262
+ conflictAlias: "TapestryButton",
263
+ j,
264
+ pkg: "@planningcenter/tapestry",
265
+ source,
266
+ })
267
+
268
+ expect(result).toBe("Button")
269
+
270
+ const tapestryImport = source.find(j.ImportDeclaration, {
271
+ source: { value: "@planningcenter/tapestry" },
272
+ })
273
+ const buttonSpec = tapestryImport.at(0).get().value.specifiers?.[0]
274
+ expect(buttonSpec?.imported?.name).toBe("Button")
275
+ expect(buttonSpec?.local).toBeNull()
276
+ })
277
+ })
278
+ })
@@ -145,6 +145,51 @@ export function removeImportFromDeclaration(
145
145
  return false
146
146
  }
147
147
 
148
+ /**
149
+ * Adds an import to the source, handling conflicts and existing imports
150
+ * Returns the final name to use for the component (original or aliased)
151
+ */
152
+ export function addImport({
153
+ component,
154
+ conflictAlias,
155
+ j,
156
+ pkg,
157
+ source,
158
+ }: {
159
+ component: string
160
+ conflictAlias: string
161
+ j: JSCodeshift
162
+ pkg: string
163
+ source: Collection
164
+ }): string {
165
+ // Check if component is already imported from the target package
166
+ const existingImportName = getImportName(component, pkg, { j, source })
167
+ if (existingImportName) {
168
+ return existingImportName
169
+ }
170
+
171
+ // Check for conflicts with imports from other packages
172
+ const hasConflict = hasConflictingImport(component, pkg, { j, source })
173
+ const finalComponentName = hasConflict ? conflictAlias : component
174
+ const alias =
175
+ finalComponentName !== component ? finalComponentName : undefined
176
+
177
+ // Handle target package import addition
178
+ const targetImport = source
179
+ .find(j.ImportDeclaration, {
180
+ source: { value: pkg },
181
+ })
182
+ .at(0)
183
+
184
+ if (targetImport.length > 0) {
185
+ addImportToExisting(targetImport, component, j, alias)
186
+ } else {
187
+ createNewImport(source, component, pkg, j, alias)
188
+ }
189
+
190
+ return finalComponentName
191
+ }
192
+
148
193
  /**
149
194
  * Manages imports after transformation - adds target imports and removes unused source imports
150
195
  */
@@ -189,24 +234,12 @@ export function manageImports(
189
234
  removeImportFromDeclaration(sourceImport, config.fromComponent)
190
235
  }
191
236
 
192
- // Handle target package import addition
193
- const targetImport = source
194
- .find(j.ImportDeclaration, {
195
- source: { value: config.toPackage },
196
- })
197
- .at(0)
198
-
199
- if (targetImport.length > 0) {
200
- const alias =
201
- targetComponentName !== config.toComponent
202
- ? targetComponentName
203
- : undefined
204
- addImportToExisting(targetImport, config.toComponent, j, alias)
205
- } else {
206
- const alias =
207
- targetComponentName !== config.toComponent
208
- ? targetComponentName
209
- : undefined
210
- createNewImport(source, config.toComponent, config.toPackage, j, alias)
211
- }
237
+ // Handle target package import addition using addImport
238
+ addImport({
239
+ component: config.toComponent,
240
+ conflictAlias: config.conflictAlias || targetComponentName,
241
+ j,
242
+ pkg: config.toPackage,
243
+ source,
244
+ })
212
245
  }