diff --git a/.changeset/config.json b/.changeset/config.json
index 50ee5ae..6e24af1 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -3,7 +3,7 @@
"changelog": [
"@changesets/changelog-github",
{
- "repo": "un-ts/lib-boilerplate"
+ "repo": "un-ts/fetch-api"
}
],
"commit": false,
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d8ed634..a5143dd 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,8 +33,8 @@ jobs:
with:
publish: yarn release
version: yarn changeset version
- commit: 'chore: release lib-boilerplate'
- title: 'chore: release lib-boilerplate'
+ commit: 'chore: release fetch-api'
+ title: 'chore: release fetch-api'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/README.md b/README.md
index 28a347c..01b6391 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
-# lib-boilerplate
+# fetch-api
-[![GitHub Actions](https://github.com/un-ts/lib-boilerplate/workflows/CI/badge.svg)](https://github.com/un-ts/lib-boilerplate/actions/workflows/ci.yml)
-[![Codecov](https://img.shields.io/codecov/c/github/un-ts/lib-boilerplate.svg)](https://codecov.io/gh/un-ts/lib-boilerplate)
-[![type-coverage](https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fun-ts%2Flib-boilerplate%2Fmain%2Fpackage.json)](https://github.com/plantain-00/type-coverage)
-[![npm](https://img.shields.io/npm/v/lib-boilerplate.svg)](https://www.npmjs.com/package/lib-boilerplate)
-[![GitHub Release](https://img.shields.io/github/release/un-ts/lib-boilerplate)](https://github.com/un-ts/lib-boilerplate/releases)
+[![GitHub Actions](https://github.com/un-ts/fetch-api/workflows/CI/badge.svg)](https://github.com/un-ts/fetch-api/actions/workflows/ci.yml)
+[![Codecov](https://img.shields.io/codecov/c/github/un-ts/fetch-api.svg)](https://codecov.io/gh/un-ts/fetch-api)
+[![type-coverage](https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fun-ts%2Ffetch-api%2Fmain%2Fpackage.json)](https://github.com/plantain-00/type-coverage)
+[![npm](https://img.shields.io/npm/v/fetch-api.svg)](https://www.npmjs.com/package/fetch-api)
+[![GitHub Release](https://img.shields.io/github/release/un-ts/fetch-api)](https://github.com/un-ts/fetch-api/releases)
[![Conventional Commits](https://img.shields.io/badge/conventional%20commits-1.0.0-yellow.svg)](https://conventionalcommits.org)
[![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com)
@@ -12,7 +12,7 @@
[![Code Style: Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
[![changesets](https://img.shields.io/badge/maintained%20with-changesets-176de3.svg)](https://github.com/changesets/changesets)
-A simple library boilerplate.
+A simple but elegant `fetch` API wrapper, use `fetch` like a charm
## TOC
@@ -30,21 +30,51 @@ A simple library boilerplate.
```sh
# pnpm
-pnpm add lib-boilerplate
+pnpm add fetch-api
# yarn
-yarn add lib-boilerplate
+yarn add fetch-api
# npm
-npm i lib-boilerplate
+npm i fetch-api
```
### API
-```js
-import echo from 'lib-boilerplate'
-
-echo()
+```ts
+import { ApiMethod, createFetchApi, fetchApi, interceptors } from 'fetch-api'
+
+// plain url, GET method
+await fetchApi('url')
+
+// with options, `body`, `query`, etc.
+await fetchApi('url', {
+ method: ApiMethod.POST, // or 'POST'
+ // plain object or array, or BodyInit
+ body: {},
+ // URLSearchParametersOptions
+ query: {
+ key: 'value',
+ },
+ // json: boolean, // whether auto stringify body to json, default true for plain object or array, otherwise false
+ // type: 'arrayBuffer' | 'blob' | 'json' | 'text' | null, `null` means plain `Response`
+})
+
+const interceptor: ApiInterceptor = (req, next) => {
+ // do something with req
+ const res = await next(req)
+ // do something with res
+ return res
+}
+
+// add interceptor
+interceptors.use(interceptor)
+
+// remove interceptor
+interceptors.eject(interceptor)
+
+// create a new isolated `fetchApi` with its own `interceptors`
+const { fetchApi, interceptors } = createFetchApi()
```
## Sponsors
diff --git a/docs/index.html b/docs/index.html
index 09735df..caaecf6 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -10,7 +10,7 @@
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
-
lib-boilerplate
+ fetch-api
diff --git a/package.json b/package.json
index 937c499..d45e2dc 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,9 @@
{
- "name": "lib-boilerplate",
+ "name": "fetch-api",
"version": "0.1.0",
"type": "module",
- "description": "A simple library boilerplate.",
- "repository": "git+https://github.com/un-ts/lib-boilerplate.git",
+ "description": "A simple but elegant `fetch` API wrapper, use `fetch` like a charm",
+ "repository": "git+https://github.com/un-ts/fetch-api.git",
"author": "JounQin (https://www.1stG.me) ",
"funding": "https://opencollective.com/unts",
"license": "MIT",
@@ -49,9 +49,8 @@
"@types/node": "^20.8.10",
"@types/react": "^18.2.36",
"@types/react-dom": "^18.2.14",
- "@types/web": "^0.0.119",
"@vitejs/plugin-react-swc": "^3.4.1",
- "@vitest/coverage-v8": "^0.34.6",
+ "@vitest/coverage-istanbul": "^0.34.6",
"commitlint": "^18.2.0",
"eslint": "^8.53.0",
"github-markdown-css": "^5.4.0",
diff --git a/src/index.ts b/src/index.ts
index f3bea86..0c39293 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1 +1,147 @@
-export default () => 'Hello World!'
+import type { URLSearchParametersOptions, ValueOf } from './types.js'
+import { CONTENT_TYPE, isPlainObject, normalizeUrl } from './utils.js'
+
+export type * from './types.js'
+export * from './utils.js'
+
+export const ApiMethod = {
+ GET: 'GET',
+ POST: 'POST',
+ PATCH: 'PATCH',
+ PUT: 'PUT',
+ DELETE: 'DELETE',
+} as const
+
+export type ApiMethod = ValueOf
+
+export interface FetchApiOptions extends Omit {
+ method?: ApiMethod
+ body?: BodyInit | object
+ query?: URLSearchParametersOptions
+ json?: boolean
+ type?: 'arrayBuffer' | 'blob' | 'json' | 'text' | null
+}
+
+export interface InterceptorRequest extends FetchApiOptions {
+ url: string
+}
+
+export type ApiInterceptor = (
+ request: InterceptorRequest,
+ next: (request: InterceptorRequest) => PromiseLike,
+) => PromiseLike | Response
+
+export interface ResponseError extends Error {
+ data?: T | null
+ response?: Response | null
+}
+
+export class ApiInterceptors {
+ readonly #interceptors: ApiInterceptor[] = []
+
+ get length() {
+ return this.#interceptors.length
+ }
+
+ at(index: number) {
+ return this.#interceptors.at(index)
+ }
+
+ use(...interceptors: ApiInterceptor[]) {
+ this.#interceptors.push(...interceptors)
+ return this
+ }
+
+ eject(interceptor: ApiInterceptor) {
+ const index = this.#interceptors.indexOf(interceptor)
+ if (index > -1) {
+ this.#interceptors.splice(index, 1)
+ return true
+ }
+ return false
+ }
+}
+
+export const createFetchApi = () => {
+ const interceptors = new ApiInterceptors()
+
+ function fetchApi(
+ url: string,
+ options: FetchApiOptions & { type: null },
+ ): Promise
+ function fetchApi(
+ url: string,
+ options: FetchApiOptions & { type: 'arraybuffer' },
+ ): Promise
+ function fetchApi(
+ url: string,
+ options: FetchApiOptions & { type: 'blob' },
+ ): Promise
+ function fetchApi(
+ url: string,
+ options: FetchApiOptions & { type: 'text' },
+ ): Promise
+ function fetchApi(
+ url: string,
+ options?: FetchApiOptions & { type?: 'json' },
+ ): Promise
+ // eslint-disable-next-line sonarjs/cognitive-complexity
+ async function fetchApi(
+ url: string,
+ {
+ method = ApiMethod.GET,
+ body,
+ headers,
+ json = body != null && (isPlainObject(body) || Array.isArray(body)),
+ type = 'json',
+ ...rest
+ }: FetchApiOptions = {},
+ ) {
+ headers = new Headers(headers)
+
+ if (json && !headers.has(CONTENT_TYPE)) {
+ headers.append(CONTENT_TYPE, 'application/json')
+ }
+
+ let index = 0
+
+ const next = async (request: InterceptorRequest) => {
+ if (index < interceptors.length) {
+ return interceptors.at(index++)!(request, next)
+ }
+ const { body, url, query, ...rest } = request
+ const response = await fetch(normalizeUrl(url, query), {
+ ...rest,
+ body: json ? JSON.stringify(body) : (body as BodyInit),
+ })
+ if (response.ok) {
+ return response
+ }
+ let data: unknown = null
+ if (type != null) {
+ try {
+ data = await response.clone()[type]()
+ } catch {
+ data = await response.clone().text()
+ }
+ }
+ throw Object.assign(new Error(response.statusText), {
+ data,
+ response,
+ })
+ }
+
+ const response = await next({
+ url,
+ method,
+ body,
+ headers,
+ ...rest,
+ })
+ return type == null ? response : response.clone()[type]()
+ }
+
+ return { interceptors, fetchApi }
+}
+
+export const { interceptors, fetchApi } = createFetchApi()
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..5e56569
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,13 @@
+export type Nullable = T | null | undefined
+
+export type ValueOf = T[keyof T]
+
+export type URLSearchParametersInit = ConstructorParameters<
+ typeof URLSearchParams
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+>[0]
+
+export type URLSearchParametersOptions =
+ | Record>
+ | URLSearchParametersInit
+ | object
diff --git a/src/utils.ts b/src/utils.ts
new file mode 100644
index 0000000..6e8e70f
--- /dev/null
+++ b/src/utils.ts
@@ -0,0 +1,44 @@
+import {
+ Nullable,
+ URLSearchParametersInit,
+ URLSearchParametersOptions,
+ ValueOf,
+} from './types.js'
+
+export const CONTENT_TYPE = 'Content-Type'
+
+// eslint-disable-next-line @typescript-eslint/unbound-method
+const { toString } = Object.prototype // type-coverage:ignore-line - TODO: report bug
+
+const objectTag = '[object Object]'
+
+export const isPlainObject = (value: unknown): value is T =>
+ toString.call(value) === objectTag
+
+export const cleanNilValues = (input: T, empty?: boolean): T => {
+ if (!isPlainObject(input)) {
+ return input
+ }
+
+ for (const _key of Object.keys(input)) {
+ const key = _key as keyof T
+ const value = input[key] as Nullable>
+ if (empty ? !value : value == null) {
+ delete input[key]
+ } else {
+ input[key] = cleanNilValues(value, empty) as (T & object)[keyof T]
+ }
+ }
+
+ return input
+}
+
+export const normalizeUrl = (
+ url: string,
+ query?: URLSearchParametersOptions,
+) => {
+ const search = new URLSearchParams(
+ cleanNilValues(query, true) as URLSearchParametersInit,
+ ).toString()
+ return search ? url + (url.includes('?') ? '&' : '?') + search : url
+}
diff --git a/test/basic.spec.ts b/test/basic.spec.ts
index 482d0dc..79d923c 100644
--- a/test/basic.spec.ts
+++ b/test/basic.spec.ts
@@ -1,5 +1,58 @@
-import echo from 'lib-boilerplate'
+import { type ApiInterceptor, fetchApi, interceptors } from 'fetch-api'
-test('it should just work', () => {
- expect(echo()).toBe('Hello World!')
+test('it should just work', async () => {
+ expect(
+ await fetchApi<{
+ id: number
+ userId: number
+ }>('https://jsonplaceholder.typicode.com/todos/1'),
+ ).toMatchInlineSnapshot(`
+ {
+ "completed": false,
+ "id": 1,
+ "title": "delectus aut autem",
+ "userId": 1,
+ }
+ `)
+})
+
+test('interceptors should just work', async () => {
+ const interceptor: ApiInterceptor = (req, next) => {
+ if (!/^https?:\/\//.test(req.url)) {
+ req.url =
+ 'https://jsonplaceholder.typicode.com' +
+ (req.url.startsWith('/') ? '' : '/') +
+ req.url
+ }
+ return next(req)
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ expect(() => fetchApi('/todos/1')).rejects.toMatchInlineSnapshot(
+ '[TypeError: Failed to parse URL from /todos/1]',
+ )
+
+ interceptors.use(interceptor)
+
+ expect(
+ await fetchApi<{
+ id: number
+ userId: number
+ }>('todos/1'),
+ ).toMatchInlineSnapshot(`
+ {
+ "completed": false,
+ "id": 1,
+ "title": "delectus aut autem",
+ "userId": 1,
+ }
+ `)
+
+ expect(interceptors.eject(interceptor)).toBe(true)
+ expect(interceptors.eject(interceptor)).toBe(false)
+
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ expect(() => fetchApi('/todos/1')).rejects.toMatchInlineSnapshot(
+ '[TypeError: Failed to parse URL from /todos/1]',
+ )
})
diff --git a/tsconfig.json b/tsconfig.json
index dd6cd35..4822b21 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,7 @@
{
"extends": "@1stg/tsconfig/node16",
"compilerOptions": {
+ "lib": ["DOM", "ESNext"],
"module": "Node16",
"rootDir": "."
}
diff --git a/vercel.json b/vercel.json
index 47d9a4f..14f9d3f 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,7 +1,7 @@
{
"version": 2,
"alias": [
- "lib-boilerplate.vercel.app"
+ "ifetch.vercel.app"
],
"github": {
"silent": true
diff --git a/vitest.config.ts b/vitest.config.ts
index 3664c9b..94ce767 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -9,11 +9,12 @@ export default defineConfig({
],
resolve: {
alias: {
- 'lib-boilerplate': './src/index.ts',
+ 'fetch-api': './src/index.ts',
},
},
test: {
coverage: {
+ provider: 'istanbul',
reporter: ['lcov', 'json', 'text'],
},
},
diff --git a/yarn.lock b/yarn.lock
index f20e690..d851c04 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -407,7 +407,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/core@npm:^7.22.5":
+"@babel/core@npm:^7.12.3, @babel/core@npm:^7.22.5":
version: 7.23.2
resolution: "@babel/core@npm:7.23.2"
dependencies:
@@ -728,7 +728,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.0":
+"@babel/parser@npm:^7.14.7, @babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.0":
version: 7.23.0
resolution: "@babel/parser@npm:7.23.0"
bin:
@@ -2089,13 +2089,6 @@ __metadata:
languageName: node
linkType: hard
-"@bcoe/v8-coverage@npm:^0.2.3":
- version: 0.2.3
- resolution: "@bcoe/v8-coverage@npm:0.2.3"
- checksum: 1a1f0e356a3bb30b5f1ced6f79c413e6ebacf130421f15fac5fcd8be5ddf98aedb4404d7f5624e3285b700e041f9ef938321f3ca4d359d5b716f96afa120d88d
- languageName: node
- linkType: hard
-
"@bloomberg/record-tuple-polyfill@npm:^0.0.4":
version: 0.0.4
resolution: "@bloomberg/record-tuple-polyfill@npm:0.0.4"
@@ -3639,7 +3632,7 @@ __metadata:
languageName: node
linkType: hard
-"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.9":
+"@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.9":
version: 0.3.20
resolution: "@jridgewell/trace-mapping@npm:0.3.20"
dependencies:
@@ -4803,13 +4796,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/istanbul-lib-coverage@npm:^2.0.1":
- version: 2.0.5
- resolution: "@types/istanbul-lib-coverage@npm:2.0.5"
- checksum: 978eaf327f9a238eb1e2828b93b4b48e288ffb88c4be81330c74477ab8b93fac41a8784260d72bdd9995535d70608f738199b6364fd3344842e924a3ec3301e7
- languageName: node
- linkType: hard
-
"@types/json-schema@npm:^7.0.9":
version: 7.0.14
resolution: "@types/json-schema@npm:7.0.14"
@@ -5026,13 +5012,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/web@npm:^0.0.119":
- version: 0.0.119
- resolution: "@types/web@npm:0.0.119"
- checksum: abaabd19a59368939e5048590d6287bb5a9707031434ca11cdfa6f348ed42744f673f02cdca0de8c744898ffdc5d100df2e606abffaacb32c903b47a2ef8ac7e
- languageName: node
- linkType: hard
-
"@types/whatwg-mimetype@npm:3.0.1":
version: 3.0.1
resolution: "@types/whatwg-mimetype@npm:3.0.1"
@@ -5179,24 +5158,20 @@ __metadata:
languageName: node
linkType: hard
-"@vitest/coverage-v8@npm:^0.34.6":
+"@vitest/coverage-istanbul@npm:^0.34.6":
version: 0.34.6
- resolution: "@vitest/coverage-v8@npm:0.34.6"
+ resolution: "@vitest/coverage-istanbul@npm:0.34.6"
dependencies:
- "@ampproject/remapping": "npm:^2.2.1"
- "@bcoe/v8-coverage": "npm:^0.2.3"
istanbul-lib-coverage: "npm:^3.2.0"
+ istanbul-lib-instrument: "npm:^6.0.0"
istanbul-lib-report: "npm:^3.0.1"
istanbul-lib-source-maps: "npm:^4.0.1"
istanbul-reports: "npm:^3.1.5"
- magic-string: "npm:^0.30.1"
picocolors: "npm:^1.0.0"
- std-env: "npm:^3.3.3"
test-exclude: "npm:^6.0.0"
- v8-to-istanbul: "npm:^9.1.0"
peerDependencies:
vitest: ">=0.32.0 <1"
- checksum: f25afdc496d44b387752b88e82e4643557711f72d88d3da363785386cbd523af3f6049f5f91a3e6afc9c2417aa1d97e09801c0e5e62cb96cc9d419cc9629de2a
+ checksum: f86ea2b11842a9528e5483683735cc402342e0b98dce00d5cd7ff2bf1da542ec704dc3ba58184f9df96798d8b0c1c6d24d6d11fad4eaac9999487e45d5a3e8b0
languageName: node
linkType: hard
@@ -9504,6 +9479,45 @@ __metadata:
languageName: node
linkType: hard
+"fetch-api@workspace:.":
+ version: 0.0.0-use.local
+ resolution: "fetch-api@workspace:."
+ dependencies:
+ "@1stg/app-config": "npm:^9.0.0"
+ "@1stg/lib-config": "npm:^12.0.0"
+ "@changesets/changelog-github": "npm:^0.4.8"
+ "@changesets/cli": "npm:^2.26.2"
+ "@mdx-js/rollup": "npm:^3.0.0"
+ "@pkgr/rollup": "npm:^4.1.3"
+ "@size-limit/preset-small-lib": "npm:^10.0.2"
+ "@types/mdx": "npm:^2.0.9"
+ "@types/node": "npm:^20.8.10"
+ "@types/react": "npm:^18.2.36"
+ "@types/react-dom": "npm:^18.2.14"
+ "@vitejs/plugin-react-swc": "npm:^3.4.1"
+ "@vitest/coverage-istanbul": "npm:^0.34.6"
+ commitlint: "npm:^18.2.0"
+ eslint: "npm:^8.53.0"
+ github-markdown-css: "npm:^5.4.0"
+ lint-staged: "npm:^13.3.0"
+ npm-run-all: "npm:^4.1.5"
+ react: "npm:^18.2.0"
+ react-dom: "npm:^18.2.0"
+ react-router-dom: "npm:^6.18.0"
+ rehype-slug: "npm:^6.0.0"
+ remark-gfm: "npm:^4.0.0"
+ simple-git-hooks: "npm:^2.9.0"
+ size-limit: "npm:^10.0.2"
+ stylelint: "npm:^15.11.0"
+ tslib: "npm:^2.6.2"
+ type-coverage: "npm:^2.27.0"
+ typescript: "npm:^5.2.2"
+ unplugin-auto-import: "npm:^0.16.7"
+ vite: "npm:^4.5.0"
+ vitest: "npm:^0.34.6"
+ languageName: unknown
+ linkType: soft
+
"figures@npm:^1.3.5":
version: 1.7.0
resolution: "figures@npm:1.7.0"
@@ -11620,6 +11634,19 @@ __metadata:
languageName: node
linkType: hard
+"istanbul-lib-instrument@npm:^6.0.0":
+ version: 6.0.1
+ resolution: "istanbul-lib-instrument@npm:6.0.1"
+ dependencies:
+ "@babel/core": "npm:^7.12.3"
+ "@babel/parser": "npm:^7.14.7"
+ "@istanbuljs/schema": "npm:^0.1.2"
+ istanbul-lib-coverage: "npm:^3.2.0"
+ semver: "npm:^7.5.4"
+ checksum: 95fd8c66e586840989cb3c7819c6da66c4742a6fedbf16b51a5c7f1898941ad07b79ddff020f479d3a1d76743ecdbf255d93c35221875687477d4b118026e7e7
+ languageName: node
+ linkType: hard
+
"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1":
version: 3.0.1
resolution: "istanbul-lib-report@npm:3.0.1"
@@ -12056,46 +12083,6 @@ __metadata:
languageName: node
linkType: hard
-"lib-boilerplate@workspace:.":
- version: 0.0.0-use.local
- resolution: "lib-boilerplate@workspace:."
- dependencies:
- "@1stg/app-config": "npm:^9.0.0"
- "@1stg/lib-config": "npm:^12.0.0"
- "@changesets/changelog-github": "npm:^0.4.8"
- "@changesets/cli": "npm:^2.26.2"
- "@mdx-js/rollup": "npm:^3.0.0"
- "@pkgr/rollup": "npm:^4.1.3"
- "@size-limit/preset-small-lib": "npm:^10.0.2"
- "@types/mdx": "npm:^2.0.9"
- "@types/node": "npm:^20.8.10"
- "@types/react": "npm:^18.2.36"
- "@types/react-dom": "npm:^18.2.14"
- "@types/web": "npm:^0.0.119"
- "@vitejs/plugin-react-swc": "npm:^3.4.1"
- "@vitest/coverage-v8": "npm:^0.34.6"
- commitlint: "npm:^18.2.0"
- eslint: "npm:^8.53.0"
- github-markdown-css: "npm:^5.4.0"
- lint-staged: "npm:^13.3.0"
- npm-run-all: "npm:^4.1.5"
- react: "npm:^18.2.0"
- react-dom: "npm:^18.2.0"
- react-router-dom: "npm:^6.18.0"
- rehype-slug: "npm:^6.0.0"
- remark-gfm: "npm:^4.0.0"
- simple-git-hooks: "npm:^2.9.0"
- size-limit: "npm:^10.0.2"
- stylelint: "npm:^15.11.0"
- tslib: "npm:^2.6.2"
- type-coverage: "npm:^2.27.0"
- typescript: "npm:^5.2.2"
- unplugin-auto-import: "npm:^0.16.7"
- vite: "npm:^4.5.0"
- vitest: "npm:^0.34.6"
- languageName: unknown
- linkType: soft
-
"lib-upng@npm:3.0.0":
version: 3.0.0
resolution: "lib-upng@npm:3.0.0"
@@ -20905,17 +20892,6 @@ __metadata:
languageName: node
linkType: hard
-"v8-to-istanbul@npm:^9.1.0":
- version: 9.1.3
- resolution: "v8-to-istanbul@npm:9.1.3"
- dependencies:
- "@jridgewell/trace-mapping": "npm:^0.3.12"
- "@types/istanbul-lib-coverage": "npm:^2.0.1"
- convert-source-map: "npm:^2.0.0"
- checksum: d6ce9f6d97c53a401098fe42018f32be81c99830bcf44ee2717332e20a7df3e364a3f322c10dab4ea94488e81dde462295149cdfb44f48e8ef2829e3afd09752
- languageName: node
- linkType: hard
-
"validate-npm-package-license@npm:^3.0.1":
version: 3.0.4
resolution: "validate-npm-package-license@npm:3.0.4"