@clerk/upgrade 1.0.9 → 1.1.0-snapshot.vb87a27f

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.
package/dist/app.js CHANGED
@@ -1,35 +1,44 @@
1
1
  import { MultiSelect, Select, TextInput } from '@inkjs/ui';
2
2
  import { Newline, Text } from 'ink';
3
- import BigText from 'ink-big-text';
4
- import Gradient from 'ink-gradient';
5
3
  import React, { useState } from 'react';
4
+ import { Header } from './components/Header.js';
5
+ import { Scan } from './components/Scan.js';
6
+ import { SDKWorkflow } from './components/SDKWorkflow.js';
6
7
  import SDKS from './constants/sdks.js';
7
- import Scan from './scan.js';
8
- import getClerkMajorVersion from './util/get-clerk-version.js';
8
+ import { getClerkMajorVersion } from './util/get-clerk-version.js';
9
9
  import guessFrameworks from './util/guess-framework.js';
10
- export default function App({
11
- _fromVersion,
12
- _toVersion,
13
- _sdk,
14
- _dir = false,
15
- _ignore = [],
16
- _yolo = false,
17
- noWarnings = false,
18
- disableTelemetry = false
19
- }) {
20
- const [yolo, setYolo] = useState(_yolo);
21
- const [sdks, setSdks] = useState(_sdk ? [_sdk] : []);
10
+
11
+ /**
12
+ * Main CLI application component for handling Clerk SDK upgrades.
13
+ *
14
+ * @param {Object} props - The `props` object.
15
+ * @param {string} [props.dir] - The directory to scan for files.
16
+ * @param {boolean} [props.disableTelemetry=false] - Flag to disable telemetry.
17
+ * @param {string} [props.fromVersion] - The current version of the SDK.
18
+ * @param {Array<string>} [props.ignore] - List of files or directories to ignore.
19
+ * @param {boolean} [props.noWarnings=false] - Flag to disable warnings.
20
+ * @param {string} [props.sdk] - The SDK to upgrade.
21
+ * @param {string} [props.toVersion] - The target version of the SDK.
22
+ * @param {boolean} [props.yolo=false] - Flag to enable YOLO mode.
23
+ *
24
+ * @returns {JSX.Element} The rendered component.
25
+ */
26
+ export default function App(props) {
27
+ const {
28
+ noWarnings = false,
29
+ disableTelemetry = false
30
+ } = props;
31
+ const [yolo, setYolo] = useState(props.yolo ?? false);
32
+ const [sdks, setSdks] = useState(props.sdk ? [props.sdk] : []);
22
33
  const [sdkGuesses, setSdkGuesses] = useState([]);
23
34
  const [sdkGuessConfirmed, setSdkGuessConfirmed] = useState(false);
24
35
  const [sdkGuessAttempted, setSdkGuessAttempted] = useState(false);
25
36
  // See comments below, can be enabled on next major
26
- // eslint-disable-next-line no-unused-vars
27
- const [fromVersion, setFromVersion] = useState(_fromVersion);
37
+ const [fromVersion, setFromVersion] = useState(props.fromVersion);
28
38
  const [fromVersionGuessAttempted, setFromVersionGuessAttempted] = useState(false);
29
- // eslint-disable-next-line no-unused-vars
30
- const [toVersion, setToVersion] = useState(_toVersion);
31
- const [dir, setDir] = useState(_dir);
32
- const [ignore, setIgnore] = useState(_ignore);
39
+ const [toVersion, setToVersion] = useState(props.toVersion);
40
+ const [dir, setDir] = useState(props.dir);
41
+ const [ignore, setIgnore] = useState(props.ignore);
33
42
  const [configComplete, setConfigComplete] = useState(false);
34
43
  const [configVerified, setConfigVerified] = useState(false);
35
44
  const [uuid, setUuid] = useState();
@@ -39,6 +48,14 @@ export default function App({
39
48
  setYolo(false);
40
49
  }
41
50
 
51
+ // Handle the individual SDK upgrade
52
+ if (sdks.length === 1) {
53
+ return /*#__PURE__*/React.createElement(SDKWorkflow, {
54
+ packageManager: props.packageManager,
55
+ sdk: sdks[0]
56
+ });
57
+ }
58
+
42
59
  // We try to guess which SDK they are using
43
60
  if (isEmpty(sdks) && isEmpty(sdkGuesses) && !sdkGuessAttempted) {
44
61
  if (!dir) {
@@ -48,17 +65,14 @@ export default function App({
48
65
  guesses,
49
66
  _uuid
50
67
  } = guessFrameworks(dir, disableTelemetry);
51
- console.log({
52
- guesses,
53
- _uuid
54
- });
68
+ // console.log({ guesses, _uuid });
55
69
  setUuid(_uuid);
56
70
  setSdkGuesses(guesses);
57
71
  setSdkGuessAttempted(true);
58
72
  }
59
73
 
60
74
  // We try to guess which version of Clerk they are using
61
- if (!fromVersion && !fromVersionGuess && !fromVersionGuessAttempted) {
75
+ if (isEmpty(sdks) && !fromVersion && !fromVersionGuess && !fromVersionGuessAttempted) {
62
76
  fromVersionGuess = getClerkMajorVersion();
63
77
  setFromVersionGuessAttempted(true);
64
78
  }
@@ -76,14 +90,9 @@ export default function App({
76
90
  color: "red"
77
91
  }, "You are already on version ", toVersion, ", so there's no need to migrate!");
78
92
  }
79
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Gradient, {
80
- name: "vice"
81
- }, /*#__PURE__*/React.createElement(BigText, {
82
- text: "Clerk Upgrade",
83
- font: "tiny"
84
- })), !configComplete && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, /*#__PURE__*/React.createElement(Text, {
93
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Header, null), !configComplete && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, /*#__PURE__*/React.createElement(Text, {
85
94
  color: "blue"
86
- }, "Hello friend!"), " We're excited to help you upgrade Clerk", fromVersion ? ` from ${fromVersion}` : '', toVersion ? ` to ${toVersion}` : '', ". Before we get started, a couple questions..."), /*#__PURE__*/React.createElement(Newline, null)), isEmpty(sdks) && !isEmpty(sdkGuesses) && !sdkGuessConfirmed && /*#__PURE__*/React.createElement(React.Fragment, null, sdkGuesses.length > 1 ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, "It looks like you are using the following Clerk SDKs in your project:"), sdkGuesses.map(guess => /*#__PURE__*/React.createElement(Text, {
95
+ }, "Hello friend!"), " We're excited to help you upgrade Clerk modules. Before we get started, a couple questions..."), /*#__PURE__*/React.createElement(Newline, null)), isEmpty(sdks) && !isEmpty(sdkGuesses) && !sdkGuessConfirmed && /*#__PURE__*/React.createElement(React.Fragment, null, sdkGuesses.length > 1 ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, "It looks like you are using the following Clerk SDKs in your project:"), sdkGuesses.map(guess => /*#__PURE__*/React.createElement(Text, {
87
96
  key: guess.value
88
97
  }, ' ', "- ", guess.label)), /*#__PURE__*/React.createElement(Text, null, "Is that right?")) : /*#__PURE__*/React.createElement(Text, null, "It looks like you are using the \"", sdkGuesses[0].label, "\" Clerk SDK in your project. Is that right?"), /*#__PURE__*/React.createElement(Select, {
89
98
  options: [{
@@ -106,7 +115,7 @@ export default function App({
106
115
  options: SDKS,
107
116
  onSubmit: value => setSdks(value),
108
117
  visibleOptionCount: SDKS.length
109
- })), !isEmpty(sdks) > 0 && fromVersion && toVersion && !dir && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, "Where would you like for us to scan for files in your project?"), /*#__PURE__*/React.createElement(Text, {
118
+ })), !isEmpty(sdks) && fromVersion && toVersion && !dir && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, "Where would you like for us to scan for files in your project?"), /*#__PURE__*/React.createElement(Text, {
110
119
  color: "gray"
111
120
  }, "(globstar syntax supported)"), /*#__PURE__*/React.createElement(TextInput, {
112
121
  defaultValue: "**/*",
package/dist/cli.js CHANGED
@@ -13,7 +13,8 @@ const cli = meow(`
13
13
  --to Major version number you're upgrading to
14
14
  --sdk Name of the SDK you're upgrading
15
15
  --dir Directory you'd like to scan for files
16
- --ignore Any files or directories you'd like to ignore
16
+ --ignore Any files or directories you'd like to ignore
17
+ --packageManager The package manager you're using (npm, yarn, pnpm)
17
18
  --noWarnings Do not print warnings, only items that must be fixed
18
19
  --disableTelemetry Do not send anonymous usage telemetry
19
20
 
@@ -42,6 +43,9 @@ const cli = meow(`
42
43
  type: 'string',
43
44
  isMultiple: true
44
45
  },
46
+ packageManager: {
47
+ type: 'string'
48
+ },
45
49
  yolo: {
46
50
  type: 'boolean'
47
51
  },
@@ -53,15 +57,16 @@ const cli = meow(`
53
57
  }
54
58
  }
55
59
  });
56
- render( /*#__PURE__*/React.createElement(App, {
57
- _fromVersion: cli.flags.from,
58
- _toVersion: cli.flags.to,
59
- _sdk: cli.flags.sdk,
60
- _dir: cli.flags.dir,
61
- _ignore: cli.flags.ignore,
62
- _yolo: cli.flags.yolo,
60
+ render(/*#__PURE__*/React.createElement(App, {
61
+ dir: cli.flags.dir,
62
+ disableTelemetry: cli.flags.disableTelemetry,
63
+ fromVersion: cli.flags.from,
64
+ ignore: cli.flags.ignore,
63
65
  noWarnings: cli.flags.noWarnings,
64
- disableTelemetry: cli.flags.disableTelemetry
66
+ packageManager: cli.flags.packageManager,
67
+ sdk: cli.flags.sdk,
68
+ toVersion: cli.flags.to,
69
+ yolo: cli.flags.yolo
65
70
  })
66
71
  // if having issues with errors being swallowed, uncomment this
67
72
  // { debug: true },
@@ -0,0 +1,154 @@
1
+ export const fixtures = [{
2
+ name: 'Basic await transform',
3
+ source: `
4
+ import { auth } from '@clerk/nextjs/server';
5
+
6
+ export function any() {
7
+ const { userId } = auth();
8
+ return new Response(JSON.stringify({ userId }));
9
+ }
10
+
11
+ export function another() {
12
+ return true;
13
+ }
14
+ `,
15
+ output: `
16
+ import { auth } from '@clerk/nextjs/server';
17
+
18
+ export async function any() {
19
+ const { userId } = await auth();
20
+ return new Response(JSON.stringify({ userId }));
21
+ }
22
+
23
+ export function another() {
24
+ return true;
25
+ }
26
+ `
27
+ }, {
28
+ name: 'auth().protect -> await auth.protect()',
29
+ source: `
30
+ import { auth } from '@clerk/nextjs/server';
31
+
32
+ export function GET() {
33
+ const { userId } = auth().protect(
34
+ (has) => has({ role: 'admin' }) || has({ role: 'org:editor' }),
35
+ );
36
+ return new Response(JSON.stringify({ userId }));
37
+ }
38
+ `,
39
+ output: `
40
+ import { auth } from '@clerk/nextjs/server';
41
+
42
+ export async function GET() {
43
+ const { userId } = await auth.protect(
44
+ (has) => has({ role: 'admin' }) || has({ role: 'org:editor' }),
45
+ );
46
+ return new Response(JSON.stringify({ userId }));
47
+ }
48
+ `
49
+ }, {
50
+ name: 'Basic clerkMiddleware()',
51
+ source: `
52
+ import {
53
+ clerkMiddleware,
54
+ createRouteMatcher
55
+ } from "@clerk/nextjs/server"
56
+
57
+ const isPublicRoute = createRouteMatcher(["/", "/contact"])
58
+
59
+ export default clerkMiddleware((auth, req) => {
60
+ auth().protect(); // for any other route, require auth
61
+ })
62
+ `,
63
+ output: `
64
+ import {
65
+ clerkMiddleware,
66
+ createRouteMatcher
67
+ } from "@clerk/nextjs/server"
68
+
69
+ const isPublicRoute = createRouteMatcher(["/", "/contact"])
70
+
71
+ export default clerkMiddleware(async (auth, req) => {
72
+ await auth.protect(); // for any other route, require auth
73
+ })
74
+ `
75
+ }, {
76
+ name: 'Complex clerkMiddleware()',
77
+ source: `
78
+ import {
79
+ clerkMiddleware,
80
+ createRouteMatcher
81
+ } from "@clerk/nextjs/server"
82
+ import createMiddleware from "next-intl/middleware"
83
+
84
+ const intlMiddleware = createMiddleware({
85
+ locales: ["en", "de"],
86
+ defaultLocale: "en",
87
+ })
88
+
89
+ const isDashboardRoute = createRouteMatcher(["/dashboard(.*)"])
90
+
91
+ export default clerkMiddleware((auth, request) => {
92
+ if (isDashboardRoute(request)) auth().protect()
93
+
94
+ return intlMiddleware(request)
95
+ })
96
+ `,
97
+ output: `
98
+ import {
99
+ clerkMiddleware,
100
+ createRouteMatcher
101
+ } from "@clerk/nextjs/server"
102
+ import createMiddleware from "next-intl/middleware"
103
+
104
+ const intlMiddleware = createMiddleware({
105
+ locales: ["en", "de"],
106
+ defaultLocale: "en",
107
+ })
108
+
109
+ const isDashboardRoute = createRouteMatcher(["/dashboard(.*)"])
110
+
111
+ export default clerkMiddleware(async (auth, request) => {
112
+ if (isDashboardRoute(request)) await auth.protect()
113
+
114
+ return intlMiddleware(request)
115
+ })
116
+ `
117
+ }, {
118
+ name: 'Complex clerkMiddleware() with protect being destructured from auth()',
119
+ source: `
120
+ import { clerkMiddleware } from '@clerk/nextjs/server';
121
+
122
+ export default clerkMiddleware(
123
+ (auth, req) => {
124
+ const { protect, sessionClaims } = auth();
125
+
126
+ protect();
127
+ },
128
+ );
129
+ `,
130
+ output: `
131
+ import { clerkMiddleware } from '@clerk/nextjs/server';
132
+
133
+ export default clerkMiddleware(
134
+ async (auth, req) => {
135
+ const {
136
+ sessionClaims
137
+ } = auth();
138
+
139
+ await auth.protect();
140
+ },
141
+ );
142
+ `
143
+ }, {
144
+ name: 'Does not transform other imports',
145
+ source: `
146
+ import { auth } from '@some/other/module';
147
+
148
+ export function any() {
149
+ const { IBauthed } = auth();
150
+ return new Response(JSON.stringify({ IBauthed }));
151
+ }
152
+ `,
153
+ output: ''
154
+ }];
@@ -0,0 +1,15 @@
1
+ import { applyTransform } from 'jscodeshift/dist/testUtils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import transformer from '../transform-async-request.cjs';
4
+ import { fixtures } from './__fixtures__/transform-async-request.fixtures';
5
+ describe('transform-async-request', () => {
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
+ });
@@ -0,0 +1,22 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { globby } from 'globby';
4
+ import { run } from 'jscodeshift/src/Runner.js';
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ export async function runCodemod(transform = 'transform-async-request', glob, options) {
7
+ const resolvedPath = resolve(__dirname, `${transform}.cjs`);
8
+ const paths = await globby(glob, {
9
+ 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)+',
10
+ // common image files
11
+ '**/*.(mp4|mkv|wmv|m4v|mov|avi|flv|webm|flac|mka|m4a|aac|ogg)+',
12
+ // common video files] }).then(files => {
13
+ '**/*.(css|scss|sass|less|styl)+' // common style files
14
+ ]
15
+ });
16
+ return await run(resolvedPath, paths ?? [], {
17
+ dry: false,
18
+ ...options,
19
+ // we must silence stdout to prevent output from interfering with ink CLI
20
+ silent: true
21
+ });
22
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Transforms the source code by modifying calls to `auth().protect()` and ensuring
3
+ * that calls to `auth` are awaited within function declarations.
4
+ *
5
+ * @param {import('jscodeshift').FileInfo} FileInfo - The parameters object
6
+ * @param {import('jscodeshift').API} api - The API object provided by jscodeshift
7
+ * @param {Object} _options - Additional options (unused)
8
+ * @returns {string|undefined} - The transformed source code if modifications were made, otherwise undefined
9
+ */
10
+ module.exports = function transformAsyncRequest({
11
+ path,
12
+ source
13
+ }, {
14
+ jscodeshift: j
15
+ }, _options) {
16
+ const root = j(source);
17
+ let dirtyFlag = false;
18
+ console.log('path', path);
19
+
20
+ // Short-circuit if the import from '@clerk/nextjs/server' is not found
21
+ if (root.find(j.ImportDeclaration, {
22
+ source: {
23
+ value: '@clerk/nextjs/server'
24
+ }
25
+ }).size() === 0) {
26
+ return undefined;
27
+ }
28
+
29
+ // Find all function expressions
30
+ root.find(j.FunctionDeclaration).forEach(func => {
31
+ let authCallFound = false;
32
+ let authProtectFound = false;
33
+
34
+ // Find the auth().protect call and transform it
35
+ j(func).find(j.CallExpression, {
36
+ callee: {
37
+ type: 'MemberExpression',
38
+ object: {
39
+ type: 'CallExpression',
40
+ callee: {
41
+ name: 'auth'
42
+ }
43
+ },
44
+ property: {
45
+ name: 'protect'
46
+ }
47
+ }
48
+ }).forEach(authProtectPath => {
49
+ // Replace auth().protect with await auth.protect
50
+ authProtectPath.node.callee = j.memberExpression(j.identifier('auth'), j.identifier('protect'));
51
+ authProtectPath.replace(j.awaitExpression(authProtectPath.node));
52
+ authProtectFound = true;
53
+ dirtyFlag = true;
54
+ });
55
+
56
+ // Find the auth() call and transform it
57
+ j(func).find(j.CallExpression, {
58
+ callee: {
59
+ name: 'auth'
60
+ }
61
+ }).forEach(authPath => {
62
+ authCallFound = true;
63
+ // Ensure the call to 'auth' is awaited
64
+ if (!j.AwaitExpression.check(authPath.parent.node)) {
65
+ j(authPath).replaceWith(j.awaitExpression(authPath.node));
66
+ dirtyFlag = true;
67
+ }
68
+ });
69
+
70
+ // Add 'async' keyword to the function declaration
71
+ if (!func.node.async && (authCallFound || authProtectFound)) {
72
+ func.node.async = true;
73
+ dirtyFlag = true;
74
+ }
75
+ });
76
+
77
+ // Find the default export which is a call to clerkMiddleware
78
+ root.find(j.ExportDefaultDeclaration).forEach(path => {
79
+ const declaration = path.node.declaration;
80
+ if (j.CallExpression.check(declaration) && j.Identifier.check(declaration.callee) && declaration.callee.name === 'clerkMiddleware') {
81
+ const middlewareFunction = declaration.arguments[0];
82
+ if (j.FunctionExpression.check(middlewareFunction) || j.ArrowFunctionExpression.check(middlewareFunction)) {
83
+ // Add async keyword to the function
84
+ if (!middlewareFunction.async) {
85
+ middlewareFunction.async = true;
86
+ dirtyFlag = true;
87
+ }
88
+
89
+ // Find auth().protect()
90
+ j(middlewareFunction.body).find(j.CallExpression, {
91
+ callee: {
92
+ type: 'MemberExpression',
93
+ object: {
94
+ type: 'CallExpression',
95
+ callee: {
96
+ type: 'Identifier',
97
+ name: 'auth'
98
+ }
99
+ },
100
+ property: {
101
+ type: 'Identifier',
102
+ name: 'protect'
103
+ }
104
+ }
105
+ }).forEach(callPath => {
106
+ const memberExpr = callPath.node.callee;
107
+ if (j.MemberExpression.check(memberExpr) && j.CallExpression.check(memberExpr.object)) {
108
+ // Transform auth().protect() to await auth.protect()
109
+ callPath.replace(j.awaitExpression(j.callExpression(j.memberExpression(j.identifier('auth'), j.identifier('protect')), [])));
110
+ dirtyFlag = true;
111
+ }
112
+ });
113
+
114
+ // Find the destructuring assignment and modify it
115
+ j(middlewareFunction.body).find(j.VariableDeclarator).forEach(varPath => {
116
+ const id = varPath.node.id;
117
+ const init = varPath.node.init;
118
+ if (j.ObjectPattern.check(id) && j.CallExpression.check(init) && j.Identifier.check(init.callee) && init.callee.name === 'auth') {
119
+ // Remove 'protect' from destructuring
120
+ id.properties = id.properties.filter(prop => {
121
+ return !(j.Identifier.check(prop.key) && prop.key.name === 'protect');
122
+ });
123
+ dirtyFlag = true;
124
+ }
125
+ });
126
+
127
+ // Replace protect() call with await auth.protect()
128
+ j(middlewareFunction.body).find(j.ExpressionStatement).forEach(exprPath => {
129
+ const expr = exprPath.node.expression;
130
+ if (j.CallExpression.check(expr) && j.Identifier.check(expr.callee) && expr.callee.name === 'protect') {
131
+ exprPath.replace(j.expressionStatement(j.awaitExpression(j.callExpression(j.memberExpression(j.identifier('auth'), j.identifier('protect')), []))));
132
+ dirtyFlag = true;
133
+ }
134
+ });
135
+ }
136
+ }
137
+ });
138
+ return dirtyFlag ? root.toSource() : undefined;
139
+ };
140
+ module.exports.parser = 'tsx';
@@ -0,0 +1,62 @@
1
+ import { TextInput } from '@inkjs/ui';
2
+ import { Newline, Text } from 'ink';
3
+ import React, { useEffect, useState } from 'react';
4
+ import { runCodemod } from '../codemods/index.js';
5
+
6
+ /**
7
+ * Codemod component that allows users to run a codemod transformation on their project files.
8
+ *
9
+ * @param {Object} props - The properties object.
10
+ * @param {Function} props.callback - The callback function to be called after the codemod is run.
11
+ * @param {string} [props.dir] - The directory to scan for files in the project.
12
+ * @param {string} props.sdk - The SDK name to be used in the codemod.
13
+ * @param {string} props.transform - The transformation to be applied by the codemod.
14
+ *
15
+ * @returns {JSX.Element} The rendered Codemod component.
16
+ */
17
+ export function Codemod(props) {
18
+ const {
19
+ callback,
20
+ sdk,
21
+ transform
22
+ } = props;
23
+ const [error, setError] = useState();
24
+ const [result, setResult] = useState(null);
25
+ const [glob, setGlob] = useState(props.glob);
26
+ useEffect(() => {
27
+ if (!glob) {
28
+ return;
29
+ }
30
+ runCodemod(transform, glob).then(res => {
31
+ setResult(res);
32
+ }).catch(err => {
33
+ setError(err);
34
+ }).finally(() => {
35
+ callback(true);
36
+ });
37
+ }, [glob]);
38
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, "Where would you like for us to scan for files in your project?"), /*#__PURE__*/React.createElement(Text, {
39
+ color: "gray"
40
+ }, "(globstar syntax supported)"), glob ? /*#__PURE__*/React.createElement(Text, null, glob.toString()) : /*#__PURE__*/React.createElement(TextInput, {
41
+ defaultValue: "**/*",
42
+ onSubmit: val => {
43
+ setGlob(val.split(/[ ,]/));
44
+ }
45
+ })), !result && !error && glob && /*#__PURE__*/React.createElement(Text, null, "Running ", /*#__PURE__*/React.createElement(Text, {
46
+ bold: true
47
+ }, "@clerk/", sdk), " codemod... ", transform), result && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, "Running ", /*#__PURE__*/React.createElement(Text, {
48
+ bold: true
49
+ }, "@clerk/", sdk), " codemod... ", transform, " complete!"), /*#__PURE__*/React.createElement(Newline, null), /*#__PURE__*/React.createElement(Text, {
50
+ bold: true
51
+ }, "Results:"), /*#__PURE__*/React.createElement(Text, {
52
+ color: "red"
53
+ }, result.error ?? 0, " errors"), /*#__PURE__*/React.createElement(Text, {
54
+ color: "green"
55
+ }, result.ok ?? 0, " ok"), /*#__PURE__*/React.createElement(Text, {
56
+ color: "yellow"
57
+ }, result.skip ?? 0, " skipped"), /*#__PURE__*/React.createElement(Text, {
58
+ color: "gray"
59
+ }, result.nochange ?? 0, " unmodified"), result.timeElapsed && /*#__PURE__*/React.createElement(Text, null, "Time elapsed: ", result.timeElapsed)), error && /*#__PURE__*/React.createElement(Text, {
60
+ color: "red"
61
+ }, error.message));
62
+ }
@@ -0,0 +1,11 @@
1
+ import BigText from 'ink-big-text';
2
+ import Gradient from 'ink-gradient';
3
+ import React from 'react';
4
+ export function Header() {
5
+ return /*#__PURE__*/React.createElement(Gradient, {
6
+ name: "vice"
7
+ }, /*#__PURE__*/React.createElement(BigText, {
8
+ text: "Clerk Upgrade",
9
+ font: "tiny"
10
+ }));
11
+ }
@@ -0,0 +1,69 @@
1
+ import { Select } from '@inkjs/ui';
2
+ import { execa } from 'execa';
3
+ import { Newline, Text } from 'ink';
4
+ import React, { useEffect, useState } from 'react';
5
+ import { getUpgradeCommand } from '../util/detect-package-manager.js';
6
+ import { getClerkSdkVersion } from '../util/get-clerk-version.js';
7
+ import { Codemod } from './Codemod.js';
8
+ import { Header } from './Header.js';
9
+ export function SDKWorkflow(props) {
10
+ const {
11
+ packageManager,
12
+ sdk
13
+ } = props;
14
+ const [done, setDone] = useState(false);
15
+ const [upgradeComplete, setUpgradeComplete] = useState(false);
16
+ const version = getClerkSdkVersion(sdk);
17
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Header, null), version === 5 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(UpgradeCommand, {
18
+ callback: setUpgradeComplete,
19
+ packageManager: packageManager,
20
+ sdk: sdk
21
+ }), upgradeComplete ? /*#__PURE__*/React.createElement(Codemod, {
22
+ callback: setDone,
23
+ sdk: sdk,
24
+ transform: "transform-async-request"
25
+ }) : null), !done && version === 6 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, "Looks like you are already on the latest version of ", /*#__PURE__*/React.createElement(Text, {
26
+ bold: true
27
+ }, "@clerk/", sdk), ". Would you like to run the associated codemod?."), upgradeComplete ? /*#__PURE__*/React.createElement(Codemod, {
28
+ sdk: sdk,
29
+ callback: setDone
30
+ }) : /*#__PURE__*/React.createElement(Select, {
31
+ options: [{
32
+ label: 'yes',
33
+ value: 'yes'
34
+ }, {
35
+ label: 'no',
36
+ value: 'no'
37
+ }],
38
+ onChange: value => {
39
+ if (value === 'yes') {
40
+ setUpgradeComplete(true);
41
+ } else {
42
+ setDone(true);
43
+ }
44
+ }
45
+ })), done && /*#__PURE__*/React.createElement(Text, null, "You are done!"));
46
+ }
47
+ function UpgradeCommand({
48
+ sdk,
49
+ callback
50
+ }) {
51
+ const [result, setResult] = useState();
52
+ const [error, setError] = useState();
53
+ const command = getUpgradeCommand(sdk);
54
+ useEffect(() => {
55
+ execa({
56
+ shell: true
57
+ })`${command}`.then(res => {
58
+ setResult(res);
59
+ callback(true);
60
+ }).catch(err => {
61
+ setError(err);
62
+ });
63
+ }, [command]);
64
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, "Running upgrade command: ", command), result && /*#__PURE__*/React.createElement(Text, {
65
+ color: "green"
66
+ }, "Upgrade complete!"), error && /*#__PURE__*/React.createElement(Text, {
67
+ color: "red"
68
+ }, "Upgrade failed!"), /*#__PURE__*/React.createElement(Newline, null));
69
+ }
@@ -5,8 +5,8 @@ import indexToPosition from 'index-to-position';
5
5
  import { Newline, Text } from 'ink';
6
6
  import path from 'path';
7
7
  import React, { useEffect, useState } from 'react';
8
- import ExpandableList from './util/expandable-list.js';
9
- export default function Scan({
8
+ import ExpandableList from '../util/expandable-list.js';
9
+ export function Scan({
10
10
  fromVersion,
11
11
  toVersion,
12
12
  sdks,
@@ -1,4 +1,7 @@
1
1
  export default [{
2
+ label: 'Next',
3
+ value: 'next'
4
+ }, {
2
5
  label: 'Core 2',
3
6
  value: 'core-2'
4
7
  }, {
@@ -0,0 +1,22 @@
1
+ import { existsSync } from 'fs';
2
+ export function detectPackageManager() {
3
+ if (existsSync('package-lock.json')) {
4
+ return 'npm';
5
+ } else if (existsSync('yarn.lock')) {
6
+ return 'yarn';
7
+ } else if (existsSync('pnpm-lock.yaml')) {
8
+ return 'pnpm';
9
+ } else {
10
+ return 'npm';
11
+ }
12
+ }
13
+ export function getUpgradeCommand(sdk, packageManager) {
14
+ switch (packageManager || detectPackageManager()) {
15
+ case 'yarn':
16
+ return `yarn add @clerk/${sdk}@latest`;
17
+ case 'pnpm':
18
+ return `pnpm add @clerk/${sdk}@latest`;
19
+ default:
20
+ return `npm install @clerk/${sdk}@latest`;
21
+ }
22
+ }
@@ -74,7 +74,7 @@ export default function ExpandableList({
74
74
  bottomRight: ' ',
75
75
  right: ''
76
76
  };
77
- memo.push( /*#__PURE__*/React.createElement(Box, {
77
+ memo.push(/*#__PURE__*/React.createElement(Box, {
78
78
  borderStyle: item.focused ? doubleBorderStyle : singleBorderStyle,
79
79
  flexDirection: "column",
80
80
  borderColor: item.focused ? 'blue' : 'white',
@@ -1,7 +1,27 @@
1
1
  import { readPackageSync } from 'read-pkg';
2
2
  import semverRegex from 'semver-regex';
3
- export default function getClerkMajorVersion() {
3
+ export function getClerkMajorVersion() {
4
4
  const pkg = readPackageSync();
5
5
  const clerk = pkg.dependencies.clerk;
6
- return clerk ? semverRegex.exec(clerk)[0][0] : false;
6
+ return clerk ? semverRegex().exec(clerk)[0][0] : false;
7
+ }
8
+ export function getClerkSdkVersion(sdk) {
9
+ const pkg = readPackageSync();
10
+ const clerkSdk = pkg.dependencies[`@clerk/${sdk}`];
11
+ if (!clerkSdk) {
12
+ return false;
13
+ }
14
+ try {
15
+ return getMajorVersion(clerkSdk.replace('^', '').replace('~', ''));
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+ function getMajorVersion(semver) {
21
+ const match = semver.match(semverRegex());
22
+ if (match) {
23
+ const [major] = match[0].split('.');
24
+ return parseInt(major, 10); // Return as an integer
25
+ }
26
+ throw new Error('Invalid semver string');
7
27
  }
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@clerk/upgrade",
3
- "version": "1.0.9",
3
+ "version": "1.1.0-snapshot.vb87a27f",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/clerk/javascript.git",
7
+ "directory": "packages/upgrade"
8
+ },
4
9
  "license": "MIT",
5
10
  "type": "module",
6
11
  "main": "dist/cli.js",
@@ -11,16 +16,12 @@
11
16
  "dist"
12
17
  ],
13
18
  "scripts": {
14
- "build": "npm run clean && NODE_ENV=production babel --out-dir=dist src --copy-files",
19
+ "build": "npm run clean && NODE_ENV=production babel --keep-file-extension --out-dir=dist src --copy-files",
15
20
  "clean": "del-cli dist/*",
16
- "dev": "babel --out-dir=dist --watch src --copy-files",
21
+ "dev": "babel --keep-file-extension --out-dir=dist --watch src --copy-files",
17
22
  "lint": "eslint src/",
18
- "lint:publint": "publint"
19
- },
20
- "repository": {
21
- "type": "git",
22
- "url": "git+https://github.com/clerk/javascript.git",
23
- "directory": "packages/upgrade"
23
+ "lint:publint": "publint",
24
+ "test": "vitest"
24
25
  },
25
26
  "babel": {
26
27
  "presets": [
@@ -28,16 +29,18 @@
28
29
  ]
29
30
  },
30
31
  "dependencies": {
31
- "@inkjs/ui": "^1.0.0",
32
+ "@inkjs/ui": "^2.0.0",
32
33
  "@jescalan/ink-markdown": "^2.0.0",
33
34
  "ejs": "3.1.10",
35
+ "execa": "9.4.1",
34
36
  "globby": "^14.0.1",
35
37
  "gray-matter": "^4.0.3",
36
38
  "index-to-position": "^0.1.2",
37
- "ink": "^4.4.1",
39
+ "ink": "^5.0.1",
38
40
  "ink-big-text": "^2.0.0",
39
41
  "ink-gradient": "^3.0.0",
40
- "ink-link": "^3.0.0",
42
+ "ink-link": "^4.1.0",
43
+ "jscodeshift": "^17.0.0",
41
44
  "marked": "^11.1.1",
42
45
  "meow": "^11.0.0",
43
46
  "react": "^18.3.1",
@@ -48,9 +51,10 @@
48
51
  "devDependencies": {
49
52
  "@babel/cli": "^7.24.7",
50
53
  "@babel/preset-react": "^7.24.7",
51
- "chalk": "^5.3.0",
54
+ "@types/jscodeshift": "^0.12.0",
52
55
  "del-cli": "^5.1.0",
53
- "eslint-config-custom": "*"
56
+ "eslint-config-custom": "*",
57
+ "vitest": "^2.1.3"
54
58
  },
55
59
  "engines": {
56
60
  "node": ">=18.17.0"