@planningcenter/tapestry-migration-cli 2.1.0 → 2.1.1-qa-380.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 (32) hide show
  1. package/package.json +12 -7
  2. package/src/components/button/index.ts +27 -0
  3. package/src/components/button/transforms/linkToButton.test.ts +426 -0
  4. package/src/components/button/transforms/linkToButton.ts +15 -0
  5. package/src/components/button/transforms/titleToLabel.test.ts +418 -0
  6. package/src/components/button/transforms/titleToLabel.ts +19 -0
  7. package/src/components/shared/actions/transformAttributeName.ts +20 -0
  8. package/src/components/shared/actions/transformElementName.test.ts +59 -0
  9. package/src/components/shared/actions/transformElementName.ts +27 -0
  10. package/src/components/shared/conditions/andConditions.test.ts +65 -0
  11. package/src/components/shared/conditions/andConditions.ts +13 -0
  12. package/src/components/shared/conditions/hasAttribute.test.ts +43 -0
  13. package/src/components/shared/conditions/hasAttribute.ts +18 -0
  14. package/src/components/shared/conditions/hasAttributeValue.test.ts +48 -0
  15. package/src/components/shared/conditions/hasAttributeValue.ts +23 -0
  16. package/src/components/shared/conditions/helpers/createJSXElement.ts +9 -0
  17. package/src/components/shared/conditions/index.test.ts +63 -0
  18. package/src/components/shared/conditions/orConditions.test.ts +76 -0
  19. package/src/components/shared/conditions/orConditions.ts +13 -0
  20. package/src/components/shared/findAttribute.ts +15 -0
  21. package/src/components/shared/transformFactories/attributeTransformFactory.test.ts +88 -0
  22. package/src/components/shared/transformFactories/attributeTransformFactory.ts +51 -0
  23. package/src/components/shared/transformFactories/componentTransformFactory.test.ts +7 -0
  24. package/src/components/shared/transformFactories/componentTransformFactory.ts +77 -0
  25. package/src/components/shared/transformFactories/helpers/manageImports.test.ts +383 -0
  26. package/src/components/shared/transformFactories/helpers/manageImports.ts +204 -0
  27. package/src/components/shared/types.ts +6 -0
  28. package/src/index.ts +47 -0
  29. package/src/jscodeshiftRunner.ts +49 -0
  30. package/src/shared/types.ts +6 -0
  31. package/dist/index.d.ts +0 -2
  32. package/dist/index.js +0 -28
