@planningcenter/tapestry-migration-cli 3.4.1-rc.9 → 3.5.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 (39) hide show
  1. package/README.md +22 -0
  2. package/dist/tapestry-react-shim.cjs +5 -1
  3. package/package.json +3 -3
  4. package/src/availableComponents.ts +13 -0
  5. package/src/components/dropdown/index.test.ts +244 -4
  6. package/src/components/dropdown/index.ts +28 -2
  7. package/src/components/dropdown/transforms/auditSpreadProps.test.ts +110 -0
  8. package/src/components/dropdown/transforms/auditSpreadProps.ts +10 -0
  9. package/src/components/dropdown/transforms/dividerToSeparator.test.ts +134 -0
  10. package/src/components/dropdown/transforms/dividerToSeparator.ts +67 -0
  11. package/src/components/dropdown/transforms/itemToAction.test.ts +19 -1
  12. package/src/components/dropdown/transforms/itemToAction.ts +14 -2
  13. package/src/components/dropdown/transforms/linkToLink.test.ts +19 -1
  14. package/src/components/dropdown/transforms/linkToLink.ts +13 -2
  15. package/src/components/dropdown/transforms/moveDropdownImport.test.ts +93 -0
  16. package/src/components/dropdown/transforms/moveDropdownImport.ts +13 -0
  17. package/src/components/dropdown/transforms/placementIdToMenu.test.ts +140 -0
  18. package/src/components/dropdown/transforms/placementIdToMenu.ts +96 -0
  19. package/src/components/dropdown/transforms/unsupportedProps.test.ts +145 -0
  20. package/src/components/dropdown/transforms/unsupportedProps.ts +12 -0
  21. package/src/components/dropdown/transforms/unsupportedPropsDivider.test.ts +143 -0
  22. package/src/components/dropdown/transforms/unsupportedPropsDivider.ts +26 -0
  23. package/src/components/dropdown/transforms/unsupportedPropsItem.test.ts +123 -0
  24. package/src/components/dropdown/transforms/unsupportedPropsItem.ts +12 -0
  25. package/src/components/dropdown/transforms/unsupportedPropsLink.test.ts +107 -0
  26. package/src/components/dropdown/transforms/unsupportedPropsLink.ts +12 -0
  27. package/src/components/dropdown/transforms/variantToKind.test.ts +292 -0
  28. package/src/components/dropdown/transforms/variantToKind.ts +138 -0
  29. package/src/components/dropdown/transforms/wrapIconTrigger.test.ts +336 -0
  30. package/src/components/dropdown/transforms/wrapIconTrigger.ts +233 -0
  31. package/src/components/dropdown/transforms/wrapMenu.test.ts +153 -0
  32. package/src/components/dropdown/transforms/wrapMenu.ts +54 -0
  33. package/src/components/dropdown/transforms/wrapTrigger.test.ts +283 -0
  34. package/src/components/dropdown/transforms/wrapTrigger.ts +98 -0
  35. package/src/components/input/transforms/unsupportedProps.test.ts +14 -13
  36. package/src/components/shared/conditions/isChildOf.test.ts +89 -0
  37. package/src/components/shared/conditions/isChildOf.ts +43 -0
  38. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +36 -0
  39. package/src/index.ts +5 -18
