@clerk/upgrade 2.0.0-snapshot.v20251204175016 → 2.0.0-snapshot.v20251211120550

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 (55) hide show
  1. package/README.md +35 -5
  2. package/dist/__tests__/fixtures/expo-old-package/package-lock.json +5 -0
  3. package/dist/__tests__/fixtures/expo-old-package/package.json +10 -0
  4. package/dist/__tests__/fixtures/expo-old-package/src/App.tsx +14 -0
  5. package/dist/__tests__/fixtures/nextjs-v6/package.json +9 -0
  6. package/dist/__tests__/fixtures/nextjs-v6/pnpm-lock.yaml +2 -0
  7. package/dist/__tests__/fixtures/nextjs-v6/src/app.tsx +17 -0
  8. package/dist/__tests__/fixtures/nextjs-v7/package.json +9 -0
  9. package/dist/__tests__/fixtures/nextjs-v7/pnpm-lock.yaml +2 -0
  10. package/dist/__tests__/fixtures/nextjs-v7/src/app.tsx +16 -0
  11. package/dist/__tests__/fixtures/no-clerk/package.json +7 -0
  12. package/dist/__tests__/fixtures/react-v6/package.json +8 -0
  13. package/dist/__tests__/fixtures/react-v6/src/App.tsx +19 -0
  14. package/dist/__tests__/fixtures/react-v6/yarn.lock +2 -0
  15. package/dist/__tests__/helpers/create-fixture.js +56 -0
  16. package/dist/__tests__/integration/cli.test.js +275 -0
  17. package/dist/__tests__/integration/config.test.js +97 -0
  18. package/dist/__tests__/integration/detect-sdk.test.js +100 -0
  19. package/dist/__tests__/integration/runner.test.js +58 -0
  20. package/dist/cli.js +172 -44
  21. package/dist/codemods/__tests__/__fixtures__/transform-align-experimental-unstable-prefixes.fixtures.js +92 -0
  22. package/dist/codemods/__tests__/__fixtures__/transform-appearance-layout-to-options.fixtures.js +9 -0
  23. package/dist/codemods/__tests__/__fixtures__/transform-clerk-react-v6.fixtures.js +13 -0
  24. package/dist/codemods/__tests__/__fixtures__/transform-remove-deprecated-appearance-props.fixtures.js +63 -0
  25. package/dist/codemods/__tests__/__fixtures__/transform-themes-to-ui-themes.fixtures.js +41 -0
  26. package/dist/codemods/__tests__/transform-align-experimental-unstable-prefixes.test.js +15 -0
  27. package/dist/codemods/__tests__/transform-appearance-layout-to-options.test.js +15 -0
  28. package/dist/codemods/__tests__/transform-remove-deprecated-appearance-props.test.js +15 -0
  29. package/dist/codemods/__tests__/transform-themes-to-ui-themes.test.js +15 -0
  30. package/dist/codemods/index.js +67 -13
  31. package/dist/codemods/transform-align-experimental-unstable-prefixes.cjs +412 -0
  32. package/dist/codemods/transform-appearance-layout-to-options.cjs +65 -0
  33. package/dist/codemods/transform-clerk-react-v6.cjs +15 -7
  34. package/dist/codemods/transform-remove-deprecated-appearance-props.cjs +109 -0
  35. package/dist/codemods/transform-remove-deprecated-props.cjs +11 -32
  36. package/dist/codemods/transform-themes-to-ui-themes.cjs +65 -0
  37. package/dist/config.js +145 -0
  38. package/dist/render.js +170 -0
  39. package/dist/runner.js +98 -0
  40. package/dist/util/detect-sdk.js +125 -0
  41. package/dist/util/package-manager.js +94 -0
  42. package/dist/versions/core-3/changes/clerk-expo-package-rename.md +23 -0
  43. package/dist/versions/core-3/changes/clerk-react-package-rename.md +22 -0
  44. package/dist/versions/core-3/index.js +40 -0
  45. package/package.json +2 -8
  46. package/dist/app.js +0 -177
  47. package/dist/components/Codemod.js +0 -149
  48. package/dist/components/Command.js +0 -56
  49. package/dist/components/Header.js +0 -11
  50. package/dist/components/SDKWorkflow.js +0 -278
  51. package/dist/components/Scan.js +0 -180
  52. package/dist/components/UpgradeSDK.js +0 -116
  53. package/dist/util/expandable-list.js +0 -173
  54. package/dist/util/get-clerk-version.js +0 -22
  55. package/dist/util/guess-framework.js +0 -69
