@clerk/upgrade 2.0.0-snapshot.v20251224145055 → 2.0.0-snapshot.v20260105214115

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.
@@ -0,0 +1,604 @@
1
+ export const fixtures = [{
2
+ name: 'Transforms Protect import',
3
+ source: `
4
+ import { Protect } from "@clerk/react"
5
+ `,
6
+ output: `
7
+ import { Show } from "@clerk/react"
8
+ `
9
+ }, {
10
+ name: 'Transforms Protect import from legacy package',
11
+ source: `
12
+ import { Protect } from "@clerk/clerk-react"
13
+ `,
14
+ output: `
15
+ import { Show } from "@clerk/clerk-react"
16
+ `
17
+ }, {
18
+ name: 'Transforms SignedIn and SignedOut imports',
19
+ source: `
20
+ import { SignedIn, SignedOut } from "@clerk/react"
21
+ `,
22
+ output: `
23
+ import { Show } from "@clerk/react";
24
+ `
25
+ }, {
26
+ name: 'Transforms Protect in TSX',
27
+ source: `
28
+ import { Protect } from "@clerk/react"
29
+
30
+ function App() {
31
+ return (
32
+ <Protect permission="org:billing:manage">
33
+ <BillingSettings />
34
+ </Protect>
35
+ )
36
+ }
37
+ `,
38
+ output: `
39
+ import { Show } from "@clerk/react"
40
+
41
+ function App() {
42
+ return (
43
+ <Show when={{
44
+ permission: "org:billing:manage"
45
+ }}>
46
+ <BillingSettings />
47
+ </Show>
48
+ );
49
+ }
50
+ `
51
+ }, {
52
+ name: 'Transforms SignedIn usage',
53
+ source: `
54
+ import { SignedIn } from "@clerk/react"
55
+
56
+ const App = () => (
57
+ <SignedIn>
58
+ <div>Child</div>
59
+ </SignedIn>
60
+ )
61
+ `,
62
+ output: `
63
+ import { Show } from "@clerk/react"
64
+
65
+ const App = () => (
66
+ <Show when="signed-in">
67
+ <div>Child</div>
68
+ </Show>
69
+ )
70
+ `
71
+ }, {
72
+ name: 'Transforms SignedOut usage',
73
+ source: `
74
+ import { SignedOut } from "@clerk/react"
75
+
76
+ const App = () => (
77
+ <SignedOut>
78
+ <div>Child</div>
79
+ </SignedOut>
80
+ )
81
+ `,
82
+ output: `
83
+ import { Show } from "@clerk/react"
84
+
85
+ const App = () => (
86
+ <Show when="signed-out">
87
+ <div>Child</div>
88
+ </Show>
89
+ )
90
+ `
91
+ }, {
92
+ name: 'Transforms SignedIn namespace import',
93
+ source: `
94
+ import * as Clerk from "@clerk/react"
95
+
96
+ const App = () => (
97
+ <Clerk.SignedIn>
98
+ <div>Child</div>
99
+ </Clerk.SignedIn>
100
+ )
101
+ `,
102
+ output: `
103
+ import * as Clerk from "@clerk/react"
104
+
105
+ const App = () => (
106
+ <Clerk.Show when="signed-in">
107
+ <div>Child</div>
108
+ </Clerk.Show>
109
+ )
110
+ `
111
+ }, {
112
+ name: 'Transforms Protect condition callback',
113
+ source: `
114
+ import { Protect } from "@clerk/react"
115
+
116
+ function App() {
117
+ return (
118
+ <Protect condition={(has) => has({ role: "admin" })}>
119
+ <Content />
120
+ </Protect>
121
+ )
122
+ }
123
+ `,
124
+ output: `
125
+ import { Show } from "@clerk/react"
126
+
127
+ function App() {
128
+ return (
129
+ <Show when={(has) => has({ role: "admin" })}>
130
+ <Content />
131
+ </Show>
132
+ );
133
+ }
134
+ `
135
+ }, {
136
+ name: 'Transforms SignedIn import with other specifiers',
137
+ source: `
138
+ import { ClerkProvider, SignedIn } from "@clerk/nextjs"
139
+ `,
140
+ output: `
141
+ import { ClerkProvider, Show } from "@clerk/nextjs"
142
+ `
143
+ }, {
144
+ name: 'Transforms ProtectProps type',
145
+ source: `
146
+ import { ProtectProps } from "@clerk/react";
147
+ type Props = ProtectProps;
148
+ `,
149
+ output: `
150
+ import { ShowProps } from "@clerk/react";
151
+ type Props = ShowProps;
152
+ `
153
+ }, {
154
+ name: 'Self-closing Protect defaults to signedIn',
155
+ source: `
156
+ import { Protect } from "@clerk/react"
157
+
158
+ const Thing = () => <Protect />
159
+ `,
160
+ output: `
161
+ import { Show } from "@clerk/react"
162
+
163
+ const Thing = () => <Show when="signed-in" />
164
+ `
165
+ }, {
166
+ name: 'Transforms Protect from hybrid package without client directive',
167
+ source: `
168
+ import { Protect } from "@clerk/nextjs"
169
+
170
+ const App = () => (
171
+ <Protect role="admin">
172
+ <div>Child</div>
173
+ </Protect>
174
+ )
175
+ `,
176
+ output: `
177
+ import { Show } from "@clerk/nextjs"
178
+
179
+ const App = () => (
180
+ <Show when={{
181
+ role: "admin"
182
+ }}>
183
+ <div>Child</div>
184
+ </Show>
185
+ )
186
+ `
187
+ }, {
188
+ name: 'Transforms SignedOut to Show with fallback prop',
189
+ source: `
190
+ import { SignedOut } from "@clerk/react"
191
+
192
+ const App = () => (
193
+ <SignedOut fallback={<Other />}>
194
+ <div>Child</div>
195
+ </SignedOut>
196
+ )
197
+ `,
198
+ output: `
199
+ import { Show } from "@clerk/react"
200
+
201
+ const App = () => (
202
+ <Show when="signed-out" fallback={<Other />}>
203
+ <div>Child</div>
204
+ </Show>
205
+ )
206
+ `
207
+ }, {
208
+ name: 'Transforms SignedOut namespace import with fallback',
209
+ source: `
210
+ import * as Clerk from "@clerk/react"
211
+
212
+ const App = () => (
213
+ <Clerk.SignedOut fallback={<Other />}>
214
+ <div>Child</div>
215
+ </Clerk.SignedOut>
216
+ )
217
+ `,
218
+ output: `
219
+ import * as Clerk from "@clerk/react"
220
+
221
+ const App = () => (
222
+ <Clerk.Show when="signed-out" fallback={<Other />}>
223
+ <div>Child</div>
224
+ </Clerk.Show>
225
+ )
226
+ `
227
+ }, {
228
+ name: 'Aliased Protect import is transformed',
229
+ source: `
230
+ import { Protect as CanAccess } from "@clerk/react"
231
+
232
+ function App() {
233
+ return (
234
+ <CanAccess permission="org:billing:manage">
235
+ <BillingSettings />
236
+ </CanAccess>
237
+ )
238
+ }
239
+ `,
240
+ output: `
241
+ import { Show as CanAccess } from "@clerk/react"
242
+
243
+ function App() {
244
+ return (
245
+ <CanAccess when={{
246
+ permission: "org:billing:manage"
247
+ }}>
248
+ <BillingSettings />
249
+ </CanAccess>
250
+ );
251
+ }
252
+ `
253
+ }, {
254
+ name: 'ProtectProps type aliases update',
255
+ source: `
256
+ import { ProtectProps } from "@clerk/react";
257
+ type Props = ProtectProps;
258
+ type Another = ProtectProps;
259
+ `,
260
+ output: `
261
+ import { ShowProps } from "@clerk/react";
262
+ type Props = ShowProps;
263
+ type Another = ShowProps;
264
+ `
265
+ }, {
266
+ name: 'Protect with fallback prop',
267
+ source: `
268
+ import { Protect } from "@clerk/react"
269
+
270
+ function App() {
271
+ return (
272
+ <Protect permission="org:billing:manage" fallback={<Unauthorized />}>
273
+ <BillingSettings />
274
+ </Protect>
275
+ )
276
+ }
277
+ `,
278
+ output: `
279
+ import { Show } from "@clerk/react"
280
+
281
+ function App() {
282
+ return (
283
+ <Show when={{
284
+ permission: "org:billing:manage"
285
+ }} fallback={<Unauthorized />}>
286
+ <BillingSettings />
287
+ </Show>
288
+ );
289
+ }
290
+ `
291
+ }, {
292
+ name: 'Protect with spread props',
293
+ source: `
294
+ import { Protect } from "@clerk/react"
295
+
296
+ const props = { permission: "org:read" }
297
+ const App = () => <Protect {...props} />
298
+ `,
299
+ output: `
300
+ import { Show } from "@clerk/react"
301
+
302
+ const props = { permission: "org:read" }
303
+ const App = () => <Show when="signed-in" {...props} />
304
+ `
305
+ }, {
306
+ name: 'Transforms Protect require destructuring',
307
+ source: `
308
+ const { Protect } = require("@clerk/react");
309
+
310
+ function App() {
311
+ return <Protect role="admin">ok</Protect>;
312
+ }
313
+ `,
314
+ output: `
315
+ const { Show } = require("@clerk/react");
316
+
317
+ function App() {
318
+ return (
319
+ <Show when={{
320
+ role: "admin"
321
+ }}>ok</Show>
322
+ );
323
+ }
324
+ `
325
+ }, {
326
+ name: 'Transforms SignedIn and SignedOut require destructuring',
327
+ source: `
328
+ const { SignedIn, SignedOut } = require("@clerk/react");
329
+
330
+ const App = () => (
331
+ <>
332
+ <SignedIn>in</SignedIn>
333
+ <SignedOut>out</SignedOut>
334
+ </>
335
+ );
336
+ `,
337
+ output: `
338
+ const {
339
+ Show
340
+ } = require("@clerk/react");
341
+
342
+ const App = () => (
343
+ <>
344
+ <Show when="signed-in">in</Show>
345
+ <Show when="signed-out">out</Show>
346
+ </>
347
+ );
348
+ `
349
+ }, {
350
+ name: 'Transforms namespace require',
351
+ source: `
352
+ const Clerk = require("@clerk/react");
353
+
354
+ const App = () => (
355
+ <Clerk.Protect role="admin">
356
+ ok
357
+ </Clerk.Protect>
358
+ );
359
+ `,
360
+ output: `
361
+ const Clerk = require("@clerk/react");
362
+
363
+ const App = () => (
364
+ <Clerk.Show when={{
365
+ role: "admin"
366
+ }}>
367
+ ok
368
+ </Clerk.Show>
369
+ );
370
+ `
371
+ }, {
372
+ name: 'Transforms Protect from other @clerk packages',
373
+ source: `
374
+ import { Protect as ProtectExpo } from "@clerk/expo";
375
+ import { Protect as ProtectVue } from "@clerk/vue";
376
+ import { Protect as ProtectChrome } from "@clerk/chrome-extension";
377
+ `,
378
+ output: `
379
+ import { Show as ProtectExpo } from "@clerk/expo";
380
+ import { Show as ProtectVue } from "@clerk/vue";
381
+ import { Show as ProtectChrome } from "@clerk/chrome-extension";
382
+ `
383
+ }, {
384
+ name: 'Transforms default import member usage',
385
+ source: `
386
+ import Clerk from "@clerk/react";
387
+
388
+ const App = () => (
389
+ <Clerk.Protect role="admin">
390
+ ok
391
+ </Clerk.Protect>
392
+ );
393
+ `,
394
+ output: `
395
+ import Clerk from "@clerk/react";
396
+
397
+ const App = () => (
398
+ <Clerk.Show when={{
399
+ role: "admin"
400
+ }}>
401
+ ok
402
+ </Clerk.Show>
403
+ );
404
+ `
405
+ }, {
406
+ name: 'Transforms Protect namespace import member usage',
407
+ source: `
408
+ import * as Clerk from "@clerk/react";
409
+
410
+ const App = () => (
411
+ <Clerk.Protect role="admin">
412
+ ok
413
+ </Clerk.Protect>
414
+ );
415
+ `,
416
+ output: `
417
+ import * as Clerk from "@clerk/react";
418
+
419
+ const App = () => (
420
+ <Clerk.Show when={{
421
+ role: "admin"
422
+ }}>
423
+ ok
424
+ </Clerk.Show>
425
+ );
426
+ `
427
+ }, {
428
+ name: 'Self-closing SignedIn and SignedOut are transformed',
429
+ source: `
430
+ import { SignedIn, SignedOut } from "@clerk/react";
431
+
432
+ const App = () => (
433
+ <>
434
+ <SignedIn />
435
+ <SignedOut />
436
+ </>
437
+ );
438
+ `,
439
+ output: `
440
+ import { Show } from "@clerk/react";
441
+
442
+ const App = () => (
443
+ <>
444
+ <Show when="signed-in" />
445
+ <Show when="signed-out" />
446
+ </>
447
+ );
448
+ `
449
+ }, {
450
+ name: 'Transforms SignedIn alias import usage',
451
+ source: `
452
+ import { SignedIn as OnlyWhenSignedIn } from "@clerk/react";
453
+
454
+ const App = () => (
455
+ <OnlyWhenSignedIn>
456
+ ok
457
+ </OnlyWhenSignedIn>
458
+ );
459
+ `,
460
+ output: `
461
+ import { Show as OnlyWhenSignedIn } from "@clerk/react";
462
+
463
+ const App = () => (
464
+ <OnlyWhenSignedIn when="signed-in">
465
+ ok
466
+ </OnlyWhenSignedIn>
467
+ );
468
+ `
469
+ }, {
470
+ name: 'Transforms Protect require destructuring with alias',
471
+ source: `
472
+ const { Protect: CanAccess } = require("@clerk/react");
473
+
474
+ const App = () => (
475
+ <CanAccess role="admin">
476
+ ok
477
+ </CanAccess>
478
+ );
479
+ `,
480
+ output: `
481
+ const { Show: CanAccess } = require("@clerk/react");
482
+
483
+ const App = () => (
484
+ <CanAccess when={{
485
+ role: "admin"
486
+ }}>
487
+ ok
488
+ </CanAccess>
489
+ );
490
+ `
491
+ }, {
492
+ name: 'Transforms import with duplicate Show specifier',
493
+ source: `
494
+ import { Protect, Show } from "@clerk/react";
495
+
496
+ const App = () => <Protect role="admin" />;
497
+ `,
498
+ output: `
499
+ import { Show } from "@clerk/react";
500
+
501
+ const App = () => <Show when={{
502
+ role: "admin"
503
+ }} />;
504
+ `
505
+ }, {
506
+ name: 'Transforms import type ProtectProps',
507
+ source: `
508
+ import type { ProtectProps } from "@clerk/react";
509
+ type Props = ProtectProps;
510
+ `,
511
+ output: `
512
+ import type { ShowProps } from "@clerk/react";
513
+ type Props = ShowProps;
514
+ `
515
+ }, {
516
+ name: 'Sorts when object keys for determinism',
517
+ source: `
518
+ import { Protect } from "@clerk/react";
519
+
520
+ const App = () => (
521
+ <Protect role="admin" permission="org:billing:manage" treatPendingAsSignedOut>
522
+ ok
523
+ </Protect>
524
+ );
525
+ `,
526
+ output: `
527
+ import { Show } from "@clerk/react";
528
+
529
+ const App = () => (
530
+ <Show
531
+ when={{
532
+ permission: "org:billing:manage",
533
+ role: "admin"
534
+ }}
535
+ treatPendingAsSignedOut>
536
+ ok
537
+ </Show>
538
+ );
539
+ `
540
+ }, {
541
+ name: 'Does not transform non-clerk Protect',
542
+ source: `
543
+ import { Protect } from "./local";
544
+
545
+ const App = () => (
546
+ <Protect role="admin">
547
+ ok
548
+ </Protect>
549
+ );
550
+ `,
551
+ output: null
552
+ }, {
553
+ name: 'Transforms self-closing namespaced Protect component',
554
+ source: `
555
+ import * as Clerk from "@clerk/react";
556
+
557
+ const App = () => <Clerk.Protect permission="org:read" />;
558
+ `,
559
+ output: `
560
+ import * as Clerk from "@clerk/react";
561
+
562
+ const App = () => <Clerk.Show when={{
563
+ permission: "org:read"
564
+ }} />;
565
+ `
566
+ }, {
567
+ name: 'Transforms namespaced SignedIn and SignedOut in same file',
568
+ source: `
569
+ import * as Clerk from "@clerk/nextjs";
570
+
571
+ const App = () => (
572
+ <>
573
+ <Clerk.SignedIn>
574
+ <Dashboard />
575
+ </Clerk.SignedIn>
576
+ <Clerk.SignedOut>
577
+ <Login />
578
+ </Clerk.SignedOut>
579
+ <Clerk.Protect role="admin">
580
+ <AdminPanel />
581
+ </Clerk.Protect>
582
+ </>
583
+ );
584
+ `,
585
+ output: `
586
+ import * as Clerk from "@clerk/nextjs";
587
+
588
+ const App = () => (
589
+ <>
590
+ <Clerk.Show when="signed-in">
591
+ <Dashboard />
592
+ </Clerk.Show>
593
+ <Clerk.Show when="signed-out">
594
+ <Login />
595
+ </Clerk.Show>
596
+ <Clerk.Show when={{
597
+ role: "admin"
598
+ }}>
599
+ <AdminPanel />
600
+ </Clerk.Show>
601
+ </>
602
+ );
603
+ `
604
+ }];
@@ -0,0 +1,20 @@
1
+ import { applyTransform } from 'jscodeshift/dist/testUtils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import transformer from '../transform-protect-to-show.cjs';
4
+ import { fixtures } from './__fixtures__/transform-protect-to-show.fixtures';
5
+ describe('transform-protect-to-show', () => {
6
+ it.each(fixtures)(`$name`, ({
7
+ source,
8
+ output
9
+ }) => {
10
+ const result = applyTransform(transformer, {}, {
11
+ source
12
+ });
13
+ if (output === null) {
14
+ // null output means no transformation should occur
15
+ expect(result).toBeFalsy();
16
+ } else {
17
+ expect(result).toEqual(output.trim());
18
+ }
19
+ });
20
+ });
@@ -0,0 +1,290 @@
1
+ const CLERK_PACKAGE_PREFIX = '@clerk/';
2
+ const isClerkPackageSource = sourceValue => {
3
+ return typeof sourceValue === 'string' && sourceValue.startsWith(CLERK_PACKAGE_PREFIX);
4
+ };
5
+
6
+ /**
7
+ * Transforms `<Protect>` component usage to `<Show>` component.
8
+ *
9
+ * Handles the following transformations:
10
+ * - `<Protect role="admin">` → `<Show when={{ role: 'admin' }}>`
11
+ * - `<Protect permission="org:read">` → `<Show when={{ permission: 'org:read' }}>`
12
+ * - `<Protect feature="user:premium">` → `<Show when={{ feature: 'user:premium' }}>`
13
+ * - `<Protect plan="pro">` → `<Show when={{ plan: 'pro' }}>`
14
+ * - `<Protect condition={(has) => ...}>` → `<Show when={(has) => ...}>`
15
+ * - `<SignedIn>...` → `<Show when="signed-in">...`
16
+ * - `<SignedOut>...` → `<Show when="signed-out">...`
17
+ *
18
+ * Also updates ESM/CJS imports from `Protect` to `Show`.
19
+ *
20
+ * @param {import('jscodeshift').FileInfo} fileInfo - The file information
21
+ * @param {import('jscodeshift').API} api - The API object provided by jscodeshift
22
+ * @returns {string|undefined} - The transformed source code if modifications were made
23
+ */
24
+ module.exports = function transformProtectToShow({
25
+ source
26
+ }, {
27
+ jscodeshift: j
28
+ }) {
29
+ const root = j(source);
30
+ let dirtyFlag = false;
31
+ const componentKindByLocalName = {};
32
+ const protectPropsLocalsToRename = [];
33
+ const namespaceImports = new Set();
34
+
35
+ // Transform ESM imports: Protect → Show, ProtectProps → ShowProps
36
+ root.find(j.ImportDeclaration).forEach(path => {
37
+ const node = path.node;
38
+ const sourceValue = node.source?.value;
39
+ if (!isClerkPackageSource(sourceValue)) {
40
+ return;
41
+ }
42
+ const specifiers = node.specifiers || [];
43
+ specifiers.forEach(spec => {
44
+ if (j.ImportDefaultSpecifier.check(spec) || j.ImportNamespaceSpecifier.check(spec)) {
45
+ if (spec.local?.name) {
46
+ namespaceImports.add(spec.local.name);
47
+ }
48
+ return;
49
+ }
50
+ if (!j.ImportSpecifier.check(spec)) {
51
+ return;
52
+ }
53
+ const originalImportedName = spec.imported.name;
54
+ if (['Protect', 'SignedIn', 'SignedOut'].includes(originalImportedName)) {
55
+ const effectiveLocalName = spec.local ? spec.local.name : originalImportedName;
56
+ componentKindByLocalName[effectiveLocalName] = originalImportedName === 'Protect' ? 'protect' : originalImportedName === 'SignedIn' ? 'signed-in' : 'signed-out';
57
+ spec.imported.name = 'Show';
58
+ if (spec.local && spec.local.name === originalImportedName) {
59
+ spec.local.name = 'Show';
60
+ }
61
+ dirtyFlag = true;
62
+ return;
63
+ }
64
+ if (spec.imported.name === 'ProtectProps') {
65
+ const effectiveLocalName = spec.local ? spec.local.name : spec.imported.name;
66
+ spec.imported.name = 'ShowProps';
67
+ if (spec.local && spec.local.name === 'ProtectProps') {
68
+ spec.local.name = 'ShowProps';
69
+ }
70
+ if (effectiveLocalName === 'ProtectProps') {
71
+ protectPropsLocalsToRename.push(effectiveLocalName);
72
+ }
73
+ dirtyFlag = true;
74
+ }
75
+ });
76
+ const seenLocalNames = new Set();
77
+ node.specifiers = specifiers.reduce((acc, spec) => {
78
+ let localName = null;
79
+ if (spec.local && j.Identifier.check(spec.local)) {
80
+ localName = spec.local.name;
81
+ } else if (j.ImportSpecifier.check(spec) && j.Identifier.check(spec.imported)) {
82
+ localName = spec.imported.name;
83
+ }
84
+ if (localName) {
85
+ if (seenLocalNames.has(localName)) {
86
+ dirtyFlag = true;
87
+ return acc;
88
+ }
89
+ seenLocalNames.add(localName);
90
+ }
91
+ acc.push(spec);
92
+ return acc;
93
+ }, []);
94
+ });
95
+
96
+ // Transform CJS requires: Protect → Show
97
+ root.find(j.VariableDeclarator).forEach(path => {
98
+ const declarator = path.node;
99
+ const init = declarator.init;
100
+ if (!init || !j.CallExpression.check(init)) {
101
+ return;
102
+ }
103
+ if (!j.Identifier.check(init.callee) || init.callee.name !== 'require') {
104
+ return;
105
+ }
106
+ const args = init.arguments || [];
107
+ if (args.length !== 1) {
108
+ return;
109
+ }
110
+ const arg = args[0];
111
+ const sourceValue = j.Literal.check(arg) ? arg.value : j.StringLiteral.check(arg) ? arg.value : null;
112
+ if (!isClerkPackageSource(sourceValue)) {
113
+ return;
114
+ }
115
+ const id = declarator.id;
116
+ if (j.Identifier.check(id)) {
117
+ namespaceImports.add(id.name);
118
+ return;
119
+ }
120
+ if (!j.ObjectPattern.check(id)) {
121
+ return;
122
+ }
123
+ const properties = id.properties || [];
124
+ const seenLocalNames = new Set();
125
+ id.properties = properties.reduce((acc, prop) => {
126
+ if (!(j.ObjectProperty.check(prop) || j.Property.check(prop))) {
127
+ acc.push(prop);
128
+ return acc;
129
+ }
130
+ if (!j.Identifier.check(prop.key)) {
131
+ acc.push(prop);
132
+ return acc;
133
+ }
134
+ const originalImportedName = prop.key.name;
135
+ const originalLocalName = j.Identifier.check(prop.value) ? prop.value.name : null;
136
+ const effectiveLocalName = originalLocalName || originalImportedName;
137
+ if (['Protect', 'SignedIn', 'SignedOut'].includes(originalImportedName)) {
138
+ componentKindByLocalName[effectiveLocalName] = originalImportedName === 'Protect' ? 'protect' : originalImportedName === 'SignedIn' ? 'signed-in' : 'signed-out';
139
+ prop.key.name = 'Show';
140
+ if (j.Identifier.check(prop.value) && prop.value.name === originalImportedName) {
141
+ prop.value.name = 'Show';
142
+ }
143
+ if (prop.shorthand) {
144
+ prop.value = j.identifier('Show');
145
+ }
146
+ dirtyFlag = true;
147
+ }
148
+ const newLocalName = j.Identifier.check(prop.value) ? prop.value.name : null;
149
+ const finalLocalName = newLocalName || (j.Identifier.check(prop.key) ? prop.key.name : null);
150
+ if (finalLocalName) {
151
+ if (seenLocalNames.has(finalLocalName)) {
152
+ dirtyFlag = true;
153
+ return acc;
154
+ }
155
+ seenLocalNames.add(finalLocalName);
156
+ }
157
+ acc.push(prop);
158
+ return acc;
159
+ }, []);
160
+ });
161
+
162
+ // Rename references to ProtectProps (only when local name was ProtectProps)
163
+ if (protectPropsLocalsToRename.length > 0) {
164
+ root.find(j.TSTypeReference, {
165
+ typeName: {
166
+ type: 'Identifier',
167
+ name: 'ProtectProps'
168
+ }
169
+ }).forEach(path => {
170
+ const typeName = path.node.typeName;
171
+ if (j.Identifier.check(typeName) && typeName.name === 'ProtectProps') {
172
+ typeName.name = 'ShowProps';
173
+ dirtyFlag = true;
174
+ }
175
+ });
176
+ }
177
+
178
+ // Transform JSX: <Protect ...> → <Show when={...}>
179
+ root.find(j.JSXElement).forEach(path => {
180
+ const openingElement = path.node.openingElement;
181
+ const closingElement = path.node.closingElement;
182
+ let kind = null;
183
+ let renameNodeToShow = null;
184
+ if (j.JSXIdentifier.check(openingElement.name)) {
185
+ const originalName = openingElement.name.name;
186
+ kind = componentKindByLocalName[originalName];
187
+ if (['Protect', 'SignedIn', 'SignedOut'].includes(originalName)) {
188
+ renameNodeToShow = node => {
189
+ if (j.JSXIdentifier.check(node)) {
190
+ node.name = 'Show';
191
+ }
192
+ };
193
+ }
194
+ } else if (j.JSXMemberExpression.check(openingElement.name)) {
195
+ const member = openingElement.name;
196
+ if ((j.JSXIdentifier.check(member.object) || j.JSXMemberExpression.check(member.object)) && j.JSXIdentifier.check(member.property)) {
197
+ const objectName = j.JSXIdentifier.check(member.object) ? member.object.name : null;
198
+ const propertyName = member.property.name;
199
+ if (objectName && namespaceImports.has(objectName) && ['Protect', 'SignedIn', 'SignedOut'].includes(propertyName)) {
200
+ kind = propertyName === 'Protect' ? 'protect' : propertyName === 'SignedIn' ? 'signed-in' : 'signed-out';
201
+ renameNodeToShow = node => {
202
+ if (j.JSXMemberExpression.check(node) && j.JSXIdentifier.check(node.property)) {
203
+ node.property.name = 'Show';
204
+ }
205
+ };
206
+ }
207
+ }
208
+ }
209
+ if (!kind) {
210
+ return;
211
+ }
212
+ if (renameNodeToShow) {
213
+ renameNodeToShow(openingElement.name);
214
+ if (closingElement && closingElement.name) {
215
+ renameNodeToShow(closingElement.name);
216
+ }
217
+ }
218
+ const attributes = openingElement.attributes || [];
219
+ const authAttributes = [];
220
+ const otherAttributes = [];
221
+ let conditionAttr = null;
222
+
223
+ // Separate auth-related attributes from other attributes
224
+ attributes.forEach(attr => {
225
+ if (!j.JSXAttribute.check(attr)) {
226
+ otherAttributes.push(attr);
227
+ return;
228
+ }
229
+ const attrName = attr.name.name;
230
+ if (attrName === 'condition') {
231
+ conditionAttr = attr;
232
+ } else if (['feature', 'permission', 'plan', 'role'].includes(attrName)) {
233
+ authAttributes.push(attr);
234
+ } else {
235
+ otherAttributes.push(attr);
236
+ }
237
+ });
238
+
239
+ // Build the `when` prop
240
+ let whenValue = null;
241
+ if (kind === 'signed-in' || kind === 'signed-out') {
242
+ whenValue = j.stringLiteral(kind === 'signed-in' ? 'signed-in' : 'signed-out');
243
+ } else if (conditionAttr) {
244
+ // condition prop becomes the when callback directly
245
+ whenValue = conditionAttr.value;
246
+ } else if (authAttributes.length > 0) {
247
+ // Build an object from auth attributes
248
+ const properties = authAttributes.map(attr => {
249
+ const key = j.identifier(attr.name.name);
250
+ let value;
251
+ if (j.JSXExpressionContainer.check(attr.value)) {
252
+ value = attr.value.expression;
253
+ } else if (j.StringLiteral.check(attr.value) || j.Literal.check(attr.value)) {
254
+ value = attr.value;
255
+ } else if (attr.value == null) {
256
+ value = j.booleanLiteral(true);
257
+ } else {
258
+ // Default string value
259
+ value = j.stringLiteral(attr.value?.value || '');
260
+ }
261
+ return j.objectProperty(key, value);
262
+ });
263
+ properties.sort((a, b) => {
264
+ const aKey = j.Identifier.check(a.key) ? a.key.name : '';
265
+ const bKey = j.Identifier.check(b.key) ? b.key.name : '';
266
+ return aKey.localeCompare(bKey);
267
+ });
268
+ whenValue = j.jsxExpressionContainer(j.objectExpression(properties));
269
+ }
270
+
271
+ // Reconstruct attributes with `when` prop
272
+ const newAttributes = [];
273
+ const defaultWhenValue = kind === 'signed-out' ? 'signed-out' : 'signed-in';
274
+ const finalWhenValue = whenValue || j.stringLiteral(defaultWhenValue);
275
+ newAttributes.push(j.jsxAttribute(j.jsxIdentifier('when'), finalWhenValue));
276
+
277
+ // Add remaining attributes (fallback, etc.)
278
+ otherAttributes.forEach(attr => newAttributes.push(attr));
279
+ openingElement.attributes = newAttributes;
280
+ dirtyFlag = true;
281
+ });
282
+ if (!dirtyFlag) {
283
+ return undefined;
284
+ }
285
+ let result = root.toSource();
286
+ // Fix double semicolons that can occur when recast reprints directive prologues
287
+ result = result.replace(/^(['"`][^'"`]+['"`]);;/gm, '$1;');
288
+ return result;
289
+ };
290
+ module.exports.parser = 'tsx';
@@ -0,0 +1,62 @@
1
+ ---
2
+ title: '`Protect`, `SignedIn`, and `SignedOut` replaced by `Show`'
3
+ matcher: '<(Protect|SignedIn|SignedOut)(\\s|>)'
4
+ matcherFlags: 'm'
5
+ category: 'breaking'
6
+ ---
7
+
8
+ The authorization control components `Protect`, `SignedIn`, and `SignedOut` have been removed in favor of a single component: `Show`.
9
+
10
+ ### Signed in / signed out
11
+
12
+ ```diff
13
+ -import { SignedIn, SignedOut } from '@clerk/react';
14
+ +import { Show } from '@clerk/react';
15
+
16
+ -<SignedIn>
17
+ +<Show when="signed-in">
18
+ <Dashboard />
19
+ -</SignedIn>
20
+ +</Show>
21
+
22
+ -<SignedOut>
23
+ +<Show when="signed-out">
24
+ <SignIn />
25
+ -</SignedOut>
26
+ +</Show>
27
+ ```
28
+
29
+ ### Authorization checks (roles/permissions/features/plans)
30
+
31
+ ```diff
32
+ -import { Protect } from '@clerk/react';
33
+ +import { Show } from '@clerk/react';
34
+
35
+ -<Protect role="admin" fallback={<p>Unauthorized</p>}>
36
+ +<Show when={{ role: 'admin' }} fallback={<p>Unauthorized</p>}>
37
+ <AdminPanel />
38
+ -</Protect>
39
+ +</Show>
40
+
41
+ -<Protect permission="org:billing:manage">
42
+ +<Show when={{ permission: 'org:billing:manage' }}>
43
+ <BillingSettings />
44
+ -</Protect>
45
+ +</Show>
46
+ ```
47
+
48
+ If you were using `condition={(has) => ...}` on `Protect`, pass that callback to `when`:
49
+
50
+ ```diff
51
+ -<Protect condition={(has) => has({ role: 'admin' }) && isAllowed}>
52
+ +<Show when={(has) => has({ role: 'admin' }) && isAllowed}>
53
+ <AdminPanel />
54
+ -</Protect>
55
+ +</Show>
56
+ ```
57
+
58
+ To auto-migrate common patterns, run the upgrade CLI (includes a `transform-protect-to-show` codemod):
59
+
60
+ ```bash
61
+ npx @clerk/upgrade --release core-3
62
+ ```
@@ -36,5 +36,5 @@ export default {
36
36
  to: 3
37
37
  }
38
38
  },
39
- codemods: ['transform-clerk-react-v6', 'transform-remove-deprecated-props', 'transform-remove-deprecated-appearance-props', 'transform-appearance-layout-to-options', 'transform-themes-to-ui-themes', 'transform-align-experimental-unstable-prefixes']
39
+ codemods: ['transform-clerk-react-v6', 'transform-remove-deprecated-props', 'transform-remove-deprecated-appearance-props', 'transform-appearance-layout-to-options', 'transform-themes-to-ui-themes', 'transform-align-experimental-unstable-prefixes', 'transform-protect-to-show']
40
40
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clerk/upgrade",
3
- "version": "2.0.0-snapshot.v20251224145055",
3
+ "version": "2.0.0-snapshot.v20260105214115",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/clerk/javascript.git",