@@ -0,0 +1,383 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import {
5
+ addImportToExisting,
6
+ createNewImport,
7
+ getImportName,
8
+ hasConflictingImport,
9
+ removeImportFromDeclaration,
10
+ } from "./manageImports"
11
+
12
+ const j = jscodeshift.withParser("tsx")
13
+
14
+ describe("componentTransformUtilities", () => {
15
+ describe("getImportName", () => {
16
+ it("should return local name for imported component", () => {
17
+ const code = `import { Button } from "@planningcenter/tapestry-react"`
18
+ const source = j(code)
19
+
20
+ const result = getImportName("Button", "@planningcenter/tapestry-react", {
21
+ j,
22
+ source,
23
+ })
24
+
25
+ expect(result).toBe("Button")
26
+ })
27
+
28
+ it("should return aliased name for renamed import", () => {
29
+ const code = `import { Button as TapestryButton } from "@planningcenter/tapestry-react"`
30
+ const source = j(code)
31
+
32
+ const result = getImportName("Button", "@planningcenter/tapestry-react", {
33
+ j,
34
+ source,
35
+ })
36
+
37
+ expect(result).toBe("TapestryButton")
38
+ })
39
+
40
+ it("should return null when component is not imported", () => {
41
+ const code = `import { Link } from "@planningcenter/tapestry-react"`
42
+ const source = j(code)
43
+
44
+ const result = getImportName("Button", "@planningcenter/tapestry-react", {
45
+ j,
46
+ source,
47
+ })
48
+
49
+ expect(result).toBe(null)
50
+ })
51
+
52
+ it("should return null when package is not imported", () => {
53
+ const code = `import { Button } from "other-package"`
54
+ const source = j(code)
55
+
56
+ const result = getImportName("Button", "@planningcenter/tapestry-react", {
57
+ j,
58
+ source,
59
+ })
60
+
61
+ expect(result).toBe(null)
62
+ })
63
+
64
+ it("should handle multiple imports from same package", () => {
65
+ const code = `import { Button, Link as MyLink } from "@planningcenter/tapestry-react"`
66
+ const source = j(code)
67
+
68
+ const buttonResult = getImportName(
69
+ "Button",
70
+ "@planningcenter/tapestry-react",
71
+ { j, source }
72
+ )
73
+ const linkResult = getImportName(
74
+ "Link",
75
+ "@planningcenter/tapestry-react",
76
+ { j, source }
77
+ )
78
+
79
+ expect(buttonResult).toBe("Button")
80
+ expect(linkResult).toBe("MyLink")
81
+ })
82
+
83
+ it("should handle multiple import declarations", () => {
84
+ const code = `
85
+ import { Button } from "@planningcenter/tapestry-react"
86
+ import { useState } from "react"
87
+ import { Link } from "@planningcenter/tapestry-react"
88
+ `
89
+ const source = j(code)
90
+
91
+ const buttonResult = getImportName(
92
+ "Button",
93
+ "@planningcenter/tapestry-react",
94
+ { j, source }
95
+ )
96
+ const linkResult = getImportName(
97
+ "Link",
98
+ "@planningcenter/tapestry-react",
99
+ { j, source }
100
+ )
101
+
102
+ expect(buttonResult).toBe("Button")
103
+ expect(linkResult).toBe("Link")
104
+ })
105
+ })
106
+
107
+ describe("hasConflictingImport", () => {
108
+ it("should return true when component is imported from different package", () => {
109
+ const code = `import { Link } from "react-router-dom"`
110
+ const source = j(code)
111
+
112
+ const result = hasConflictingImport(
113
+ "Link",
114
+ "@planningcenter/tapestry-react",
115
+ { j, source }
116
+ )
117
+
118
+ expect(result).toBe(true)
119
+ })
120
+
121
+ it("should return false when component is only imported from excluded package", () => {
122
+ const code = `import { Link } from "@planningcenter/tapestry-react"`
123
+ const source = j(code)
124
+
125
+ const result = hasConflictingImport(
126
+ "Link",
127
+ "@planningcenter/tapestry-react",
128
+ { j, source }
129
+ )
130
+
131
+ expect(result).toBe(false)
132
+ })
133
+
134
+ it("should return false when component is not imported at all", () => {
135
+ const code = `import { Button } from "@planningcenter/tapestry-react"`
136
+ const source = j(code)
137
+
138
+ const result = hasConflictingImport(
139
+ "Link",
140
+ "@planningcenter/tapestry-react",
141
+ { j, source }
142
+ )
143
+
144
+ expect(result).toBe(false)
145
+ })
146
+
147
+ it("should return true for default import conflicts", () => {
148
+ const code = `import Link from "next/link"`
149
+ const source = j(code)
150
+
151
+ const result = hasConflictingImport(
152
+ "Link",
153
+ "@planningcenter/tapestry-react",
154
+ { j, source }
155
+ )
156
+
157
+ expect(result).toBe(true)
158
+ })
159
+
160
+ it("should handle multiple conflicting sources", () => {
161
+ const code = `
162
+ import { Link } from "react-router-dom"
163
+ import { Link as NextLink } from "next/link"
164
+ import { Button } from "@planningcenter/tapestry-react"
165
+ `
166
+ const source = j(code)
167
+
168
+ const result = hasConflictingImport(
169
+ "Link",
170
+ "@planningcenter/tapestry-react",
171
+ { j, source }
172
+ )
173
+
174
+ expect(result).toBe(true)
175
+ })
176
+ })
177
+
178
+ describe("addImportToExisting", () => {
179
+ it("should add new import to existing import declaration", () => {
180
+ const code = `import { Button } from "@planningcenter/tapestry-react"`
181
+ const source = j(code)
182
+ const importPath = source.find(j.ImportDeclaration).at(0)
183
+
184
+ addImportToExisting(importPath.get(), "Link", j)
185
+
186
+ const output = source.toSource()
187
+ expect(output).toContain(
188
+ 'import { Button, Link } from "@planningcenter/tapestry-react"'
189
+ )
190
+ })
191
+
192
+ it("should add aliased import to existing declaration", () => {
193
+ const code = `import { Button } from "@planningcenter/tapestry-react"`
194
+ const source = j(code)
195
+ const importPath = source.find(j.ImportDeclaration).at(0)
196
+
197
+ addImportToExisting(importPath.get(), "Link", j, "TLink")
198
+
199
+ const output = source.toSource()
200
+ expect(output).toContain(
201
+ 'import { Button, Link as TLink } from "@planningcenter/tapestry-react"'
202
+ )
203
+ })
204
+
205
+ it("should not add duplicate imports", () => {
206
+ const code = `import { Button, Link } from "@planningcenter/tapestry-react"`
207
+ const source = j(code)
208
+ const importPath = source.find(j.ImportDeclaration).at(0)
209
+
210
+ addImportToExisting(importPath.get(), "Link", j)
211
+
212
+ const output = source.toSource()
213
+ // Should still only have one Link import
214
+ expect(output).toContain(
215
+ 'import { Button, Link } from "@planningcenter/tapestry-react"'
216
+ )
217
+ })
218
+
219
+ it("should handle empty specifiers list", () => {
220
+ const code = `import {} from "@planningcenter/tapestry-react"`
221
+ const source = j(code)
222
+ const importPath = source.find(j.ImportDeclaration).at(0)
223
+
224
+ addImportToExisting(importPath.get(), "Button", j)
225
+
226
+ const output = source.toSource()
227
+ expect(output).toContain(
228
+ 'import { Button } from "@planningcenter/tapestry-react"'
229
+ )
230
+ })
231
+ })
232
+
233
+ describe("createNewImport", () => {
234
+ it("should create new import declaration", () => {
235
+ const code = `const test = "existing code"`
236
+ const source = j(code)
237
+
238
+ createNewImport(source, "Button", "@planningcenter/tapestry-react", j)
239
+
240
+ const output = source.toSource()
241
+ expect(output).toContain(
242
+ 'import { Button } from "@planningcenter/tapestry-react";'
243
+ )
244
+ expect(output).toContain('const test = "existing code"')
245
+ })
246
+
247
+ it("should create new import with alias", () => {
248
+ const code = `const test = "existing"`
249
+ const source = j(code)
250
+
251
+ createNewImport(
252
+ source,
253
+ "Link",
254
+ "@planningcenter/tapestry-react",
255
+ j,
256
+ "TLink"
257
+ )
258
+
259
+ const output = source.toSource()
260
+ expect(output).toContain(
261
+ 'import { Link as TLink } from "@planningcenter/tapestry-react";'
262
+ )
263
+ })
264
+
265
+ it("should add new import after existing imports", () => {
266
+ const code = `
267
+ import React from "react"
268
+ import { useState } from "react"
269
+
270
+ const component = () => null
271
+ `
272
+ const source = j(code)
273
+
274
+ createNewImport(source, "Button", "@planningcenter/tapestry-react", j)
275
+
276
+ const output = source.toSource()
277
+ const lines = output.split("\n")
278
+ const reactImportIndex = lines.findIndex((line) =>
279
+ line.includes('import React from "react"')
280
+ )
281
+ const useStateImportIndex = lines.findIndex((line) =>
282
+ line.includes('import { useState } from "react"')
283
+ )
284
+ const buttonImportIndex = lines.findIndex((line) =>
285
+ line.includes('import { Button } from "@planningcenter/tapestry-react"')
286
+ )
287
+
288
+ expect(reactImportIndex).toBeLessThan(buttonImportIndex)
289
+ expect(useStateImportIndex).toBeLessThan(buttonImportIndex)
290
+ })
291
+
292
+ it("should handle empty file", () => {
293
+ const code = ``
294
+ const source = j(code)
295
+
296
+ createNewImport(source, "Button", "@planningcenter/tapestry-react", j)
297
+
298
+ const output = source.toSource()
299
+ expect(output).toBe(
300
+ 'import { Button } from "@planningcenter/tapestry-react";'
301
+ )
302
+ })
303
+ })
304
+
305
+ describe("removeImportFromDeclaration", () => {
306
+ it("should remove import specifier", () => {
307
+ const code = `import { Button, Link } from "@planningcenter/tapestry-react"`
308
+ const source = j(code)
309
+ const importPath = source.find(j.ImportDeclaration).at(0)
310
+
311
+ const result = removeImportFromDeclaration(importPath.get(), "Button")
312
+
313
+ expect(result).toBe(true)
314
+ const output = source.toSource()
315
+ expect(output).toContain(
316
+ 'import { Link } from "@planningcenter/tapestry-react"'
317
+ )
318
+ expect(output).not.toContain("Button")
319
+ })
320
+
321
+ it("should remove entire import if no specifiers left", () => {
322
+ const code = `
323
+ import React from "react"
324
+ import { Button } from "@planningcenter/tapestry-react"
325
+ const component = () => null
326
+ `
327
+ const source = j(code)
328
+ const tapestryImport = source
329
+ .find(j.ImportDeclaration)
330
+ .filter(
331
+ (path) => path.value.source.value === "@planningcenter/tapestry-react"
332
+ )
333
+ .at(0)
334
+
335
+ const result = removeImportFromDeclaration(tapestryImport.get(), "Button")
336
+
337
+ expect(result).toBe(true)
338
+ const output = source.toSource()
339
+ expect(output).not.toContain("@planningcenter/tapestry-react")
340
+ expect(output).toContain('import React from "react"') // Other imports should remain
341
+ })
342
+
343
+ it("should return false if import not found", () => {
344
+ const code = `import { Button } from "@planningcenter/tapestry-react"`
345
+ const source = j(code)
346
+ const importPath = source.find(j.ImportDeclaration).at(0)
347
+
348
+ const result = removeImportFromDeclaration(importPath.get(), "Link")
349
+
350
+ expect(result).toBe(false)
351
+ const output = source.toSource()
352
+ expect(output).toContain("Button") // Should be unchanged
353
+ })
354
+
355
+ it("should handle single import removal", () => {
356
+ const code = `import { Button } from "@planningcenter/tapestry-react"`
357
+ const source = j(code)
358
+ const importPath = source.find(j.ImportDeclaration).at(0)
359
+
360
+ const result = removeImportFromDeclaration(importPath.get(), "Button")
361
+
362
+ expect(result).toBe(true)
363
+ expect(source.find(j.ImportDeclaration).length).toBe(0)
364
+ })
365
+
366
+ it("should preserve other specifiers", () => {
367
+ const code = `import { Button, Link, Input } from "@planningcenter/tapestry-react"`
368
+ const source = j(code)
369
+ const importPath = source.find(j.ImportDeclaration).at(0)
370
+
371
+ const result = removeImportFromDeclaration(importPath.get(), "Link")
372
+
373
+ expect(result).toBe(true)
374
+ const output = source.toSource()
375
+ expect(output).toContain("Button")
376
+ expect(output).toContain("Input")
377
+ expect(output).not.toContain("Link")
378
+ expect(output).toContain(
379
+ 'import { Button, Input } from "@planningcenter/tapestry-react"'
380
+ )
381
+ })
382
+ })
383
+ })
@@ -0,0 +1,204 @@
1
+ import { Collection, ImportSpecifier, JSCodeshift } from "jscodeshift"
2
+
3
+ import { TransformCondition } from "../../types"
4
+
5
+ /**
6
+ * Generic import name getter that works with any package
7
+ */
8
+ export function getImportName(
9
+ importName: string,
10
+ packageName: string,
11
+ { source, j }: { j: JSCodeshift; source: Collection }
12
+ ): string | null {
13
+ let localName: string | null = null
14
+
15
+ source
16
+ .find(j.ImportDeclaration, {
17
+ source: { value: packageName },
18
+ })
19
+ .forEach((path) => {
20
+ const specifiers = path.value.specifiers || []
21
+
22
+ specifiers.forEach((spec) => {
23
+ if (
24
+ spec.type === "ImportSpecifier" &&
25
+ spec.imported?.name === importName
26
+ ) {
27
+ localName = (spec.local?.name as string) || importName
28
+ }
29
+ })
30
+ })
31
+
32
+ return localName
33
+ }
34
+
35
+ /**
36
+ * Checks if a component is already imported from sources other than the specified package
37
+ */
38
+ export function hasConflictingImport(
39
+ componentName: string,
40
+ excludePackage: string,
41
+ { source, j }: { j: JSCodeshift; source: Collection }
42
+ ): boolean {
43
+ const conflictingImport = source.find(j.ImportDeclaration).filter((path) => {
44
+ if (path.value.source.value === excludePackage) {
45
+ return false
46
+ }
47
+
48
+ return (
49
+ path.value.specifiers?.some(
50
+ (spec) =>
51
+ (spec.type === "ImportSpecifier" &&
52
+ spec.imported?.name === componentName) ||
53
+ (spec.type === "ImportDefaultSpecifier" &&
54
+ spec.local?.name === componentName)
55
+ ) || false
56
+ )
57
+ })
58
+
59
+ return conflictingImport.length > 0
60
+ }
61
+
62
+ /**
63
+ * Adds an import to an existing import declaration
64
+ */
65
+ export function addImportToExisting(
66
+ importPath: Collection,
67
+ componentName: string,
68
+ j: JSCodeshift,
69
+ alias?: string
70
+ ): void {
71
+ const specifiers = importPath.get().value.specifiers || []
72
+
73
+ const hasImport = specifiers.some(
74
+ (spec: ImportSpecifier) =>
75
+ spec.type === "ImportSpecifier" && spec.imported?.name === componentName
76
+ )
77
+
78
+ if (!hasImport) {
79
+ const importSpecifier = alias
80
+ ? j.importSpecifier(j.identifier(componentName), j.identifier(alias))
81
+ : j.importSpecifier(j.identifier(componentName))
82
+
83
+ specifiers.push(importSpecifier)
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Creates a new import declaration
89
+ */
90
+ export function createNewImport(
91
+ source: Collection,
92
+ componentName: string,
93
+ packageName: string,
94
+ j: JSCodeshift,
95
+ alias?: string
96
+ ): void {
97
+ const importSpecifier = alias
98
+ ? j.importSpecifier(j.identifier(componentName), j.identifier(alias))
99
+ : j.importSpecifier(j.identifier(componentName))
100
+
101
+ const lastImport = source.find(j.ImportDeclaration).at(-1)
102
+ if (lastImport.length > 0) {
103
+ lastImport.insertAfter(
104
+ j.importDeclaration([importSpecifier], j.stringLiteral(packageName))
105
+ )
106
+ } else {
107
+ const program = source.find(j.Program)
108
+ if (program.length > 0) {
109
+ program
110
+ .get("body", 0)
111
+ .insertBefore(
112
+ j.importDeclaration([importSpecifier], j.stringLiteral(packageName))
113
+ )
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Removes an import from an import declaration and cleans up empty imports
120
+ */
121
+ export function removeImportFromDeclaration(
122
+ importPath: Collection,
123
+ componentName: string
124
+ ): boolean {
125
+ const specifiers = importPath.get().value.specifiers || []
126
+
127
+ const importIndex = specifiers.findIndex(
128
+ (spec: ImportSpecifier) =>
129
+ spec.type === "ImportSpecifier" && spec.imported?.name === componentName
130
+ )
131
+
132
+ if (importIndex >= 0) {
133
+ specifiers.splice(importIndex, 1)
134
+
135
+ // Remove entire import if no specifiers left
136
+ if (specifiers.length === 0) {
137
+ importPath.get().replace()
138
+ }
139
+ return true
140
+ }
141
+ return false
142
+ }
143
+
144
+ /**
145
+ * Manages imports after transformation - adds target imports and removes unused source imports
146
+ */
147
+ export function manageImports(
148
+ source: Collection,
149
+ config: {
150
+ /** Condition that must be met for the transform to occur */
151
+ condition: TransformCondition
152
+ /** Optional alias to use if target component conflicts with existing imports */
153
+ conflictAlias?: string
154
+ /** The source component name to transform from */
155
+ fromComponent: string
156
+ /** The package to import the source component from */
157
+ fromPackage: string
158
+ /** The target component name to transform to */
159
+ toComponent: string
160
+ /** The package to import the target component from */
161
+ toPackage: string
162
+ },
163
+ sourceComponentName: string,
164
+ targetComponentName: string,
165
+ j: JSCodeshift
166
+ ): void {
167
+ // Check if source component is still being used
168
+ const stillUsesSource =
169
+ source.find(j.JSXOpeningElement, {
170
+ name: { name: sourceComponentName },
171
+ }).length > 0
172
+
173
+ // Handle source package import cleanup
174
+ const sourceImport = source
175
+ .find(j.ImportDeclaration, {
176
+ source: { value: config.fromPackage },
177
+ })
178
+ .at(0)
179
+
180
+ if (sourceImport.length > 0 && !stillUsesSource) {
181
+ removeImportFromDeclaration(sourceImport, config.fromComponent)
182
+ }
183
+
184
+ // Handle target package import addition
185
+ const targetImport = source
186
+ .find(j.ImportDeclaration, {
187
+ source: { value: config.toPackage },
188
+ })
189
+ .at(0)
190
+
191
+ if (targetImport.length > 0) {
192
+ const alias =
193
+ targetComponentName !== config.toComponent
194
+ ? targetComponentName
195
+ : undefined
196
+ addImportToExisting(targetImport, config.toComponent, j, alias)
197
+ } else {
198
+ const alias =
199
+ targetComponentName !== config.toComponent
200
+ ? targetComponentName
201
+ : undefined
202
+ createNewImport(source, config.toComponent, config.toPackage, j, alias)
203
+ }
204
+ }
@@ -0,0 +1,6 @@
1
+ import { JSXElement } from "jscodeshift"
2
+
3
+ /**
4
+ * Condition function that determines whether a JSX element should be transformed
5
+ */
6
+ export type TransformCondition = (element: JSXElement) => boolean
package/src/index.ts ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ import { Command } from "commander"
4
+
5
+ import { runTransforms } from "./jscodeshiftRunner"
6
+
7
+ const program = new Command()
8
+
9
+ program
10
+ .name("tapestry-migration-cli")
11
+ .description("CLI tool for Tapestry migrations")
12
+
13
+ const COMPONENTS = ["button"]
14
+
15
+ program
16
+ .command("run")
17
+ .description("Run a migration of a component from Tapestry React to Tapestry")
18
+ .argument("<component-name>", "The name of the component to migrate")
19
+ .option("-f, --fix", "Write the changes")
20
+ .option("-p, --path <path>", "The path to the folder/file to migrate")
21
+ .option("-v, --verbose", "Verbose output")
22
+ .action((componentName, options) => {
23
+ console.log("Hello from Tapestry Migration CLI! 🎨")
24
+ console.log(`Component: ${componentName}`)
25
+ const key = componentName.toLowerCase()
26
+
27
+ if (COMPONENTS.includes(key)) {
28
+ runTransforms(key, options)
29
+ } else {
30
+ console.log(
31
+ `Invalid component name: ${componentName}. Valid components are: ${COMPONENTS.join(", ")}`
32
+ )
33
+ }
34
+ })
35
+
36
+ program
37
+ .command("help")
38
+ .description("Show help information")
39
+ .action(() => {
40
+ program.help()
41
+ })
42
+
43
+ program.parse()
44
+
45
+ if (!process.argv.slice(2).length) {
46
+ program.outputHelp()
47
+ }
@@ -0,0 +1,49 @@
1
+ import { execSync } from "child_process"
2
+ import { existsSync } from "fs"
3
+ import { dirname, resolve } from "path"
4
+ import { fileURLToPath } from "url"
5
+
6
+ import { Options } from "./shared/types"
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = dirname(__filename)
10
+
11
+ export function runTransforms(key: string, options: Options): void {
12
+ const transformPath = resolve(__dirname, "components", key, "index.ts")
13
+ const targetPath = options.path
14
+
15
+ if (!existsSync(targetPath)) {
16
+ console.error(`❌ Path not found: ${targetPath}`)
17
+ return
18
+ }
19
+
20
+ console.log("🎯 Running transforms...")
21
+ console.log(`📁 Target: ${targetPath}`)
22
+ console.log(`🔧 Transform: ${transformPath}`)
23
+
24
+ try {
25
+ const command = [
26
+ "npx",
27
+ "--prefer-offline",
28
+ "jscodeshift",
29
+ "-t",
30
+ transformPath,
31
+ targetPath,
32
+ options.fix ? "" : "--dry",
33
+ options.verbose ? "--verbose=2" : "",
34
+ "--parser=tsx",
35
+ ]
36
+ .filter(Boolean)
37
+ .join(" ")
38
+
39
+ console.log(`🚀 Running: ${command}`)
40
+
41
+ execSync(command, {
42
+ cwd: process.cwd(),
43
+ env: process.env,
44
+ stdio: "inherit",
45
+ })
46
+ } catch (error) {
47
+ console.error("❌ Transform failed:", error)
48
+ }
49
+ }
@@ -0,0 +1,6 @@
1
+ export interface Options {
2
+ fix?: boolean
3
+ jsTheme?: string
4
+ path: string
5
+ verbose?: boolean
6
+ }
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};