diff --git a/.changeset/small-dots-scream.md b/.changeset/small-dots-scream.md new file mode 100644 index 00000000000..ff44a7842d9 --- /dev/null +++ b/.changeset/small-dots-scream.md @@ -0,0 +1,5 @@ +--- +'@clerk/upgrade': patch +--- + +Add entry for Sign-in Client Trust Status diff --git a/packages/upgrade/src/__tests__/integration/runner.test.js b/packages/upgrade/src/__tests__/integration/runner.test.js index e3e5b2fcc72..f6a883acc72 100644 --- a/packages/upgrade/src/__tests__/integration/runner.test.js +++ b/packages/upgrade/src/__tests__/integration/runner.test.js @@ -56,6 +56,9 @@ describe('runScans', () => { it('respects ignore patterns', async () => { const config = await loadConfig('nextjs', 6); + // Filter to only changes with matchers for this test + config.changes = config.changes.filter(change => change.matcher); + const options = { dir: fixture.path, ignore: ['**/src/**'], @@ -65,4 +68,69 @@ describe('runScans', () => { expect(results).toEqual([]); }); + + it('always includes changes without matchers', async () => { + const config = await loadConfig('nextjs', 6); + // Add a change without a matcher + config.changes = [ + { + title: 'Test change without matcher', + matcher: null, + packages: ['*'], + category: 'breaking', + warning: false, + docsAnchor: 'test-change', + content: 'This is a test change', + }, + ]; + + const options = { + dir: fixture.path, + ignore: [], + }; + + const results = await runScans(config, 'nextjs', options); + + expect(results).toHaveLength(1); + expect(results[0].title).toBe('Test change without matcher'); + expect(results[0].instances).toEqual([]); + }); + + it('includes both changes with and without matchers', async () => { + const config = await loadConfig('nextjs', 6); + // Add a change without a matcher and one with a matcher + config.changes = [ + { + title: 'Change without matcher', + matcher: null, + packages: ['*'], + category: 'breaking', + warning: false, + docsAnchor: 'change-without-matcher', + content: 'This change has no matcher', + }, + { + title: 'Change with matcher', + matcher: new RegExp('clerkMiddleware', 'g'), + packages: ['*'], + category: 'breaking', + warning: false, + docsAnchor: 'change-with-matcher', + content: 'This change has a matcher', + }, + ]; + + const options = { + dir: fixture.path, + ignore: [], + }; + + const results = await runScans(config, 'nextjs', options); + + // Should include both changes + expect(results.length).toBeGreaterThanOrEqual(1); + const changeWithoutMatcher = results.find(r => r.title === 'Change without matcher'); + expect(changeWithoutMatcher).toBeDefined(); + expect(changeWithoutMatcher.instances).toEqual([]); + }); }); diff --git a/packages/upgrade/src/runner.js b/packages/upgrade/src/runner.js index 3bd07a0ebac..99f5683a90b 100644 --- a/packages/upgrade/src/runner.js +++ b/packages/upgrade/src/runner.js @@ -59,12 +59,27 @@ export async function runCodemods(config, sdk, options) { } export async function runScans(config, sdk, options) { - const matchers = loadMatchers(config, sdk); + const changes = loadMatchers(config, sdk); - if (matchers.length === 0) { + if (changes.length === 0) { return []; } + const changesWithMatchers = changes.filter(change => change.matcher); + const changesWithoutMatchers = changes.filter(change => !change.matcher); + + const results = {}; + + // Always include changes without matchers + for (const change of changesWithoutMatchers) { + results[change.title] = { instances: [], ...change }; + } + + // Handle scans with matchers + if (changesWithMatchers.length === 0) { + return Object.values(results); + } + const spinner = createSpinner('Scanning files for breaking changes...'); try { @@ -75,15 +90,13 @@ export async function runScans(config, sdk, options) { ignore: [...GLOBBY_IGNORE, ...(options.ignore || [])], }); - const results = {}; - for (let idx = 0; idx < files.length; idx++) { const file = files[idx]; spinner.update(`Scanning ${path.basename(file)} (${idx + 1}/${files.length})`); const content = await fs.readFile(file, 'utf8'); - for (const matcher of matchers) { + for (const matcher of changesWithMatchers) { const matches = findMatches(content, matcher.matcher); if (matches.length === 0) { @@ -127,10 +140,6 @@ function loadMatchers(config, sdk) { } return config.changes.filter(change => { - if (!change.matcher) { - return false; - } - const packages = change.packages || ['*']; return packages.includes('*') || packages.includes(sdk); }); diff --git a/packages/upgrade/src/versions/core-3/changes/needs-client-trust-sign-in-status-added.md b/packages/upgrade/src/versions/core-3/changes/needs-client-trust-sign-in-status-added.md new file mode 100644 index 00000000000..c209bebb0de --- /dev/null +++ b/packages/upgrade/src/versions/core-3/changes/needs-client-trust-sign-in-status-added.md @@ -0,0 +1,27 @@ +--- +title: 'Sign-in Client Trust status handling' +category: 'breaking' +--- + +We've added a new Sign-in status of `needs_client_trust` which, given the conditions listed, will need to be handled in your application. + +[TODO: Documentation Link] + +Prerequisites: + +- [Passwords and Client Trust](https://dashboard.clerk.com/~/user-authentication/user-and-authentication?user_auth_tab=password) are enabled. +- You've opted-in to the Client Trust `needs_client_trust` [Update](https://dashboard.clerk.com/~/updates). +- Sign-in with [Email](https://dashboard.clerk.com/~/user-authentication/user-and-authentication) and/or [Phone](https://dashboard.clerk.com/~/user-authentication/user-and-authentication?user_auth_tab=phone) identifiers are enabled. +- If [Email](https://dashboard.clerk.com/~/user-authentication/user-and-authentication) or [SMS](https://dashboard.clerk.com/~/user-authentication/user-and-authentication?user_auth_tab=phone) sign-in verification aren't enabled. + +While your application may differ, we've provided an example change below. Please reach out to [Support](mailto:support@clerk.dev) if you have any questions. + +```diff +- const { signIn } = useSignIn() +- signIn.attemptFirstFactor({ strategy: 'password', password: '...' }) +- if (signIn.status === 'complete') {/* ... */ } ++ const { signIn } = useSignIn() ++ signIn.attemptFirstFactor({ strategy: 'password', password: '...' }) ++ if (signIn.status === 'needs_client_trust') { /* ... */ } ++ else if (signIn.status === 'complete') { /* ... */ } +```