@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 +44 -35
- package/dist/cli.js +14 -9
- package/dist/codemods/__tests__/__fixtures__/transform-async-request.fixtures.js +154 -0
- package/dist/codemods/__tests__/transform-async-request.test.js +15 -0
- package/dist/codemods/index.js +22 -0
- package/dist/codemods/transform-async-request.cjs +140 -0
- package/dist/components/Codemod.js +62 -0
- package/dist/components/Header.js +11 -0
- package/dist/components/SDKWorkflow.js +69 -0
- package/dist/{scan.js → components/Scan.js} +2 -2
- package/dist/constants/versions.js +3 -0
- package/dist/util/detect-package-manager.js +22 -0
- package/dist/util/expandable-list.js +1 -1
- package/dist/util/get-clerk-version.js +22 -2
- package/package.json +18 -14
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
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
27
|
-
const [fromVersion, setFromVersion] = useState(_fromVersion);
|
|
37
|
+
const [fromVersion, setFromVersion] = useState(props.fromVersion);
|
|
28
38
|
const [fromVersionGuessAttempted, setFromVersionGuessAttempted] = useState(false);
|
|
29
|
-
|
|
30
|
-
const [
|
|
31
|
-
const [
|
|
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(
|
|
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
|
|
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)
|
|
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
|
-
|
|
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(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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 '
|
|
9
|
-
export
|
|
8
|
+
import ExpandableList from '../util/expandable-list.js';
|
|
9
|
+
export function Scan({
|
|
10
10
|
fromVersion,
|
|
11
11
|
toVersion,
|
|
12
12
|
sdks,
|
|
@@ -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(
|
|
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
|
|
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.
|
|
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": "^
|
|
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": "^
|
|
39
|
+
"ink": "^5.0.1",
|
|
38
40
|
"ink-big-text": "^2.0.0",
|
|
39
41
|
"ink-gradient": "^3.0.0",
|
|
40
|
-
"ink-link": "^
|
|
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
|
-
"
|
|
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"
|