@@ -0,0 +1,89 @@
1
+ import jscodeshift, { JSXElement } from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import { isChildOf } from "./isChildOf"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function getElements(source: string): {
9
+ elements: JSXElement[]
10
+ source: ReturnType<typeof j>
11
+ } {
12
+ const parsed = j(source)
13
+ const elements = parsed
14
+ .find(j.JSXElement)
15
+ .paths()
16
+ .map((p) => p.value)
17
+ return { elements, source: parsed }
18
+ }
19
+
20
+ describe("isChildOf", () => {
21
+ it("returns true for a direct child of the parent", () => {
22
+ const { source, elements } = getElements(`<Dropdown><Divider /></Dropdown>`)
23
+ const divider = elements.find(
24
+ (el) =>
25
+ el.openingElement.name.type === "JSXIdentifier" &&
26
+ el.openingElement.name.name === "Divider"
27
+ )!
28
+ const condition = isChildOf("Dropdown", source, j)
29
+ expect(condition(divider)).toBe(true)
30
+ })
31
+
32
+ it("returns true for a deeply nested descendant", () => {
33
+ const { source, elements } = getElements(
34
+ `<Dropdown><DropdownMenu><Divider /></DropdownMenu></Dropdown>`
35
+ )
36
+ const divider = elements.find(
37
+ (el) =>
38
+ el.openingElement.name.type === "JSXIdentifier" &&
39
+ el.openingElement.name.name === "Divider"
40
+ )!
41
+ const condition = isChildOf("Dropdown", source, j)
42
+ expect(condition(divider)).toBe(true)
43
+ })
44
+
45
+ it("returns false for an element outside the parent", () => {
46
+ const { source, elements } = getElements(`<div><Divider /></div>`)
47
+ const divider = elements.find(
48
+ (el) =>
49
+ el.openingElement.name.type === "JSXIdentifier" &&
50
+ el.openingElement.name.name === "Divider"
51
+ )!
52
+ const condition = isChildOf("Dropdown", source, j)
53
+ expect(condition(divider)).toBe(false)
54
+ })
55
+
56
+ it("returns false when no parent element exists in source", () => {
57
+ const { source, elements } = getElements(`<Divider />`)
58
+ const divider = elements[0]
59
+ const condition = isChildOf("Dropdown", source, j)
60
+ expect(condition(divider)).toBe(false)
61
+ })
62
+
63
+ it("returns true for child inside one of multiple parents", () => {
64
+ const { source, elements } = getElements(
65
+ `<div><Dropdown><Divider /></Dropdown></div>`
66
+ )
67
+ const divider = elements.find(
68
+ (el) =>
69
+ el.openingElement.name.type === "JSXIdentifier" &&
70
+ el.openingElement.name.name === "Divider"
71
+ )!
72
+ const condition = isChildOf("Dropdown", source, j)
73
+ expect(condition(divider)).toBe(true)
74
+ })
75
+
76
+ it("handles sibling elements — child inside parent returns true, sibling does not", () => {
77
+ const { source, elements } = getElements(
78
+ `<div><Dropdown><Divider /></Dropdown><Divider /></div>`
79
+ )
80
+ const [insideDivider, outsideDivider] = elements.filter(
81
+ (el) =>
82
+ el.openingElement.name.type === "JSXIdentifier" &&
83
+ el.openingElement.name.name === "Divider"
84
+ )
85
+ const condition = isChildOf("Dropdown", source, j)
86
+ expect(condition(insideDivider)).toBe(true)
87
+ expect(condition(outsideDivider)).toBe(false)
88
+ })
89
+ })
@@ -0,0 +1,43 @@
1
+ import { Collection, JSCodeshift, JSXElement } from "jscodeshift"
2
+
3
+ import { TransformCondition } from "../types"
4
+
5
+ function elementKey(element: JSXElement): string | null {
6
+ const loc = element.loc
7
+ if (!loc) return null
8
+ return `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`
9
+ }
10
+
11
+ function collectDescendantKeys(element: JSXElement, result: Set<string>): void {
12
+ for (const child of element.children ?? []) {
13
+ if (child.type === "JSXElement") {
14
+ const key = elementKey(child)
15
+ if (key) result.add(key)
16
+ collectDescendantKeys(child, result)
17
+ }
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Returns a condition that is true only when the element is a descendant of a
23
+ * JSX element with the given local name. Matches by source location so the
24
+ * condition works across separate jscodeshift parses of the same source text.
25
+ */
26
+ export function isChildOf(
27
+ parentLocalName: string,
28
+ source: Collection,
29
+ j: JSCodeshift
30
+ ): TransformCondition {
31
+ const descendantKeys = new Set<string>()
32
+
33
+ source
34
+ .find(j.JSXOpeningElement, { name: { name: parentLocalName } })
35
+ .forEach((path) => {
36
+ collectDescendantKeys(path.parent.value as JSXElement, descendantKeys)
37
+ })
38
+
39
+ return (element: JSXElement) => {
40
+ const key = elementKey(element)
41
+ return key !== null && descendantKeys.has(key)
42
+ }
43
+ }
@@ -56,6 +56,7 @@ export const CHECKBOX_RADIO_SUPPORTED_PROPS = [
56
56
 
57
57
  export const INPUT_SPECIFIC_PROPS = [
58
58
  "autoComplete",
59
+ "autoFocus",
59
60
  "autoWidth",
60
61
  "defaultValue",
61
62
  "description",
@@ -197,3 +198,38 @@ export const DATE_PICKER_SUPPORTED_PROPS = [
197
198
  ...COMMON_PROPS,
198
199
  ...DATE_PICKER_SPECIFIC_PROPS,
199
200
  ]
201
+
202
+ export const DROPDOWN_SPECIFIC_PROPS = ["onClose", "onOpen"]
203
+
204
+ export const DROPDOWN_SUPPORTED_PROPS = [
205
+ ...COMMON_PROPS.filter((prop) => prop !== "size"),
206
+ ...DROPDOWN_SPECIFIC_PROPS,
207
+ ]
208
+
209
+ export const DROPDOWN_ITEM_BASE_PROPS = [
210
+ "destructive",
211
+ "staffOnly",
212
+ "text",
213
+ "textValue",
214
+ "value",
215
+ ]
216
+
217
+ // Dropdown.Item (old) → DropdownAction (new)
218
+ // onSelect→onAction, value→id, text→textValue handled by itemToAction
219
+ export const DROPDOWN_ITEM_SUPPORTED_PROPS = [
220
+ ...COMMON_PROPS,
221
+ ...DROPDOWN_ITEM_BASE_PROPS,
222
+ "disabled",
223
+ "onAction",
224
+ "onSelect",
225
+ ]
226
+
227
+ // Dropdown.Link (old) → DropdownLink (new)
228
+ // to→href handled by linkToLink; disabled intentionally NOT included
229
+ export const DROPDOWN_LINK_SUPPORTED_PROPS = [
230
+ ...COMMON_PROPS,
231
+ ...DROPDOWN_ITEM_BASE_PROPS,
232
+ "external",
233
+ "href",
234
+ "to",
235
+ ]
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { Command } from "commander"
4
4
 
5
+ import { AVAILABLE_CLI_COMPONENTS } from "./availableComponents"
5
6
  import { runTransforms } from "./jscodeshiftRunner"
6
7
  import { normalizeComponentName } from "./utils/componentNameNormalizer"
7
8
 
@@ -11,24 +12,10 @@ program
11
12
  .name("tapestry-migration-cli")
12
13
  .description("CLI tool for Tapestry migrations")
13
14
 
14
- const COMPONENTS_SET = new Set([
15
- "button",
16
- "checkbox",
17
- "date-picker",
18
- "dropdown",
19
- "input",
20
- "link",
21
- "radio",
22
- "select",
23
- "text-area",
24
- "time-field",
25
- "toggle-switch",
26
- ])
27
-
28
15
  const COMPONENTS_SENTENCE = new Intl.ListFormat("en", {
29
16
  style: "long",
30
17
  type: "conjunction",
31
- }).format([...COMPONENTS_SET])
18
+ }).format([...AVAILABLE_CLI_COMPONENTS])
32
19
 
33
20
  program
34
21
  .command("run")
@@ -53,9 +40,9 @@ program
53
40
  "MIGRATION_REPORT.md"
54
41
  )
55
42
  .action((componentName, options) => {
56
- const key = normalizeComponentName(componentName, COMPONENTS_SET)
43
+ const key = normalizeComponentName(componentName, AVAILABLE_CLI_COMPONENTS)
57
44
 
58
- if (key && COMPONENTS_SET.has(key)) {
45
+ if (key && AVAILABLE_CLI_COMPONENTS.has(key)) {
59
46
  console.log(`🎨 Migrating ${componentName} components...`)
60
47
  console.log(`📁 Target: ${options.path}`)
61
48
  console.log(
@@ -65,7 +52,7 @@ program
65
52
  } else {
66
53
  console.error(`❌ Invalid component: "${componentName}"`)
67
54
  console.error(
68
- `✅ Available components: ${[...COMPONENTS_SET].join(", ")}`
55
+ `✅ Available components: ${[...AVAILABLE_CLI_COMPONENTS].join(", ")}`
69
56
  )
70
57
  process.exit(1)
71
58
  }