diff --git a/.changeset/vue-protect-to-show-codemod.md b/.changeset/vue-protect-to-show-codemod.md new file mode 100644 index 00000000000..bd628f70eb8 --- /dev/null +++ b/.changeset/vue-protect-to-show-codemod.md @@ -0,0 +1,5 @@ +--- +'@clerk/upgrade': patch +--- + +Add Vue-specific `transform-protect-to-show-vue` codemod that handles `.vue` SFC files with proper Vue v-bind syntax for the Protect to Show migration. diff --git a/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-protect-to-show-vue.fixtures.js b/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-protect-to-show-vue.fixtures.js new file mode 100644 index 00000000000..1dcd0fff575 --- /dev/null +++ b/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-protect-to-show-vue.fixtures.js @@ -0,0 +1,435 @@ +export const fixtures = [ + { + name: 'Transforms Protect import in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms SignedIn in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms SignedOut in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms Protect with permission in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms Protect with feature in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms Protect with plan in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms Protect with multiple auth props in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms Protect with condition in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms Protect with fallback in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms self-closing Protect in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms self-closing SignedIn in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms SignedOut with fallback in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms multiple components in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms ProtectProps type in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms Protect with dynamic binding in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Does not transform non-.vue files', + path: 'component.tsx', + source: `import { Protect } from "@clerk/vue" + +const App = () => ( + + + +)`, + output: null, + }, + { + name: 'Does not transform non-clerk imports in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: null, + }, + { + name: 'Transforms Protect with no props to default signed-in', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, + { + name: 'Transforms Nuxt import in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: null, // #clerk is not @clerk/* so should not transform + }, + { + name: 'Preserves other attributes on Protect in Vue SFC', + path: 'component.vue', + source: ` + +`, + output: ` + +`, + }, +]; diff --git a/packages/upgrade/src/codemods/__tests__/transform-protect-to-show-vue.test.js b/packages/upgrade/src/codemods/__tests__/transform-protect-to-show-vue.test.js new file mode 100644 index 00000000000..2e7591b72db --- /dev/null +++ b/packages/upgrade/src/codemods/__tests__/transform-protect-to-show-vue.test.js @@ -0,0 +1,18 @@ +import { applyTransform } from 'jscodeshift/dist/testUtils'; +import { describe, expect, it } from 'vitest'; + +import transformer from '../transform-protect-to-show-vue.cjs'; +import { fixtures } from './__fixtures__/transform-protect-to-show-vue.fixtures'; + +describe('transform-protect-to-show-vue', () => { + it.each(fixtures)(`$name`, ({ source, output, path }) => { + const result = applyTransform(transformer, {}, { source, path }); + + if (output === null) { + // null output means no transformation should occur + expect(result).toBeFalsy(); + } else { + expect(result).toEqual(output.trim()); + } + }); +}); diff --git a/packages/upgrade/src/codemods/transform-protect-to-show-vue.cjs b/packages/upgrade/src/codemods/transform-protect-to-show-vue.cjs new file mode 100644 index 00000000000..b3077aef3dd --- /dev/null +++ b/packages/upgrade/src/codemods/transform-protect-to-show-vue.cjs @@ -0,0 +1,393 @@ +const CLERK_PACKAGE_PREFIX = '@clerk/'; + +const isClerkPackageSource = sourceValue => { + return typeof sourceValue === 'string' && sourceValue.startsWith(CLERK_PACKAGE_PREFIX); +}; + +/** + * Vue-specific codemod to transform ``, ``, and `` components to ``. + * + * This codemod handles Vue SFC (.vue) files, transforming both: + * - Script imports (ESM/CJS) + * - Template component usage with Vue's v-bind syntax + * + * Template transformations: + * - `` → `` + * - `` → `` + * - `` → `` + * - `` → `` + * - `` → `` + * + * @param {import('jscodeshift').FileInfo} fileInfo - The file information + * @param {import('jscodeshift').API} api - The API object provided by jscodeshift + * @returns {string|undefined} - The transformed source code if modifications were made + */ +module.exports = function transformProtectToShowVue(fileInfo, { jscodeshift: j }) { + const { source, path: filePath } = fileInfo; + + // Only process .vue files + if (!filePath || !filePath.endsWith('.vue')) { + return undefined; + } + + let dirtyFlag = false; + let result = source; + + // Track which components were imported from @clerk/* + const importedComponents = new Set(); + + // Extract and transform + const scriptMatch = result.match(/(]*>)([\s\S]*?)(<\/script\b[^>]*>)/i); + if (scriptMatch) { + const [fullMatch, openTag, scriptContent, closeTag] = scriptMatch; + const scriptResult = transformScript(scriptContent, j); + + if (scriptResult.transformed !== null) { + result = result.replace(fullMatch, openTag + scriptResult.transformed + closeTag); + dirtyFlag = true; + } + + // Track imported components + for (const comp of scriptResult.importedComponents) { + importedComponents.add(comp); + } + } + + // Only transform template if we found clerk imports + if (importedComponents.size > 0) { + // Extract and transform