@planningcenter/tapestry-migration-cli 2.5.0-rc.1 → 2.5.0-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.
- package/package.json +2 -2
- package/src/components/button/transforms/iconLeftToPrefix.ts +2 -2
- package/src/components/button/transforms/iconRightToSuffix.ts +2 -2
- package/src/components/button/transforms/iconToIconButton.ts +13 -1
- package/src/components/button/transforms/innerRefToRef.ts +2 -2
- package/src/components/button/transforms/spinnerToLoadingButton.ts +2 -2
- package/src/components/button/transforms/titleToLabel.ts +2 -2
- package/src/components/shared/actions/addComment.ts +10 -3
- package/src/components/shared/actions/addCommentToAttribute.ts +3 -1
- package/src/components/shared/actions/transformAttributeName.ts +16 -2
- package/src/components/shared/transformFactories/attributeCombineFactory.ts +33 -1
- package/src/components/shared/transformFactories/stylePropTransformFactory.ts +20 -0
- package/src/reportGenerator.ts +27 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/tapestry-migration-cli",
|
|
3
|
-
"version": "2.5.0-rc.
|
|
3
|
+
"version": "2.5.0-rc.2",
|
|
4
4
|
"description": "CLI tool for Tapestry migrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "699c1e99cb5f9e0dec6ce4a1558e3f5d17be9e42"
|
|
55
55
|
}
|
|
@@ -8,7 +8,7 @@ const transform = attributeTransformFactory({
|
|
|
8
8
|
condition: hasAttribute("iconLeft"),
|
|
9
9
|
targetComponent: "Button",
|
|
10
10
|
targetPackage: "@planningcenter/tapestry-react",
|
|
11
|
-
transform: (element, { j, source }) => {
|
|
11
|
+
transform: (element, { j, options, source }) => {
|
|
12
12
|
const name = addImport({
|
|
13
13
|
component: "Icon",
|
|
14
14
|
conflictAlias: "TRIcon",
|
|
@@ -25,7 +25,7 @@ const transform = attributeTransformFactory({
|
|
|
25
25
|
})
|
|
26
26
|
if (!updatedElement) return false
|
|
27
27
|
|
|
28
|
-
transformAttributeName("iconLeft", "prefix", { element })
|
|
28
|
+
transformAttributeName("iconLeft", "prefix", { element, j, options })
|
|
29
29
|
return true
|
|
30
30
|
},
|
|
31
31
|
})
|
|
@@ -8,7 +8,7 @@ const transform = attributeTransformFactory({
|
|
|
8
8
|
condition: hasAttribute("iconRight"),
|
|
9
9
|
targetComponent: "Button",
|
|
10
10
|
targetPackage: "@planningcenter/tapestry-react",
|
|
11
|
-
transform: (element, { j, source }) => {
|
|
11
|
+
transform: (element, { j, options, source }) => {
|
|
12
12
|
const name = addImport({
|
|
13
13
|
component: "Icon",
|
|
14
14
|
conflictAlias: "TRIcon",
|
|
@@ -25,7 +25,7 @@ const transform = attributeTransformFactory({
|
|
|
25
25
|
})
|
|
26
26
|
if (!updatedElement) return false
|
|
27
27
|
|
|
28
|
-
transformAttributeName("iconRight", "suffix", { element })
|
|
28
|
+
transformAttributeName("iconRight", "suffix", { element, j, options })
|
|
29
29
|
return true
|
|
30
30
|
},
|
|
31
31
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { JSXIdentifier } from "jscodeshift"
|
|
2
2
|
|
|
3
|
+
import { addComment } from "../../shared/actions/addComment"
|
|
3
4
|
import { convertAttributeFromObjectToJSXElement } from "../../shared/actions/convertAttributeFromObjectToJSXElement"
|
|
4
5
|
import { removeUnusedImport } from "../../shared/actions/removeUnusedImport"
|
|
5
6
|
import { hasAttribute } from "../../shared/conditions/hasAttribute"
|
|
@@ -10,7 +11,7 @@ const transform = attributeTransformFactory({
|
|
|
10
11
|
condition: hasAttribute("icon"),
|
|
11
12
|
targetComponent: "Button",
|
|
12
13
|
targetPackage: "@planningcenter/tapestry-react",
|
|
13
|
-
transform: (element, { j, source }) => {
|
|
14
|
+
transform: (element, { j, options, source }) => {
|
|
14
15
|
const name = addImport({
|
|
15
16
|
component: "Icon",
|
|
16
17
|
conflictAlias: "TRIcon",
|
|
@@ -46,6 +47,17 @@ const transform = attributeTransformFactory({
|
|
|
46
47
|
source,
|
|
47
48
|
})
|
|
48
49
|
|
|
50
|
+
if (options?.verbose) {
|
|
51
|
+
addComment({
|
|
52
|
+
commentKind: "change",
|
|
53
|
+
element,
|
|
54
|
+
j,
|
|
55
|
+
scope: "component",
|
|
56
|
+
source,
|
|
57
|
+
text: `converted Button with icon prop to IconButton`,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
return true
|
|
50
62
|
},
|
|
51
63
|
})
|
|
@@ -6,8 +6,8 @@ const transform = attributeTransformFactory({
|
|
|
6
6
|
condition: hasAttribute("innerRef"),
|
|
7
7
|
targetComponent: "Button",
|
|
8
8
|
targetPackage: "@planningcenter/tapestry-react",
|
|
9
|
-
transform: (element) => {
|
|
10
|
-
return transformAttributeName("innerRef", "ref", { element })
|
|
9
|
+
transform: (element, { j, options }) => {
|
|
10
|
+
return transformAttributeName("innerRef", "ref", { element, j, options })
|
|
11
11
|
},
|
|
12
12
|
})
|
|
13
13
|
|
|
@@ -6,8 +6,8 @@ export default attributeTransformFactory({
|
|
|
6
6
|
condition: hasAttribute("spinner"),
|
|
7
7
|
targetComponent: "Button",
|
|
8
8
|
targetPackage: "@planningcenter/tapestry-react",
|
|
9
|
-
transform: (element) => {
|
|
10
|
-
transformAttributeName("spinner", "loading", { element })
|
|
9
|
+
transform: (element, { j, options }) => {
|
|
10
|
+
transformAttributeName("spinner", "loading", { element, j, options })
|
|
11
11
|
|
|
12
12
|
return true
|
|
13
13
|
},
|
|
@@ -8,11 +8,11 @@ const transform: Transform = attributeTransformFactory({
|
|
|
8
8
|
condition: hasAttribute("title"),
|
|
9
9
|
targetComponent: "Button",
|
|
10
10
|
targetPackage: "@planningcenter/tapestry-react",
|
|
11
|
-
transform: (element: JSXElement) =>
|
|
11
|
+
transform: (element: JSXElement, { j, options }) =>
|
|
12
12
|
transformAttributeName(
|
|
13
13
|
"title",
|
|
14
14
|
() => (hasAttribute("icon")(element) ? "aria-label" : "label"),
|
|
15
|
-
{ element }
|
|
15
|
+
{ element, j, options }
|
|
16
16
|
),
|
|
17
17
|
})
|
|
18
18
|
|
|
@@ -12,14 +12,16 @@ export function addComment({
|
|
|
12
12
|
j,
|
|
13
13
|
source,
|
|
14
14
|
scope,
|
|
15
|
+
commentKind = "todo",
|
|
15
16
|
}: {
|
|
17
|
+
commentKind?: "todo" | "change"
|
|
16
18
|
element: JSXElement
|
|
17
19
|
j: JSCodeshift
|
|
18
20
|
scope: string
|
|
19
21
|
source: Collection
|
|
20
22
|
text: string
|
|
21
23
|
}) {
|
|
22
|
-
const commentText = formatComment(text, scope)
|
|
24
|
+
const commentText = formatComment(text, scope, commentKind)
|
|
23
25
|
if (tryInsertJSXComment(source, element, commentText, j)) {
|
|
24
26
|
return
|
|
25
27
|
}
|
|
@@ -64,6 +66,11 @@ function tryInsertJSXComment(
|
|
|
64
66
|
return found
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
export function formatComment(
|
|
68
|
-
|
|
69
|
+
export function formatComment(
|
|
70
|
+
text: string,
|
|
71
|
+
scope: string,
|
|
72
|
+
commentKind: "todo" | "change" = "todo"
|
|
73
|
+
): string {
|
|
74
|
+
const prefix = commentKind === "change" ? "CHANGED" : "TODO"
|
|
75
|
+
return ` ${prefix}: tapestry-migration (${scope}): ${text} `
|
|
69
76
|
}
|
|
@@ -6,8 +6,10 @@ export function addCommentToAttribute({
|
|
|
6
6
|
text,
|
|
7
7
|
attribute,
|
|
8
8
|
j,
|
|
9
|
+
commentKind = "todo",
|
|
9
10
|
}: {
|
|
10
11
|
attribute: JSXAttribute | JSXSpreadAttribute
|
|
12
|
+
commentKind?: "todo" | "change"
|
|
11
13
|
j: JSCodeshift
|
|
12
14
|
text: string
|
|
13
15
|
}) {
|
|
@@ -15,7 +17,7 @@ export function addCommentToAttribute({
|
|
|
15
17
|
((attribute.type === "JSXAttribute" && attribute.name.name) as string) ||
|
|
16
18
|
"spreadAttribute"
|
|
17
19
|
const comment = j.commentBlock(
|
|
18
|
-
formatComment(text, attributeName),
|
|
20
|
+
formatComment(text, attributeName, commentKind),
|
|
19
21
|
true,
|
|
20
22
|
false
|
|
21
23
|
)
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { JSXElement } from "jscodeshift"
|
|
1
|
+
import { JSCodeshift, JSXElement, Options } from "jscodeshift"
|
|
2
2
|
|
|
3
3
|
import { findAttribute } from "../findAttribute"
|
|
4
|
+
import { addCommentToAttribute } from "./addCommentToAttribute"
|
|
4
5
|
|
|
5
6
|
export function transformAttributeName(
|
|
6
7
|
name: string,
|
|
7
8
|
nameTransform: string | ((element: JSXElement) => string),
|
|
8
|
-
{
|
|
9
|
+
{
|
|
10
|
+
element,
|
|
11
|
+
j,
|
|
12
|
+
options,
|
|
13
|
+
}: { element: JSXElement; j?: JSCodeshift; options?: Options }
|
|
9
14
|
): boolean {
|
|
10
15
|
if (!nameTransform) return false
|
|
11
16
|
const attributes = element.openingElement.attributes || []
|
|
@@ -16,5 +21,14 @@ export function transformAttributeName(
|
|
|
16
21
|
typeof nameTransform === "string" ? nameTransform : nameTransform(element)
|
|
17
22
|
attribute.name.name = resolvedName
|
|
18
23
|
|
|
24
|
+
if (options?.verbose && j && attribute.type === "JSXAttribute") {
|
|
25
|
+
addCommentToAttribute({
|
|
26
|
+
attribute,
|
|
27
|
+
commentKind: "change",
|
|
28
|
+
j,
|
|
29
|
+
text: `renamed from ${name}`,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
return true
|
|
20
34
|
}
|
|
@@ -3,10 +3,12 @@ import {
|
|
|
3
3
|
JSCodeshift,
|
|
4
4
|
JSXAttribute,
|
|
5
5
|
JSXElement,
|
|
6
|
+
Options,
|
|
6
7
|
Transform,
|
|
7
8
|
} from "jscodeshift"
|
|
8
9
|
|
|
9
10
|
import { addAttribute, Conditional } from "../actions/addAttribute"
|
|
11
|
+
import { addCommentToAttribute } from "../actions/addCommentToAttribute"
|
|
10
12
|
import { getAttribute } from "../actions/getAttribute"
|
|
11
13
|
import { removeAttribute } from "../actions/removeAttribute"
|
|
12
14
|
import { hasAttribute } from "../conditions/hasAttribute"
|
|
@@ -263,7 +265,11 @@ export function attributeCombineFactory({
|
|
|
263
265
|
targetPackage: packageName,
|
|
264
266
|
transform: (
|
|
265
267
|
element: JSXElement,
|
|
266
|
-
{
|
|
268
|
+
{
|
|
269
|
+
j,
|
|
270
|
+
options,
|
|
271
|
+
source,
|
|
272
|
+
}: { j: JSCodeshift; options: Options; source: Collection }
|
|
267
273
|
) => {
|
|
268
274
|
const attributes: AttributeWithName[] = sourceAttributes.map(
|
|
269
275
|
(name: string) => ({
|
|
@@ -294,6 +300,32 @@ export function attributeCombineFactory({
|
|
|
294
300
|
}
|
|
295
301
|
addAttribute({ element, j, name: targetAttribute, value: mappingResult })
|
|
296
302
|
|
|
303
|
+
if (options?.verbose) {
|
|
304
|
+
const addedAttr = getAttribute({ element, name: targetAttribute })
|
|
305
|
+
if (addedAttr && addedAttr.type === "JSXAttribute") {
|
|
306
|
+
const v1 = getAttributeStringValue(
|
|
307
|
+
attributes[0].attribute,
|
|
308
|
+
defaults[sourceAttributes[0]] || "",
|
|
309
|
+
valueNormalizers[sourceAttributes[0]]
|
|
310
|
+
)
|
|
311
|
+
const v2 = getAttributeStringValue(
|
|
312
|
+
attributes[1].attribute,
|
|
313
|
+
defaults[sourceAttributes[1]] || "",
|
|
314
|
+
valueNormalizers[sourceAttributes[1]]
|
|
315
|
+
)
|
|
316
|
+
const fromParts = [
|
|
317
|
+
`${sourceAttributes[0]}=${v1 ?? "<unsupported>"}`,
|
|
318
|
+
`${sourceAttributes[1]}=${v2 ?? "<unsupported>"}`,
|
|
319
|
+
]
|
|
320
|
+
addCommentToAttribute({
|
|
321
|
+
attribute: addedAttr,
|
|
322
|
+
commentKind: "change",
|
|
323
|
+
j,
|
|
324
|
+
text: `derived from ${fromParts.join(" ")}`,
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
297
329
|
return true
|
|
298
330
|
},
|
|
299
331
|
})
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
stylePropNames,
|
|
14
14
|
} from "../../../../dist/tapestry-react-shim.cjs"
|
|
15
15
|
import { addComment } from "../../shared/actions/addComment"
|
|
16
|
+
import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
|
|
16
17
|
import { getAttribute } from "../../shared/actions/getAttribute"
|
|
17
18
|
import { removeAttribute } from "../../shared/actions/removeAttribute"
|
|
18
19
|
import { attributeTransformFactory } from "./attributeTransformFactory"
|
|
@@ -323,6 +324,25 @@ export function stylePropTransformFactory(config: {
|
|
|
323
324
|
// Only apply styles if there are actual CSS properties to add
|
|
324
325
|
if (Object.keys(styles).length > 0) {
|
|
325
326
|
applyStylesToComponent({ element, j, styles })
|
|
327
|
+
|
|
328
|
+
if (options.verbose) {
|
|
329
|
+
const styleAttr = getAttribute({ element, name: "style" })
|
|
330
|
+
if (styleAttr && styleAttr.type === "JSXAttribute") {
|
|
331
|
+
const folded = Object.keys(allStyleProps)
|
|
332
|
+
const direct = Object.keys(directStyleProps)
|
|
333
|
+
const parts = [] as string[]
|
|
334
|
+
if (folded.length) parts.push(`props: ${folded.join(", ")}`)
|
|
335
|
+
if (direct.length) parts.push(`direct: ${direct.join(", ")}`)
|
|
336
|
+
if (parts.length) {
|
|
337
|
+
addCommentToAttribute({
|
|
338
|
+
attribute: styleAttr,
|
|
339
|
+
commentKind: "change",
|
|
340
|
+
j,
|
|
341
|
+
text: `migrated style from ${parts.join("; ")}`,
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
326
346
|
}
|
|
327
347
|
} catch (error) {
|
|
328
348
|
console.log("Error processing style props:", error)
|
package/src/reportGenerator.ts
CHANGED
|
@@ -12,6 +12,7 @@ interface CommentData {
|
|
|
12
12
|
line: number
|
|
13
13
|
scope: string
|
|
14
14
|
text: string
|
|
15
|
+
type: "todo" | "changed"
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
interface CommentStats {
|
|
@@ -20,6 +21,7 @@ interface CommentStats {
|
|
|
20
21
|
files: string[]
|
|
21
22
|
normalizedText: string
|
|
22
23
|
scope: string
|
|
24
|
+
type: "todo" | "changed"
|
|
23
25
|
weight: number
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -60,14 +62,17 @@ function extractCommentsFromFile(filePath: string): CommentData[] {
|
|
|
60
62
|
// Match both JSX and regular comment formats:
|
|
61
63
|
// {/* TODO: tapestry-migration (scope): text */}
|
|
62
64
|
// /* TODO: tapestry-migration (scope): text */
|
|
65
|
+
// {/* CHANGED: tapestry-migration (scope): text */}
|
|
66
|
+
// /* CHANGED: tapestry-migration (scope): text */
|
|
63
67
|
// Use lazy matching with [\s\S]*? to capture everything including * until we hit the closing */
|
|
64
68
|
const commentRegex =
|
|
65
|
-
/(?:\{\/\*|\/\*)\s*TODO:\s*tapestry-migration\s*\(([^)]+)\):\s*([\s\S]*?)\s*\*\/\}?/g
|
|
69
|
+
/(?:\{\/\*|\/\*)\s*(TODO|CHANGED):\s*tapestry-migration\s*\(([^)]+)\):\s*([\s\S]*?)\s*\*\/\}?/g
|
|
66
70
|
|
|
67
71
|
let match
|
|
68
72
|
while ((match = commentRegex.exec(content)) !== null) {
|
|
69
|
-
const
|
|
70
|
-
const
|
|
73
|
+
const commentType = match[1].trim().toLowerCase() as "todo" | "changed"
|
|
74
|
+
const scope = match[2].trim()
|
|
75
|
+
const text = match[3].trim()
|
|
71
76
|
|
|
72
77
|
// Calculate line number
|
|
73
78
|
const beforeMatch = content.substring(0, match.index)
|
|
@@ -78,6 +83,7 @@ function extractCommentsFromFile(filePath: string): CommentData[] {
|
|
|
78
83
|
line,
|
|
79
84
|
scope,
|
|
80
85
|
text,
|
|
86
|
+
type: commentType,
|
|
81
87
|
})
|
|
82
88
|
}
|
|
83
89
|
|
|
@@ -228,7 +234,7 @@ function aggregateComments(
|
|
|
228
234
|
|
|
229
235
|
for (const comment of comments) {
|
|
230
236
|
const normalizedText = normalizeCommentText(comment.text)
|
|
231
|
-
const key = `${comment.scope}|||${normalizedText}`
|
|
237
|
+
const key = `${comment.type}|||${comment.scope}|||${normalizedText}`
|
|
232
238
|
|
|
233
239
|
if (statsMap.has(key)) {
|
|
234
240
|
const stats = statsMap.get(key)!
|
|
@@ -248,6 +254,7 @@ function aggregateComments(
|
|
|
248
254
|
files: [comment.file],
|
|
249
255
|
normalizedText,
|
|
250
256
|
scope: comment.scope,
|
|
257
|
+
type: comment.type,
|
|
251
258
|
weight,
|
|
252
259
|
})
|
|
253
260
|
}
|
|
@@ -314,6 +321,19 @@ function generateMarkdownReport(
|
|
|
314
321
|
lines.push(`- **Total Comments:** ${totalComments}`)
|
|
315
322
|
lines.push(`- **Unique Comment Types:** ${uniqueCommentTypes}`)
|
|
316
323
|
lines.push(`- **Affected Files:** ${affectedFiles}`)
|
|
324
|
+
|
|
325
|
+
// Add breakdown by comment type
|
|
326
|
+
const todoComments = allComments.filter((c) => c.type === "todo")
|
|
327
|
+
const changedComments = allComments.filter((c) => c.type === "changed")
|
|
328
|
+
lines.push("")
|
|
329
|
+
lines.push("### Comment Breakdown")
|
|
330
|
+
lines.push("")
|
|
331
|
+
lines.push(
|
|
332
|
+
`- **TODO Comments:** ${todoComments.length} (requires manual attention)`
|
|
333
|
+
)
|
|
334
|
+
lines.push(
|
|
335
|
+
`- **CHANGED Comments:** ${changedComments.length} (automatic transformations)`
|
|
336
|
+
)
|
|
317
337
|
lines.push("")
|
|
318
338
|
lines.push("### Effort by Difficulty")
|
|
319
339
|
lines.push("")
|
|
@@ -373,7 +393,9 @@ function generateMarkdownReport(
|
|
|
373
393
|
lines.push("")
|
|
374
394
|
|
|
375
395
|
for (const stat of scopeStats) {
|
|
376
|
-
|
|
396
|
+
const typeLabel = stat.type === "changed" ? "CHANGED" : "TODO"
|
|
397
|
+
const typeEmoji = stat.type === "changed" ? "✅" : "⚠️"
|
|
398
|
+
lines.push(`#### ${typeEmoji} [${typeLabel}] ${stat.normalizedText}`)
|
|
377
399
|
lines.push("")
|
|
378
400
|
lines.push(`- **Occurrences:** ${stat.count}`)
|
|
379
401
|
lines.push(`- **Files affected:** ${stat.files.length}`)
|