@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.
- package/README.md +22 -0
- package/dist/tapestry-react-shim.cjs +5 -1
- package/package.json +3 -3
- package/src/availableComponents.ts +13 -0
- package/src/components/dropdown/index.test.ts +244 -4
- package/src/components/dropdown/index.ts +28 -2
- package/src/components/dropdown/transforms/auditSpreadProps.test.ts +110 -0
- package/src/components/dropdown/transforms/auditSpreadProps.ts +10 -0
- package/src/components/dropdown/transforms/dividerToSeparator.test.ts +134 -0
- package/src/components/dropdown/transforms/dividerToSeparator.ts +67 -0
- package/src/components/dropdown/transforms/itemToAction.test.ts +19 -1
- package/src/components/dropdown/transforms/itemToAction.ts +14 -2
- package/src/components/dropdown/transforms/linkToLink.test.ts +19 -1
- package/src/components/dropdown/transforms/linkToLink.ts +13 -2
- package/src/components/dropdown/transforms/moveDropdownImport.test.ts +93 -0
- package/src/components/dropdown/transforms/moveDropdownImport.ts +13 -0
- package/src/components/dropdown/transforms/placementIdToMenu.test.ts +140 -0
- package/src/components/dropdown/transforms/placementIdToMenu.ts +96 -0
- package/src/components/dropdown/transforms/unsupportedProps.test.ts +145 -0
- package/src/components/dropdown/transforms/unsupportedProps.ts +12 -0
- package/src/components/dropdown/transforms/unsupportedPropsDivider.test.ts +143 -0
- package/src/components/dropdown/transforms/unsupportedPropsDivider.ts +26 -0
- package/src/components/dropdown/transforms/unsupportedPropsItem.test.ts +123 -0
- package/src/components/dropdown/transforms/unsupportedPropsItem.ts +12 -0
- package/src/components/dropdown/transforms/unsupportedPropsLink.test.ts +107 -0
- package/src/components/dropdown/transforms/unsupportedPropsLink.ts +12 -0
- package/src/components/dropdown/transforms/variantToKind.test.ts +292 -0
- package/src/components/dropdown/transforms/variantToKind.ts +138 -0
- package/src/components/dropdown/transforms/wrapIconTrigger.test.ts +336 -0
- package/src/components/dropdown/transforms/wrapIconTrigger.ts +233 -0
- package/src/components/dropdown/transforms/wrapMenu.test.ts +153 -0
- package/src/components/dropdown/transforms/wrapMenu.ts +54 -0
- package/src/components/dropdown/transforms/wrapTrigger.test.ts +283 -0
- package/src/components/dropdown/transforms/wrapTrigger.ts +98 -0
- package/src/components/input/transforms/unsupportedProps.test.ts +14 -13
- package/src/components/shared/conditions/isChildOf.test.ts +89 -0
- package/src/components/shared/conditions/isChildOf.ts +43 -0
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +36 -0
- 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([...
|
|
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,
|
|
43
|
+
const key = normalizeComponentName(componentName, AVAILABLE_CLI_COMPONENTS)
|
|
57
44
|
|
|
58
|
-
if (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: ${[...
|
|
55
|
+
`✅ Available components: ${[...AVAILABLE_CLI_COMPONENTS].join(", ")}`
|
|
69
56
|
)
|
|
70
57
|
process.exit(1)
|
|
71
58
|
}
|