remix-utils
Advanced tools
Comparing version 1.0.2 to 2.0.0
121
package.json
{ | ||
"name": "remix-utils", | ||
"version": "1.0.2", | ||
"version": "2.0.0", | ||
"license": "MIT", | ||
"main": "dist/index.js", | ||
"typings": "dist/index.d.ts", | ||
"files": [ | ||
"dist", | ||
"src" | ||
], | ||
"engines": { | ||
"node": ">=14" | ||
"node": ">=16" | ||
}, | ||
"scripts": { | ||
"start": "tsdx watch", | ||
"build": "tsdx build", | ||
"test": "tsdx test", | ||
"lint": "tsdx lint", | ||
"prepare": "tsdx build", | ||
"size": "size-limit", | ||
"analyze": "size-limit --why" | ||
"build": "tsc --project tsconfig.json", | ||
"typecheck": "tsc --project tsconfig.json --noEmit", | ||
"lint": "eslint --ext .ts,.tsx src/", | ||
"test": "jest --config=config/jest.config.ts --passWithNoTests", | ||
"setup": "remix setup", | ||
"postinstall": "npm run setup" | ||
}, | ||
"author": { | ||
"name": "Sergio Xalambrí", | ||
"url": "https://sergiodxa.com", | ||
"email": "hello@sergiodxa.com" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/sergiodxa/remix-utils" | ||
}, | ||
"keywords": [ | ||
"remix", | ||
"remix.run", | ||
"react", | ||
"utils", | ||
"request", | ||
"response", | ||
"csrf", | ||
"redirect-back", | ||
"outlet", | ||
"client-only", | ||
"hydrated", | ||
"body parser" | ||
], | ||
"peerDependencies": { | ||
"@remix-run/node": "^0.17.0", | ||
"@remix-run/react": "^0.17.0", | ||
"@remix-run/node": "^0.20.1", | ||
"@remix-run/react": "^0.20.1", | ||
"react": "^17.0.2", | ||
"remix": "^0.17.0" | ||
"remix": "^0.20.1" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "tsdx lint" | ||
} | ||
}, | ||
"prettier": { | ||
"printWidth": 80, | ||
"semi": true, | ||
"singleQuote": true, | ||
"trailingComma": "es5" | ||
}, | ||
"author": "Sergio Xalambrí", | ||
"module": "dist/remix-utils.esm.js", | ||
"size-limit": [ | ||
{ | ||
"path": "dist/remix-utils.cjs.production.min.js", | ||
"limit": "10 KB" | ||
}, | ||
{ | ||
"path": "dist/remix-utils.esm.js", | ||
"limit": "10 KB" | ||
} | ||
], | ||
"devDependencies": { | ||
"@babel/core": "^7.16.0", | ||
"@babel/preset-env": "^7.16.0", | ||
"@babel/preset-react": "^7.16.0", | ||
"@babel/preset-typescript": "^7.16.0", | ||
"@jest/types": "^27.2.5", | ||
"@remix-run/dev": "^0.20.1", | ||
"@remix-run/node": "^0.20.1", | ||
"@remix-run/react": "^0.20.1", | ||
"@size-limit/preset-small-lib": "^4.10.2", | ||
"husky": "^6.0.0", | ||
"size-limit": "^4.10.2", | ||
"tsdx": "^0.14.1", | ||
"tslib": "^2.2.0", | ||
"typescript": "^4.2.4", | ||
"@remix-run/node": "^0.17.0", | ||
"@remix-run/react": "^0.17.0", | ||
"@testing-library/jest-dom": "^5.15.0", | ||
"@testing-library/react": "^12.1.2", | ||
"@types/react": "^17.0.14", | ||
"@typescript-eslint/eslint-plugin": "^5.3.0", | ||
"@typescript-eslint/parser": "^5.3.0", | ||
"babel-jest": "^27.3.1", | ||
"eslint": "^7.26.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-import-resolver-typescript": "^2.5.0", | ||
"eslint-plugin-cypress": "^2.11.3", | ||
"eslint-plugin-import": "^2.22.1", | ||
"eslint-plugin-jest": "^24.3.6", | ||
"eslint-plugin-jest-dom": "^3.9.0", | ||
"eslint-plugin-jsx-a11y": "^6.4.1", | ||
"eslint-plugin-prettier": "^3.4.0", | ||
"eslint-plugin-promise": "^5.1.0", | ||
"eslint-plugin-react": "^7.26.1", | ||
"eslint-plugin-react-hooks": "^4.2.0", | ||
"eslint-plugin-testing-library": "^4.3.0", | ||
"eslint-plugin-unicorn": "^32.0.1", | ||
"jest": "^27.3.1", | ||
"jest-fetch-mock": "^3.0.3", | ||
"react": "^17.0.2", | ||
"remix": "^0.17.0" | ||
"react-router-dom": "^6.0.0-beta.6", | ||
"remix": "^0.20.1", | ||
"ts-node": "^10.4.0", | ||
"typescript": "^4.2.4" | ||
}, | ||
"dependencies": { | ||
"prettier": "^2.4.1", | ||
"type-fest": "^2.5.2" | ||
} | ||
} |
469
README.md
# Remix Utils | ||
This package contains simple utility functions and types to use together with [Remix.run](https://remix.run). | ||
This package contains simple utility functions to use with [Remix.run](https://remix.run). | ||
**To install it you need to have a valid Remix license** | ||
## Installation | ||
```bash | ||
yarn add remix-utils | ||
npm install remix-utils | ||
npm install remix-utils remix @remix-run/node @remix-run/react react | ||
``` | ||
Remember you need to also install `remix`, `@remix-run/node`, `@remix-run/react` and `react`. For the first three you need a paid Remix license. | ||
## API Reference | ||
## Imports | ||
### ClientOnly | ||
The ClientOnly component lets you render the children element only on the client-side, avoiding rendering it the server-side. | ||
You can, optionally, provide a fallback component to be used on SSR. | ||
```tsx | ||
import { ClientOnly } from "remix-utils/react"; | ||
export default function View() { | ||
return ( | ||
<ClientOnly fallback={<SimplerStaticVersion />}> | ||
<ComplexComponentNeedingBrowserEnvironment /> | ||
</ClientOnly> | ||
); | ||
} | ||
``` | ||
This component is handy when you have some complex component that needs a browser environment to work, like a chart or a map. This way, you can avoid rendering it server-side and instead use a simpler static version like an SVG or even a loading UI. | ||
The rendering flow will be: | ||
- SSR: Always render the fallback. | ||
- CSR First Render: Always render the fallback. | ||
- CSR Update: Update to render the actual component. | ||
- CSR Futur Renders: Always render the actual component, don't bother to render the fallback. | ||
This component uses the `useHydrated` hook internally. | ||
### CSRF | ||
The CSRF related functions let you implement CSRF protection on your application. | ||
This part of Remix Utils needs packages from `remix-utils/server` and `remix-utils/react` to work. | ||
#### Generate the authenticity token | ||
In the server, we need to add to our `root` component the following. | ||
```ts | ||
import { redirectBack, parseBody, json } from "remix-utils"; | ||
import type { | ||
LoaderArgs, | ||
LoaderReturn, | ||
ActionArgs, | ||
ActionReturn, | ||
LinksArgs, | ||
LinksReturn, | ||
MetaArgs, | ||
MetaReturn, | ||
HeadersArgs, | ||
HeadersReturn, | ||
} from "remix-utils"; | ||
import type { LoaderFunction } from "remix"; | ||
import { createAuthenticityToken, json } from "remix-utils/server"; | ||
import { getSession, commitSession } from "~/services/session.server"; | ||
interface LoaderData { | ||
csrf: string; | ||
} | ||
export let loader: LoaderFunction = async ({ request }) => { | ||
let session = await getSession(request.headers.get("cookie")); | ||
let token = createAuthenticityToken(session); | ||
return json<LoaderData>({ csrf: token }, { | ||
headers: await commitSession(session) | ||
}); | ||
} | ||
``` | ||
## API | ||
The `createAuthenticityToken` function receives a session object and stores the authenticity token there using the `csrf` key (you can pass the key name as a second argument). Finally, you need to return the token in a `json` response and commit the session. | ||
### `redirectBack` | ||
#### Render the AuthenticityTokenProvider | ||
This function is a wrapper of the `redirect` helper from Remix, contrarian to Remix's version this one receives the whole request object as first value and an object with the response init and a fallback URL. | ||
You need to read the authenticity token and render the `AuthenticityTokenProvider` component wrapping your code in your root. | ||
The response created with this function will have the `Location` header pointing to the `Referer` header from the request, or if not available the fallback URL provided in the second argument. | ||
```tsx | ||
import { Outlet, useLoaderData } from "remix"; | ||
import { Document } from "~/components/document"; | ||
export default function Root() { | ||
let { csrf } = useLoaderData<LoaderData>(); | ||
return ( | ||
<AuthenticityTokenProvider value={csrf}> | ||
<Document> | ||
<Outlet /> | ||
</Document> | ||
</AuthenticityTokenProvider> | ||
); | ||
} | ||
``` | ||
With this, your whole app can access the authenticity token generated in the root. | ||
#### Rendering a Form | ||
When you create a form in some route, you can use the `AuthenticityTokenInput` component to add the authenticity token to the form. | ||
```tsx | ||
import { Form } from "remix"; | ||
import { AuthenticityTokenInput } from "remix-utils/react"; | ||
export default function SomeRoute() { | ||
return ( | ||
<Form method="post"> | ||
<AuthenticityTokenInput /> | ||
<input type="text" name="something" /> | ||
</Form> | ||
); | ||
} | ||
``` | ||
Note that the authenticity token is only really needed for a form that mutates the data somehow. If you have a search form making a GET request, you don't need to add the authenticity token there. | ||
This `AuthenticityTokenInput` will get the authenticity token from the `AuthenticityTokenProvider` component and add it to the form as the value of a hidden input with the name `csrf`. You can customize the field name using the `name` prop. | ||
```tsx | ||
<AuthenticityTokenInput name="customName" /> | ||
``` | ||
You should only customize the name if you also changed it on `createAuthenticityToken`. | ||
##### Alternative: Using `useAuthenticityToken` and `useFetcher`. | ||
If you need to use `useFetcher` (or `useSubmit`) instead of `Form` you can also get the authenticity token with the `useAuthenticityToken` hook. | ||
```tsx | ||
import { useFetcher } from "remix"; | ||
import { useAuthenticityToken } from "remix-utils/react"; | ||
export function useMarkAsRead() { | ||
let fetcher = useFetcher(); | ||
let csrf = useAuthenticityToken(); | ||
return function submit(data) { | ||
fetcher.submit({ csrf, ...data }, { action: "/action", method: "post" }); | ||
}; | ||
} | ||
``` | ||
#### Verify in the Action | ||
Finally, you need to verify the authenticity token in the action you received the request. | ||
```ts | ||
import { redirectBack } from "remix-utils"; | ||
import type { ActionArgs, ActionReturn } from "remix-utils"; | ||
import type { ActionFunction } from "remix"; | ||
import { verifyAuthenticityToken, redirectBack } from "remix-utils/server"; | ||
import { getSession, commitSession } from "~/services/session.server"; | ||
export function action({ request }: ActionArgs): ActionReturn { | ||
return redirectBack(request, { fallback: "/" }); | ||
export let action: ActionFunction = async ({ request }) => { | ||
let session = await getSession(request.headers.get("Cookie")); | ||
await verifyAuthenticityToken(session); | ||
// do something here | ||
return redirectBack(request, { fallback: "/fallback"}) | ||
} | ||
``` | ||
This helper is more useful when used in an action so you can send the user to the same URL it was before. | ||
Suppose the authenticity token is missing on the session, the request body, or doesn't match. In that case, the function will throw an Unprocessable Entity response that you can either catch and handle manually or let pass and render your CatchBoundary. | ||
### `parseBody` | ||
### Outlet & useParentData | ||
This function receives the whole request and returns a promise with an instance of `URLSearchParams`, and the body of the request already parsed. | ||
This wrapper of the Remix Outlet component lets you pass an optional `data` prop, then using the `useParentData` hook, you can access that data. | ||
Helpful to pass information from parent to child routes, for example, the authenticated user data. | ||
```tsx | ||
// parent route | ||
import { Outlet } from "remix-utils/react"; | ||
export default function Parent() { | ||
return <Outlet data={{ something: "here" }} />; | ||
} | ||
``` | ||
```tsx | ||
// child route | ||
import { useParentData } from "remix-utils/react"; | ||
export default function Child() { | ||
const data = useParentData(); | ||
return <div>{data.something}</div>; | ||
} | ||
``` | ||
### useHydrated | ||
This lets you detect if your component is already hydrated. This means the JS for the element loaded client-side and React is running. | ||
With useHydrated, you can render different things on the server and client while ensuring the hydration will not have a mismatched HTML. | ||
```ts | ||
import { parseBody, redirectBack } from "remix-utils"; | ||
import type { ActionArgs, ActionReturn } from "remix-utils"; | ||
import { useHydrated } from "remix-utils/react"; | ||
export function Component() { | ||
let isHydrated = useHydrated(); | ||
if (isHydrated) { | ||
return <ClientOnlyComponent />; | ||
} | ||
return <ServerFallback />; | ||
} | ||
``` | ||
When doing SSR, the value of `isHydrated` will always be `false`. The first client-side render `isHydrated` will still be false, and then it will change to `true`. | ||
After the first client-side render, future components rendered calling this hook will receive `true` as the value of `isHydrated`. This way, your server fallback UI will never be rendered on a route transition. | ||
### useShouldHydrate | ||
If you are building a Remix application where most routes are static, and you want to avoid loading client-side JS, you can use this hook, plus some conventions, to detect if one or more active routes needs JS and only render the Scripts component in that case. | ||
In your document component, you can call this hook to dynamically render the Scripts component if needed. | ||
```tsx | ||
import type { ReactNode } from "react"; | ||
import { Links, LiveReload, Meta, Scripts, } from "remix"; | ||
import { useShouldHydrate } from "remix-utils/react"; | ||
interface DocumentProps { | ||
children: ReactNode; | ||
title?: string; | ||
} | ||
export function Document({ children, title, }: DocumentProps) { | ||
let shouldHydrate = useShouldHydrate(); | ||
return ( | ||
<html lang="en"> | ||
<head> | ||
<meta charSet="utf-8" /> | ||
<link rel="icon" href="/favicon.png" type="image/png" /> | ||
{title ? <title>{title}</title> : null} | ||
<Meta /> | ||
<Links /> | ||
</head> | ||
<body> | ||
{children} | ||
{shouldHydrate && <Scripts />} | ||
{process.env.NODE_ENV === "development" && <LiveReload />} | ||
</body> | ||
</html> | ||
); | ||
} | ||
``` | ||
Now, you can export a `handle` object with the `hydrate` property as `true` in any route module. | ||
```ts | ||
export let handle = { hydrate: true }; | ||
``` | ||
This will mark the route as requiring JS hydration. | ||
In some cases, a route may need JS based on the data the loader returned. For example, if you have a component to purchase a product, but only authenticated users can see it, you don't need JS until the user is authenticated. In that case, you can make `hydrate` be a function receiving your loader data. | ||
```ts | ||
export let handle = { | ||
hydate(data: LoaderData) { | ||
return data.user.isAuthenticated; | ||
}, | ||
}; | ||
``` | ||
The `useShouldHydrate` hook will detect `hydrate` as a function and call it using the route data. | ||
### Body Parser | ||
These utilities let you parse the request body with a simple function call. You can parse it to a string, a URLSearchParams instance, or an object. | ||
#### toString | ||
This function receives the whole request and returns a promise with the body as a string. | ||
```ts | ||
import { bodyParser, redirectBack } from "remix-utils/server"; | ||
import type { ActionFunction } from "remix"; | ||
import { updateUser } from "../services/users"; | ||
export function action({ request, params }: ActionArgs): ActionReturn { | ||
const body = await parseBody(request); | ||
export let action: ActionFunction = async ({ request }) => { | ||
let body = await bodyParser.toString(request); | ||
body = new URLSearchParams(body); | ||
await updateUser(params.id, { username: body.get("username") }); | ||
@@ -70,12 +286,70 @@ return redirectBack(request, { fallback: "/" }); | ||
This is a simple wrapper over doing `new URLSearchParams(await request.text());`. | ||
#### toSearchParams | ||
### `json` | ||
This function receives the whole request and returns a promise with an instance of `URLSearchParams`, and the request's body is already parsed. | ||
This function is a typed version of the `json` helper provided by Remix, it accepts a generic (defaults to `unknown`) and ensure at the compiler lever that the data you are sending from your loader matches the provided type. It's more useful when you create a type or interface for your whole route so you can share it between `json` and `useRouteData` to ensure you are not missing or adding extra parameters to the response. | ||
```ts | ||
import { bodyParser, redirectBack } from "remix-utils/server"; | ||
import type { ActionFunction } from "remix"; | ||
import { updateUser } from "../services/users"; | ||
export let action: ActionFunction = async ({ request, params }) => { | ||
const body = await bodyParser.toSearchParams(request); | ||
await updateUser(params.id, { username: body.get("username") }); | ||
return redirectBack(request, { fallback: "/" }); | ||
} | ||
``` | ||
This is the same as doing: | ||
```ts | ||
let body = await bodyParser.toString(request); | ||
return new URLSearchParams(body); | ||
``` | ||
#### toJSON | ||
This function receives the whole request and returns a promise with an unknown value. That value is going to be the body of the request. | ||
The result is typed as `unknown` to force you to validate the object to ensure it's what you expect. This is because there's no way for TypeScript to know what the type of the body is since it's an entirely dynamic value. | ||
```ts | ||
import { bodyParser, redirectBack } from "remix-utils/server"; | ||
import type { ActionFunction } from "remix"; | ||
import { hasUsername } from "../validations/users"; | ||
import { updateUser } from "~/services/users"; | ||
export let action: ActionFunction = async ({ request }) => { | ||
const body = await bodyParser.toJSON(request); | ||
hasUsername(body); // this should throw if body doesn't have username | ||
// from this point you can do `body.username` | ||
await updateUser(params.id, { username: body.username }); | ||
return redirectBack(request, { fallback: "/" }); | ||
} | ||
``` | ||
This is the same as doing: | ||
```ts | ||
let body = await bodyParser.toSearchParams(request); | ||
return Object.fromEntries(params.entries()) as unknown; | ||
``` | ||
### Responses | ||
#### Typed JSON | ||
This function is a typed version of the `json` helper provided by Remix. It accepts a generic with the type of data you are going to send in the response. | ||
This helps ensure that the data you are sending from your loader matches the provided type at the compiler lever. It's more useful when you create an interface or type for your loader so you can share it between `json` and `useLoaderData` to help you avoid missing or extra parameters in the response. | ||
Again, this is not doing any kind of validation on the data you send. It's just a type checker. | ||
The generic extends JsonValue from [type-fest](https://github.com/sindresorhus/type-fest/blob/ff96fef37b84137e1600eebce108c2b797427c1f/source/basic.d.ts#L45), this limit the type of data you can send to anything that can be serializable, so you are not going to be able to send BigInt, functions, Symbols, etc. If `JSON.stringify` fails to try to stringify that value, it will not be supported. | ||
```tsx | ||
import { useRouteData } from "remix"; | ||
import { json } from "remix-utils"; | ||
import type { LoaderArgs, LoaderReturn } from "remix-utils"; | ||
import { useLoaderData } from "remix"; | ||
import type { LoaderFunction } from "remix"; | ||
import { json } from "remix-utils/server"; | ||
@@ -85,13 +359,13 @@ import { getUser } from "../services/users"; | ||
interface RouteData { | ||
interface LoaderData { | ||
user: User; | ||
} | ||
export async function loader({ request }: LoaderArgs): LoaderReturn { | ||
export let loader: LoaderFunction = async ({request}) => { | ||
const user = await getUser(request); | ||
return json<RouteData>({ user }); | ||
return json<LoaderData>({ user }); | ||
} | ||
export default function View() { | ||
const { user } = useRouteData<RouteData>(); | ||
const { user } = useLoaderData<LoaderData>(); | ||
return <h1>Hello, {user.name}</h1>; | ||
@@ -101,40 +375,100 @@ } | ||
### Types | ||
#### Redirect Back | ||
This package exports a list of useful types together with the utility functions, you can import them with | ||
This function is a wrapper of the `redirect` helper from Remix, contrarian to Remix's version. This one receives the whole request object as the first value and an object with the response init and a fallback URL. | ||
The response created with this function will have the `Location` header pointing to the `Referer` header from the request, or if not available, the fallback URL provided in the second argument. | ||
```ts | ||
import type { | ||
LoaderArgs, | ||
LoaderReturn, | ||
ActionArgs, | ||
ActionReturn, | ||
LinksArgs, | ||
LinksReturn, | ||
MetaArgs, | ||
MetaReturn, | ||
HeadersArgs, | ||
HeadersReturn, | ||
} from "remix-utils"; | ||
import { redirectBack } from "remix-utils/server"; | ||
import type { ActionFunction } from "remix"; | ||
export let action: ActionFunction = async ({ request }) => { | ||
await redirectBack(request, { fallback: "/" }); | ||
} | ||
``` | ||
This types are generated from the `LoaderFunction`, `ActionFunction`, `LinksFunction`, `MetaFunction` and `HeadersFunction` exported by Remix itself, this ensure they will be up to date with your Remix version. | ||
This helper is more useful when used in a generic action to send the user to the same URL it was before. | ||
All the `*Args` types are the first argument of the equivalent `*Funtion` type. | ||
All the `*Return` types are the return type of the equivalent `*Funtion` type. | ||
#### Bad Request | ||
They are exported in case you don't want to use arrow functions (like me) and still want the types so instead of doing: | ||
Helper function to create a Bad Request (400) response with a JSON body. | ||
```ts | ||
export const loader: LoaderFunction = async (args) => {}; | ||
import { badRequest } from "remix-utils/server"; | ||
import type { ActionFunction } from "remix"; | ||
export let action: ActionFunction = async () => { | ||
throw badRequest({ message: "You forgot something in the form." }); | ||
} | ||
``` | ||
You can do: | ||
#### Unauthorized | ||
Helper function to create an Unauthorized (401) response with a JSON body. | ||
```ts | ||
export async function loader(args: LoaderArgs): LoaderReturn {} | ||
import { unauthorized } from "remix-utils/server"; | ||
import type { LoaderFunction } from "remix"; | ||
export let loader: LoaderFunction = async () => { | ||
// usually what you really want is to throw a redirect to the login page | ||
throw unauthorized({ message: "You need to login." }); | ||
} | ||
``` | ||
Since all of them are TypeScript types they will not impact your bundle size in case you prefer to use the normal `*Function` types from Remix. | ||
#### Forbidden | ||
Helper function to create a Forbidden (403) response with a JSON body. | ||
```ts | ||
import { forbidden } from "remix-utils/server"; | ||
import type { LoaderFunction } from "remix"; | ||
export let loader: LoaderFunction = async () => { | ||
throw forbidden({ message: "You don't have access for this." }); | ||
} | ||
``` | ||
#### Not Found | ||
Helper function to create a Not Found (404) response with a JSON body. | ||
```ts | ||
import { notFound } from "remix-utils/server"; | ||
import type { LoaderFunction } from "remix"; | ||
export let loader: LoaderFunction = async () => { | ||
throw notFound({ message: "This doesn't exists." }); | ||
} | ||
``` | ||
#### Unprocessable Entity | ||
Helper function to create an Unprocessable Entity (422) response with a JSON body. | ||
```ts | ||
import { unprocessableEntity } from "remix-utils/server"; | ||
import type { LoaderFunction } from "remix"; | ||
export let loader: LoaderFunction = async () => { | ||
throw unprocessableEntity({ message: "This doesn't exists." }); | ||
} | ||
``` | ||
This is used by the CSRF validation. You probably don't want to use it directly. | ||
#### Server Error | ||
Helper function to create a Server Error (500) response with a JSON body. | ||
```ts | ||
import { serverError } from "remix-utils/server"; | ||
import type { LoaderFunction } from "remix"; | ||
export let loader: LoaderFunction = async () => { | ||
throw serverError({ message: "Something unexpected happened." }); | ||
} | ||
``` | ||
## Author | ||
@@ -147,2 +481,1 @@ | ||
- MIT License | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
47409
23
761
1
478
0
6
36
1
1
+ Addedprettier@^2.4.1
+ Addedtype-fest@^2.5.2
+ Addedprettier@2.8.8(transitive)
+ Addedremix@0.20.1(transitive)
+ Addedtype-fest@2.19.0(transitive)
- Removedremix@0.17.5(transitive)