mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
17 Commits
next-auth@
...
next-auth@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afb1fcdae3 | ||
|
|
a21db8950f | ||
|
|
e8371ab23a | ||
|
|
9cdeb2ce7d | ||
|
|
89829d8a88 | ||
|
|
aedabc8d3f | ||
|
|
9f2cdad457 | ||
|
|
b107ca4946 | ||
|
|
6590993fdc | ||
|
|
0ea96796b2 | ||
|
|
8ec940bd6a | ||
|
|
e3bcdf83f1 | ||
|
|
4084297334 | ||
|
|
c9827960b1 | ||
|
|
946a825865 | ||
|
|
c57d8c997e | ||
|
|
e2b92bf04f |
2
.github/pr-labeler.yml
vendored
2
.github/pr-labeler.yml
vendored
@@ -10,7 +10,7 @@ providers:
|
||||
|
||||
adapters:
|
||||
- packages/next-auth/src/adapters.ts
|
||||
- packages/*-adapter/**
|
||||
- packages/adapter-*/**
|
||||
|
||||
dgraph:
|
||||
- packages/adapter-dgraph/**
|
||||
|
||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@@ -109,9 +109,9 @@ jobs:
|
||||
uses: NejcZdovc/comment-pr@v1
|
||||
with:
|
||||
message:
|
||||
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/next-auth/v/${{ env.VERSION }})! \
|
||||
```sh\npnpm add next-auth@${{ env.VERSION }}\n``` \
|
||||
```sh\nyarn add next-auth@${{ env.VERSION }}\n``` \
|
||||
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/next-auth/v/${{ env.VERSION }})!\n \
|
||||
```sh\npnpm add next-auth@${{ env.VERSION }}\n```\n \
|
||||
```sh\nyarn add next-auth@${{ env.VERSION }}\n```\n \
|
||||
```sh\nnpm i next-auth@${{ env.VERSION }}\n```"
|
||||
env:
|
||||
VERSION: ${{ steps.determine-version.outputs.version }}
|
||||
|
||||
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting me@iaincollins.com or info@balazsorban.com and yo@ndo.dev.
|
||||
reported by contacting info@balazsorban.com, yo@ndo.dev, thvu@hey.com and me@iaincollins.com.
|
||||
All complaints will be reviewed and investigated and will result in a response
|
||||
that is deemed necessary and appropriate to the circumstances. The project team
|
||||
is obligated to maintain confidentiality with regard to the reporter of an
|
||||
|
||||
@@ -13,7 +13,7 @@ If you contact us regarding a serious issue:
|
||||
- We will disclose the issue (and credit you, with your consent) once a fix to resolve the issue has been released.
|
||||
- If 90 days has elapsed and we still don't have a fix, we will disclose the issue publicly.
|
||||
|
||||
The best way to report an issue is by contacting us via email at info@balazsorban.com or me@iaincollins.com and yo@ndo.dev, or raise a public issue requesting someone get in touch with you via whatever means you prefer for more details. (Please do not disclose sensitive details publicly at this stage.)
|
||||
The best way to report an issue is by contacting us via email at info@balazsorban.com, yo@ndo.dev, thvu@hey.com and me@iaincollins.com, or raise a public issue requesting someone get in touch with you via whatever means you prefer for more details. (Please do not disclose sensitive details publicly at this stage.)
|
||||
|
||||
> For less serious issues (e.g. RFC compliance for unsupported flows or potential issues that may cause a problem in the future) it is appropriate to submit these these publically as bug reports or feature requests or to raise a question to open a discussion around them.
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// This is an example of to protect an API route
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, options)
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// This is an example of how to access a session from an API route
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from '../auth/[...nextauth]';
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import Layout from "../components/layout"
|
||||
import { authOptions } from './api/auth/[...nextauth]';
|
||||
|
||||
export default function Page() {
|
||||
// As this page uses Server Side Rendering, the `session` will be already
|
||||
@@ -40,8 +41,8 @@ export async function getServerSideProps(context) {
|
||||
return {
|
||||
props: {
|
||||
session: await unstable_getServerSession(
|
||||
contex.req,
|
||||
contex.res,
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
),
|
||||
},
|
||||
|
||||
@@ -137,6 +137,10 @@ Callbacks are asynchronous functions you can use to control what happens when an
|
||||
|
||||
Specify URLs to be used if you want to create custom sign in, and error pages. Pages specified will override the corresponding built-in page.
|
||||
|
||||
:::note
|
||||
This should match the `pages` configuration that's found in `[...nextauth].ts`.
|
||||
:::
|
||||
|
||||
#### Example (default value)
|
||||
|
||||
```js
|
||||
|
||||
@@ -61,17 +61,20 @@ There should also be further details logged when this occurs, such as the error
|
||||
|
||||
### Signin / Callback
|
||||
|
||||
#### GET_AUTHORIZATION_URL_ERROR
|
||||
|
||||
This error can occur when we cannot get the OAuth v1 request token and generate the authorization URL.
|
||||
|
||||
Please double check your OAuth v1 provider settings, especially the OAuth token and OAuth token secret.
|
||||
|
||||
#### SIGNIN_OAUTH_ERROR
|
||||
|
||||
This error can occur in one of a few places, first during the redirect to the authorization URL of the provider. Next, in the signin flow while creating the PKCE code verifier. Finally, during the generation of the CSRF Token hash in the internal state during signin.
|
||||
This error occurs during the redirection to the authorization URL of the OAuth provider. Possible causes:
|
||||
|
||||
Please check your OAuth provider settings and make sure your URLs and other options are correctly set on the provider side.
|
||||
1. Cookie handling
|
||||
Either PKCE code verifier or the generation of the CSRF token hash in the internal state failed.
|
||||
|
||||
If set, check your [`cookies` configuration](/configuration/options#cookies), and make sure the browser is not blocking/restricting cookies.
|
||||
|
||||
2. OAuth misconfiguration
|
||||
|
||||
Please check your OAuth provider and make sure your URLs and other options are correctly set.
|
||||
|
||||
If you are using an OAuth v1 provider, check your OAuth v1 provider settings, especially the OAuth token and OAuth token secret.
|
||||
|
||||
#### CALLBACK_OAUTH_ERROR
|
||||
|
||||
@@ -149,13 +152,7 @@ This error occurs when there was an issue deleting the session from the database
|
||||
|
||||
---
|
||||
|
||||
### Other
|
||||
|
||||
#### SEND_VERIFICATION_EMAIL_ERROR
|
||||
|
||||
This error occurs when the Email Authentication Provider is unable to send an email.
|
||||
|
||||
Check your mail server configuration.
|
||||
### Configuration
|
||||
|
||||
#### MISSING_NEXTAUTH_API_ROUTE_ERROR
|
||||
|
||||
@@ -167,6 +164,23 @@ Make sure the file is there and the filename is written correctly.
|
||||
|
||||
In production, we expect you to define a `secret` property in your configuration. In development, this is shown as a warning for convenience. [Read more](/configuration/options#secret)
|
||||
|
||||
|
||||
#### AUTH_ON_ERROR_PAGE_ERROR
|
||||
|
||||
You have a custom error page defined that was rendered due to an error, but the page also required authentication. To avoid an infinite redirect loop, NextAuth.js bailed out and rendered its default error page instead.
|
||||
|
||||
If you are using a Middleware, make sure you include the same `pages` configuration in your `middleware.ts` and `[...nextauth].ts` files. Or use the `matcher` option to only require authentication for certain sites (and exclude your custom error page).
|
||||
|
||||
If you do not use a Middleware, make sure you don't try redirecting the user to the sign-in page when hitting your custom error page.
|
||||
|
||||
Useful links:
|
||||
|
||||
- https://next-auth.js.org/configuration/nextjs#pages
|
||||
- https://next-auth.js.org/configuration/pages
|
||||
- https://nextjs.org/docs/advanced-features/middleware#matcher
|
||||
|
||||
### Other
|
||||
|
||||
#### oauth_callback_error expected 200 OK with body but no body was returned
|
||||
|
||||
This error might happen with some of the providers. It happens due to `openid-client`(which is peer dependency) node version mismatch. For instance, `openid-client` requires `>=14.2.0` for `lts/fermium` and has similar limits for the other versions. For the full list of the compatible node versions please see [package.json](https://github.com/panva/node-openid-client/blob/2a84e46992e1ebeaf685c3f87b65663d126e81aa/package.json#L78)
|
||||
|
||||
@@ -329,7 +329,7 @@ JSON Web Tokens can be used for session tokens, but are also used for lots of ot
|
||||
|
||||
Avoid storing any data in a token that might be problematic if it were to be decrypted in the future.
|
||||
|
||||
- If you do not explicitly specify a secret for for NextAuth.js, existing sessions will be invalidated any time your NextAuth.js configuration changes, as NextAuth.js will default to an auto-generated secret. Since v4 this only impacts development and generating a secret is required in production.
|
||||
- If you do not explicitly specify a secret for NextAuth.js, existing sessions will be invalidated any time your NextAuth.js configuration changes, as NextAuth.js will default to an auto-generated secret. Since v4 this only impacts development and generating a secret is required in production.
|
||||
|
||||
|
||||
</p>
|
||||
|
||||
@@ -148,13 +148,9 @@ Because of how `_app` is written, it won't unnecessarily contact the `/api/auth/
|
||||
|
||||
More information can be found in the following [GitHub Issue](https://github.com/nextauthjs/next-auth/issues/1210).
|
||||
|
||||
### NextAuth.js + React-Query
|
||||
### NextAuth.js + React Query
|
||||
|
||||
There is also an alternative client-side API library based upon [`react-query`](https://www.npmjs.com/package/react-query) available under [`nextauthjs/react-query`](https://github.com/nextauthjs/react-query).
|
||||
|
||||
If you use `react-query` in your project already, you can leverage it with NextAuth.js to handle the client-side session management for you as well. This replaces NextAuth.js's native `useSession` and `SessionProvider` from `next-auth/react`.
|
||||
|
||||
See repository [`README`](https://github.com/nextauthjs/react-query) for more details.
|
||||
You can create your own session management solution using data fetching libraries like [React Query](https://tanstack.com/query/v4/docs/adapters/react-query) or [SWR](https://swr.vercel.app). You can use the [original implementation of `@next-auth/react-query`](https://github.com/nextauthjs/react-query) and look at the [`next-auth/react` source code](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/react/index.tsx) as a starting point.
|
||||
|
||||
---
|
||||
|
||||
@@ -531,4 +527,4 @@ export default function App({
|
||||
</SessionProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -223,3 +223,31 @@ providers: [
|
||||
})
|
||||
],
|
||||
```
|
||||
|
||||
## Normalizing the email address
|
||||
|
||||
By default, NextAuth.js will normalize the email address. It treats values as case-insensitive (which is technically not compliant to the [RFC 2821 spec](https://datatracker.ietf.org/doc/html/rfc2821), but in practice this causes more problems than it solves, eg. when looking up users by e-mail from databases.) and also removes any secondary email address that was passed in as a comma-separated list. You can apply your own normalization via the `normalizeIdentifier` method on the `EmailProvider`. The following example shows the default behavior:
|
||||
```ts
|
||||
EmailProvider({
|
||||
// ...
|
||||
normalizeIdentifier(identifier: string): string {
|
||||
// Get the first two elements only,
|
||||
// separated by `@` from user input.
|
||||
let [local, domain] = identifier.toLowerCase().trim().split("@")
|
||||
// The part before "@" can contain a ","
|
||||
// but we remove it on the domain part
|
||||
domain = domain.split(",")[0]
|
||||
return `${local}@${domain}`
|
||||
|
||||
// You can also throw an error, which will redirect the user
|
||||
// to the error page with error=EmailSignin in the URL
|
||||
// if (identifier.split("@").length > 2) {
|
||||
// throw new Error("Only one email allowed")
|
||||
// }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
:::warning
|
||||
Always make sure this returns a single e-mail address, even if multiple ones were passed in.
|
||||
:::
|
||||
@@ -15,7 +15,7 @@ https://gitlab.com/-/profile/applications
|
||||
|
||||
The **Gitlab Provider** comes with a set of default options:
|
||||
|
||||
- [Gitlab Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/gitlab.js)
|
||||
- [Gitlab Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/gitlab.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
|
||||
@@ -46,27 +46,15 @@ This Provider template only has a one hour access token to it and only has the "
|
||||
|
||||
```js
|
||||
providers: [
|
||||
{
|
||||
id: "reddit",
|
||||
name: "Reddit",
|
||||
RedditProvider({
|
||||
clientId: process.env.REDDIT_CLIENT_ID,
|
||||
clientSecret: process.env.REDDIT_CLIENT_SECRET,
|
||||
scope: "identity mysubreddits read", //Check Reddit API Documentation for more. The identity scope is required.
|
||||
type: "oauth",
|
||||
version: "2.0",
|
||||
params: { grant_type: "authorization_code" },
|
||||
accessTokenUrl: " https://www.reddit.com/api/v1/access_token",
|
||||
authorizationUrl:
|
||||
"https://www.reddit.com/api/v1/authorize?response_type=code&duration=permanent",
|
||||
profileUrl: "https://oauth.reddit.com/api/v1/me",
|
||||
profile: (profile) => {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.name,
|
||||
email: null,
|
||||
}
|
||||
authorization: {
|
||||
params: {
|
||||
duration: 'permanent',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ If you contact us regarding a serious issue:
|
||||
- We will disclose the issue (and credit you, with your consent) once a fix to resolve the issue has been released.
|
||||
- If 90 days has elapsed and we still don't have a fix, we will disclose the issue publicly.
|
||||
|
||||
The best way to report an issue is by contacting us via email at info@balazsorban.com or me@iaincollins.com and yo@ndo.dev, or raise a public issue requesting someone get in touch with you via whatever means you prefer for more details. (Please do not disclose sensitive details publicly at this stage.)
|
||||
The best way to report an issue is by contacting us via email at info@balazsorban.com, yo@ndo.dev, thvu@hey.com and me@iaincollins.com, or raise a public issue requesting someone get in touch with you via whatever means you prefer for more details. (Please do not disclose sensitive details publicly at this stage.)
|
||||
|
||||
:::note
|
||||
For less serious issues (e.g. RFC compliance for unsupported flows or potential issues that may cause a problem in the future) it is appropriate to submit these these publically as bug reports or feature requests or to raise a question to open a discussion around them.
|
||||
|
||||
@@ -37,6 +37,12 @@ Twitter OAuth 2.0 is currently in beta as certain changes might still be necessa
|
||||
|
||||
Some APIs are still experimental; they may be changed or removed in the future. Use at your own risk.
|
||||
|
||||
#### DEBUG_ENABLED
|
||||
|
||||
You have enabled the `debug` option. It is meant for development only, to help you catch issues in your authentication flow and you should consider removing this option when deploying to production. One way of only allowing debugging while not in production is to set `debug: process.env.NODE_ENV !== "production"`, so you can commit this without needing to change the value.
|
||||
|
||||
If you want to log debug messages during production anyway, we recommend setting the [`logger` option](/configuration/options#logger) with proper sanitization of potentially sensitive user information.
|
||||
|
||||
## Adapter
|
||||
|
||||
### ADAPTER_TYPEORM_UPDATING_ENTITIES
|
||||
|
||||
10
package.json
10
package.json
@@ -15,13 +15,12 @@
|
||||
"dev:app": "turbo run dev --parallel --no-deps --no-cache --filter=next-auth-app",
|
||||
"dev:docs": "turbo run dev --parallel --no-deps --no-cache --filter=next-auth-docs",
|
||||
"version:pr": "node ./config/version-pr",
|
||||
"release": "ts-node scripts/release"
|
||||
"release": "release"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.6.0",
|
||||
"@commitlint/parse": "16.0.0",
|
||||
"@balazsorban/monorepo-release": "0.0.4",
|
||||
"@types/node": "^17.0.25",
|
||||
"@types/semver": "7.3.9",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"eslint": "^7.32.0",
|
||||
@@ -31,15 +30,10 @@
|
||||
"eslint-plugin-jest": "^25.3.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"git-log-parser": "1.2.0",
|
||||
"husky": "^7.0.4",
|
||||
"prettier": "2.4.1",
|
||||
"pretty-quick": "^3.1.2",
|
||||
"semver": "7.3.5",
|
||||
"stream-to-array": "2.3.0",
|
||||
"ts-node": "10.8.2",
|
||||
"turbo": "1.3.1",
|
||||
"type-fest": "2.16.0",
|
||||
"typescript": "^4.5.2"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/dgraph-adapter",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"description": "Dgraph adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"next-auth": "workspace:*"
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||
@@ -50,4 +50,4 @@
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@next-auth/dynamodb-adapter",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"description": "AWS DynamoDB adapter for next-auth.",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
@@ -32,7 +32,7 @@
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/lib-dynamodb": "^3.36.1",
|
||||
"next-auth": "workspace:*"
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-dynamodb": "^3.36.1",
|
||||
@@ -43,4 +43,4 @@
|
||||
"jest": "^27.4.3",
|
||||
"next-auth": "workspace:*"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/fauna-adapter",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"description": "Fauna Adapter for NextAuth",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -41,7 +41,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"faunadb": "^4.3.0",
|
||||
"next-auth": "workspace:*"
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fauna-labs/fauna-schema-migrate": "^2.1.3",
|
||||
@@ -54,4 +54,4 @@
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/firebase-adapter",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Firebase adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"firebase": "^9.7.0",
|
||||
"next-auth": "workspace:*"
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||
@@ -43,4 +43,4 @@
|
||||
"jest": "^27.4.3",
|
||||
"next-auth": "workspace:*"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/mongodb-adapter",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"description": "mongoDB adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -32,7 +32,7 @@
|
||||
],
|
||||
"peerDependencies": {
|
||||
"mongodb": "^4.1.1",
|
||||
"next-auth": "workspace:*"
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||
@@ -44,4 +44,4 @@
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/neo4j-adapter",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"description": "neo4j adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -34,7 +34,7 @@
|
||||
],
|
||||
"peerDependencies": {
|
||||
"neo4j-driver": "^4.0.0",
|
||||
"next-auth": "workspace:*"
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||
@@ -50,4 +50,4 @@
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/pouchdb-adapter",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"description": "PouchDB adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -30,7 +30,7 @@
|
||||
"dist"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"next-auth": "workspace:*",
|
||||
"next-auth": "^3",
|
||||
"pouchdb": "^7.2.2",
|
||||
"pouchdb-find": "^7.2.2"
|
||||
},
|
||||
@@ -51,4 +51,4 @@
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/prisma-adapter",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"description": "Prisma adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -37,7 +37,7 @@
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@prisma/client": ">=2.26.0 || >=3",
|
||||
"next-auth": "workspace:*"
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||
@@ -51,4 +51,4 @@
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/upstash-redis-adapter",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"description": "Upstash adapter for next-auth. It uses Upstash's connectionless (HTTP based) Redis client.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
@@ -31,7 +31,7 @@
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@upstash/redis": "^1.0.1",
|
||||
"next-auth": "workspace:*"
|
||||
"next-auth": "^4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||
@@ -49,4 +49,4 @@
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
// @ts-check
|
||||
// We aim to have the same support as Next.js
|
||||
// https://nextjs.org/docs/getting-started#system-requirements
|
||||
// https://nextjs.org/docs/basic-features/supported-browsers-features
|
||||
|
||||
/** @type {import("@babel/core").ConfigFunction} */
|
||||
module.exports = (api) => {
|
||||
const isTest = api.env("test")
|
||||
if (isTest) {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/** @type {import('@jest/types').Config.InitialOptions} */
|
||||
module.exports = {
|
||||
transform: {
|
||||
"\\.(js|jsx|ts|tsx)$": ["@swc/jest", require("./swc.config")],
|
||||
},
|
||||
rootDir: "../src",
|
||||
setupFilesAfterEnv: ["../config/jest-setup.js"],
|
||||
testMatch: ["**/*.test.js"],
|
||||
// collectCoverageFrom: ["!client/__tests__/**"],
|
||||
// coverageDirectory: "../coverage",
|
||||
testEnvironment: "jsdom",
|
||||
watchPlugins: [
|
||||
"jest-watch-typeahead/filename",
|
||||
"jest-watch-typeahead/testname",
|
||||
],
|
||||
}
|
||||
34
packages/next-auth/config/jest.config.js
Normal file
34
packages/next-auth/config/jest.config.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = {
|
||||
projects: [
|
||||
{
|
||||
displayName: "core",
|
||||
testMatch: ["**/*.test.ts"],
|
||||
rootDir: ".",
|
||||
setupFilesAfterEnv: ["./config/jest-setup.js"],
|
||||
transform: {
|
||||
"\\.(js|jsx|ts|tsx)$": ["@swc/jest", require("./swc.config")],
|
||||
},
|
||||
coveragePathIgnorePatterns: ["tests"],
|
||||
},
|
||||
{
|
||||
displayName: "client",
|
||||
testMatch: ["**/*.test.js"],
|
||||
setupFilesAfterEnv: ["./config/jest-setup.js"],
|
||||
rootDir: ".",
|
||||
transform: {
|
||||
"\\.(js|jsx|ts|tsx)$": ["@swc/jest", require("./swc.config")],
|
||||
},
|
||||
testEnvironment: "jsdom",
|
||||
coveragePathIgnorePatterns: ["__tests__"],
|
||||
},
|
||||
],
|
||||
watchPlugins: [
|
||||
"jest-watch-typeahead/filename",
|
||||
"jest-watch-typeahead/testname",
|
||||
],
|
||||
collectCoverage: true,
|
||||
coverageDirectory: "../coverage",
|
||||
coverageReporters: ["html", "text-summary"],
|
||||
collectCoverageFrom: ["src/**/*.(js|jsx|ts|tsx)"],
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/** @type {import('@jest/types').Config.InitialOptions} */
|
||||
module.exports = {
|
||||
transform: {
|
||||
"\\.(js|jsx|ts|tsx)$": ["@swc/jest", require("./swc.config")],
|
||||
},
|
||||
rootDir: "..",
|
||||
testMatch: ["**/*.test.ts"],
|
||||
setupFilesAfterEnv: ["./config/jest-setup.js"],
|
||||
watchPlugins: [
|
||||
"jest-watch-typeahead/filename",
|
||||
"jest-watch-typeahead/testname",
|
||||
],
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "next-auth",
|
||||
"version": "4.10.1",
|
||||
"version": "4.10.2",
|
||||
"description": "Authentication for Next.js",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||
@@ -42,9 +42,7 @@
|
||||
"build:js": "pnpm clean && pnpm generate-providers && tsc && babel --config-file ./config/babel.config.js src --out-dir . --extensions \".tsx,.ts,.js,.jsx\"",
|
||||
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir . && node config/wrap-css.js",
|
||||
"watch:css": "postcss --config config/postcss.config.js --watch src/**/*.css --base src --dir .",
|
||||
"test:client": "jest --config ./config/jest.client.config.js",
|
||||
"test:core": "jest --config ./config/jest.core.config.js",
|
||||
"test": "pnpm test:core && pnpm test:client",
|
||||
"test": "jest --config ./config/jest.config.js",
|
||||
"prepublishOnly": "pnpm build",
|
||||
"generate-providers": "node ./config/generate-providers.js",
|
||||
"setup": "pnpm generate-providers",
|
||||
@@ -139,4 +137,4 @@
|
||||
"**/tests",
|
||||
"**/__tests__"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ test("when the fetch fails it'll throw a client fetch error", async () => {
|
||||
await waitFor(() => {
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
|
||||
path: "csrf",
|
||||
url: "/api/auth/csrf",
|
||||
error: new SyntaxError("Unexpected token s in JSON at position 0"),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -57,7 +57,7 @@ test("when failing to fetch the providers, it'll log the error", async () => {
|
||||
await waitFor(() => {
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
|
||||
path: "providers",
|
||||
url: "/api/auth/providers",
|
||||
error: new SyntaxError("Unexpected token s in JSON at position 0"),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -71,7 +71,7 @@ test("if there's an error fetching the session, it should log it", async () => {
|
||||
await waitFor(() => {
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
|
||||
path: "session",
|
||||
url: "/api/auth/session",
|
||||
error: new SyntaxError("Unexpected token S in JSON at position 0"),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -256,7 +256,7 @@ test("when it fails to fetch the providers, it redirected back to signin page",
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", {
|
||||
error: "Error when retrieving providers",
|
||||
path: "providers",
|
||||
url: "/api/auth/providers",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,20 +35,17 @@ export async function fetchData<T = any>(
|
||||
logger: LoggerInstance,
|
||||
{ ctx, req = ctx?.req }: CtxOrReq = {}
|
||||
): Promise<T | null> {
|
||||
const url = `${apiBaseUrl(__NEXTAUTH)}/${path}`
|
||||
try {
|
||||
const options = req?.headers.cookie
|
||||
? { headers: { cookie: req.headers.cookie } }
|
||||
: {}
|
||||
const res = await fetch(`${apiBaseUrl(__NEXTAUTH)}/${path}`, options)
|
||||
const res = await fetch(url, options)
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw data
|
||||
return Object.keys(data).length > 0 ? data : null // Return null if data empty
|
||||
} catch (error) {
|
||||
logger.error("CLIENT_FETCH_ERROR", {
|
||||
error: error as Error,
|
||||
path,
|
||||
...(req ? { header: req.headers } : {}),
|
||||
})
|
||||
logger.error("CLIENT_FETCH_ERROR", { error: error as Error, url })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,19 +90,34 @@ export async function NextAuthHandler<
|
||||
|
||||
const assertionResult = assertConfig({ options: userOptions, req })
|
||||
|
||||
if (typeof assertionResult === "string") {
|
||||
logger.warn(assertionResult)
|
||||
if (Array.isArray(assertionResult)) {
|
||||
assertionResult.forEach(logger.warn)
|
||||
} else if (assertionResult instanceof Error) {
|
||||
// Bail out early if there's an error in the user config
|
||||
const { pages, theme } = userOptions
|
||||
logger.error(assertionResult.code, assertionResult)
|
||||
if (pages?.error) {
|
||||
return {
|
||||
redirect: `${pages.error}?error=Configuration`,
|
||||
|
||||
const authOnErrorPage =
|
||||
pages?.error &&
|
||||
req.action === "signin" &&
|
||||
req.query?.callbackUrl.startsWith(pages.error)
|
||||
|
||||
if (!pages?.error || authOnErrorPage) {
|
||||
if (authOnErrorPage) {
|
||||
logger.error(
|
||||
"AUTH_ON_ERROR_PAGE_ERROR",
|
||||
new Error(
|
||||
`The error page ${pages?.error} should not require authentication`
|
||||
)
|
||||
)
|
||||
}
|
||||
const render = renderPage({ theme })
|
||||
return render.error({ error: "configuration" })
|
||||
}
|
||||
|
||||
return {
|
||||
redirect: `${pages.error}?error=Configuration`,
|
||||
}
|
||||
const render = renderPage({ theme })
|
||||
return render.error({ error: "configuration" })
|
||||
}
|
||||
|
||||
const { action, providerId, error, method = "GET" } = req
|
||||
|
||||
@@ -9,8 +9,9 @@ import {
|
||||
import parseUrl from "../../utils/parse-url"
|
||||
import { defaultCookies } from "./cookie"
|
||||
|
||||
import type { NextAuthHandlerParams, RequestInternal } from ".."
|
||||
import type { RequestInternal } from ".."
|
||||
import type { WarningCode } from "../../utils/logger"
|
||||
import type { NextAuthOptions } from "../types"
|
||||
|
||||
type ConfigError =
|
||||
| MissingAPIRoute
|
||||
@@ -19,7 +20,7 @@ type ConfigError =
|
||||
| MissingAuthorize
|
||||
| MissingAdapter
|
||||
|
||||
let twitterWarned = false
|
||||
let warned = false
|
||||
|
||||
function isValidHttpUrl(url: string, baseUrl: string) {
|
||||
try {
|
||||
@@ -37,13 +38,28 @@ function isValidHttpUrl(url: string, baseUrl: string) {
|
||||
*
|
||||
* REVIEW: Make some of these and corresponding docs less Next.js specific?
|
||||
*/
|
||||
export function assertConfig(
|
||||
params: NextAuthHandlerParams & {
|
||||
req: RequestInternal
|
||||
}
|
||||
): ConfigError | WarningCode | undefined {
|
||||
export function assertConfig(params: {
|
||||
options: NextAuthOptions
|
||||
req: RequestInternal
|
||||
}): ConfigError | WarningCode[] {
|
||||
const { options, req } = params
|
||||
|
||||
const warnings: WarningCode[] = []
|
||||
|
||||
if (!warned) {
|
||||
if (!req.host) warnings.push("NEXTAUTH_URL")
|
||||
|
||||
// TODO: Make this throw an error in next major. This will also get rid of `NODE_ENV`
|
||||
if (!options.secret && process.env.NODE_ENV !== "production")
|
||||
warnings.push("NO_SECRET")
|
||||
|
||||
if (options.debug) warnings.push("DEBUG_ENABLED")
|
||||
}
|
||||
|
||||
if (!options.secret && process.env.NODE_ENV === "production") {
|
||||
return new MissingSecret("Please define a `secret` in production.")
|
||||
}
|
||||
|
||||
// req.query isn't defined when asserting `unstable_getServerSession` for example
|
||||
if (!req.query?.nextauth && !req.action) {
|
||||
return new MissingAPIRoute(
|
||||
@@ -51,14 +67,6 @@ export function assertConfig(
|
||||
)
|
||||
}
|
||||
|
||||
if (!options.secret) {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
return new MissingSecret("Please define a `secret` in production.")
|
||||
} else {
|
||||
return "NO_SECRET"
|
||||
}
|
||||
}
|
||||
|
||||
const callbackUrlParam = req.query?.callbackUrl as string | undefined
|
||||
|
||||
const url = parseUrl(req.host)
|
||||
@@ -69,9 +77,6 @@ export function assertConfig(
|
||||
)
|
||||
}
|
||||
|
||||
// This is below the callbackUrlParam check because it would obscure the error
|
||||
if (!req.host) return "NEXTAUTH_URL"
|
||||
|
||||
const { callbackUrl: defaultCallbackUrl } = defaultCookies(
|
||||
options.useSecureCookies ?? url.base.startsWith("https://")
|
||||
)
|
||||
@@ -119,8 +124,10 @@ export function assertConfig(
|
||||
return new MissingAdapter("E-mail login requires an adapter.")
|
||||
}
|
||||
|
||||
if (!twitterWarned && hasTwitterOAuth2) {
|
||||
twitterWarned = true
|
||||
return "TWITTER_OAUTH_2_BETA"
|
||||
if (!warned) {
|
||||
if (hasTwitterOAuth2) warnings.push("TWITTER_OAUTH_2_BETA")
|
||||
warned = true
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
@@ -9,9 +9,8 @@ import type { InternalOptions } from "../../types"
|
||||
export default async function email(
|
||||
identifier: string,
|
||||
options: InternalOptions<"email">
|
||||
) {
|
||||
const { url, adapter, provider, logger, callbackUrl, theme } = options
|
||||
|
||||
): Promise<string> {
|
||||
const { url, adapter, provider, callbackUrl, theme } = options
|
||||
// Generate token
|
||||
const token =
|
||||
(await provider.generateVerificationToken?.()) ??
|
||||
@@ -22,34 +21,31 @@ export default async function email(
|
||||
Date.now() + (provider.maxAge ?? ONE_DAY_IN_SECONDS) * 1000
|
||||
)
|
||||
|
||||
// Save in database
|
||||
// @ts-expect-error
|
||||
await adapter.createVerificationToken({
|
||||
identifier,
|
||||
token: hashToken(token, options),
|
||||
expires,
|
||||
})
|
||||
|
||||
// Generate a link with email, unhashed token and callback url
|
||||
const params = new URLSearchParams({ callbackUrl, token, email: identifier })
|
||||
const _url = `${url}/callback/${provider.id}?${params}`
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
// Send to user
|
||||
await provider.sendVerificationRequest({
|
||||
provider.sendVerificationRequest({
|
||||
identifier,
|
||||
token,
|
||||
expires,
|
||||
url: _url,
|
||||
provider,
|
||||
theme,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error("SEND_VERIFICATION_EMAIL_ERROR", {
|
||||
}),
|
||||
// Save in database
|
||||
// @ts-expect-error // verified in `assertConfig`
|
||||
adapter.createVerificationToken({
|
||||
identifier,
|
||||
url,
|
||||
error: error as Error,
|
||||
})
|
||||
throw new Error("SEND_VERIFICATION_EMAIL_ERROR")
|
||||
}
|
||||
token: hashToken(token, options),
|
||||
expires,
|
||||
}),
|
||||
])
|
||||
|
||||
return `${url}/verify-request?${new URLSearchParams({
|
||||
provider: provider.id,
|
||||
type: provider.type,
|
||||
})}`
|
||||
}
|
||||
|
||||
@@ -14,66 +14,63 @@ import type { Cookie } from "../cookie"
|
||||
*
|
||||
* [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/) | [OAuth 1](https://oauth.net/core/1.0a/#auth_step2)
|
||||
*/
|
||||
export default async function getAuthorizationUrl(params: {
|
||||
export default async function getAuthorizationUrl({
|
||||
options,
|
||||
query,
|
||||
}: {
|
||||
options: InternalOptions<"oauth">
|
||||
query: RequestInternal["query"]
|
||||
}) {
|
||||
const { options, query } = params
|
||||
const { logger, provider } = options
|
||||
try {
|
||||
let params: any = {}
|
||||
let params: any = {}
|
||||
|
||||
if (typeof provider.authorization === "string") {
|
||||
const parsedUrl = new URL(provider.authorization)
|
||||
const parsedParams = Object.fromEntries(parsedUrl.searchParams.entries())
|
||||
params = { ...params, ...parsedParams }
|
||||
} else {
|
||||
params = { ...params, ...provider.authorization?.params }
|
||||
}
|
||||
|
||||
params = { ...params, ...query }
|
||||
|
||||
// Handle OAuth v1.x
|
||||
if (provider.version?.startsWith("1.")) {
|
||||
const client = oAuth1Client(options)
|
||||
const tokens = (await client.getOAuthRequestToken(params)) as any
|
||||
const url = `${
|
||||
// @ts-expect-error
|
||||
provider.authorization?.url ?? provider.authorization
|
||||
}?${new URLSearchParams({
|
||||
oauth_token: tokens.oauth_token,
|
||||
oauth_token_secret: tokens.oauth_token_secret,
|
||||
...tokens.params,
|
||||
})}`
|
||||
|
||||
logger.debug("GET_AUTHORIZATION_URL", { url })
|
||||
return { redirect: url }
|
||||
}
|
||||
|
||||
const client = await openidClient(options)
|
||||
|
||||
const authorizationParams: AuthorizationParameters = params
|
||||
const cookies: Cookie[] = []
|
||||
|
||||
const state = await createState(options)
|
||||
if (state) {
|
||||
authorizationParams.state = state.value
|
||||
cookies.push(state.cookie)
|
||||
}
|
||||
|
||||
const pkce = await createPKCE(options)
|
||||
if (pkce) {
|
||||
authorizationParams.code_challenge = pkce.code_challenge
|
||||
authorizationParams.code_challenge_method = pkce.code_challenge_method
|
||||
cookies.push(pkce.cookie)
|
||||
}
|
||||
|
||||
const url = client.authorizationUrl(authorizationParams)
|
||||
|
||||
logger.debug("GET_AUTHORIZATION_URL", { url, cookies })
|
||||
return { redirect: url, cookies }
|
||||
} catch (error) {
|
||||
logger.error("GET_AUTHORIZATION_URL_ERROR", error as Error)
|
||||
throw error
|
||||
if (typeof provider.authorization === "string") {
|
||||
const parsedUrl = new URL(provider.authorization)
|
||||
const parsedParams = Object.fromEntries(parsedUrl.searchParams.entries())
|
||||
params = { ...params, ...parsedParams }
|
||||
} else {
|
||||
params = { ...params, ...provider.authorization?.params }
|
||||
}
|
||||
|
||||
params = { ...params, ...query }
|
||||
|
||||
// Handle OAuth v1.x
|
||||
if (provider.version?.startsWith("1.")) {
|
||||
const client = oAuth1Client(options)
|
||||
const tokens = (await client.getOAuthRequestToken(params)) as any
|
||||
const url = `${
|
||||
// @ts-expect-error
|
||||
provider.authorization?.url ?? provider.authorization
|
||||
}?${new URLSearchParams({
|
||||
oauth_token: tokens.oauth_token,
|
||||
oauth_token_secret: tokens.oauth_token_secret,
|
||||
...tokens.params,
|
||||
})}`
|
||||
|
||||
logger.debug("GET_AUTHORIZATION_URL", { url, provider })
|
||||
return { redirect: url }
|
||||
}
|
||||
|
||||
const client = await openidClient(options)
|
||||
|
||||
const authorizationParams: AuthorizationParameters = params
|
||||
const cookies: Cookie[] = []
|
||||
|
||||
const state = await createState(options)
|
||||
if (state) {
|
||||
authorizationParams.state = state.value
|
||||
cookies.push(state.cookie)
|
||||
}
|
||||
|
||||
const pkce = await createPKCE(options)
|
||||
if (pkce) {
|
||||
authorizationParams.code_challenge = pkce.code_challenge
|
||||
authorizationParams.code_challenge_method = pkce.code_challenge_method
|
||||
cookies.push(pkce.cookie)
|
||||
}
|
||||
|
||||
const url = client.authorizationUrl(authorizationParams)
|
||||
|
||||
logger.debug("GET_AUTHORIZATION_URL", { url, cookies, provider })
|
||||
return { redirect: url, cookies }
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ export default async function oAuthCallback(params: {
|
||||
logger.error("OAUTH_CALLBACK_HANDLER_ERROR", {
|
||||
error,
|
||||
error_description: query?.error_description,
|
||||
body,
|
||||
providerId: provider.id,
|
||||
})
|
||||
logger.debug("OAUTH_CALLBACK_HANDLER_ERROR", { body })
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -199,10 +199,7 @@ async function getProfile({
|
||||
// all providers, so we return an empty object; the user should then be
|
||||
// redirected back to the sign up page. We log the error to help developers
|
||||
// who might be trying to debug this when configuring a new provider.
|
||||
logger.error("OAUTH_PARSE_PROFILE_ERROR", {
|
||||
error: error as Error,
|
||||
OAuthProfile,
|
||||
})
|
||||
logger.error("OAUTH_PARSE_PROFILE_ERROR", error as Error)
|
||||
return {
|
||||
profile: null,
|
||||
account: null,
|
||||
|
||||
@@ -26,20 +26,33 @@ export default async function signin(params: {
|
||||
const response = await getAuthorizationUrl({ options, query })
|
||||
return response
|
||||
} catch (error) {
|
||||
logger.error("SIGNIN_OAUTH_ERROR", { error: error as Error, provider })
|
||||
logger.error("SIGNIN_OAUTH_ERROR", {
|
||||
error: error as Error,
|
||||
providerId: provider.id,
|
||||
})
|
||||
return { redirect: `${url}/error?error=OAuthSignin` }
|
||||
}
|
||||
} else if (provider.type === "email") {
|
||||
/**
|
||||
* @note Technically the part of the email address local mailbox element
|
||||
* (everything before the @ symbol) should be treated as 'case sensitive'
|
||||
* according to RFC 2821, but in practice this causes more problems than
|
||||
* it solves. We treat email addresses as all lower case. If anyone
|
||||
* complains about this we can make strict RFC 2821 compliance an option.
|
||||
*/
|
||||
const email = body?.email?.toLowerCase()
|
||||
|
||||
let email: string = body?.email
|
||||
if (!email) return { redirect: `${url}/error?error=EmailSignin` }
|
||||
const normalizer: (identifier: string) => string =
|
||||
provider.normalizeIdentifier ??
|
||||
((identifier) => {
|
||||
// Get the first two elements only,
|
||||
// separated by `@` from user input.
|
||||
let [local, domain] = identifier.toLowerCase().trim().split("@")
|
||||
// The part before "@" can contain a ","
|
||||
// but we remove it on the domain part
|
||||
domain = domain.split(",")[0]
|
||||
return `${local}@${domain}`
|
||||
})
|
||||
|
||||
try {
|
||||
email = normalizer(body?.email)
|
||||
} catch (error) {
|
||||
logger.error("SIGNIN_EMAIL_ERROR", { error, providerId: provider.id })
|
||||
return { redirect: `${url}/error?error=EmailSignin` }
|
||||
}
|
||||
|
||||
// Verified in `assertConfig`
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -79,18 +92,12 @@ export default async function signin(params: {
|
||||
}
|
||||
|
||||
try {
|
||||
await emailSignin(email, options)
|
||||
const redirect = await emailSignin(email, options)
|
||||
return { redirect }
|
||||
} catch (error) {
|
||||
logger.error("SIGNIN_EMAIL_ERROR", error as Error)
|
||||
logger.error("SIGNIN_EMAIL_ERROR", { error, providerId: provider.id })
|
||||
return { redirect: `${url}/error?error=EmailSignin` }
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
provider: provider.id,
|
||||
type: provider.type,
|
||||
})
|
||||
|
||||
return { redirect: `${url}/verify-request?${params}` }
|
||||
}
|
||||
return { redirect: `${url}/signin` }
|
||||
}
|
||||
|
||||
@@ -101,13 +101,18 @@ async function handleMiddleware(
|
||||
options: NextAuthMiddlewareOptions | undefined,
|
||||
onSuccess?: (token: JWT | null) => Promise<NextMiddlewareResult>
|
||||
) {
|
||||
const { pathname, search, origin } = req.nextUrl
|
||||
|
||||
const signInPage = options?.pages?.signIn ?? "/api/auth/signin"
|
||||
const errorPage = options?.pages?.error ?? "/api/auth/error"
|
||||
const basePath = parseUrl(process.env.NEXTAUTH_URL).path
|
||||
// Avoid infinite redirect loop
|
||||
const publicPaths = [signInPage, errorPage, "/_next", "/favicon.ico"]
|
||||
|
||||
// Avoid infinite redirects/invalid response
|
||||
// on paths that never require authentication
|
||||
if (
|
||||
req.nextUrl.pathname.startsWith(basePath) ||
|
||||
[signInPage, errorPage].includes(req.nextUrl.pathname)
|
||||
pathname.startsWith(basePath) ||
|
||||
publicPaths.some((p) => pathname.startsWith(p))
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -119,7 +124,7 @@ async function handleMiddleware(
|
||||
`\nhttps://next-auth.js.org/errors#no_secret`
|
||||
)
|
||||
|
||||
const errorUrl = new URL(errorPage, req.nextUrl.origin)
|
||||
const errorUrl = new URL(errorPage, origin)
|
||||
errorUrl.searchParams.append("error", "Configuration")
|
||||
|
||||
return NextResponse.redirect(errorUrl)
|
||||
@@ -139,11 +144,8 @@ async function handleMiddleware(
|
||||
if (isAuthorized) return await onSuccess?.(token)
|
||||
|
||||
// the user is not logged in, redirect to the sign-in page
|
||||
const signInUrl = new URL(signInPage, req.nextUrl.origin)
|
||||
signInUrl.searchParams.append(
|
||||
"callbackUrl",
|
||||
`${req.nextUrl.pathname}${req.nextUrl.search}`
|
||||
)
|
||||
const signInUrl = new URL(signInPage, origin)
|
||||
signInUrl.searchParams.append("callbackUrl", `${pathname}${search}`)
|
||||
return NextResponse.redirect(signInUrl)
|
||||
}
|
||||
|
||||
@@ -178,7 +180,7 @@ export type WithAuthArgs =
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
* [Documentation](https://next-auth.js.org/getting-started/middleware)
|
||||
* [Documentation](https://next-auth.js.org/configuration/nextjs#middleware)
|
||||
*/
|
||||
export function withAuth(...args: WithAuthArgs) {
|
||||
if (!args.length || args[0] instanceof NextRequest) {
|
||||
|
||||
@@ -46,6 +46,21 @@ export interface EmailConfig extends CommonProviderOptions {
|
||||
generateVerificationToken?: () => Awaitable<string>
|
||||
/** If defined, it is used to hash the verification token when saving to the database . */
|
||||
secret?: string
|
||||
/**
|
||||
* Normalizes the user input before sending the verification request.
|
||||
*
|
||||
* ⚠️ Always make sure this method returns a single email address.
|
||||
*
|
||||
* @note Technically, the part of the email address local mailbox element
|
||||
* (everything before the `@` symbol) should be treated as 'case sensitive'
|
||||
* according to RFC 2821, but in practice this causes more problems than
|
||||
* it solves, e.g.: when looking up users by e-mail from databases.
|
||||
* By default, we treat email addresses as all lower case,
|
||||
* but you can override this function to change this behavior.
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/providers/email#normalizing-the-e-mail-address) | [RFC 2821](https://tools.ietf.org/html/rfc2821) | [Email syntax](https://en.wikipedia.org/wiki/Email_address#Syntax)
|
||||
*/
|
||||
normalizeIdentifier?: (identifier: string) => string
|
||||
options: EmailUserConfig
|
||||
}
|
||||
|
||||
@@ -79,7 +94,7 @@ export default function Email(options: EmailUserConfig): EmailConfig {
|
||||
})
|
||||
const failed = result.rejected.concat(result.pending).filter(Boolean)
|
||||
if (failed.length) {
|
||||
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
|
||||
throw new Error(`Email (${failed.join(", ")}) could not be sent`)
|
||||
}
|
||||
},
|
||||
options,
|
||||
|
||||
@@ -19,7 +19,11 @@ function hasErrorProperty(
|
||||
return !!(x as any)?.error
|
||||
}
|
||||
|
||||
export type WarningCode = "NEXTAUTH_URL" | "NO_SECRET" | "TWITTER_OAUTH_2_BETA"
|
||||
export type WarningCode =
|
||||
| "NEXTAUTH_URL"
|
||||
| "NO_SECRET"
|
||||
| "TWITTER_OAUTH_2_BETA"
|
||||
| "DEBUG_ENABLED"
|
||||
|
||||
/**
|
||||
* Override any of the methods, and the rest will use the default logger.
|
||||
|
||||
167
packages/next-auth/tests/email.test.ts
Normal file
167
packages/next-auth/tests/email.test.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { createCSRF, handler, mockAdapter } from "./lib"
|
||||
import EmailProvider from "../src/providers/email"
|
||||
|
||||
it("Send e-mail to the only address correctly", async () => {
|
||||
const { secret, csrf } = await createCSRF()
|
||||
const sendVerificationRequest = jest.fn()
|
||||
const signIn = jest.fn(() => true)
|
||||
|
||||
const email = "email@example.com"
|
||||
const { res } = await handler(
|
||||
{
|
||||
adapter: mockAdapter(),
|
||||
providers: [EmailProvider({ sendVerificationRequest })],
|
||||
callbacks: { signIn },
|
||||
secret,
|
||||
},
|
||||
{
|
||||
path: "signin/email",
|
||||
requestInit: {
|
||||
method: "POST",
|
||||
headers: { cookie: csrf.cookie },
|
||||
body: JSON.stringify({ email: email, csrfToken: csrf.value }),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(res.redirect).toBe(
|
||||
"http://localhost:3000/api/auth/verify-request?provider=email&type=email"
|
||||
)
|
||||
|
||||
expect(signIn).toBeCalledTimes(1)
|
||||
expect(signIn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
user: expect.objectContaining({ email }),
|
||||
})
|
||||
)
|
||||
|
||||
expect(sendVerificationRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ identifier: email })
|
||||
)
|
||||
})
|
||||
|
||||
it("Send e-mail to first address only", async () => {
|
||||
const { secret, csrf } = await createCSRF()
|
||||
const sendVerificationRequest = jest.fn()
|
||||
const signIn = jest.fn(() => true)
|
||||
|
||||
const firstEmail = "email@email.com"
|
||||
const email = `${firstEmail},email@email2.com`
|
||||
const { res } = await handler(
|
||||
{
|
||||
adapter: mockAdapter(),
|
||||
providers: [EmailProvider({ sendVerificationRequest })],
|
||||
callbacks: { signIn },
|
||||
secret,
|
||||
},
|
||||
{
|
||||
path: "signin/email",
|
||||
requestInit: {
|
||||
method: "POST",
|
||||
headers: { cookie: csrf.cookie },
|
||||
body: JSON.stringify({ email: email, csrfToken: csrf.value }),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(res.redirect).toBe(
|
||||
"http://localhost:3000/api/auth/verify-request?provider=email&type=email"
|
||||
)
|
||||
|
||||
expect(signIn).toBeCalledTimes(1)
|
||||
expect(signIn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
user: expect.objectContaining({ email: firstEmail }),
|
||||
})
|
||||
)
|
||||
|
||||
expect(sendVerificationRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ identifier: firstEmail })
|
||||
)
|
||||
})
|
||||
|
||||
it("Send e-mail to address with first domain", async () => {
|
||||
const { secret, csrf } = await createCSRF()
|
||||
const sendVerificationRequest = jest.fn()
|
||||
const signIn = jest.fn(() => true)
|
||||
|
||||
const firstEmail = "email@email.com"
|
||||
const email = `${firstEmail},email2.com`
|
||||
const { res } = await handler(
|
||||
{
|
||||
adapter: mockAdapter(),
|
||||
providers: [EmailProvider({ sendVerificationRequest })],
|
||||
callbacks: { signIn },
|
||||
secret,
|
||||
},
|
||||
{
|
||||
path: "signin/email",
|
||||
requestInit: {
|
||||
method: "POST",
|
||||
headers: { cookie: csrf.cookie },
|
||||
body: JSON.stringify({ email: email, csrfToken: csrf.value }),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(res.redirect).toBe(
|
||||
"http://localhost:3000/api/auth/verify-request?provider=email&type=email"
|
||||
)
|
||||
|
||||
expect(signIn).toBeCalledTimes(1)
|
||||
expect(signIn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
user: expect.objectContaining({ email: firstEmail }),
|
||||
})
|
||||
)
|
||||
|
||||
expect(sendVerificationRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ identifier: firstEmail })
|
||||
)
|
||||
})
|
||||
|
||||
it("Redirect to error page if multiple addresses aren't allowed", async () => {
|
||||
const { secret, csrf } = await createCSRF()
|
||||
const sendVerificationRequest = jest.fn()
|
||||
const signIn = jest.fn()
|
||||
const error = new Error("Only one email allowed")
|
||||
const { res, log } = await handler(
|
||||
{
|
||||
adapter: mockAdapter(),
|
||||
callbacks: { signIn },
|
||||
providers: [
|
||||
EmailProvider({
|
||||
sendVerificationRequest,
|
||||
normalizeIdentifier(identifier) {
|
||||
if (identifier.split("@").length > 2) throw error
|
||||
return identifier
|
||||
},
|
||||
}),
|
||||
],
|
||||
secret,
|
||||
},
|
||||
{
|
||||
path: "signin/email",
|
||||
requestInit: {
|
||||
method: "POST",
|
||||
headers: { cookie: csrf.cookie },
|
||||
body: JSON.stringify({
|
||||
email: "email@email.com,email@email2.com",
|
||||
csrfToken: csrf.value,
|
||||
}),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(signIn).toBeCalledTimes(0)
|
||||
expect(sendVerificationRequest).toBeCalledTimes(0)
|
||||
|
||||
expect(log.error.mock.calls[0]).toEqual([
|
||||
"SIGNIN_EMAIL_ERROR",
|
||||
{ error, providerId: "email" },
|
||||
])
|
||||
|
||||
expect(res.redirect).toBe(
|
||||
"http://localhost:3000/api/auth/error?error=EmailSignin"
|
||||
)
|
||||
})
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { NextApiRequest } from "next"
|
||||
import { MissingSecret } from "../src/core/errors"
|
||||
import { unstable_getServerSession } from "../src/next"
|
||||
import { mockLogger } from "./lib"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createHash } from "crypto"
|
||||
import type { LoggerInstance, NextAuthOptions } from "../src"
|
||||
import { NextAuthHandler } from "../src/core"
|
||||
import type { LoggerInstance, NextAuthOptions } from "../src"
|
||||
import type { Adapter } from "../src/adapters"
|
||||
|
||||
export const mockLogger: () => LoggerInstance = () => ({
|
||||
error: jest.fn(() => {}),
|
||||
@@ -56,3 +57,10 @@ export function createCSRF() {
|
||||
csrf: { value, token, cookie: `next-auth.csrf-token=${value}|${token}` },
|
||||
}
|
||||
}
|
||||
|
||||
export function mockAdapter(): Adapter {
|
||||
return {
|
||||
createVerificationToken: jest.fn(() => {}),
|
||||
getUserByEmail: jest.fn(() => {}),
|
||||
} as Adapter
|
||||
}
|
||||
|
||||
@@ -19,5 +19,12 @@
|
||||
"next": ["node_modules/next"]
|
||||
}
|
||||
},
|
||||
"exclude": ["./*.js", "./*.d.ts", "config", "**/__tests__", "tests"]
|
||||
"exclude": [
|
||||
"./*.js",
|
||||
"./*.d.ts",
|
||||
"config",
|
||||
"**/__tests__",
|
||||
"tests",
|
||||
"coverage"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/tsconfig",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
@@ -7,4 +8,4 @@
|
||||
"adapters.json",
|
||||
"base.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
133
pnpm-lock.yaml
generated
133
pnpm-lock.yaml
generated
@@ -5,9 +5,8 @@ importers:
|
||||
.:
|
||||
specifiers:
|
||||
'@actions/core': ^1.6.0
|
||||
'@commitlint/parse': 16.0.0
|
||||
'@balazsorban/monorepo-release': 0.0.4
|
||||
'@types/node': ^17.0.25
|
||||
'@types/semver': 7.3.9
|
||||
'@typescript-eslint/eslint-plugin': ^5.10.2
|
||||
'@typescript-eslint/parser': ^4.33.0
|
||||
eslint: ^7.32.0
|
||||
@@ -17,21 +16,15 @@ importers:
|
||||
eslint-plugin-jest: ^25.3.0
|
||||
eslint-plugin-node: ^11.1.0
|
||||
eslint-plugin-promise: ^6.0.0
|
||||
git-log-parser: 1.2.0
|
||||
husky: ^7.0.4
|
||||
prettier: 2.4.1
|
||||
pretty-quick: ^3.1.2
|
||||
semver: 7.3.5
|
||||
stream-to-array: 2.3.0
|
||||
ts-node: 10.8.2
|
||||
turbo: 1.3.1
|
||||
type-fest: 2.16.0
|
||||
typescript: ^4.5.2
|
||||
devDependencies:
|
||||
'@actions/core': 1.9.0
|
||||
'@commitlint/parse': 16.0.0
|
||||
'@balazsorban/monorepo-release': 0.0.4
|
||||
'@types/node': 17.0.45
|
||||
'@types/semver': 7.3.9
|
||||
'@typescript-eslint/eslint-plugin': 5.29.0_3ekaj7j3owlolnuhj3ykrb7u7i
|
||||
'@typescript-eslint/parser': 4.33.0_hxadhbs2xogijvk7vq4t2azzbu
|
||||
eslint: 7.32.0
|
||||
@@ -41,15 +34,10 @@ importers:
|
||||
eslint-plugin-jest: 25.7.0_vibe533nrfhlkvcegtsn4treva
|
||||
eslint-plugin-node: 11.1.0_eslint@7.32.0
|
||||
eslint-plugin-promise: 6.0.0_eslint@7.32.0
|
||||
git-log-parser: 1.2.0
|
||||
husky: 7.0.4
|
||||
prettier: 2.4.1
|
||||
pretty-quick: 3.1.3_prettier@2.4.1
|
||||
semver: 7.3.5
|
||||
stream-to-array: 2.3.0
|
||||
ts-node: 10.8.2_x2utdhayajzrh747hktprshhby
|
||||
turbo: 1.3.1
|
||||
type-fest: 2.16.0
|
||||
typescript: 4.7.4
|
||||
|
||||
apps/dev:
|
||||
@@ -3642,6 +3630,17 @@ packages:
|
||||
'@babel/helper-validator-identifier': 7.16.7
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
/@balazsorban/monorepo-release/0.0.4:
|
||||
resolution: {integrity: sha512-jjYc05vcRueT+nC7BD7C0D2JjE+H8xDdAIfwjtlbMHTnTwPx2KYXrbWohbL7bGVN8ZbhJDmXkXOQjppSrZCQBw==}
|
||||
engines: {node: '>=16.16.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@commitlint/parse': 17.0.0
|
||||
git-log-parser: 1.2.0
|
||||
semver: 7.3.7
|
||||
stream-to-array: 2.3.0
|
||||
dev: true
|
||||
|
||||
/@bcoe/v8-coverage/0.2.3:
|
||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||
dev: true
|
||||
@@ -3655,29 +3654,22 @@ packages:
|
||||
engines: {node: '>=0.1.90'}
|
||||
requiresBuild: true
|
||||
|
||||
/@commitlint/parse/16.0.0:
|
||||
resolution: {integrity: sha512-F9EjFlMw4MYgBEqoRrWZZKQBzdiJzPBI0qFDFqwUvfQsMmXEREZ242T4R5bFwLINWaALFLHEIa/FXEPa6QxCag==}
|
||||
engines: {node: '>=v12'}
|
||||
/@commitlint/parse/17.0.0:
|
||||
resolution: {integrity: sha512-cKcpfTIQYDG1ywTIr5AG0RAiLBr1gudqEsmAGCTtj8ffDChbBRxm6xXs2nv7GvmJN7msOt7vOKleLvcMmRa1+A==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
'@commitlint/types': 16.2.1
|
||||
'@commitlint/types': 17.0.0
|
||||
conventional-changelog-angular: 5.0.13
|
||||
conventional-commits-parser: 3.2.4
|
||||
dev: true
|
||||
|
||||
/@commitlint/types/16.2.1:
|
||||
resolution: {integrity: sha512-7/z7pA7BM0i8XvMSBynO7xsB3mVQPUZbVn6zMIlp/a091XJ3qAXRXc+HwLYhiIdzzS5fuxxNIHZMGHVD4HJxdA==}
|
||||
engines: {node: '>=v12'}
|
||||
/@commitlint/types/17.0.0:
|
||||
resolution: {integrity: sha512-hBAw6U+SkAT5h47zDMeOu3HSiD0SODw4Aq7rRNh1ceUmL7GyLKYhPbUvlRWqZ65XjBLPHZhFyQlRaPNz8qvUyQ==}
|
||||
engines: {node: '>=v14'}
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
dev: true
|
||||
|
||||
/@cspotcode/source-map-support/0.8.1:
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
dev: true
|
||||
|
||||
/@dabh/diagnostics/2.0.3:
|
||||
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
|
||||
dependencies:
|
||||
@@ -5501,13 +5493,6 @@ packages:
|
||||
'@jridgewell/resolve-uri': 3.0.7
|
||||
'@jridgewell/sourcemap-codec': 1.4.13
|
||||
|
||||
/@jridgewell/trace-mapping/0.3.9:
|
||||
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.0.7
|
||||
'@jridgewell/sourcemap-codec': 1.4.13
|
||||
dev: true
|
||||
|
||||
/@js-joda/core/3.2.0:
|
||||
resolution: {integrity: sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg==}
|
||||
dev: true
|
||||
@@ -6444,22 +6429,6 @@ packages:
|
||||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
/@tsconfig/node10/1.0.9:
|
||||
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
||||
dev: true
|
||||
|
||||
/@tsconfig/node12/1.0.11:
|
||||
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
|
||||
dev: true
|
||||
|
||||
/@tsconfig/node14/1.0.3:
|
||||
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
|
||||
dev: true
|
||||
|
||||
/@tsconfig/node16/1.0.3:
|
||||
resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==}
|
||||
dev: true
|
||||
|
||||
/@types/aria-query/4.2.2:
|
||||
resolution: {integrity: sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==}
|
||||
dev: true
|
||||
@@ -6956,10 +6925,6 @@ packages:
|
||||
/@types/scheduler/0.16.2:
|
||||
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
|
||||
|
||||
/@types/semver/7.3.9:
|
||||
resolution: {integrity: sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==}
|
||||
dev: true
|
||||
|
||||
/@types/serve-index/1.9.1:
|
||||
resolution: {integrity: sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==}
|
||||
dependencies:
|
||||
@@ -7766,10 +7731,6 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/arg/4.1.3:
|
||||
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
|
||||
dev: true
|
||||
|
||||
/arg/5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
dev: false
|
||||
@@ -9437,10 +9398,6 @@ packages:
|
||||
readable-stream: 3.6.0
|
||||
dev: true
|
||||
|
||||
/create-require/1.1.1:
|
||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
dev: true
|
||||
|
||||
/cross-env/5.2.1:
|
||||
resolution: {integrity: sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
@@ -10570,11 +10527,6 @@ packages:
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
|
||||
dev: true
|
||||
|
||||
/diff/4.0.2:
|
||||
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
dev: true
|
||||
|
||||
/dir-glob/3.0.1:
|
||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -20936,37 +20888,6 @@ packages:
|
||||
yargs-parser: 20.2.9
|
||||
dev: true
|
||||
|
||||
/ts-node/10.8.2_x2utdhayajzrh747hktprshhby:
|
||||
resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.9
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.3
|
||||
'@types/node': 17.0.45
|
||||
acorn: 8.7.1
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 4.7.4
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/tsconfig-paths/3.14.1:
|
||||
resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==}
|
||||
dependencies:
|
||||
@@ -21206,11 +21127,6 @@ packages:
|
||||
engines: {node: '>=12.20'}
|
||||
dev: false
|
||||
|
||||
/type-fest/2.16.0:
|
||||
resolution: {integrity: sha512-qpaThT2HQkFb83gMOrdKVsfCN7LKxP26Yq+smPzY1FqoHRjqmjqHXA7n5Gkxi8efirtbeEUxzfEdePthQWCuHw==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: true
|
||||
|
||||
/type-is/1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -21761,10 +21677,6 @@ packages:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
hasBin: true
|
||||
|
||||
/v8-compile-cache-lib/3.0.1:
|
||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||
dev: true
|
||||
|
||||
/v8-compile-cache/2.3.0:
|
||||
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
|
||||
dev: true
|
||||
@@ -22424,11 +22336,6 @@ packages:
|
||||
yargs-parser: 21.0.1
|
||||
dev: true
|
||||
|
||||
/yn/3.1.1:
|
||||
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/yocto-queue/0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
import type {
|
||||
Commit,
|
||||
GroupedCommits as GrouppedCommits,
|
||||
PackageToRelease,
|
||||
} from "./types"
|
||||
|
||||
import { debug, pkgJson, execSync } from "./utils"
|
||||
import semver from "semver"
|
||||
import parseCommit from "@commitlint/parse"
|
||||
import gitLog from "git-log-parser"
|
||||
import streamToArray from "stream-to-array"
|
||||
|
||||
export async function analyze(options: {
|
||||
dryRun: boolean
|
||||
packages: Record<string, string>
|
||||
BREAKING_COMMIT_MSG: string
|
||||
RELEASE_COMMIT_MSG: string
|
||||
RELEASE_COMMIT_TYPES: string[]
|
||||
}): Promise<PackageToRelease[]> {
|
||||
const {
|
||||
packages,
|
||||
BREAKING_COMMIT_MSG,
|
||||
RELEASE_COMMIT_MSG,
|
||||
RELEASE_COMMIT_TYPES,
|
||||
} = options
|
||||
|
||||
const packageFolders = Object.values(options.packages)
|
||||
|
||||
console.log("Identifying latest tag...")
|
||||
const latestTag = execSync("git describe --tags --abbrev=0", {
|
||||
stdio: "pipe",
|
||||
})
|
||||
.toString()
|
||||
.trim()
|
||||
console.log(`Latest tag identified: ${latestTag}`)
|
||||
|
||||
console.log()
|
||||
|
||||
console.log("Identifying commits since the latest tag...")
|
||||
|
||||
const range = `${latestTag}..HEAD`
|
||||
|
||||
// Get the commits since the latest tag
|
||||
const commitsSinceLatestTag = await new Promise<Commit[]>(
|
||||
(resolve, reject) => {
|
||||
const stream = gitLog.parse({ _: range })
|
||||
streamToArray(stream, (err: Error, arr: any[]) => {
|
||||
if (err) return reject(err)
|
||||
|
||||
Promise.all(
|
||||
arr.map(async (d) => {
|
||||
const parsed = await parseCommit(d.subject)
|
||||
|
||||
return { ...d, parsed }
|
||||
})
|
||||
).then((res) => resolve(res.filter(Boolean)))
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
console.log(commitsSinceLatestTag.length, `commits found since ${latestTag}`)
|
||||
debug(
|
||||
"Analyzing the following commits:",
|
||||
commitsSinceLatestTag.map((c) => ` ${c.subject}`).join("\n")
|
||||
)
|
||||
|
||||
const lastCommit = commitsSinceLatestTag[0]
|
||||
|
||||
if (lastCommit?.parsed.raw === RELEASE_COMMIT_MSG) {
|
||||
debug("Already released...")
|
||||
return []
|
||||
}
|
||||
|
||||
console.log()
|
||||
console.log("Identifying commits that touched package code...")
|
||||
function getChangedFiles(commitSha: string) {
|
||||
return execSync(
|
||||
`git diff-tree --no-commit-id --name-only -r ${commitSha}`,
|
||||
{ stdio: "pipe" }
|
||||
)
|
||||
.toString()
|
||||
.trim()
|
||||
.split("\n")
|
||||
}
|
||||
const packageCommits = commitsSinceLatestTag.filter(({ commit }) => {
|
||||
const changedFiles = getChangedFiles(commit.short)
|
||||
return packageFolders.some((packageFolder) =>
|
||||
changedFiles.some((changedFile) => changedFile.startsWith(packageFolder))
|
||||
)
|
||||
})
|
||||
|
||||
console.log(packageCommits.length, "commits touched package code")
|
||||
|
||||
console.log()
|
||||
|
||||
console.log("Identifying packages that need a new release...")
|
||||
|
||||
const packagesNeedRelease: string[] = []
|
||||
const grouppedPackages = packageCommits.reduce((acc, commit) => {
|
||||
const changedFilesInCommit = getChangedFiles(commit.commit.short)
|
||||
|
||||
for (const [pkg, src] of Object.entries(packages)) {
|
||||
if (
|
||||
changedFilesInCommit.some((changedFile) => changedFile.startsWith(src))
|
||||
) {
|
||||
if (!(pkg in acc)) {
|
||||
acc[pkg] = { features: [], bugfixes: [], other: [], breaking: [] }
|
||||
}
|
||||
const { type } = commit.parsed
|
||||
if (RELEASE_COMMIT_TYPES.includes(type)) {
|
||||
if (!packagesNeedRelease.includes(pkg)) {
|
||||
packagesNeedRelease.push(pkg)
|
||||
}
|
||||
if (type === "feat") {
|
||||
acc[pkg].features.push(commit)
|
||||
if (commit.body.includes(BREAKING_COMMIT_MSG)) {
|
||||
const [, changesBody] = commit.body.split(BREAKING_COMMIT_MSG)
|
||||
acc[pkg].breaking.push({
|
||||
...commit,
|
||||
body: changesBody.trim(),
|
||||
})
|
||||
}
|
||||
} else acc[pkg].bugfixes.push(commit)
|
||||
} else {
|
||||
acc[pkg].other.push(commit)
|
||||
}
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<string, GrouppedCommits>)
|
||||
|
||||
if (packagesNeedRelease.length) {
|
||||
console.log(
|
||||
packagesNeedRelease.length,
|
||||
`new release(s) needed: ${packagesNeedRelease.join(", ")}`
|
||||
)
|
||||
} else {
|
||||
console.log("No packages needed a new release, BYE!")
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
console.log()
|
||||
|
||||
const packagesToRelease: PackageToRelease[] = []
|
||||
for await (const pkgName of packagesNeedRelease) {
|
||||
const commits = grouppedPackages[pkgName]
|
||||
const releaseType: semver.ReleaseType = commits.breaking.length
|
||||
? "major" // 1.x.x
|
||||
: commits.features.length
|
||||
? "minor" // x.1.x
|
||||
: "patch" // x.x.1
|
||||
|
||||
const packageJson = await pkgJson.read(packages[pkgName])
|
||||
const oldVersion = packageJson.version!
|
||||
const newSemVer = semver.parse(semver.inc(oldVersion, releaseType))!
|
||||
|
||||
packagesToRelease.push({
|
||||
name: pkgName,
|
||||
oldVersion,
|
||||
newVersion: `${newSemVer.major}.${newSemVer.minor}.${newSemVer.patch}`,
|
||||
commits,
|
||||
path: packages[pkgName],
|
||||
})
|
||||
}
|
||||
|
||||
return packagesToRelease
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
export const config = {
|
||||
releaseBranches: ["main"],
|
||||
// TODO: Generate dynamically
|
||||
packages: {
|
||||
"next-auth": "packages/next-auth",
|
||||
"@next-auth/dgraph-adapter": "packages/adapter-dgraph",
|
||||
"@next-auth/fauna-adapter": "packages/adapter-fauna",
|
||||
"@next-auth/mikro-orm-adapter": "packages/adapter-mikro-orm",
|
||||
"@next-auth/neo4j-adapter": "packages/adapter-neo4j",
|
||||
"@next-auth/prisma-adapter": "packages/adapter-prisma",
|
||||
"@next-auth/upstash-redis-adapter": "packages/adapter-upstash-redis",
|
||||
"@next-auth/dynamodb-adapter": "packages/adapter-dynamodb",
|
||||
"@next-auth/firebase-adapter": "packages/adapter-firebase",
|
||||
"@next-auth/mongodb-adapter": "packages/adapter-mongodb",
|
||||
"@next-auth/pouchdb-adapter": "packages/adapter-pouchdb",
|
||||
"@next-auth/sequelize-adapter": "packages/adapter-sequelize",
|
||||
"@next-auth/typeorm-legacy-adapter": "packages/adapter-typeorm-legacy",
|
||||
},
|
||||
rootDir: process.cwd(),
|
||||
BREAKING_COMMIT_MSG: "BREAKING CHANGE:",
|
||||
RELEASE_COMMIT_MSG: "chore(release): bump package version(s) [skip ci]",
|
||||
RELEASE_COMMIT_TYPES: ["feat", "fix"],
|
||||
dryRun:
|
||||
!process.env.CI ||
|
||||
!!process.env.DRY_RUN ||
|
||||
process.argv.includes("--dry-run"),
|
||||
verbose: !!process.env.VERBOSE || process.argv.includes("--verbose"),
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { config } from "./config"
|
||||
import { shouldSkip } from "./skip"
|
||||
import { verify as verify } from "./verify"
|
||||
import { analyze } from "./analyze"
|
||||
import { publish } from "./publish"
|
||||
import { debug } from "./utils"
|
||||
|
||||
async function run() {
|
||||
if (config.dryRun) {
|
||||
console.log("\nPerforming dry run, no packages will be published!\n")
|
||||
}
|
||||
|
||||
if (shouldSkip({ releaseBranches: config.releaseBranches })) {
|
||||
return
|
||||
}
|
||||
|
||||
if (config.dryRun) {
|
||||
console.log("\nDry run, skip validation...\n")
|
||||
} else {
|
||||
await verify()
|
||||
}
|
||||
|
||||
const packages = await analyze(config)
|
||||
|
||||
if (!packages.length) return
|
||||
|
||||
debug(
|
||||
"Packages to release:",
|
||||
packages
|
||||
.map((p) =>
|
||||
JSON.stringify(
|
||||
{
|
||||
...p,
|
||||
commits: `${p.commits.features.length} feature(s), ${p.commits.bugfixes.length} bugfixe(s), ${p.commits.other.length} other(s) and ${p.commits.breaking.length} breaking change(s)`,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
.join("\n")
|
||||
)
|
||||
|
||||
await publish({ ...config, packages })
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.log(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -1,134 +0,0 @@
|
||||
import type { Commit, PackageToRelease } from "./types"
|
||||
|
||||
import { debug, pkgJson, execSync } from "./utils"
|
||||
|
||||
export async function publish(options: {
|
||||
dryRun: boolean
|
||||
packages: PackageToRelease[]
|
||||
RELEASE_COMMIT_MSG: string
|
||||
}) {
|
||||
const { dryRun, packages, RELEASE_COMMIT_MSG } = options
|
||||
|
||||
execSync("pnpm build")
|
||||
|
||||
for await (const pkg of packages) {
|
||||
if (dryRun) {
|
||||
console.log(
|
||||
`Dry run, \`npm publish\` would have released package \`${pkg.name}\` with version "${pkg.newVersion}".`
|
||||
)
|
||||
} else {
|
||||
console.log(
|
||||
`Writing version "${pkg.newVersion}" to package.json for package \`${pkg.name}\``
|
||||
)
|
||||
await pkgJson.update(pkg.path, { version: pkg.newVersion })
|
||||
console.log("package.json file has been written, publishing...")
|
||||
}
|
||||
|
||||
let npmPublish = `pnpm publish --access public --registry=https://registry.npmjs.org --no-git-checks`
|
||||
// We use different tokens for `next-auth` and `@next-auth/*` packages
|
||||
|
||||
if (pkg.name === "next-auth") {
|
||||
process.env.NPM_TOKEN = process.env.NPM_TOKEN_PKG
|
||||
} else {
|
||||
process.env.NPM_TOKEN = process.env.NPM_TOKEN_ORG
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log(
|
||||
`Dry run, skip \`npm publish\` for package \`${pkg.name}\`...\n`
|
||||
)
|
||||
npmPublish += " --dry-run --silent"
|
||||
} else {
|
||||
execSync(
|
||||
"echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc",
|
||||
{ cwd: pkg.path }
|
||||
)
|
||||
}
|
||||
|
||||
execSync(npmPublish, { cwd: pkg.path })
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
console.log("Dry run, skip release commit...")
|
||||
} else {
|
||||
execSync(`git add -A && git commit -m "${RELEASE_COMMIT_MSG}"`)
|
||||
console.log("Commited.")
|
||||
}
|
||||
|
||||
for (const pkg of packages) {
|
||||
const { name, oldVersion, newVersion } = pkg
|
||||
const gitTag = `${name}@v${newVersion}`
|
||||
|
||||
console.log(
|
||||
`\n\n-------------------------------\n${name} ${oldVersion} -> ${newVersion}`
|
||||
)
|
||||
|
||||
const changelog = createChangelog(pkg)
|
||||
debug("Changelog generated", changelog)
|
||||
|
||||
if (dryRun) {
|
||||
console.log(`Dry run, skip git tag/release notes for package \`${name}\``)
|
||||
} else {
|
||||
console.log(`Creating git tag...`)
|
||||
execSync(`git tag ${gitTag}`)
|
||||
execSync("git push --tags")
|
||||
console.log(`Creating GitHub release notes...`)
|
||||
execSync(`gh release create ${gitTag} --notes '${changelog}'`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
execSync(`git push`)
|
||||
}
|
||||
}
|
||||
|
||||
function createChangelog(pkg: PackageToRelease) {
|
||||
const {
|
||||
commits: { features, breaking, bugfixes, other },
|
||||
} = pkg
|
||||
console.log(`Creating changelog for package \`${pkg.name}\`...`)
|
||||
|
||||
let changelog = ``
|
||||
changelog += listGroup("Features", features)
|
||||
changelog += listGroup("Bugfixes", bugfixes)
|
||||
changelog += listGroup("Other", other)
|
||||
|
||||
if (breaking.length) {
|
||||
changelog += `
|
||||
## BREAKING CHANGES
|
||||
|
||||
${breaking.map((c) => ` - ${c.body}`).join("\n")}`
|
||||
}
|
||||
|
||||
return changelog
|
||||
}
|
||||
|
||||
function sortByScope(commits: Commit[]) {
|
||||
return commits.sort((a, b) => {
|
||||
if (a.parsed.scope && b.parsed.scope) {
|
||||
return a.parsed.scope.localeCompare(b.parsed.scope)
|
||||
} else if (a.parsed.scope) return -1
|
||||
else if (b.parsed.scope) return 1
|
||||
return a.body.localeCompare(b.body)
|
||||
})
|
||||
}
|
||||
|
||||
function header(c: Commit) {
|
||||
let h = c.parsed.subject
|
||||
if (c.parsed.scope) {
|
||||
h = `**${c.parsed.scope}**: ${h} (${c.commit.short})`
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
function listGroup(heading: string, commits: Commit[]) {
|
||||
if (!commits.length) return ""
|
||||
const list = sortByScope(commits)
|
||||
.map((c) => ` - ${header(c)}`)
|
||||
.join("\n")
|
||||
return `## ${heading}
|
||||
|
||||
${list}
|
||||
|
||||
`
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
export function shouldSkip(options: { releaseBranches: string[] }) {
|
||||
if (!process.env.CI) {
|
||||
return false
|
||||
}
|
||||
const { releaseBranches } = options
|
||||
|
||||
const branch = process.env.GITHUB_REF_NAME
|
||||
if (!branch || !releaseBranches.includes(branch)) {
|
||||
console.log(`\nSkipping release for branch "${branch}"`)
|
||||
console.log(
|
||||
`Releases are only triggered for the following branches: ${releaseBranches.join(
|
||||
", "
|
||||
)}\n`
|
||||
)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true
|
||||
},
|
||||
"ts-node": {
|
||||
"swc": true
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
export interface Commit {
|
||||
commit: CommitOrTree
|
||||
tree: CommitOrTree
|
||||
author: AuthorOrCommitter
|
||||
committer: AuthorOrCommitter
|
||||
subject: string
|
||||
body: string
|
||||
parsed: Parsed
|
||||
}
|
||||
|
||||
export interface CommitOrTree {
|
||||
long: string
|
||||
short: string
|
||||
}
|
||||
|
||||
export interface AuthorOrCommitter {
|
||||
name: string
|
||||
email: string
|
||||
date: string
|
||||
}
|
||||
|
||||
export interface Parsed {
|
||||
type: string
|
||||
scope?: string | null
|
||||
subject: string
|
||||
merge?: null
|
||||
header: string
|
||||
body?: null
|
||||
footer?: null
|
||||
notes?: null[] | null
|
||||
references?: null[] | null
|
||||
mentions?: null[] | null
|
||||
revert?: null
|
||||
raw: string
|
||||
}
|
||||
|
||||
export interface Package {
|
||||
name: string
|
||||
srcDir: string
|
||||
peerDependencies?: string[]
|
||||
}
|
||||
|
||||
export interface BranchConfig {
|
||||
prerelease: boolean
|
||||
ghRelease: boolean
|
||||
}
|
||||
|
||||
export interface PackageToRelease {
|
||||
name: string
|
||||
newVersion: string
|
||||
oldVersion: string
|
||||
commits: GroupedCommits
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface GroupedCommits {
|
||||
features: Commit[]
|
||||
bugfixes: Commit[]
|
||||
other: Commit[]
|
||||
breaking: Commit[]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { PackageJson } from "type-fest"
|
||||
|
||||
import fs from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { execSync as nodeExecSync } from "node:child_process"
|
||||
import { config } from "./config"
|
||||
|
||||
async function read(directory: string): Promise<PackageJson> {
|
||||
const content = await fs.readFile(
|
||||
path.join(process.cwd(), directory, "package.json"),
|
||||
"utf8"
|
||||
)
|
||||
return JSON.parse(content)
|
||||
}
|
||||
|
||||
async function update(
|
||||
directory: string,
|
||||
data: Partial<PackageJson>
|
||||
): Promise<void> {
|
||||
const original = await pkgJson.read(directory)
|
||||
const content = JSON.stringify({ ...original, ...data }, null, 2)
|
||||
await fs.writeFile(
|
||||
path.join(process.cwd(), directory, "package.json"),
|
||||
content,
|
||||
"utf8"
|
||||
)
|
||||
}
|
||||
|
||||
export const pkgJson = { read, update }
|
||||
|
||||
export function debug(...args: any[]): void {
|
||||
if (!config.verbose) return
|
||||
const [first, ...rest] = args
|
||||
console.log(`\n[debug] ${first}\n`, ...rest, "\n")
|
||||
}
|
||||
|
||||
export function execSync(...args: Parameters<typeof nodeExecSync>) {
|
||||
return nodeExecSync(args[0], { stdio: "inherit", ...args[1] })
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export async function verify() {
|
||||
if (!process.env.NPM_TOKEN_PKG) {
|
||||
throw new Error("NPM_TOKEN_PKG is not set")
|
||||
}
|
||||
if (!process.env.NPM_TOKEN_ORG) {
|
||||
throw new Error("NPM_TOKEN_ORG is not set")
|
||||
}
|
||||
if (!process.env.RELEASE_TOKEN) {
|
||||
throw new Error("RELEASE_TOKEN is not set")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user