@@ -0,0 +1,15 @@
1
+ import { applyTransform } from 'jscodeshift/dist/testUtils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import transformer from '../transform-themes-to-ui-themes.cjs';
4
+ import { fixtures } from './__fixtures__/transform-themes-to-ui-themes.fixtures';
5
+ describe('transform-themes-to-ui-themes', () => {
6
+ it.each(fixtures)('$name', ({
7
+ source,
8
+ output
9
+ }) => {
10
+ const result = applyTransform(transformer, {}, {
11
+ source
12
+ });
13
+ expect(result).toEqual(output.trim());
14
+ });
15
+ });
@@ -1,32 +1,86 @@
1
1
  import { dirname, resolve } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
+ import chalk from 'chalk';
3
4
  import { globby } from 'globby';
4
5
  import { run } from 'jscodeshift/src/Runner.js';
5
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const CODEMOD_CONFIG = {
8
+ 'transform-remove-deprecated-props': {
9
+ renderSummary: renderDeprecatedPropsSummary
10
+ }
11
+ };
12
+ const GLOBBY_IGNORE = ['**/*.md', 'node_modules/**', '**/node_modules/**', '.git/**', '**/*.json', 'package.json', '**/package.json', 'package-lock.json', '**/package-lock.json', 'yarn.lock', '**/yarn.lock', 'pnpm-lock.yaml', '**/pnpm-lock.yaml', 'yalc.lock', '**/*.{ico,png,webp,svg,gif,jpg,jpeg}', '**/*.{mp4,mkv,wmv,m4v,mov,avi,flv,webm,flac,mka,m4a,aac,ogg}', '**/*.{css,scss,sass,less,styl}'];
6
13
  export async function runCodemod(transform = 'transform-async-request', glob, options = {}) {
7
14
  if (!transform) {
8
15
  throw new Error('No transform provided');
9
16
  }
10
17
  const resolvedPath = resolve(__dirname, `${transform}.cjs`);
11
18
  const paths = await globby(glob, {
12
- ignore: ['**/*.md', 'node_modules/**', '**/node_modules/**', '.git/**', '**/*.json', 'package.json', '**/package.json', 'package-lock.json', '**/package-lock.json', 'yarn.lock', '**/yarn.lock', 'pnpm-lock.yaml', '**/pnpm-lock.yaml', 'yalc.lock', '**/*.(ico|png|webp|svg|gif|jpg|jpeg)+',
13
- // common image files
14
- '**/*.(mp4|mkv|wmv|m4v|mov|avi|flv|webm|flac|mka|m4a|aac|ogg)+',
15
- // common video files] }).then(files => {
16
- '**/*.(css|scss|sass|less|styl)+' // common style files
17
- ]
19
+ ignore: GLOBBY_IGNORE
18
20
  });
19
- const clerkUpgradeStats = options.clerkUpgradeStats ?? {};
20
- const result = await run(resolvedPath, paths ?? [], {
21
- dry: false,
21
+ if (options.skipCodemods) {
22
+ return {
23
+ stats: {
24
+ total: 0,
25
+ modified: 0,
26
+ deleted: 0
27
+ }
28
+ };
29
+ }
30
+
31
+ // First pass: dry run to collect stats (jscodeshift only reports stats in dry mode)
32
+ const dryResult = await run(resolvedPath, paths ?? [], {
22
33
  ...options,
23
- // expose a mutable stats bag so individual transforms can record structured information
24
- clerkUpgradeStats,
25
- // we must silence stdout to prevent output from interfering with ink CLI
34
+ dry: true,
26
35
  silent: true
27
36
  });
37
+ let result = {};
38
+ if (!options.dryRun) {
39
+ // Second pass: apply the changes
40
+ result = await run(resolvedPath, paths ?? [], {
41
+ ...options,
42
+ dry: false,
43
+ silent: true
44
+ });
45
+ }
46
+ if (options.dry) {
47
+ return dryResult;
48
+ }
49
+
50
+ // Merge stats from dry run into final result
28
51
  return {
29
52
  ...result,
30
- clerkUpgradeStats
53
+ stats: dryResult.stats
31
54
  };
55
+ }
56
+ export function getCodemodConfig(transform) {
57
+ return CODEMOD_CONFIG[transform] || null;
58
+ }
59
+ function renderDeprecatedPropsSummary(stats) {
60
+ if (!stats) {
61
+ return;
62
+ }
63
+ const userButtonCount = stats.userbuttonAfterSignOutPropsRemoved || 0;
64
+ const hideSlugCount = stats.hideSlugRemoved || 0;
65
+ const beforeEmitCount = stats.beforeEmitTransformed || 0;
66
+ if (!userButtonCount && !hideSlugCount && !beforeEmitCount) {
67
+ return;
68
+ }
69
+ console.log(chalk.yellow.bold('Manual intervention may be required:'));
70
+ if (userButtonCount > 0) {
71
+ console.log(chalk.yellow(`• Removed ${userButtonCount} UserButton sign-out redirect prop(s)`));
72
+ console.log(chalk.gray(' To configure sign-out redirects:'));
73
+ console.log(chalk.gray(' - Global: Add afterSignOutUrl to <ClerkProvider>'));
74
+ console.log(chalk.gray(' - Per-button: Use <SignOutButton redirectUrl="...">'));
75
+ console.log(chalk.gray(' - Programmatic: clerk.signOut({ redirectUrl: "..." })'));
76
+ }
77
+ if (hideSlugCount > 0) {
78
+ console.log(chalk.yellow(`• Removed ${hideSlugCount} hideSlug prop(s)`));
79
+ console.log(chalk.gray(' Slugs are now managed in the Clerk Dashboard.'));
80
+ }
81
+ if (beforeEmitCount > 0) {
82
+ console.log(chalk.yellow(`• Transformed ${beforeEmitCount} setActive({ beforeEmit }) → setActive({ navigate })`));
83
+ console.log(chalk.gray(' The callback now receives an object with session property.'));
84
+ }
85
+ console.log('');
32
86
  }
@@ -0,0 +1,412 @@
1
+ const SPECIFIC_RENAMES = Object.freeze({
2
+ __experimental_createTheme: 'createTheme',
3
+ __experimental_simple: 'simple',
4
+ __unstable__createClerkClient: 'createClerkClient',
5
+ __unstable__environment: '__internal_environment',
6
+ __unstable__onAfterResponse: '__internal_onAfterResponse',
7
+ __unstable__onAfterSetActive: '__internal_onAfterSetActive',
8
+ __unstable__onBeforeRequest: '__internal_onBeforeRequest',
9
+ __unstable__onBeforeSetActive: '__internal_onBeforeSetActive',
10
+ __unstable__setEnvironment: '__internal_setEnvironment',
11
+ __unstable__updateProps: '__internal_updateProps',
12
+ __unstable_invokeMiddlewareOnAuthStateChange: '__internal_invokeMiddlewareOnAuthStateChange',
13
+ experimental__simple: 'simple',
14
+ experimental_createTheme: 'createTheme'
15
+ });
16
+ const REMOVED_PROPS = new Set(['__unstable_manageBillingUrl', '__unstable_manageBillingLabel', '__unstable_manageBillingMembersLimit', 'experimental__forceOauthFirst']);
17
+ const UI_THEME_NAMES = new Set(['createTheme', 'simple', 'experimental_createTheme', '__experimental_createTheme', 'experimental__simple', '__experimental_simple']);
18
+ const UI_THEME_SOURCE = '@clerk/ui/themes/experimental';
19
+ const UI_LEGACY_SOURCES = new Set(['@clerk/ui', '@clerk/ui/themes', UI_THEME_SOURCE]);
20
+ const CHROME_CLIENT_NAMES = new Set(['__unstable__createClerkClient', 'createClerkClient']);
21
+ const CHROME_BACKGROUND_SOURCE = '@clerk/chrome-extension/background';
22
+ const CHROME_LEGACY_SOURCE = '@clerk/chrome-extension';
23
+
24
+ /**
25
+ * Transforms experimental and unstable prefixed identifiers to their stable or internal equivalents.
26
+ * Also moves theme-related imports to @clerk/ui/themes/experimental and Chrome extension imports
27
+ * to @clerk/chrome-extension/background. Removes deprecated billing-related props.
28
+ *
29
+ * @param {Object} file - The file object containing the source code
30
+ * @param {string} file.source - The source code to transform
31
+ * @param {Object} api - The jscodeshift API
32
+ * @param {Function} api.jscodeshift - The jscodeshift function
33
+ * @returns {string|undefined} The transformed source code, or undefined if no changes were made
34
+ */
35
+ module.exports = function transformAlignExperimentalUnstablePrefixes({
36
+ source
37
+ }, {
38
+ jscodeshift: j
39
+ }) {
40
+ const root = j(source);
41
+ let dirty = false;
42
+ const maybeRename = name => {
43
+ if (!name || REMOVED_PROPS.has(name) || !Object.hasOwn(SPECIFIC_RENAMES, name)) {
44
+ return null;
45
+ }
46
+ return SPECIFIC_RENAMES[name];
47
+ };
48
+ const renameIdentifier = node => {
49
+ const newName = maybeRename(node.name);
50
+ if (newName && newName !== node.name) {
51
+ node.name = newName;
52
+ dirty = true;
53
+ }
54
+ };
55
+ const renameLiteral = node => {
56
+ if (typeof node.value !== 'string') {
57
+ return;
58
+ }
59
+ const newName = maybeRename(node.value);
60
+ if (newName && newName !== node.value) {
61
+ node.value = newName;
62
+ dirty = true;
63
+ }
64
+ };
65
+ const getPropertyName = key => {
66
+ if (j.Identifier.check(key)) {
67
+ return key.name;
68
+ }
69
+ if (j.Literal.check(key)) {
70
+ return key.value;
71
+ }
72
+ if (j.StringLiteral && j.StringLiteral.check(key)) {
73
+ return key.value;
74
+ }
75
+ return null;
76
+ };
77
+ const renamePropertyKey = (key, computed = false) => {
78
+ if (REMOVED_PROPS.has(getPropertyName(key))) {
79
+ return null;
80
+ }
81
+ if (j.Identifier.check(key)) {
82
+ const newName = maybeRename(key.name);
83
+ if (newName && newName !== key.name) {
84
+ key.name = newName;
85
+ dirty = true;
86
+ }
87
+ return key;
88
+ }
89
+ if (!computed && (j.Literal.check(key) || j.StringLiteral && j.StringLiteral.check(key))) {
90
+ const newName = maybeRename(key.value);
91
+ if (newName && newName !== key.value) {
92
+ key.value = newName;
93
+ dirty = true;
94
+ }
95
+ return key;
96
+ }
97
+ return key;
98
+ };
99
+ const mergeImportSpecifiers = (targetImport, specifiers) => {
100
+ const existingKeys = new Set((targetImport.node.specifiers || []).map(spec => `${spec.local ? spec.local.name : spec.imported?.name ?? spec.imported?.value ?? ''}`));
101
+ specifiers.forEach(spec => {
102
+ const key = spec.local ? spec.local.name : spec.imported?.name;
103
+ if (!existingKeys.has(key)) {
104
+ targetImport.node.specifiers = targetImport.node.specifiers || [];
105
+ targetImport.node.specifiers.push(spec);
106
+ existingKeys.add(key);
107
+ dirty = true;
108
+ }
109
+ });
110
+ };
111
+ root.find(j.ImportSpecifier).forEach(path => {
112
+ const imported = path.node.imported;
113
+ if (j.Identifier.check(imported)) {
114
+ const originalImportedName = imported.name;
115
+ renameIdentifier(imported);
116
+ if ((!path.node.local || path.node.local.name === originalImportedName) && imported.name !== originalImportedName) {
117
+ path.node.local = j.identifier(imported.name);
118
+ dirty = true;
119
+ }
120
+ }
121
+ if (path.node.local) {
122
+ renameIdentifier(path.node.local);
123
+ }
124
+ });
125
+ root.find(j.ExportSpecifier).forEach(path => {
126
+ if (j.Identifier.check(path.node.exported)) {
127
+ renameIdentifier(path.node.exported);
128
+ }
129
+ if (j.Identifier.check(path.node.local)) {
130
+ renameIdentifier(path.node.local);
131
+ }
132
+ });
133
+ const handleMemberExpression = path => {
134
+ const {
135
+ node
136
+ } = path;
137
+ if (!node.computed && j.Identifier.check(node.property)) {
138
+ renameIdentifier(node.property);
139
+ } else if (node.computed && (j.Literal.check(node.property) || j.StringLiteral && j.StringLiteral.check(node.property))) {
140
+ renameLiteral(node.property);
141
+ }
142
+ };
143
+ root.find(j.MemberExpression).forEach(handleMemberExpression);
144
+ if (j.OptionalMemberExpression) {
145
+ root.find(j.OptionalMemberExpression).forEach(handleMemberExpression);
146
+ }
147
+ root.find(j.Property).forEach(path => {
148
+ const {
149
+ node
150
+ } = path;
151
+ const propName = getPropertyName(node.key);
152
+ if (propName && REMOVED_PROPS.has(propName) && !node.computed) {
153
+ path.prune();
154
+ dirty = true;
155
+ return;
156
+ }
157
+ renamePropertyKey(node.key, node.computed);
158
+ if (j.Identifier.check(node.value)) {
159
+ renameIdentifier(node.value);
160
+ }
161
+ });
162
+ root.find(j.ObjectPattern).forEach(path => {
163
+ path.node.properties.forEach(prop => {
164
+ if (!prop) {
165
+ return;
166
+ }
167
+ const keyName = getPropertyName(prop.key);
168
+ if (keyName && REMOVED_PROPS.has(keyName) && !prop.computed) {
169
+ return;
170
+ }
171
+ if (prop.key) {
172
+ renamePropertyKey(prop.key, prop.computed);
173
+ }
174
+ if (prop.value && j.Identifier.check(prop.value)) {
175
+ renameIdentifier(prop.value);
176
+ }
177
+ });
178
+ });
179
+ root.find(j.Identifier).filter(path => maybeRename(path.node.name)).forEach(path => {
180
+ renameIdentifier(path.node);
181
+ });
182
+ root.find(j.JSXOpeningElement).forEach(path => {
183
+ const attributes = path.node.attributes || [];
184
+ path.node.attributes = attributes.filter(attr => {
185
+ if (!j.JSXAttribute.check(attr) || !j.JSXIdentifier.check(attr.name)) {
186
+ return true;
187
+ }
188
+ const name = attr.name.name;
189
+ if (REMOVED_PROPS.has(name)) {
190
+ dirty = true;
191
+ return false;
192
+ }
193
+ const newName = maybeRename(name);
194
+ if (newName && newName !== name) {
195
+ attr.name.name = newName;
196
+ dirty = true;
197
+ }
198
+ return true;
199
+ });
200
+ });
201
+ const normalizeUiThemeSpecifier = spec => {
202
+ if (!j.ImportSpecifier.check(spec)) {
203
+ return null;
204
+ }
205
+ const importedName = spec.imported?.name ?? spec.imported?.value;
206
+ if (!importedName || !UI_THEME_NAMES.has(importedName)) {
207
+ return null;
208
+ }
209
+ const newImportedName = maybeRename(importedName) || importedName;
210
+ const newImported = j.identifier(newImportedName);
211
+ const newLocal = spec.local && spec.local.name !== importedName ? j.identifier(spec.local.name) : j.identifier(newImportedName);
212
+ return j.importSpecifier(newImported, newLocal.name === newImported.name ? null : newLocal);
213
+ };
214
+ root.find(j.ImportDeclaration).forEach(path => {
215
+ const source = path.node.source?.value;
216
+ if (!UI_LEGACY_SOURCES.has(source) && source !== CHROME_LEGACY_SOURCE) {
217
+ return;
218
+ }
219
+ if (UI_LEGACY_SOURCES.has(source)) {
220
+ const specifiers = path.node.specifiers || [];
221
+ const moveSpecifiers = [];
222
+ const remainingSpecifiers = [];
223
+ specifiers.forEach(spec => {
224
+ const normalized = normalizeUiThemeSpecifier(spec);
225
+ if (normalized) {
226
+ moveSpecifiers.push(normalized);
227
+ return;
228
+ }
229
+ remainingSpecifiers.push(spec);
230
+ });
231
+ if (source === UI_THEME_SOURCE) {
232
+ if (moveSpecifiers.length) {
233
+ path.node.specifiers = moveSpecifiers.concat(remainingSpecifiers.filter(spec => !moveSpecifiers.some(m => m.imported.name === spec.imported?.name)));
234
+ dirty = true;
235
+ }
236
+ return;
237
+ }
238
+ if (moveSpecifiers.length) {
239
+ const targetImport = root.find(j.ImportDeclaration, {
240
+ source: {
241
+ value: UI_THEME_SOURCE
242
+ }
243
+ }).at(0);
244
+ if (targetImport.size() > 0) {
245
+ mergeImportSpecifiers(targetImport.get(), moveSpecifiers);
246
+ } else {
247
+ const newImport = j.importDeclaration(moveSpecifiers, j.literal(UI_THEME_SOURCE));
248
+ j(path).insertAfter(newImport);
249
+ dirty = true;
250
+ }
251
+ if (remainingSpecifiers.length) {
252
+ path.node.specifiers = remainingSpecifiers;
253
+ } else {
254
+ j(path).remove();
255
+ }
256
+ }
257
+ }
258
+ if (source === CHROME_LEGACY_SOURCE) {
259
+ const specifiers = path.node.specifiers || [];
260
+ const moveSpecifiers = [];
261
+ const remainingSpecifiers = [];
262
+ specifiers.forEach(spec => {
263
+ if (!j.ImportSpecifier.check(spec)) {
264
+ remainingSpecifiers.push(spec);
265
+ return;
266
+ }
267
+ const importedName = spec.imported?.name ?? spec.imported?.value;
268
+ if (!CHROME_CLIENT_NAMES.has(importedName)) {
269
+ remainingSpecifiers.push(spec);
270
+ return;
271
+ }
272
+ const newImportedName = maybeRename(importedName) || importedName;
273
+ const newImported = j.identifier(newImportedName);
274
+ const newLocal = spec.local && spec.local.name !== importedName ? j.identifier(spec.local.name) : j.identifier(newImportedName);
275
+ moveSpecifiers.push(j.importSpecifier(newImported, newLocal.name === newImported.name ? null : newLocal));
276
+ });
277
+ if (moveSpecifiers.length) {
278
+ const targetImport = root.find(j.ImportDeclaration, {
279
+ source: {
280
+ value: CHROME_BACKGROUND_SOURCE
281
+ }
282
+ }).at(0);
283
+ if (targetImport.size() > 0) {
284
+ mergeImportSpecifiers(targetImport.get(), moveSpecifiers);
285
+ } else {
286
+ const newImport = j.importDeclaration(moveSpecifiers, j.literal(CHROME_BACKGROUND_SOURCE));
287
+ j(path).insertAfter(newImport);
288
+ dirty = true;
289
+ }
290
+ if (remainingSpecifiers.length) {
291
+ path.node.specifiers = remainingSpecifiers;
292
+ } else {
293
+ j(path).remove();
294
+ }
295
+ }
296
+ }
297
+ });
298
+ root.find(j.VariableDeclarator, {
299
+ init: {
300
+ callee: {
301
+ name: 'require'
302
+ }
303
+ }
304
+ }).filter(path => {
305
+ const arg = path.node.init.arguments?.[0];
306
+ return arg && (j.Literal.check(arg) || j.StringLiteral && j.StringLiteral.check(arg)) && UI_LEGACY_SOURCES.has(arg.value);
307
+ }).forEach(path => {
308
+ const id = path.node.id;
309
+ if (!j.ObjectPattern.check(id)) {
310
+ return;
311
+ }
312
+ const moveProps = [];
313
+ const keepProps = [];
314
+ id.properties.forEach(prop => {
315
+ if (!prop || !prop.key) {
316
+ return;
317
+ }
318
+ const keyName = getPropertyName(prop.key);
319
+ if (!keyName) {
320
+ keepProps.push(prop);
321
+ return;
322
+ }
323
+ if (!UI_THEME_NAMES.has(keyName)) {
324
+ keepProps.push(prop);
325
+ return;
326
+ }
327
+ const renamed = maybeRename(keyName) || keyName;
328
+ const keyIdentifier = j.identifier(renamed);
329
+ const valueIdentifier = prop.value && j.Identifier.check(prop.value) && prop.value.name !== keyName ? j.identifier(prop.value.name) : j.identifier(renamed);
330
+ const newProp = j.property('init', keyIdentifier, valueIdentifier);
331
+ newProp.shorthand = keyIdentifier.name === valueIdentifier.name;
332
+ moveProps.push(newProp);
333
+ });
334
+ if (!moveProps.length) {
335
+ return;
336
+ }
337
+ const parentDecl = path.parent.node;
338
+ const kind = parentDecl.kind || 'const';
339
+ const newDeclarator = j.variableDeclarator(j.objectPattern(moveProps), j.callExpression(j.identifier('require'), [j.literal(UI_THEME_SOURCE)]));
340
+ const newDeclaration = j.variableDeclaration(kind, [newDeclarator]);
341
+ j(path.parent).insertAfter(newDeclaration);
342
+ dirty = true;
343
+ if (keepProps.length) {
344
+ id.properties = keepProps;
345
+ } else {
346
+ j(path).remove();
347
+ }
348
+ });
349
+ root.find(j.VariableDeclarator, {
350
+ init: {
351
+ callee: {
352
+ name: 'require'
353
+ }
354
+ }
355
+ }).filter(path => {
356
+ const arg = path.node.init.arguments?.[0];
357
+ return arg && (j.Literal.check(arg) || j.StringLiteral && j.StringLiteral.check(arg)) && arg.value === CHROME_LEGACY_SOURCE;
358
+ }).forEach(path => {
359
+ const id = path.node.id;
360
+ if (!j.ObjectPattern.check(id)) {
361
+ return;
362
+ }
363
+ const moveProps = [];
364
+ const keepProps = [];
365
+ id.properties.forEach(prop => {
366
+ if (!prop || !prop.key) {
367
+ return;
368
+ }
369
+ const keyName = getPropertyName(prop.key);
370
+ if (!keyName || !CHROME_CLIENT_NAMES.has(keyName)) {
371
+ keepProps.push(prop);
372
+ return;
373
+ }
374
+ const renamed = maybeRename(keyName) || keyName;
375
+ const keyIdentifier = j.identifier(renamed);
376
+ const valueIdentifier = prop.value && j.Identifier.check(prop.value) && prop.value.name !== keyName ? j.identifier(prop.value.name) : j.identifier(renamed);
377
+ const newProp = j.property('init', keyIdentifier, valueIdentifier);
378
+ newProp.shorthand = keyIdentifier.name === valueIdentifier.name;
379
+ moveProps.push(newProp);
380
+ });
381
+ if (!moveProps.length) {
382
+ return;
383
+ }
384
+ const parentDecl = path.parent.node;
385
+ const kind = parentDecl.kind || 'const';
386
+ const newDeclarator = j.variableDeclarator(j.objectPattern(moveProps), j.callExpression(j.identifier('require'), [j.literal(CHROME_BACKGROUND_SOURCE)]));
387
+ const newDeclaration = j.variableDeclaration(kind, [newDeclarator]);
388
+ j(path.parent).insertAfter(newDeclaration);
389
+ dirty = true;
390
+ if (keepProps.length) {
391
+ id.properties = keepProps;
392
+ } else {
393
+ j(path).remove();
394
+ }
395
+ });
396
+ root.find(j.ObjectExpression).forEach(path => {
397
+ const props = path.node.properties || [];
398
+ path.node.properties = props.filter(prop => {
399
+ if (!prop || !prop.key) {
400
+ return true;
401
+ }
402
+ const propName = getPropertyName(prop.key);
403
+ if (propName && REMOVED_PROPS.has(propName) && !prop.computed) {
404
+ dirty = true;
405
+ return false;
406
+ }
407
+ return true;
408
+ });
409
+ });
410
+ return dirty ? root.toSource() : undefined;
411
+ };
412
+ module.exports.parser = 'tsx';
@@ -0,0 +1,65 @@
1
+ const isStringLiteral = node => node && node.type === 'Literal' && typeof node.value === 'string' || node && node.type === 'StringLiteral' && typeof node.value === 'string';
2
+ const getPropertyName = key => {
3
+ if (!key) {
4
+ return null;
5
+ }
6
+ if (key.type === 'Identifier') {
7
+ return key.name;
8
+ }
9
+ if (isStringLiteral(key)) {
10
+ return key.value;
11
+ }
12
+ return null;
13
+ };
14
+ module.exports = function transformAppearanceLayoutToOptions({
15
+ source
16
+ }, {
17
+ jscodeshift: j
18
+ }) {
19
+ const root = j(source);
20
+ let dirty = false;
21
+ const renameLayoutKey = prop => {
22
+ const keyName = getPropertyName(prop?.key);
23
+ if (!prop || keyName !== 'layout') {
24
+ return false;
25
+ }
26
+ if (prop.computed && !isStringLiteral(prop.key)) {
27
+ return false;
28
+ }
29
+ if (j.Identifier.check(prop.key)) {
30
+ prop.key.name = 'options';
31
+ } else if (isStringLiteral(prop.key)) {
32
+ prop.key.value = 'options';
33
+ } else {
34
+ prop.key = j.identifier('options');
35
+ prop.computed = false;
36
+ }
37
+ return true;
38
+ };
39
+ root.find(j.JSXAttribute, {
40
+ name: {
41
+ name: 'appearance'
42
+ }
43
+ }).forEach(path => {
44
+ const {
45
+ value
46
+ } = path.node;
47
+ if (!value || !j.JSXExpressionContainer.check(value)) {
48
+ return;
49
+ }
50
+ const expression = value.expression;
51
+ if (j.ObjectExpression.check(expression)) {
52
+ let changed = false;
53
+ (expression.properties || []).forEach(prop => {
54
+ if (renameLayoutKey(prop)) {
55
+ changed = true;
56
+ }
57
+ });
58
+ if (changed) {
59
+ dirty = true;
60
+ }
61
+ }
62
+ });
63
+ return dirty ? root.toSource() : undefined;
64
+ };
65
+ module.exports.parser = 'tsx';
@@ -42,16 +42,18 @@ module.exports = function transformClerkReactV6({
42
42
  }
43
43
  if (legacySpecifiers.length > 0 && nonLegacySpecifiers.length > 0) {
44
44
  // Mixed import: keep non-legacy on targetPackage, emit a new import for legacy hooks
45
- node.specifiers = nonLegacySpecifiers;
46
- node.source = j.literal(targetPackage);
45
+ // Use replaceWith to avoid formatting issues with insertAfter
46
+ const mainImport = j.importDeclaration(nonLegacySpecifiers, j.stringLiteral(targetPackage));
47
47
  if (importKind) {
48
- node.importKind = importKind;
48
+ mainImport.importKind = importKind;
49
49
  }
50
- const legacyImportDecl = j.importDeclaration(legacySpecifiers, j.literal(`${targetPackage}/legacy`));
50
+ // Preserve leading comments/whitespace from original import
51
+ mainImport.comments = node.comments;
52
+ const legacyImport = j.importDeclaration(legacySpecifiers, j.stringLiteral(`${targetPackage}/legacy`));
51
53
  if (importKind) {
52
- legacyImportDecl.importKind = importKind;
54
+ legacyImport.importKind = importKind;
53
55
  }
54
- j(path).insertAfter(legacyImportDecl);
56
+ j(path).replaceWith([mainImport, legacyImport]);
55
57
  dirtyFlag = true;
56
58
  return;
57
59
  }
@@ -127,6 +129,12 @@ module.exports = function transformClerkReactV6({
127
129
  dirtyFlag = true;
128
130
  });
129
131
  });
130
- return dirtyFlag ? root.toSource() : undefined;
132
+ if (!dirtyFlag) {
133
+ return undefined;
134
+ }
135
+ let result = root.toSource();
136
+ // Fix double semicolons that can occur when recast reprints directive prologues (e.g., "use client";)
137
+ result = result.replace(/^(['"`][^'"`]+['"`]);;/gm, '$1;');
138
+ return result;
131
139
  };
132
140
  module.exports.parser = 'tsx';