auth 1.1.1 → 1.2.1
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/README.md +30 -21
- package/commands/add.js +66 -32
- package/commands/secret.js +1 -1
- package/lib/apple-gen-secret.js +101 -0
- package/lib/meta.js +28 -2
- package/lib/write-env.js +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -68,65 +68,72 @@ We have an [OpenCollective](https://opencollective.com/nextauth) for companies a
|
|
|
68
68
|
<tr>
|
|
69
69
|
<td align="center" valign="top">
|
|
70
70
|
<a href="https://clerk.com?utm_source=sponsorship&utm_medium=github&utm_campaign=authjs&utm_content=sponsor" target="_blank">
|
|
71
|
-
<img
|
|
71
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/49538330?s=200&v=4" alt="Clerk Logo" />
|
|
72
72
|
</a><br />
|
|
73
73
|
<div>Clerk</div>
|
|
74
74
|
<sub>💵</sub>
|
|
75
75
|
</td>
|
|
76
|
+
<td align="center" valign="top">
|
|
77
|
+
<a href="https://a0.to/signup/nextauthjs" target="_blank">
|
|
78
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/2824157?v=4" alt="Auth0 Logo" />
|
|
79
|
+
</a><br />
|
|
80
|
+
<div>Auth0</div>
|
|
81
|
+
<sub>💵</sub>
|
|
82
|
+
</td>
|
|
76
83
|
<td align="center" valign="top">
|
|
77
84
|
<a href="https://fusionauth.io" target="_blank">
|
|
78
|
-
<img
|
|
85
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/41974756?s=200&v=4" alt="FusionAuth Logo" />
|
|
79
86
|
</a><br />
|
|
80
87
|
<div>FusionAuth</div>
|
|
81
88
|
<sub>💵</sub>
|
|
82
89
|
</td>
|
|
83
90
|
<td align="center" valign="top">
|
|
84
91
|
<a href="https://www.beyondidentity.com" target="_blank">
|
|
85
|
-
<img
|
|
92
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/69811361?s=200&v=4" alt="Beyond Identity Logo" />
|
|
86
93
|
</a><br />
|
|
87
94
|
<div>Beyond Identity</div>
|
|
88
95
|
<sub>💵</sub>
|
|
89
96
|
</td>
|
|
90
97
|
<td align="center" valign="top">
|
|
91
98
|
<a href="https://stytch.com" target="_blank">
|
|
92
|
-
<img
|
|
99
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/69983493?s=200&v=4" alt="Stytch Logo" />
|
|
93
100
|
</a><br />
|
|
94
101
|
<div>Stytch</div>
|
|
95
102
|
<sub>💵</sub>
|
|
96
103
|
</td>
|
|
97
104
|
<td align="center" valign="top">
|
|
98
105
|
<a href="https://prisma.io" target="_blank">
|
|
99
|
-
<img
|
|
106
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/17219288?s=200&v=4" alt="Prisma Logo" />
|
|
100
107
|
</a><br />
|
|
101
108
|
<div>Prisma</div>
|
|
102
109
|
<sub>💵</sub>
|
|
103
110
|
</td>
|
|
111
|
+
</tr>
|
|
112
|
+
<tr>
|
|
104
113
|
<td align="center" valign="top">
|
|
105
114
|
<a href="https://lowdefy.com" target="_blank">
|
|
106
|
-
<img
|
|
115
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/47087496?s=200&v=4" alt="Lowdefy Logo" />
|
|
107
116
|
</a><br />
|
|
108
117
|
<div>Lowdefy</div>
|
|
109
118
|
<sub>💵</sub>
|
|
110
119
|
</td>
|
|
111
120
|
<td align="center" valign="top">
|
|
112
121
|
<a href="https://www.descope.com" target="_blank">
|
|
113
|
-
<img
|
|
122
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/97479186?s=200&v=4" alt="Descope Logo" />
|
|
114
123
|
</a><br />
|
|
115
124
|
<div>Descope</div>
|
|
116
125
|
<sub>💵</sub>
|
|
117
126
|
</td>
|
|
118
|
-
</tr>
|
|
119
|
-
<tr>
|
|
120
127
|
<td align="center" valign="top">
|
|
121
128
|
<a href="https://badass.dev" target="_blank">
|
|
122
|
-
<img
|
|
129
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/136839242?v=4" alt="Badass Courses Logo" />
|
|
123
130
|
</a><br />
|
|
124
131
|
<div>Badass Courses</div>
|
|
125
132
|
<sub>💵</sub>
|
|
126
133
|
</td>
|
|
127
134
|
<td align="center" valign="top">
|
|
128
135
|
<a href="https://github.com/encoredev/encore" target="_blank">
|
|
129
|
-
<img
|
|
136
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/50438175?v=4" alt="Encore Logo" />
|
|
130
137
|
</a><br />
|
|
131
138
|
<div>Encore</div>
|
|
132
139
|
<sub>💵</sub>
|
|
@@ -139,29 +146,31 @@ We have an [OpenCollective](https://opencollective.com/nextauth) for companies a
|
|
|
139
146
|
<sub>💵</sub>
|
|
140
147
|
</td>
|
|
141
148
|
<td align="center" valign="top">
|
|
142
|
-
<a href="https://
|
|
143
|
-
<img
|
|
149
|
+
<a href="https://www.netlight.com/" target="_blank">
|
|
150
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/1672348?s=200&v=4" alt="Netlight logo" />
|
|
144
151
|
</a><br />
|
|
145
|
-
<div>
|
|
152
|
+
<div>Netlight</div>
|
|
146
153
|
<sub>☁️</sub>
|
|
147
154
|
</td>
|
|
155
|
+
</tr>
|
|
156
|
+
<tr>
|
|
148
157
|
<td align="center" valign="top">
|
|
149
|
-
<a href="https://
|
|
150
|
-
<img
|
|
158
|
+
<a href="https://checklyhq.com" target="_blank">
|
|
159
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/25982255?s=200&v=4" alt="Checkly Logo" />
|
|
151
160
|
</a><br />
|
|
152
|
-
<div>
|
|
161
|
+
<div>Checkly</div>
|
|
153
162
|
<sub>☁️</sub>
|
|
154
163
|
</td>
|
|
155
164
|
<td align="center" valign="top">
|
|
156
|
-
<a href="https://
|
|
157
|
-
<img
|
|
165
|
+
<a href="https://superblog.ai/" target="_blank">
|
|
166
|
+
<img height="96" src="https://d33wubrfki0l68.cloudfront.net/cdc4a3833bd878933fcc131655878dbf226ac1c5/10cd6/images/logo_bolt_small.png" alt="superblog Logo" />
|
|
158
167
|
</a><br />
|
|
159
|
-
<div>
|
|
168
|
+
<div>superblog</div>
|
|
160
169
|
<sub>☁️</sub>
|
|
161
170
|
</td>
|
|
162
171
|
<td align="center" valign="top">
|
|
163
172
|
<a href="https://vercel.com" target="_blank">
|
|
164
|
-
<img
|
|
173
|
+
<img height="96" src="https://avatars.githubusercontent.com/u/14985020?s=200&v=4" alt="Vercel Logo" />
|
|
165
174
|
</a><br />
|
|
166
175
|
<div>Vercel</div>
|
|
167
176
|
<sub>☁️</sub>
|
package/commands/add.js
CHANGED
|
@@ -3,12 +3,33 @@
|
|
|
3
3
|
import * as y from "yoctocolors"
|
|
4
4
|
import open from "open"
|
|
5
5
|
import clipboard from "clipboardy"
|
|
6
|
-
import { select, input, password } from "@inquirer/prompts"
|
|
6
|
+
import { select, input, password, number } from "@inquirer/prompts"
|
|
7
7
|
import { requireFramework } from "../lib/detect.js"
|
|
8
8
|
import { updateEnvFile } from "../lib/write-env.js"
|
|
9
9
|
import { providers, frameworks } from "../lib/meta.js"
|
|
10
|
-
import { secret } from "./index.js"
|
|
11
10
|
import { link, markdownToAnsi } from "../lib/markdown.js"
|
|
11
|
+
import { appleGenSecret } from "../lib/apple-gen-secret.js"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} label
|
|
15
|
+
* @param {string} [defaultValue]
|
|
16
|
+
*/
|
|
17
|
+
async function promptInput(label, defaultValue) {
|
|
18
|
+
return input({
|
|
19
|
+
message: `Paste ${y.magenta(label)}:`,
|
|
20
|
+
validate: (value) => !!value,
|
|
21
|
+
default: defaultValue,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @param {string} label */
|
|
26
|
+
async function promptPassword(label) {
|
|
27
|
+
return password({
|
|
28
|
+
message: `Paste ${y.magenta(label)}:`,
|
|
29
|
+
mask: true,
|
|
30
|
+
validate: (value) => !!value,
|
|
31
|
+
})
|
|
32
|
+
}
|
|
12
33
|
|
|
13
34
|
const choices = Object.entries(providers)
|
|
14
35
|
.filter(([, { setupUrl }]) => !!setupUrl)
|
|
@@ -17,19 +38,19 @@ const choices = Object.entries(providers)
|
|
|
17
38
|
/** @param {string | undefined} providerId */
|
|
18
39
|
export async function action(providerId) {
|
|
19
40
|
try {
|
|
20
|
-
|
|
21
|
-
providerId
|
|
41
|
+
const pid =
|
|
42
|
+
providerId ??
|
|
43
|
+
(await select({
|
|
22
44
|
message: "What provider do you want to set up?",
|
|
23
45
|
choices: choices,
|
|
24
|
-
})
|
|
25
|
-
}
|
|
46
|
+
}))
|
|
26
47
|
|
|
27
|
-
const provider = providers[
|
|
48
|
+
const provider = providers[pid]
|
|
28
49
|
if (!provider?.setupUrl) {
|
|
29
50
|
console.error(
|
|
30
51
|
y.red(
|
|
31
52
|
`Missing instructions for ${
|
|
32
|
-
provider?.name ??
|
|
53
|
+
provider?.name ?? pid
|
|
33
54
|
}.\nInstructions are available for: ${y.bold(
|
|
34
55
|
choices.map((choice) => choice.name).join(", ")
|
|
35
56
|
)}`
|
|
@@ -78,35 +99,48 @@ ${y.bold("Callback URL (copied to clipboard)")}: ${url}`
|
|
|
78
99
|
|
|
79
100
|
await open(provider.setupUrl)
|
|
80
101
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
if (providerId === "apple") {
|
|
103
|
+
const clientId = await promptInput("Client ID")
|
|
104
|
+
const keyId = await promptInput("Key ID")
|
|
105
|
+
const teamId = await promptInput("Team ID")
|
|
106
|
+
const privateKey = await input({
|
|
107
|
+
message: "Path to Private Key",
|
|
108
|
+
validate: (value) => !!value,
|
|
109
|
+
default: "./private-key.p8",
|
|
110
|
+
})
|
|
90
111
|
|
|
91
|
-
|
|
112
|
+
const expiresInDays =
|
|
113
|
+
(await number({
|
|
114
|
+
message: "Expires in days (default: 180)",
|
|
115
|
+
required: false,
|
|
116
|
+
default: 180,
|
|
117
|
+
})) ?? 180
|
|
92
118
|
|
|
93
|
-
|
|
119
|
+
console.log(y.dim("Updating environment variable file..."))
|
|
94
120
|
|
|
95
|
-
|
|
96
|
-
[`${varPrefix}_ID`]: clientId,
|
|
97
|
-
[`${varPrefix}_SECRET`]: clientSecret,
|
|
98
|
-
})
|
|
121
|
+
await updateEnvFile({ AUTH_APPLE_ID: clientId })
|
|
99
122
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
)
|
|
107
|
-
|
|
123
|
+
const secret = await appleGenSecret({
|
|
124
|
+
teamId,
|
|
125
|
+
clientId,
|
|
126
|
+
keyId,
|
|
127
|
+
privateKey,
|
|
128
|
+
expiresInDays,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
await updateEnvFile({ AUTH_APPLE_SECRET: secret })
|
|
132
|
+
} else {
|
|
133
|
+
const clientId = await promptInput("Client ID")
|
|
134
|
+
const clientSecret = await promptPassword("Client Secret")
|
|
108
135
|
|
|
109
|
-
|
|
136
|
+
console.log(y.dim("Updating environment variable file..."))
|
|
137
|
+
|
|
138
|
+
const varPrefix = `AUTH_${pid.toUpperCase()}`
|
|
139
|
+
await updateEnvFile({
|
|
140
|
+
[`${varPrefix}_ID`]: clientId,
|
|
141
|
+
[`${varPrefix}_SECRET`]: clientSecret,
|
|
142
|
+
})
|
|
143
|
+
}
|
|
110
144
|
|
|
111
145
|
console.log("\n🎉 Done! You can now use this provider in your app.")
|
|
112
146
|
} catch (error) {
|
package/commands/secret.js
CHANGED
|
@@ -48,7 +48,7 @@ export async function action(options) {
|
|
|
48
48
|
|
|
49
49
|
try {
|
|
50
50
|
await requireFramework(options.path)
|
|
51
|
-
await updateEnvFile({ [key]: value }, options.path
|
|
51
|
+
await updateEnvFile({ [key]: value }, options.path)
|
|
52
52
|
} catch (error) {
|
|
53
53
|
console.error(y.red(error))
|
|
54
54
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import * as y from "yoctocolors"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates an Apple client secret.
|
|
7
|
+
*
|
|
8
|
+
* @param {object} options
|
|
9
|
+
* @param {string} options.teamId - Apple Team ID.
|
|
10
|
+
* @param {string} options.clientId - Apple Client ID.
|
|
11
|
+
* @param {string} options.keyId - Apple Key ID.
|
|
12
|
+
* @param {string} options.privateKey - Apple Private Key.
|
|
13
|
+
* @param {number} options.expiresInDays - Days until the secret expires.
|
|
14
|
+
*
|
|
15
|
+
* @see https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret
|
|
16
|
+
*/
|
|
17
|
+
export async function appleGenSecret({
|
|
18
|
+
teamId: iss,
|
|
19
|
+
clientId: sub,
|
|
20
|
+
keyId: kid,
|
|
21
|
+
privateKey,
|
|
22
|
+
expiresInDays,
|
|
23
|
+
}) {
|
|
24
|
+
const expiresIn = 86400 * expiresInDays
|
|
25
|
+
const exp = Math.ceil(Date.now() / 1000) + expiresIn
|
|
26
|
+
|
|
27
|
+
const secret = await signJWT(sub, iss, kid, privateKey, exp)
|
|
28
|
+
|
|
29
|
+
console.log(
|
|
30
|
+
y.green(
|
|
31
|
+
`Apple client secret generated. Valid until: ${new Date(exp * 1000)}`
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return secret
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param {string} sub - Apple client ID.
|
|
41
|
+
* @param {string} iss - Apple team ID.
|
|
42
|
+
* @param {string} kid - Apple key ID.
|
|
43
|
+
* @param {string} privateKeyPath - Apple private key.
|
|
44
|
+
* @param {Date} exp - Expiry date.
|
|
45
|
+
*/
|
|
46
|
+
async function signJWT(sub, iss, kid, privateKeyPath, exp) {
|
|
47
|
+
const header = { alg: "ES256", kid }
|
|
48
|
+
|
|
49
|
+
const payload = {
|
|
50
|
+
iss,
|
|
51
|
+
iat: Date.now() / 1000,
|
|
52
|
+
exp,
|
|
53
|
+
aud: "https://appleid.apple.com",
|
|
54
|
+
sub,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const parts = [
|
|
58
|
+
toBase64Url(encoder.encode(JSON.stringify(header))),
|
|
59
|
+
toBase64Url(encoder.encode(JSON.stringify(payload))),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
const privateKey = fs.readFileSync(path.resolve(privateKeyPath), "utf8")
|
|
63
|
+
|
|
64
|
+
const signature = await sign(parts.join("."), privateKey)
|
|
65
|
+
|
|
66
|
+
parts.push(toBase64Url(signature))
|
|
67
|
+
return parts.join(".")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const encoder = new TextEncoder()
|
|
71
|
+
function toBase64Url(data) {
|
|
72
|
+
return btoa(String.fromCharCode(...new Uint8Array(data)))
|
|
73
|
+
.replace(/\+/g, "-")
|
|
74
|
+
.replace(/\//g, "_")
|
|
75
|
+
.replace(/=+$/, "")
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function sign(data, private_key) {
|
|
79
|
+
const pem = private_key.replace(
|
|
80
|
+
/-----BEGIN PRIVATE KEY-----|\n|-----END PRIVATE KEY-----/g,
|
|
81
|
+
""
|
|
82
|
+
)
|
|
83
|
+
const binaryDerString = atob(pem)
|
|
84
|
+
const binaryDer = new Uint8Array(
|
|
85
|
+
[...binaryDerString].map((char) => char.charCodeAt(0))
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const privateKey = await globalThis.crypto.subtle.importKey(
|
|
89
|
+
"pkcs8",
|
|
90
|
+
binaryDer.buffer,
|
|
91
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
92
|
+
true,
|
|
93
|
+
["sign"]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return await globalThis.crypto.subtle.sign(
|
|
97
|
+
{ name: "ECDSA", hash: { name: "SHA-256" } },
|
|
98
|
+
privateKey,
|
|
99
|
+
encoder.encode(data)
|
|
100
|
+
)
|
|
101
|
+
}
|
package/lib/meta.js
CHANGED
|
@@ -56,7 +56,19 @@ export const providers = {
|
|
|
56
56
|
coinbase: { name: "Coinbase", setupUrl: undefined },
|
|
57
57
|
credentials: { name: "Credentials", setupUrl: undefined },
|
|
58
58
|
descope: { name: "Descope", setupUrl: undefined },
|
|
59
|
-
discord: {
|
|
59
|
+
discord: {
|
|
60
|
+
name: "Discord",
|
|
61
|
+
setupUrl: "https://discord.com/developers/applications",
|
|
62
|
+
instructions: `\
|
|
63
|
+
1. Click on *New Application*
|
|
64
|
+
2. Set *Application name* (can be anything)
|
|
65
|
+
3. Click on *OAuth2* in the side menu
|
|
66
|
+
4. Click on *Add Redirect* and paste the callback URI (on your clipboard)
|
|
67
|
+
5. Click on *Save Changes*
|
|
68
|
+
6. Copy and paste the *Client ID*
|
|
69
|
+
7. Copy and paste the *Client Secret*
|
|
70
|
+
`,
|
|
71
|
+
},
|
|
60
72
|
dribbble: { name: "Dribbble", setupUrl: undefined },
|
|
61
73
|
dropbox: { name: "Dropbox", setupUrl: undefined },
|
|
62
74
|
"duende-identity-server6": {
|
|
@@ -98,7 +110,21 @@ export const providers = {
|
|
|
98
110
|
kakao: { name: "Kakao", setupUrl: undefined },
|
|
99
111
|
keycloak: { name: "Keycloak", setupUrl: undefined },
|
|
100
112
|
line: { name: "Line", setupUrl: undefined },
|
|
101
|
-
linkedin: {
|
|
113
|
+
linkedin: {
|
|
114
|
+
name: "LinkedIn",
|
|
115
|
+
setupUrl: "https://linkedin.com/developers/apps",
|
|
116
|
+
instructions: `\
|
|
117
|
+
1. Click on *Create app*
|
|
118
|
+
2. Set *App name* (can be anything)
|
|
119
|
+
3. Create a *LinkedIn page*
|
|
120
|
+
4. Set *LinkedIn Page URL* (LinkedIn page you created)
|
|
121
|
+
5. Set *App logo* (can be anything)
|
|
122
|
+
6. Navigate to *Auth* in the top menu
|
|
123
|
+
7. Set *Authorized redirect URLs for your app* (paste the callback URI on your clipboard)
|
|
124
|
+
8. Copy and paste the *Client ID*
|
|
125
|
+
9. Copy and paste the *Primary Client Secret*
|
|
126
|
+
`,
|
|
127
|
+
},
|
|
102
128
|
mailchimp: { name: "Mailchimp", setupUrl: undefined },
|
|
103
129
|
mailru: { name: "Mail.ru", setupUrl: undefined },
|
|
104
130
|
mastodon: { name: "Mastodon", setupUrl: undefined },
|
package/lib/write-env.js
CHANGED
|
@@ -13,7 +13,7 @@ import { detectFramework } from "./detect.js"
|
|
|
13
13
|
* @param {string|undefined} envPath
|
|
14
14
|
* @param {boolean} comment
|
|
15
15
|
*/
|
|
16
|
-
export async function updateEnvFile(env, envPath = "", comment =
|
|
16
|
+
export async function updateEnvFile(env, envPath = "", comment = true) {
|
|
17
17
|
const framework = await detectFramework(envPath)
|
|
18
18
|
const dotEnvFile = frameworks[framework]?.envFile
|
|
19
19
|
const file = join(process.cwd(), envPath, dotEnvFile)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"homepage": "https://cli.authjs.dev",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,20 +32,21 @@
|
|
|
32
32
|
"cli"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@inkeep/ai-api": "0.
|
|
35
|
+
"@inkeep/ai-api": "0.8.0",
|
|
36
36
|
"@inquirer/prompts": "5.1.0",
|
|
37
37
|
"clipboardy": "4.0.0",
|
|
38
38
|
"commander": "12.1.0",
|
|
39
39
|
"open": "10.1.0",
|
|
40
|
-
"ora": "8.0
|
|
40
|
+
"ora": "8.1.0",
|
|
41
41
|
"prompts": "2.4.2",
|
|
42
|
-
"yoctocolors": "2.1.
|
|
42
|
+
"yoctocolors": "2.1.1",
|
|
43
|
+
"zod": "3.23.8"
|
|
43
44
|
},
|
|
44
45
|
"prettier": {
|
|
45
46
|
"semi": false
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
|
-
"@types/node": "
|
|
49
|
+
"@types/node": "18"
|
|
49
50
|
},
|
|
50
51
|
"engines": {
|
|
51
52
|
"node": ">=18"
|