@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/tapestry-migration-cli",
3
- "version": "2.5.0-rc.1",
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": "e5ff2455e4766e1a91998ab096ea8c77f00f9bae"
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(text: string, scope: string): string {
68
- return ` TODO: tapestry-migration (${scope}): ${text} `
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
- { element }: { element: JSXElement }
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
- { j, source }: { j: JSCodeshift; source: Collection }
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)
@@ -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 scope = match[1].trim()
70
- const text = match[2].trim()
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
- lines.push(`#### ${stat.normalizedText}`)
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}`)