diff --git a/.agent/rules.md b/.agent/rules.md new file mode 100644 index 000000000..45a4c0338 --- /dev/null +++ b/.agent/rules.md @@ -0,0 +1,204 @@ +# Val Codebase Instructions + +Instructions for AI assistants working with the Val content management system codebase. + +## General rules + +1. Never add @ts-expect-error unless explicitly being allowed to do so +2. Never use as any unless explicitly being allowed to do so +3. Ask if you need to use type assertions (`as Something`) - we try to avoid those + +## Type System Architecture + +### Core Type Hierarchy + +Val has a dual type system: **Source** types define data shape, **Selector** types is the user facing types. + +``` +Source (data) → Selector (access) +───────────────────────────────────────────── +ImageSource → ImageSelector +FileSource → FileSelector +RemoteSource → GenericSelector +RichTextSource → RichTextSelector +SourceObject → ObjectSelector +SourceArray → ArraySelector +string/number/boolean → StringSelector/NumberSelector/BooleanSelector +``` + +### Key Type Definitions + +**Source** (`packages/core/src/source/index.ts`): + +```typescript +export type Source = + | SourcePrimitive // string | number | boolean | null + | SourceObject // { [key: string]: Source } + | SourceArray // readonly Source[] + | RemoteSource + | FileSource + | ImageSource + | RichTextSource; +``` + +**SelectorSource** (`packages/core/src/selector/index.ts`): + +```typescript +export type SelectorSource = + | SourcePrimitive + | undefined + | readonly SelectorSource[] + | { [key: string]: SelectorSource } + | ImageSource + | FileSource + | RemoteSource + | RichTextSource + | GenericSelector; +``` + +**GenericSelector** (`packages/core/src/selector/index.ts`): + +```typescript +class GenericSelector { + [GetSource]: T; // The actual source value + [GetSchema]: Schema | undefined; // Schema for validation + [Path]: SourcePath | undefined; // Path in the module tree + [ValError]: Error | undefined; // Type errors +} +``` + +### CRITICAL: Adding New Source Types + +When adding a new source type, it **MUST** be added to BOTH unions: + +1. `Source` in `packages/core/src/source/index.ts` +2. `SelectorSource` in `packages/core/src/selector/index.ts` + +Additionally: 3. Create selector type in `packages/core/src/selector/{name}.ts` 4. Add mapping in `Selector` conditional type in `packages/core/src/selector/index.ts` + +### FORBIDDEN: Type Intersection Hacks + +**NEVER** use type intersections (`&`) to force a type to satisfy constraints: + +```typescript +// ❌ WRONG - This is a hack that hides the real problem +export type RichTextSelector = GenericSelector & Source>; + +// ✅ CORRECT - Add missing types to SelectorSource union +export type SelectorSource = + | ...existing types... + | ImageSource // Add missing type here +``` + +If you see `Type 'X' does not satisfy the constraint 'Source'`, the fix is almost always adding a type to `SelectorSource`, NOT using intersections. + +## Schema System + +### Schema-Source Relationship + +Each Schema class validates and types its corresponding Source type: + +| Schema | Source | Factory | +| ------------------- | ------------------- | --------------------- | +| `ImageSchema` | `ImageSource` | `s.image()` | +| `FileSchema` | `FileSource` | `s.file()` | +| `RichTextSchema` | `RichTextSource` | `s.richtext(options)` | +| `ObjectSchema` | `SourceObject` | `s.object({...})` | +| `ArraySchema` | `SourceArray` | `s.array(schema)` | + +## Module System + +### c.define() Pattern + +```typescript +c.define( + "/content/page.val.ts", // Module path + s.object({...}), // Schema + { ... } // Source data matching schema +) +``` + +### Source Constructors + +```typescript +c.image("/public/val/logo.png", { + width: 100, + height: 100, + mimeType: "image/png", +}); +c.file("/public/val/doc.pdf", { mimeType: "application/pdf" }); +c.remote("https://...", { mimeType: "image/jpeg" }); +``` + +## UI Architecture + +### Shadow DOM Isolation + +The Val UI runs inside a Shadow DOM for CSS/JS isolation from the host page: + +```typescript +// packages/ui/spa/components/ShadowRoot.tsx +const root = node.attachShadow({ mode: "open" }); +// ID: "val-shadow-root" +``` + +**Implications:** + +- CSS must target `:host` (not `:root`) for shadow DOM styles +- External stylesheets must be loaded inside the shadow root +- `document.querySelector` won't find elements inside shadow DOM +- Use `shadowRoot.querySelector` or React refs instead + +### CSS Architecture + +```css +/* packages/ui/spa/index.css */ +@layer base { + :host, /* Shadow DOM */ + :root { + /* Regular DOM fallback */ + --background: ...; + --foreground: ...; + } +} +``` + +- Dark mode: `[data-mode="dark"]` selector +- CSS loaded via `/api/val/static/{VERSION}/spa/index.css` +- Event `val-css-loaded` dispatched when styles are ready + +### Tailwind Configuration + +```javascript +// packages/ui/tailwind.config.js +darkMode: ["class", '[data-mode="dark"]']; +``` + +Custom color tokens map to CSS variables (e.g., `bg-background` → `var(--background)`). + +## Testing + +Run tests from root dir with: + +```bash +pnpm test # All tests +pnpm test packages/core/src/... # Specific test file +pnpm run -r typecheck # Type checking +pnpm lint +pnpm format +``` + +### Test rules + +1. Never "fix" an issue by changing the test file +2. Prefer to define test data in a type-safe manner using `s` and `c` from `initVal`. Search for examples. + +## Common Fixes + +### "Type 'X' does not satisfy constraint 'Source'" + +→ Add the type to `SelectorSource` union in `packages/core/src/selector/index.ts` + +### "Property 'X' does not exist on type 'never'" + +→ Check if all variants are handled in conditional types (especially in `ImageNode`, `RichTextSource`) diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 000000000..e5b6d8d6a --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000..1cb5ec4bc --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", + "changelog": false, + "commit": false, + "fixed": [], + "linked": [["@valbuild/*"]], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": ["example-*", "val-test-*"] +} diff --git a/.changeset/typescript-bundler-opaque.md b/.changeset/typescript-bundler-opaque.md new file mode 100644 index 000000000..586febab5 --- /dev/null +++ b/.changeset/typescript-bundler-opaque.md @@ -0,0 +1,5 @@ +--- +"@valbuild/server": patch +--- + +Make the internal `typescript` import bundler-opaque (via `node:module`'s `createRequire`). This prevents webpack and Turbopack from tracing into TypeScript's `lib/typescript.js` and emitting "Can't resolve 'source-map-support'" warnings when consumers build Next.js apps that depend on `@valbuild/server` (directly or via `@valbuild/next`). No runtime or API changes. diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 120000 index 000000000..8d157ee00 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +../.agent/rules.md \ No newline at end of file diff --git a/.cursor/rules/val-rules.md b/.cursor/rules/val-rules.md new file mode 120000 index 000000000..12477a339 --- /dev/null +++ b/.cursor/rules/val-rules.md @@ -0,0 +1 @@ +../../.agent/rules.md \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 120000 index 000000000..8d157ee00 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +../.agent/rules.md \ No newline at end of file diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 000000000..3c23177c1 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,94 @@ +name: Check +on: push +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + - uses: actions/setup-node@v3 + with: + node-version-file: ".nvmrc" + - uses: pnpm/action-setup@v2 + with: + version: 10 + - run: pnpm install --frozen-lockfile + - run: pnpm run lint + format: + name: Check format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + - uses: actions/setup-node@v3 + with: + node-version-file: ".nvmrc" + - uses: pnpm/action-setup@v2 + with: + version: 10 + - run: pnpm install --frozen-lockfile + - run: pnpm run format + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + - uses: actions/setup-node@v3 + with: + node-version-file: ".nvmrc" + - uses: pnpm/action-setup@v2 + with: + version: 10 + - run: pnpm install --frozen-lockfile + - run: pnpm run build + build-next: + name: Build Next.js example + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + - uses: actions/setup-node@v3 + with: + node-version-file: ".nvmrc" + - uses: pnpm/action-setup@v2 + with: + version: 10 + - run: pnpm install --frozen-lockfile + - run: pnpm run build + working-directory: examples/next + typecheck: + name: Typecheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + - uses: actions/setup-node@v3 + with: + node-version-file: ".nvmrc" + - uses: pnpm/action-setup@v2 + with: + version: 10 + - run: pnpm install --frozen-lockfile + - run: pnpm run -r typecheck + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + persist-credentials: false + - uses: actions/setup-node@v3 + with: + node-version-file: ".nvmrc" + - uses: pnpm/action-setup@v2 + with: + version: 10 + - run: pnpm install --frozen-lockfile + - run: pnpm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..b8a82dc3d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,31 @@ +name: Release +on: + push: + branches: + - main +concurrency: ${{ github.workflow }}-${{ github.ref }} +permissions: + id-token: write # Required for OIDC + contents: write # Required for changesets to commit/push + pull-requests: write # Required for changesets to create version PRs +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: ".nvmrc" + registry-url: "https://registry.npmjs.org" + - uses: pnpm/action-setup@v2 + with: + version: 10 + - run: pnpm install --frozen-lockfile + - uses: changesets/action@v1 + with: + publish: pnpm run release + version: pnpm run version-packages + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_CONFIG_PROVENANCE: true diff --git a/.gitignore b/.gitignore index 84333e28e..c5596e241 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ node_modules/ .next dist tsconfig.tsbuildinfo +.envrc +.tmp +.DS_Store \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 000000000..cc1b7f164 --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +tsconfig.tsbuildinfo diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..18c92ea98 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v24 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..3bb9b6b38 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,19 @@ +# next.js +examples/next*/.next/ +examples/next*/out/ + +# production +examples/next*/build + +trials + +dist +tsconfig.tsbuildinfo + +packages/ui/server/.tmp +out + +.tmp +examples/next*/.val/* + +pnpm-lock.yaml diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/.vscode/settings.json b/.vscode/settings.json index 8c000955f..1d11035a7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,7 @@ "**/.DS_Store": true, "**/Thumbs.db": true, "**/node_modules": true - } -} \ No newline at end of file + }, + "cSpell.words": ["flexsearch", "hotspot", "remirror", "retryable", "stega"], + "typescript.preferences.importModuleSpecifier": "relative" +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..4c4b37302 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright (c) 2025 Fredrik Ekholdt and Blank AS + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 69e195b54..b29b71543 100644 --- a/README.md +++ b/README.md @@ -1 +1,147 @@ -# Val +

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+

+ +## Table of contents + +- [Installation](./packages/next/README.md#Installation) +- [Introduction](#content-as-code) +- [Examples](#examples) +- [Documentation](#documentation) + +## Content as code + +Val is a CMS library where **content** is **TypeScript** / **JavaScript** files stored in your git repo. + +As a CMS, Val is useful because: + +- editors can **change content** without having to ask developers to do it for them (and nobody wants that) +- a **well-documented** way to **structure content** +- **image**, **richtext**, ... support is built-in +- built-in **visual editing** which lets editors click-then-edit content (and therefore **code**!) directly in your app + ![Visual editing](https://val.build/docs/images/visual-editing.png) +- an embedded studio which lets editors navigate the content structure and make updates there + ![Studio](https://val.build/docs/images/studio.png) + +
+Definition: editor +An editor in this context, is a non-technical person that edits content in your application (technical writer, proof-reader, legal, ...). +
+ +
+ +But, with the benefits of **hard-coded** content: + +- works seamlessly **locally** or with git **branches** +- content is **type-checked** so you can spend less time on figuring out why something isn't working + ![Type check error](https://val.build/docs/images/type-check-error.png) +- content can be refactored (change names, etc) just as if it was hard-coded (because it is) + ![Renaming](https://val.build/docs/images/renaming.gif) +- works as normal with your **favorite IDE** without any plugins: search for content, references to usages, ... + ![References](https://val.build/docs/images/references.gif) +- **no** need for **code-gen** and extra build steps +- **fast** since the content is literally hosted with the application +- content is **always there** and can never fail (since it is not loaded from somewhere) +- no need to worry about **caching** since content is not fetched from some DB or cloud service +- no need to manage different **environments** with different versions of content +- works out of the box with Vercel **preview links** +- **resolve conflicts** like you normally resolve conflicts: **in git** + +Compared to other CMSs, Val has the following advantages: + +- **easy** to _grok_: Val is designed to have a minimum of boilerplate and there's **0** query languages to learn +- Vals abstraction model is based on **JSON** and TypeScript (Record, keyof). What this means is that there is nothing to learn (for you or your future colleagues): no "a collection is..." or a "a document is sort of a..." - just JSON and regular TypeScript types +- **no signup** required to use it locally +- **no fees** for content that is in your code: your content is your code, and your code is... yours +- **minimal** API surface: Val is designed to not "infect" your code base +- **easy to remove**: since your content is already in your code and Val is designed to have a minimal surface, it's easy to remove if you want to switch +- eslint rules and [VS Code extension](https://marketplace.visualstudio.com/items?itemName=valbuild.vscode-val-build) for an even **better-than-hard-coded** developer experience + +
+Upcoming feature: i18n +Val will soon have support for i18n. Follow this repository to get notified when this is the case. +
+ +
+Upcoming feature: remote content +Having hard-coded content is great for landing pages, product pages and other pages where the amount of content is manageable. + +However, checking in the 10 000th blog entry in git might feel wrong (though we would say it is ok). + +Therefore, Val will add `remote content` support which enables you to seamlessly move content to the cloud and back again as desired. +You code will still be the one truth, but the actual content will be hosted on [val.build](https://val.build). + +`.remote()` support will also make it possible to have remote images to avoid having to put them in your repository. + +There will also be specific support for remote i18n, which will make it possible to split which languages are defined in code, and which are fetched from remote. + +More details on `.remote()` will follow later. + +
+ +## When to NOT use Val + +Val is designed to work well on a single web-app, and currently only supports Next 14+ (more meta-frameworks will supported) and GitHub (more Git providers will follow). + +Unless your application fits these requirements, you should have a look elsewhere (at least for now). + +In addition, if you have a "content model", i.e. content schemas, that rarely change and you plan on using them in a lot of different applications (web, mobile, etc), Val will most likely not be a great fit. + +If that is the case, we recommend having a look at [Sanity](https://sanity.io) instead (we have no affiliation, but if we didn't have Val we would use Sanity). + +**NOTE**: Our experience is that, however nice it sounds, it is hard to "nail" the content model down. Usually content (on, for example, your landing page) is derived from what you want to present, not vice-versa. In addition, you should think carefully whether you _really_ want to present the exact same content on all these different surfaces. + +
+ Upcoming feature: external content +Val will support external content, i.e. content from other Val projects, CMSs or DBs some time in the future. +
+ +## Examples + +Examples can found [here](./examples). + +## Documentation + +Val currently only support Next.js. + +To get started using Next.js, checkout the [README](./packages/next/README.md). + +## Get in touch + +Join us on [discord](https://discord.gg/cZzqPvaX8k) to get help or give us feedback. diff --git a/babel.config.js b/babel.config.js index fb119885f..9d6167918 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,5 @@ +/** @type {import("@babel/core").TransformOptions} */ module.exports = { presets: ["@babel/preset-env", "@babel/preset-typescript"], + babelrcRoots: [".", "./packages/*"], }; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..0a253f482 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,69 @@ +const { defineConfig, globalIgnores } = require("eslint/config"); + +const globals = require("globals"); +const tsParser = require("@typescript-eslint/parser"); +const react = require("eslint-plugin-react"); +const typescriptEslint = require("@typescript-eslint/eslint-plugin"); +const js = require("@eslint/js"); + +const { FlatCompat } = require("@eslint/eslintrc"); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +module.exports = defineConfig([ + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + + parser: tsParser, + ecmaVersion: "latest", + sourceType: "module", + parserOptions: {}, + }, + + extends: compat.extends( + "eslint:recommended", + "plugin:react/jsx-runtime", + "plugin:@typescript-eslint/recommended", + "prettier", + ), + + plugins: { + react, + "@typescript-eslint": typescriptEslint, + }, + + rules: { + // Fix for @typescript-eslint/no-unused-expressions rule compatibility with flat config + "@typescript-eslint/no-unused-expressions": [ + "error", + { + allowShortCircuit: false, + allowTernary: false, + allowTaggedTemplates: false, + }, + ], + }, + + settings: { + react: { + version: "detect", + }, + }, + }, + globalIgnores([ + "examples/next*", + "**/trials", + "**/dist", + "**/out", + "**/tsconfig.tsbuildinfo", + "**/*.js", + ]), +]); diff --git a/examples/next/.eslintrc.json b/examples/next/.eslintrc.json index bffb357a7..1465c36cc 100644 --- a/examples/next/.eslintrc.json +++ b/examples/next/.eslintrc.json @@ -1,3 +1,3 @@ { - "extends": "next/core-web-vitals" + "extends": ["next/core-web-vitals", "plugin:@valbuild/recommended"] } diff --git a/examples/next/.gitignore b/examples/next/.gitignore index c87c9b392..c65e12938 100644 --- a/examples/next/.gitignore +++ b/examples/next/.gitignore @@ -34,3 +34,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# Val +.val \ No newline at end of file diff --git a/examples/next/.npmrc b/examples/next/.npmrc new file mode 100644 index 000000000..f69058391 --- /dev/null +++ b/examples/next/.npmrc @@ -0,0 +1,3 @@ +# Force hoisting of @types/react to ensure single instance for examples/next +public-hoist-pattern[]=*@types/react* +public-hoist-pattern[]=*@types/react-dom* diff --git a/examples/next/.nvmrc b/examples/next/.nvmrc new file mode 120000 index 000000000..15bb0fb5b --- /dev/null +++ b/examples/next/.nvmrc @@ -0,0 +1 @@ +../../.nvmrc \ No newline at end of file diff --git a/examples/next/README.md b/examples/next/README.md index c87e0421d..2a36ba4af 100644 --- a/examples/next/README.md +++ b/examples/next/README.md @@ -1,34 +1,22 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +# Val NextJS example -## Getting Started +This is a Next JS project with Val enabled. -First, run the development server: +## File structure -```bash -npm run dev -# or -yarn dev -``` +The following files are required in Val NextJS project: -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +- `/val.config.ts` - this is the main Val config file +- `/val.modules.ts` - defines the modules that are editable by Val +- `/app/(val)/api/val/[[...val]]/route.ts` - all API call to Val goes via these endpoints +- `/app/(val)/val/[[...val]]/page.tsx` - this is the URL to the Val full-screen app +- `/val/server.ts` - this is the helper library for server related functions -You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. +Additionally the following files are needed: -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. +- `/val/client.ts` - this is the helper library for **Client Components** +- `/val/rsc.ts` - this is the helper library for **React Server Components** -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. +### Content -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +Check out the `.val.ts` files where content is defined to learn more. diff --git a/examples/next/app/(val)/api/val/[[...val]]/route.ts b/examples/next/app/(val)/api/val/[[...val]]/route.ts new file mode 100644 index 000000000..186769d7e --- /dev/null +++ b/examples/next/app/(val)/api/val/[[...val]]/route.ts @@ -0,0 +1,8 @@ +import { valNextAppRouter } from "../../../../../val/server"; + +export const GET = valNextAppRouter; +export const POST = valNextAppRouter; +export const PATCH = valNextAppRouter; +export const DELETE = valNextAppRouter; +export const PUT = valNextAppRouter; +export const HEAD = valNextAppRouter; diff --git a/examples/next/app/(val)/val/[[...val]]/page.tsx b/examples/next/app/(val)/val/[[...val]]/page.tsx new file mode 100644 index 000000000..f93c371c8 --- /dev/null +++ b/examples/next/app/(val)/val/[[...val]]/page.tsx @@ -0,0 +1,6 @@ +import { ValApp } from "@valbuild/next"; +import { config } from "../../../../val.config"; + +export default function Val() { + return ; +} diff --git a/examples/next/app/blogs/[blog]/page.tsx b/examples/next/app/blogs/[blog]/page.tsx new file mode 100644 index 000000000..57b0d9ef7 --- /dev/null +++ b/examples/next/app/blogs/[blog]/page.tsx @@ -0,0 +1,28 @@ +"use server"; +import { notFound } from "next/navigation"; +import { fetchVal, fetchValRoute } from "../../../val/rsc"; +import blogsVal from "./page.val"; +import Link from "next/link"; +import authorsVal from "../../../content/authors.val"; +import { ValRichText } from "@valbuild/next"; + +export default async function BlogPage({ + params, +}: { + params: Promise<{ blog: string }>; +}) { + const blog = await fetchValRoute(blogsVal, params); + const authors = await fetchVal(authorsVal); + if (!blog) { + return notFound(); + } + const author = authors[blog.author]; + return ( +
+

{blog.title}

+ + + {blog.link.label} +
+ ); +} diff --git a/examples/next/app/blogs/[blog]/page.val.ts b/examples/next/app/blogs/[blog]/page.val.ts new file mode 100644 index 000000000..23a2ee8a4 --- /dev/null +++ b/examples/next/app/blogs/[blog]/page.val.ts @@ -0,0 +1,525 @@ +import { c, nextAppRouter, s } from "_/val.config"; +import authorsVal from "../../../content/authors.val"; +import { linkSchema } from "../../../components/link.val"; + +const blogSchema = s.object({ + title: s.string(), + content: s.richtext({ + inline: { + a: s.route(), + }, + }), + author: s.keyOf(authorsVal), + link: linkSchema, +}); + +export default c.define( + "/app/blogs/[blog]/page.val.ts", + s.router(nextAppRouter, blogSchema), + { + "/blogs/blog2": { + title: "Blog 2", + content: [ + { + tag: "p", + children: ["Blog 2 content"], + }, + ], + author: "freekh", + link: { + label: "Read more", + href: "/blogs/blog1", + }, + }, + "/blogs/blog1": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-2": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-3": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-4": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-5": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-6": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-7": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-8": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-9": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-10": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-11": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-12": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-13": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-14": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-15": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-16": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-17": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-18": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-19": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-20": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-21": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-22": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-23": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-24": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-25": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-26": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-27": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-28": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-29": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-30": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-31": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-32": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-33": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-34": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + "/blogs/blog-35": { + title: "Blog 1", + content: [ + { + tag: "p", + children: ["Blog 1 content"], + }, + ], + author: "freekh", + link: { + label: "See more", + href: "/generic/test/foo", + }, + }, + }, +); diff --git a/examples/next/app/external.val.ts b/examples/next/app/external.val.ts new file mode 100644 index 000000000..5209a3d19 --- /dev/null +++ b/examples/next/app/external.val.ts @@ -0,0 +1,16 @@ +import { c, s, externalPageRouter } from "../val.config"; + +export default c.define( + "/app/external.val.ts", + s.router(externalPageRouter, s.object({ title: s.string() })), + { + "https://www.google.com": { title: "Google" }, + "https://www.youtube.com": { title: "YouTube" }, + "https://www.facebook.com": { title: "Facebook" }, + "https://www.twitter.com": { title: "Twitter" }, + "https://www.instagram.com": { title: "Instagram" }, + "https://www.linkedin.com": { title: "LinkedIn" }, + "https://www.github.com": { title: "GitHub" }, + "https://val.build": { title: "Val Homepage" }, + }, +); diff --git a/examples/next/app/generic/[[...path]]/page.tsx b/examples/next/app/generic/[[...path]]/page.tsx new file mode 100644 index 000000000..3d3afabbb --- /dev/null +++ b/examples/next/app/generic/[[...path]]/page.tsx @@ -0,0 +1,22 @@ +"use client"; +import { notFound } from "next/navigation"; +import { useValRoute } from "../../../val/client"; +import pageVal from "./page.val"; + +export default function GenericPage({ + params, +}: { + params: { path: string[] }; +}) { + const content = useValRoute(pageVal, params); + if (!content) { + notFound(); + } + return ( +
+

{content.title}

+

{content.content}

+
{content.exampleCode}
+
+ ); +} diff --git a/examples/next/app/generic/[[...path]]/page.val.ts b/examples/next/app/generic/[[...path]]/page.val.ts new file mode 100644 index 000000000..778295c76 --- /dev/null +++ b/examples/next/app/generic/[[...path]]/page.val.ts @@ -0,0 +1,27 @@ +import { c, nextAppRouter, s } from "_/val.config"; + +const genericPageSchema = s.object({ + title: s.string(), + url: s.route(), + content: s.string().render({ as: "textarea" }), + exampleCode: s.string().render({ as: "code", language: "typescript" }), +}); + +export default c.define( + "/app/generic/[[...path]]/page.val.ts", + s.router(nextAppRouter, genericPageSchema), + { + "/generic": { + url: "/generic", + title: "Generic", + content: "Generic content in a textarea", + exampleCode: 'console.log("Val is great for documentation")', + }, + "/generic/test/foo": { + url: "https://www.google.com", + title: "Test", + content: "hva er det som skjer noen ganger?", + exampleCode: "function contentAsCode() {\n return true;\n}", + }, + }, +); diff --git a/examples/next/app/globals.css b/examples/next/app/globals.css new file mode 100644 index 000000000..551f03c0f --- /dev/null +++ b/examples/next/app/globals.css @@ -0,0 +1,37 @@ +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +main { + max-width: 500px; + margin: 0 auto; +} + +a { + color: inherit; +} + +.font-bold { + font-weight: bold; +} + +.italic { + font-style: italic; +} + +.line-through { + text-decoration: line-through; +} + +img { + width: 100%; + height: auto; +} diff --git a/examples/next/app/head.tsx b/examples/next/app/head.tsx new file mode 100644 index 000000000..f11b25969 --- /dev/null +++ b/examples/next/app/head.tsx @@ -0,0 +1,10 @@ +export default function Head() { + return ( + <> + Create Next App + + + + + ); +} diff --git a/examples/next/app/layout.tsx b/examples/next/app/layout.tsx new file mode 100644 index 000000000..f85e4bee5 --- /dev/null +++ b/examples/next/app/layout.tsx @@ -0,0 +1,26 @@ +import { ValProvider } from "@valbuild/next"; +import { config } from "../val.config"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} diff --git a/examples/next/app/page.tsx b/examples/next/app/page.tsx new file mode 100644 index 000000000..a4dc0d39f --- /dev/null +++ b/examples/next/app/page.tsx @@ -0,0 +1,57 @@ +import { notFound } from "next/navigation"; +import { fetchVal, fetchValRoute } from "../val/rsc"; +import pageVal from "./page.val"; +import { ValImage, ValRichText } from "@valbuild/next"; +import authorsVal from "../content/authors.val"; +import Link from "next/link"; +import { val } from "../val.config"; + +export default async function Home({ params }: { params: unknown }) { + const page = await fetchValRoute(pageVal, params); + if (page === null) { + notFound(); + } + const authors = await fetchVal(authorsVal); + const author = authors[page.author]; + return ( +
+
+

{page.hero.title}

+ + {author?.name && } +
{page.tags.join(", ")}
+
+ + {page.hero.link.text} + +
+
+
+ {page.text && ( + + )} +
+
+ {page.video.text} +
+
+ ); +} diff --git a/examples/next/app/page.val.ts b/examples/next/app/page.val.ts new file mode 100644 index 000000000..9415c0844 --- /dev/null +++ b/examples/next/app/page.val.ts @@ -0,0 +1,261 @@ +import { s, c, type t, nextAppRouter } from "../val.config"; +import authorsVal from "../content/authors.val"; +import image from "../schema/image.val"; + +export const schema = s.object({ + /** + * Objects: + */ + hero: s.object({ + title: s.string().minLength(4), + image: s.image(), + link: s.object({ + text: s.string(), + href: s.route(), + }), + }), + /** + * Arrays and string: + */ + tags: s.array(s.string().regexp(/CMS|github|react|NextJS|headless/)), + /** + * Reference to other content: + */ + author: s.keyOf(authorsVal), + link: s.route(), + /** + * Rich Text that is optional: + */ + text: s + .richtext({ + // enables all features + // styling: + style: { + bold: true, // enables bold, ... + italic: true, + lineThrough: true, + }, + block: { + h2: true, // enables h2 blocks, ... + ul: true, + }, + inline: { + a: true, + }, + }) + .nullable(), + video: s.object({ + text: s.string(), + file: s.file({ accept: "video/*" }), + }), + // Boolean: + featured: s.boolean(), +}); + +export type Content = t.inferSchema; +export type Image = t.inferSchema; +export default c.define("/app/page.val.ts", s.router(nextAppRouter, schema), { + "/": { + video: { + text: "Val is more than just basics - here's a video for example", + file: c.file("/public/val/file_example.webm", { + mimeType: "video/webm", + }), + }, + link: "/blogs/blog1", + hero: { + title: "Content as code", + image: c.image("/public/val/logo_7adc7.png", { + width: 944, + height: 944, + mimeType: "image/png", + alt: "Val logo", + }), + link: { + text: "Example blog article", + href: "/blogs/blog1", + }, + }, + tags: ["CMS", "react", "github", "NextJS"], + author: "freekh", + text: [ + { + tag: "p", + children: [ + "Val is a CMS where ", + { tag: "span", styles: ["bold"], children: ["content"] }, + " is ", + { tag: "span", styles: ["bold"], children: ["code"] }, + " in your git repo.", + ], + }, + { tag: "p", children: [{ tag: "br" }] }, + { tag: "p", children: ["Val is a CMS, which is useful because:"] }, + { + tag: "ul", + children: [ + { + tag: "li", + children: [ + { + tag: "p", + children: [ + "editors can ", + { + tag: "span", + styles: ["bold"], + children: ["change content"], + }, + " without developer interactions", + ], + }, + ], + }, + { + tag: "li", + children: [ + { + tag: "p", + children: [ + { tag: "span", styles: ["bold"], children: ["images"] }, + " can be managed without checking in code", + ], + }, + ], + }, + { + tag: "li", + children: [ + { + tag: "p", + children: [ + { tag: "span", styles: ["bold"], children: ["i18n"] }, + " support is easy to add", + ], + }, + ], + }, + { + tag: "li", + children: [ + { + tag: "p", + children: [ + "a ", + { + tag: "span", + styles: ["bold"], + children: ["well-documented"], + }, + " way to ", + { + tag: "span", + styles: ["bold"], + children: ["structure content"], + }, + ], + }, + ], + }, + ], + }, + { tag: "p", children: [{ tag: "br" }] }, + { + tag: "p", + children: ["But, with all the benefits of having content hard-coded:"], + }, + { + tag: "ul", + children: [ + { + tag: "li", + children: [ + { + tag: "p", + children: [ + "works as normal with your ", + { + tag: "span", + styles: ["bold"], + children: ["favorite IDE"], + }, + " without any plugins: search for content, references to usages, ...", + ], + }, + ], + }, + { + tag: "li", + children: [ + { + tag: "p", + children: [ + "content is ", + { + tag: "span", + styles: ["bold"], + children: ["type-checked"], + }, + " so you see when something is wrong immediately", + ], + }, + ], + }, + { + tag: "li", + children: [ + { + tag: "p", + children: [ + "content can be ", + { tag: "span", styles: ["bold"], children: ["refactored"] }, + " (change names, etc) just as if it was hard-coded (because it sort of is)", + ], + }, + ], + }, + { + tag: "li", + children: [ + { + tag: "p", + children: [ + "work ", + { tag: "span", styles: ["bold"], children: ["locally"] }, + " or in ", + { tag: "span", styles: ["bold"], children: ["branches"] }, + " just as if you didn't use a CMS", + ], + }, + ], + }, + { + tag: "li", + children: [ + { + tag: "p", + children: [ + { + tag: "span", + styles: ["bold"], + children: ["no need for code-gen"], + }, + " and extra build steps", + ], + }, + ], + }, + ], + }, + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "https://val.build", children: ["Val"] }, + " for more information.", + ], + }, + ], + featured: false, + }, +}); diff --git a/examples/next/components/link.val.ts b/examples/next/components/link.val.ts new file mode 100644 index 000000000..3803a9ef1 --- /dev/null +++ b/examples/next/components/link.val.ts @@ -0,0 +1,6 @@ +import { s } from "../val.config"; + +export const linkSchema = s.object({ + label: s.string(), + href: s.route(), +}); diff --git a/examples/next/content/authors.val.ts b/examples/next/content/authors.val.ts new file mode 100644 index 000000000..46807fd7e --- /dev/null +++ b/examples/next/content/authors.val.ts @@ -0,0 +1,52 @@ +import { s, c, type t } from "../val.config"; +import mediaVal from "./media.val"; + +export const schema = s + .record( + s.object({ + name: s.string().minLength(2), + birthdate: s.date().from("1900-01-01").to("2024-01-01").nullable(), + image: s.image(mediaVal).nullable(), + }), + ) + .render({ + as: "list", + select: ({ val }) => ({ + title: val.name, + subtitle: val.birthdate, + }), + }); + +export type Author = t.inferSchema; +export default c.define("/content/authors.val.ts", schema, { + teddy: { + name: "Theodor René Carlsen", + birthdate: null, + image: null, + }, + freekh: { + name: "Fredrik Ekholdt", + birthdate: "1981-12-30", + image: null, + }, + erlamd: { + name: "Erlend Åmdal", + birthdate: null, + image: null, + }, + thoram: { + name: "Thomas Ramirez", + birthdate: null, + image: null, + }, + isabjo: { + name: "Isak Bjørnstad", + birthdate: null, + image: null, + }, + kimmid: { + name: "Kim Midtlid", + birthdate: null, + image: null, + }, +}); diff --git a/examples/next/content/media.val.ts b/examples/next/content/media.val.ts new file mode 100644 index 000000000..ef551bce2 --- /dev/null +++ b/examples/next/content/media.val.ts @@ -0,0 +1,18 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/media.val.ts", + s.images({ + accept: "image/*", + directory: "/public/val/images", + alt: s.string().minLength(4), + }), + { + "/public/val/images/logo.png": { + width: 800, + height: 600, + mimeType: "image/png", + alt: "An example image", + }, + }, +); diff --git a/examples/next/next.config.js b/examples/next/next.config.js index 063c147b3..197de226e 100644 --- a/examples/next/next.config.js +++ b/examples/next/next.config.js @@ -1,5 +1,14 @@ +const withPreconstruct = require("@preconstruct/next"); + /** @type {import('next').NextConfig} */ -const withPlugins = require("next-compose-plugins"); -const withTM = require("next-transpile-modules")(["@val/react", "@val/lib"]); +const nextConfig = { + images: { + remotePatterns: [ + { + hostname: "localhost", + }, + ], + }, +}; -module.exports = withPlugins([withTM]); +module.exports = withPreconstruct(nextConfig); diff --git a/examples/next/package.json b/examples/next/package.json index bbefee748..aec32e421 100644 --- a/examples/next/package.json +++ b/examples/next/package.json @@ -1,29 +1,43 @@ { - "name": "val-next", + "name": "example-next", "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -p 3456", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "typecheck": "tsc --noEmit" }, "dependencies": { - "@types/react": "18.0.17", - "@val/lib": "*", - "@val/react": "*", - "next": "^12.1.6", - "next-compose-plugins": "^2.2.0", - "next-transpile-modules": "^9.0.0", - "react": "18.2.0", - "react-dom": "18.2.0", - "@val/server": "*" + "@preconstruct/next": "^4.0.0", + "@valbuild/cli": "workspace:*", + "@valbuild/next": "workspace:*", + "next": "^14.0.4", + "prettier": "^3.3.3", + "react": ">=18.2.0", + "react-dom": ">=18.2.0", + "styled-jsx": "^5.1.2" }, "devDependencies": { - "@types/node": "18.7.13", - "@types/react-dom": "18.0.6", + "@types/node": "20.10.0", + "@types/react": "18.2.38", + "@types/react-dom": "18.2.17", + "@valbuild/cli": "workspace:*", + "@valbuild/eslint-plugin": "workspace:*", "eslint": "8.23.0", "eslint-config-next": "12.2.5", - "typescript": "^4.8.2" + "ts-node": "^10.9.1", + "typescript": "5.1.3" + }, + "peerDependencies": { + "react": ">=18.2.0 || ^19.0 || ^19.0.0-rc", + "react-dom": ">=18.2.0 || ^19.0 || ^19.0.0-rc", + "typescript": ">=5.1.3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } } diff --git a/examples/next/pages/_app.tsx b/examples/next/pages/_app.tsx deleted file mode 100644 index 98032ad91..000000000 --- a/examples/next/pages/_app.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import type { AppProps } from "next/app"; -import { - CSSProperties, - forwardRef, - MouseEventHandler, - useEffect, - useRef, - useState, -} from "react"; - -const ValEditButton = forwardRef< - HTMLButtonElement, - { - left?: CSSProperties["left"]; - top?: CSSProperties["top"]; - display?: CSSProperties["display"]; - onClick: MouseEventHandler; - } ->(function ValEditButton({ left, top: top, display, onClick }, ref) { - return ( - -

ValCMS

-
- {selectedIds.map((id) => ( - - ))} - -
- - ); -}; - -function MyApp({ Component, pageProps }: AppProps) { - const buttonElementRef = useRef(null); - const [selectedIds, setSelectedIds] = useState([]); - const [currentIds, setCurrentIds] = useState([]); - const [editButtonProps, setEditButtonProps] = useState<{ - left?: CSSProperties["left"]; - top?: CSSProperties["top"]; - display?: CSSProperties["display"]; - }>({ left: 0, top: "auto", display: "none" }); - useEffect(() => { - let timeout: NodeJS.Timeout; - - const listener = (e: MouseEvent) => { - const currentElement = document.elementFromPoint(e.clientX, e.clientY); - const valId = currentElement?.getAttribute("data-val-ids"); - if (currentElement) { - if (valId) { - setCurrentIds(valId.split(",")); - clearTimeout(timeout); - const rect = currentElement.getBoundingClientRect(); - setEditButtonProps({ - display: "block", - left: rect.left + "px", - top: rect.top + "px", - }); - } else if (currentElement === buttonElementRef.current) { - clearTimeout(timeout); - } else { - timeout = setTimeout(() => { - setEditButtonProps({ display: "none" }); - }, 1000); - } - } - }; - document.addEventListener("mousemove", listener, { passive: true }); - return () => { - document.removeEventListener("mousemove", listener); - clearTimeout(timeout); - }; - }, []); - return ( -
- - { - setSelectedIds(currentIds); - }} - /> - { - setSelectedIds([]); - }} - /> -
- ); -} - -export default MyApp; diff --git a/examples/next/pages/blog.val.ts b/examples/next/pages/blog.val.ts deleted file mode 100644 index 9b090b17f..000000000 --- a/examples/next/pages/blog.val.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { s, val } from "../val.config"; - -export default val.content("/pages/blog", () => - s.object({ title: s.string(), text: s.string() }).static({ - title: "1672911735", - text: "Blipp blopp", - }) -); diff --git a/examples/next/pages/index.tsx b/examples/next/pages/index.tsx deleted file mode 100644 index cb4896f3f..000000000 --- a/examples/next/pages/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { NextPage } from "next"; -import { useVal } from "../val.config"; -import blogVal from "./blog.val"; - -const Home: NextPage = () => { - const blog = useVal(blogVal); - return ( -
-

{blog.title}

-

{blog.text}

-
- ); -}; - -export default Home; diff --git a/examples/next/public/favicon.ico b/examples/next/public/favicon.ico new file mode 100644 index 000000000..718d6fea4 Binary files /dev/null and b/examples/next/public/favicon.ico differ diff --git a/examples/next/public/val/file_example.webm b/examples/next/public/val/file_example.webm new file mode 100644 index 000000000..745652868 Binary files /dev/null and b/examples/next/public/val/file_example.webm differ diff --git a/examples/next/public/val/images/logo.png b/examples/next/public/val/images/logo.png new file mode 100644 index 000000000..55f165c2b Binary files /dev/null and b/examples/next/public/val/images/logo.png differ diff --git a/examples/next/public/valcms-logo.svg b/examples/next/public/valcms-logo.svg deleted file mode 100644 index 69b604a8f..000000000 --- a/examples/next/public/valcms-logo.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/examples/next/schema/image.val.ts b/examples/next/schema/image.val.ts new file mode 100644 index 000000000..416e5670a --- /dev/null +++ b/examples/next/schema/image.val.ts @@ -0,0 +1,5 @@ +import { s } from "../val.config"; + +const image = s.image(); + +export default image; diff --git a/examples/next/tsconfig.json b/examples/next/tsconfig.json index 48fdb7a0f..f3d1517d8 100644 --- a/examples/next/tsconfig.json +++ b/examples/next/tsconfig.json @@ -1,12 +1,7 @@ { "compilerOptions": { - "baseUrl": ".", - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -14,19 +9,21 @@ "noEmit": true, "esModuleInterop": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, - "jsxImportSource": "@val/react" + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"], + "_/*": ["./*"] + } }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx" - ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/examples/next/val.config.ts b/examples/next/val.config.ts index 58b79ca29..068807b4a 100644 --- a/examples/next/val.config.ts +++ b/examples/next/val.config.ts @@ -1,6 +1,25 @@ -import { Schema } from "@val/lib"; -import { initVal } from "@val/lib"; +import { initVal } from "@valbuild/next"; -const { useVal, s, val } = initVal(); +const { s, c, val, config, nextAppRouter, externalPageRouter } = initVal({ + project: "valbuild/val-examples-next", + root: "/examples/next", + defaultTheme: "dark", + ai: { + chat: { + experimental: { + enable: true, + }, + suggestions: [ + "Summarize", + "Fix typos at this page", + "Create a blog page", + ], + title: "Ask me anything", + description: + "Val can answer questions about the content and how it was built.", + }, + }, +}); -export { useVal, s, val }; +export type { t } from "@valbuild/next"; +export { s, c, val, config, nextAppRouter, externalPageRouter }; diff --git a/examples/next/val.modules.ts b/examples/next/val.modules.ts new file mode 100644 index 000000000..88a1873ba --- /dev/null +++ b/examples/next/val.modules.ts @@ -0,0 +1,11 @@ +import { modules } from "@valbuild/next"; +import { config } from "./val.config"; + +export default modules(config, [ + { def: () => import("./content/authors.val") }, + { def: () => import("./app/blogs/[blog]/page.val") }, + { def: () => import("./app/generic/[[...path]]/page.val") }, + { def: () => import("./content/media.val") }, + { def: () => import("./app/page.val") }, + { def: () => import("./app/external.val") }, +]); diff --git a/examples/next/val/client.ts b/examples/next/val/client.ts new file mode 100644 index 000000000..8b24c5d86 --- /dev/null +++ b/examples/next/val/client.ts @@ -0,0 +1,11 @@ +import "client-only"; +import { initValClient } from "@valbuild/next/client"; +import { config } from "../val.config"; + +const { + useValStega: useVal, + useValRouteStega: useValRoute, + useValRouteUrl, +} = initValClient(config); + +export { useVal, useValRoute, useValRouteUrl }; diff --git a/examples/next/val/rsc.ts b/examples/next/val/rsc.ts new file mode 100644 index 000000000..122752d11 --- /dev/null +++ b/examples/next/val/rsc.ts @@ -0,0 +1,17 @@ +import "server-only"; +import { initValRsc } from "@valbuild/next/rsc"; +import { config } from "../val.config"; +import valModules from "../val.modules"; +import { cookies, draftMode, headers } from "next/headers"; + +const { + fetchValStega: fetchVal, + fetchValRouteStega: fetchValRoute, + fetchValRouteUrl, +} = initValRsc(config, valModules, { + draftMode, + headers, + cookies, +}); + +export { fetchVal, fetchValRoute, fetchValRouteUrl }; diff --git a/examples/next/val/server.ts b/examples/next/val/server.ts new file mode 100644 index 000000000..a5502965a --- /dev/null +++ b/examples/next/val/server.ts @@ -0,0 +1,21 @@ +import "server-only"; +import { initValServer } from "@valbuild/next/server"; +import { config } from "../val.config"; +import { draftMode } from "next/headers"; +import valModules from "../val.modules"; +import prettier from "prettier"; + +const { valNextAppRouter } = initValServer( + valModules, + { ...config }, + { + draftMode, + formatter: (code, filePath) => { + return prettier.format(code, { + filepath: filePath, + }); + }, + }, +); + +export { valNextAppRouter }; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 000000000..0b4768764 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,2 @@ +/** @type {import("jest").Config} */ +module.exports = {}; diff --git a/jest.preset.js b/jest.preset.js new file mode 100644 index 000000000..7de32166d --- /dev/null +++ b/jest.preset.js @@ -0,0 +1,12 @@ +/** @type {import("@babel/core").TransformOptions} */ +const babelOptions = { + root: __dirname, +}; + +/** @type {import("jest").Config} */ +module.exports = { + testEnvironment: "node", + transform: { + "\\.[jt]sx?": ["babel-jest", babelOptions], + }, +}; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index afb882c9a..000000000 --- a/package-lock.json +++ /dev/null @@ -1,17160 +0,0 @@ -{ - "name": "@val/root", - "version": "0.0.1", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@val/root", - "version": "0.0.1", - "hasInstallScript": true, - "workspaces": [ - "packages/*", - "examples/*" - ], - "dependencies": { - "@babel/core": "^7.18.13", - "@babel/plugin-transform-runtime": "^7.18.10", - "@babel/preset-env": "^7.18.10", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@types/react": "^18.0.9", - "react": "16.14.0", - "react-dom": "16.14.0", - "react-test-renderer": "16.8.6" - }, - "devDependencies": { - "@preconstruct/cli": "^2.1.5" - } - }, - "examples/next": { - "name": "val-next", - "version": "0.1.0", - "dependencies": { - "@types/react": "18.0.17", - "@val/lib": "*", - "@val/react": "*", - "@val/server": "*", - "next": "^12.1.6", - "next-compose-plugins": "^2.2.0", - "next-transpile-modules": "^9.0.0", - "react": "18.2.0", - "react-dom": "18.2.0" - }, - "devDependencies": { - "@types/node": "18.7.13", - "@types/react-dom": "18.0.6", - "eslint": "8.23.0", - "eslint-config-next": "12.2.5", - "typescript": "^4.8.2" - } - }, - "examples/next/node_modules/@next/swc-android-arm-eabi": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.2.5.tgz", - "integrity": "sha512-cPWClKxGhgn2dLWnspW+7psl3MoLQUcNqJqOHk2BhNcou9ARDtC0IjQkKe5qcn9qg7I7U83Gp1yh2aesZfZJMA==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-android-arm64": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.2.5.tgz", - "integrity": "sha512-vMj0efliXmC5b7p+wfcQCX0AfU8IypjkzT64GiKJD9PgiA3IILNiGJr1fw2lyUDHkjeWx/5HMlMEpLnTsQslwg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-darwin-arm64": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.2.5.tgz", - "integrity": "sha512-VOPWbO5EFr6snla/WcxUKtvzGVShfs302TEMOtzYyWni6f9zuOetijJvVh9CCTzInnXAZMtHyNhefijA4HMYLg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-darwin-x64": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.2.5.tgz", - "integrity": "sha512-5o8bTCgAmtYOgauO/Xd27vW52G2/m3i5PX7MUYePquxXAnX73AAtqA3WgPXBRitEB60plSKZgOTkcpqrsh546A==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-freebsd-x64": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.2.5.tgz", - "integrity": "sha512-yYUbyup1JnznMtEBRkK4LT56N0lfK5qNTzr6/DEyDw5TbFVwnuy2hhLBzwCBkScFVjpFdfiC6SQAX3FrAZzuuw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-linux-arm-gnueabihf": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.2.5.tgz", - "integrity": "sha512-2ZE2/G921Acks7UopJZVMgKLdm4vN4U0yuzvAMJ6KBavPzqESA2yHJlm85TV/K9gIjKhSk5BVtauIUntFRP8cg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-linux-arm64-gnu": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.2.5.tgz", - "integrity": "sha512-/I6+PWVlz2wkTdWqhlSYYJ1pWWgUVva6SgX353oqTh8njNQp1SdFQuWDqk8LnM6ulheVfSsgkDzxrDaAQZnzjQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-linux-arm64-musl": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.2.5.tgz", - "integrity": "sha512-LPQRelfX6asXyVr59p5sTpx5l+0yh2Vjp/R8Wi4X9pnqcayqT4CUJLiHqCvZuLin3IsFdisJL0rKHMoaZLRfmg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-linux-x64-gnu": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.2.5.tgz", - "integrity": "sha512-0szyAo8jMCClkjNK0hknjhmAngUppoRekW6OAezbEYwHXN/VNtsXbfzgYOqjKWxEx3OoAzrT3jLwAF0HdX2MEw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-linux-x64-musl": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.2.5.tgz", - "integrity": "sha512-zg/Y6oBar1yVnW6Il1I/08/2ukWtOG6s3acdJdEyIdsCzyQi4RLxbbhkD/EGQyhqBvd3QrC6ZXQEXighQUAZ0g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-win32-arm64-msvc": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.2.5.tgz", - "integrity": "sha512-3/90DRNSqeeSRMMEhj4gHHQlLhhKg5SCCoYfE3kBjGpE63EfnblYUqsszGGZ9ekpKL/R4/SGB40iCQr8tR5Jiw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-win32-ia32-msvc": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.2.5.tgz", - "integrity": "sha512-hGLc0ZRAwnaPL4ulwpp4D2RxmkHQLuI8CFOEEHdzZpS63/hMVzv81g8jzYA0UXbb9pus/iTc3VRbVbAM03SRrw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/@next/swc-win32-x64-msvc": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.2.5.tgz", - "integrity": "sha512-7h5/ahY7NeaO2xygqVrSG/Y8Vs4cdjxIjowTZ5W6CKoTKn7tmnuxlUc2h74x06FKmbhAd9agOjr/AOKyxYYm9Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "examples/next/node_modules/next": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/next/-/next-12.2.5.tgz", - "integrity": "sha512-tBdjqX5XC/oFs/6gxrZhjmiq90YWizUYU6qOWAfat7zJwrwapJ+BYgX2PmiacunXMaRpeVT4vz5MSPSLgNkrpA==", - "license": "MIT", - "dependencies": { - "@next/env": "12.2.5", - "@swc/helpers": "0.4.3", - "caniuse-lite": "^1.0.30001332", - "postcss": "8.4.14", - "styled-jsx": "5.0.4", - "use-sync-external-store": "1.2.0" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=12.22.0" - }, - "optionalDependencies": { - "@next/swc-android-arm-eabi": "12.2.5", - "@next/swc-android-arm64": "12.2.5", - "@next/swc-darwin-arm64": "12.2.5", - "@next/swc-darwin-x64": "12.2.5", - "@next/swc-freebsd-x64": "12.2.5", - "@next/swc-linux-arm-gnueabihf": "12.2.5", - "@next/swc-linux-arm64-gnu": "12.2.5", - "@next/swc-linux-arm64-musl": "12.2.5", - "@next/swc-linux-x64-gnu": "12.2.5", - "@next/swc-linux-x64-musl": "12.2.5", - "@next/swc-win32-arm64-msvc": "12.2.5", - "@next/swc-win32-ia32-msvc": "12.2.5", - "@next/swc-win32-x64-msvc": "12.2.5" - }, - "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^6.0.0 || ^7.0.0", - "react": "^17.0.2 || ^18.0.0-0", - "react-dom": "^17.0.2 || ^18.0.0-0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "examples/next/node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "examples/next/node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "examples/next/node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "examples/next/node_modules/styled-jsx": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.4.tgz", - "integrity": "sha512-sDFWLbg4zR+UkNzfk5lPilyIgtpddfxXEULxhujorr5jtePTUqiPDc5BC0v1NRqTr/WaFBGQQUoYToGlF4B2KQ==", - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "examples/next/node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@ampproject/remapping/node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", - "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/@babel/parser": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz", - "integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==", - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/traverse": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz", - "integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.13", - "@babel/types": "^7.18.13", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz", - "integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.13", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "license": "MIT", - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", - "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", - "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", - "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz", - "integrity": "sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", - "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", - "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", - "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz", - "integrity": "sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", - "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.10.tgz", - "integrity": "sha512-95NLBP59VWdfK2lyLKe6eTMq9xg+yWKzxzxbJ1wcYNi1Auz200+83fMDADjRxBvc2QQor5zja2yTQzXGhk2GtQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-function-name": "^7.18.9", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.10", - "@babel/types": "^7.18.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.10.tgz", - "integrity": "sha512-TYk3OA0HKL6qNryUayb5UUEhM/rkOQozIBEA5ITXh5DWrSp0TlUQXMyZmnWxG/DizSWBeeQ0Zbc5z8UGaaqoeg==", - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", - "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", - "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", - "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", - "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz", - "integrity": "sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz", - "integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "license": "MIT", - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", - "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", - "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz", - "integrity": "sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-validator-identifier": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", - "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.10.tgz", - "integrity": "sha512-gCy7Iikrpu3IZjYZolFE4M1Sm+nrh1/6za2Ewj77Z+XirT4TsbJcvOFOyF+fRPwU6AKKK136CZxx6L8AbSFG6A==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.18.10" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", - "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", - "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", - "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.9", - "babel-plugin-polyfill-corejs2": "^0.3.2", - "babel-plugin-polyfill-corejs3": "^0.5.3", - "babel-plugin-polyfill-regenerator": "^0.4.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz", - "integrity": "sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.18.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.12.tgz", - "integrity": "sha512-2vjjam0cum0miPkenUbQswKowuxs/NjMwIKEq0zwegRxXk12C9YOF9STXnaUptITOtOJHKHpzvvWYOjbm6tc0w==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-typescript": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript/node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz", - "integrity": "sha512-hDvXp+QYxSRL+23mpAlSGxHMDyIGChm0/AwTfTAAK5Ufe40nCsyNdaYCGuK91phn/fVu9kqayImRDkvNAgdrsA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", - "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.18.10", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.9", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.18.9", - "@babel/plugin-transform-classes": "^7.18.9", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.18.9", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.18.9", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.18.9", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.18.10", - "babel-plugin-polyfill-corejs2": "^0.3.2", - "babel-plugin-polyfill-corejs3": "^0.5.3", - "babel-plugin-polyfill-regenerator": "^0.4.0", - "core-js-compat": "^3.22.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", - "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-react-display-name": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.18.6", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-pure-annotations": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", - "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz", - "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-js-pure": "^3.20.2", - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.10.tgz", - "integrity": "sha512-J7ycxg0/K9XCtLyHf0cz2DqDihonJeIo+z+HEdRe9YuT8TY4A66i+Ab2/xZCEW7Ro60bPCBBfqqboHSamoV3+g==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.10", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.10.tgz", - "integrity": "sha512-0+sW7e3HjQbiHbj1NeU/vN8ornohYlacAfZIaXhdoGweQqgcNy69COVciYYqEXJ/v+9OBA7Frxm4CVAuNqKeNA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.10", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", - "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", - "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", - "dev": true, - "license": "Apache-2.0", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", - "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", - "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", - "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/reporters": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.2.0", - "jest-config": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-resolve-dependencies": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "jest-watcher": "^29.3.1", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", - "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", - "dev": true, - "dependencies": { - "expect": "^29.3.1", - "jest-snapshot": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", - "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.2.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", - "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", - "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/types": "^29.3.1", - "jest-mock": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", - "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", - "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", - "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", - "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", - "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", - "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@next/env": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-12.2.5.tgz", - "integrity": "sha512-vLPLV3cpPGjUPT3PjgRj7e3nio9t6USkuew3JE/jMeon/9Mvp1WyR18v3iwnCuX7eUAm1HmAbJHHLAbcu/EJcw==", - "license": "MIT" - }, - "node_modules/@next/eslint-plugin-next": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-12.2.5.tgz", - "integrity": "sha512-VBjVbmqEzGiOTBq4+wpeVXt/KgknnGB6ahvC/AxiIGnN93/RCSyXhFRI4uSfftM2Ba3w7ZO7076bfKasZsA0fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "7.1.7" - } - }, - "node_modules/@next/eslint-plugin-next/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@preconstruct/cli": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@preconstruct/cli/-/cli-2.2.1.tgz", - "integrity": "sha512-G+sUV9o8l6Ds/82qJZYTXkCsVqPXLuD+bv+nVQeo3OL+eqzO/uAiBBFVp0DMcBJiyQYeU9nb+V8q22/PPaepDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.5.5", - "@babel/core": "^7.7.7", - "@babel/helper-module-imports": "^7.10.4", - "@babel/runtime": "^7.7.7", - "@preconstruct/hook": "^0.4.0", - "@rollup/plugin-alias": "^3.1.1", - "@rollup/plugin-commonjs": "^15.0.0", - "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "builtin-modules": "^3.1.0", - "chalk": "^4.1.0", - "dataloader": "^2.0.0", - "detect-indent": "^6.0.0", - "enquirer": "^2.3.6", - "estree-walker": "^2.0.1", - "fast-deep-equal": "^2.0.1", - "fast-glob": "^3.2.4", - "fs-extra": "^9.0.1", - "is-ci": "^2.0.0", - "is-reference": "^1.2.1", - "jest-worker": "^26.3.0", - "magic-string": "^0.25.7", - "meow": "^7.1.0", - "ms": "^2.1.2", - "normalize-path": "^3.0.0", - "npm-packlist": "^2.1.2", - "p-limit": "^3.0.2", - "parse-glob": "^3.0.4", - "parse-json": "^5.1.0", - "quick-lru": "^5.1.1", - "resolve": "^1.17.0", - "resolve-from": "^5.0.0", - "rollup": "^2.32.0", - "semver": "^7.3.4", - "terser": "^5.2.1", - "v8-compile-cache": "^2.1.1" - }, - "bin": { - "preconstruct": "bin.js" - } - }, - "node_modules/@preconstruct/cli/node_modules/fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@preconstruct/cli/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@preconstruct/hook": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@preconstruct/hook/-/hook-0.4.0.tgz", - "integrity": "sha512-a7mrlPTM3tAFJyz43qb4pPVpUx8j8TzZBFsNFqcKcE/sEakNXRlQAuCT4RGZRf9dQiiUnBahzSIWawU4rENl+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.7.7", - "@babel/plugin-transform-modules-commonjs": "^7.7.5", - "pirates": "^4.0.1", - "source-map-support": "^0.5.16" - } - }, - "node_modules/@rollup/plugin-alias": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.9.tgz", - "integrity": "sha512-QI5fsEvm9bDzt32k39wpOwZhVzRcL5ydcffUHMyLVaVaLeC70I8TJZ17F1z1eMoLu4E/UOcH9BWVkKpIKdrfiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "slash": "^3.0.0" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz", - "integrity": "sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "commondir": "^1.0.1", - "estree-walker": "^2.0.1", - "glob": "^7.1.6", - "is-reference": "^1.2.1", - "magic-string": "^0.25.7", - "resolve": "^1.17.0" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^2.22.0" - } - }, - "node_modules/@rollup/plugin-json": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", - "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.0.8" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/pluginutils/node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz", - "integrity": "sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@swc/helpers": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.3.tgz", - "integrity": "sha512-6JrF+fdUK2zbGpJIlN7G3v966PQjyx/dPt1T9km2wj+EUBqgrxCk3uX4Kct16MIm9gGxfKRcfax2hVf5jvlTzA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@ts-morph/common": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.18.1.tgz", - "integrity": "sha512-RVE+zSRICWRsfrkAw5qCAK+4ZH9kwEFv5h0+/YeHTLieWP7F4wWq4JsKFuNWG+fYh/KF+8rAtgdj5zb2mm+DVA==", - "dependencies": { - "fast-glob": "^3.2.12", - "minimatch": "^5.1.0", - "mkdirp": "^1.0.4", - "path-browserify": "^1.0.1" - } - }, - "node_modules/@ts-morph/common/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, - "node_modules/@types/babel__core": { - "version": "7.1.20", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", - "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz", - "integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.31", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.32", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz", - "integrity": "sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.5.tgz", - "integrity": "sha512-H2cSxkKgVmqNHXP7TC2L/WUorrZu8ZigyRywfVzv6EyBlxj39n4C00hjXYQWsbwqgElaj/CiAeSRmk5GoaKTgw==", - "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", - "dev": true - }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "18.7.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz", - "integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "license": "MIT" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "node_modules/@types/react": { - "version": "18.0.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", - "integrity": "sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ==", - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", - "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "license": "MIT" - }, - "node_modules/@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "dev": true, - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.18", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.18.tgz", - "integrity": "sha512-eIJR1UER6ur3EpKM3d+2Pgd+ET+k6Kn9B4ZItX0oPjjVI5PrfaRjKyLT5UYendDpLuoiJMNJvovLQbEXqhsPaw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.35.1.tgz", - "integrity": "sha512-XL2TBTSrh3yWAsMYpKseBYTVpvudNf69rPOWXWVBI08My2JVT5jR66eTt4IgQFHA/giiKJW5dUD4x/ZviCKyGg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "5.35.1", - "@typescript-eslint/types": "5.35.1", - "@typescript-eslint/typescript-estree": "5.35.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.35.1.tgz", - "integrity": "sha512-kCYRSAzIW9ByEIzmzGHE50NGAvAP3wFTaZevgWva7GpquDyFPFcmvVkFJGWJJktg/hLwmys/FZwqM9EKr2u24Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.35.1", - "@typescript-eslint/visitor-keys": "5.35.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.35.1.tgz", - "integrity": "sha512-FDaujtsH07VHzG0gQ6NDkVVhi1+rhq0qEvzHdJAQjysN+LHDCKDKCBRlZFFE0ec0jKxiv0hN63SNfExy0KrbQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.35.1.tgz", - "integrity": "sha512-JUqE1+VRTGyoXlDWWjm6MdfpBYVq+hixytrv1oyjYIBEOZhBCwtpp5ZSvBt4wIA1MKWlnaC2UXl2XmYGC3BoQA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.35.1", - "@typescript-eslint/visitor-keys": "5.35.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.35.1.tgz", - "integrity": "sha512-cEB1DvBVo1bxbW/S5axbGPE6b7FIMAbo3w+AGq6zNDA7+NYJOIkKj/sInfTv4edxd4PxJSgdN4t6/pbvgA+n5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.35.1", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@val/lib": { - "resolved": "packages/lib", - "link": true - }, - "node_modules/@val/react": { - "resolved": "packages/react", - "link": true - }, - "node_modules/@val/server": { - "resolved": "packages/server", - "link": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true, - "license": "ISC" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/axe-core": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", - "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==", - "dev": true, - "license": "MPL-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/babel-jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", - "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.3.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.2.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "license": "MIT", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-dynamic-import-node/node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", - "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz", - "integrity": "sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.2", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", - "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.2", - "core-js-compat": "^3.21.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz", - "integrity": "sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", - "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.2.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/browserslist/node_modules/caniuse-lite": { - "version": "1.0.30001373", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz", - "integrity": "sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001383", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001383.tgz", - "integrity": "sha512-swMpEoTp5vDoGBZsYZX7L7nXHe6dsHxi9o6/LKf/f0LukVtnrxly5GVb/fWdCDTqi/yw6Km6tiJ0pmBacm0gbg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/code-block-writer": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-11.0.3.tgz", - "integrity": "sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==" - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/core-js-compat": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.24.1.tgz", - "integrity": "sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.21.3", - "semver": "7.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/core-js-pure": { - "version": "3.25.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.25.0.tgz", - "integrity": "sha512-IeHpLwk3uoci37yoI2Laty59+YqH9x5uR65/yiA0ARAJrTrN4YU0rmauLWfvqOuk77SlNJXj2rM6oT/dBD87+A==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", - "license": "MIT" - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/dataloader": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.1.0.tgz", - "integrity": "sha512-qTcEYLen3r7ojZNgVUaRggOI+KM7jrKxXeSHhogh/TWxYMeONEMqY+hmkobiYQozsGIyg9OYVzO4ZIfoB4I0pQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "license": "MIT", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.210", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.210.tgz", - "integrity": "sha512-kSiX4tuyZijV7Cz0MWVmGT8K2siqaOA4Z66K5dCttPPRh0HicOcOAEj1KlC8O8J1aOS/1M8rGofOzksLKaHWcQ==", - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", - "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint/eslintrc": "^1.3.1", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "@humanwhocodes/module-importer": "^1.0.1", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-next": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-12.2.5.tgz", - "integrity": "sha512-SOowilkqPzW6DxKp3a3SYlrfPi5Ajs9MIzp9gVfUDxxH9QFM5ElkR1hX5m/iICJuvCbWgQqFBiA3mCMozluniw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@next/eslint-plugin-next": "12.2.5", - "@rushstack/eslint-patch": "^1.1.3", - "@typescript-eslint/parser": "^5.21.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-import-resolver-typescript": "^2.7.1", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.29.4", - "eslint-plugin-react-hooks": "^4.5.0" - }, - "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0", - "typescript": ">=3.3.1" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-typescript": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.1.tgz", - "integrity": "sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "debug": "^4.3.4", - "glob": "^7.2.0", - "is-glob": "^4.0.3", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", - "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.9", - "aria-query": "^4.2.2", - "array-includes": "^3.1.5", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.4.3", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.2", - "language-tags": "^1.0.5", - "minimatch": "^3.1.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.31.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.1.tgz", - "integrity": "sha512-j4/2xWqt/R7AZzG8CakGHA6Xa/u7iR8Q3xCxY+AUghdT92bnIDOBEefV456OeH0QvBcroVc0eyvrrLSyQGYIfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.5", - "array.prototype.flatmap": "^1.3.0", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.1", - "object.values": "^1.1.5", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "license": "MIT" - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-base/node_modules/glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^2.0.0" - } - }, - "node_modules/glob-base/node_modules/is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "license": "ISC" - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "node_modules/ignore-walk": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", - "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "minimatch": "^3.0.4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob/node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", - "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", - "dev": true, - "dependencies": { - "@jest/core": "^29.3.1", - "@jest/types": "^29.3.1", - "import-local": "^3.0.2", - "jest-cli": "^29.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", - "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", - "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "p-limit": "^3.1.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", - "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", - "dev": true, - "dependencies": { - "@jest/core": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", - "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.3.1", - "@jest/types": "^29.3.1", - "babel-jest": "^29.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.3.1", - "jest-environment-node": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", - "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", - "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", - "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "jest-util": "^29.3.1", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", - "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", - "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-haste-map/node_modules/jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", - "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", - "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", - "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.3.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", - "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-util": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", - "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", - "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", - "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^29.2.0", - "jest-snapshot": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", - "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", - "dev": true, - "dependencies": { - "@jest/console": "^29.3.1", - "@jest/environment": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.2.0", - "jest-environment-node": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-leak-detector": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-resolve": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-util": "^29.3.1", - "jest-watcher": "^29.3.1", - "jest-worker": "^29.3.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jest-runtime": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", - "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/globals": "^29.3.1", - "@jest/source-map": "^29.2.0", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", - "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-haste-map": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "natural-compare": "^1.4.0", - "pretty-format": "^29.3.1", - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", - "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", - "dev": true, - "dependencies": { - "@jest/types": "^29.3.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "leven": "^3.1.0", - "pretty-format": "^29.3.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", - "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.3.1", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "language-subtag-registry": "~0.3.2" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/meow": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", - "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next-compose-plugins": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/next-compose-plugins/-/next-compose-plugins-2.2.1.tgz", - "integrity": "sha512-OjJ+fV15FXO2uQXQagLD4C0abYErBjyjE0I0FHpOEIB8upw0hg1ldFP6cqHTJBH1cZqy96OeR3u1dJ+Ez2D4Bg==", - "license": "MIT" - }, - "node_modules/next-transpile-modules": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/next-transpile-modules/-/next-transpile-modules-9.0.0.tgz", - "integrity": "sha512-VCNFOazIAnXn1hvgYYSTYMnoWgKgwlYh4lm1pKbSfiB3kj5ZYLcKVhfh3jkPOg1cnd9DP+pte9yCUocdPEUBTQ==", - "license": "MIT", - "dependencies": { - "enhanced-resolve": "^5.7.0", - "escalade": "^3.1.1" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "license": "MIT" - }, - "node_modules/nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-bundled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", - "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true, - "license": "ISC" - }, - "node_modules/npm-packlist": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz", - "integrity": "sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.6", - "ignore-walk": "^3.0.3", - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - }, - "bin": { - "npm-packlist": "bin/index.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.hasown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", - "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-glob/node_modules/is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", - "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - }, - "peerDependencies": { - "react": "^16.14.0" - } - }, - "node_modules/react-dom/node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/react-test-renderer": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz", - "integrity": "sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.13.6" - }, - "peerDependencies": { - "react": "^16.0.0" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "license": "MIT" - }, - "node_modules/regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/regexpu-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", - "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.0.1", - "regjsgen": "^0.6.0", - "regjsparser": "^0.8.2", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/scheduler": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz", - "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "dependencies": { - "semver": "~7.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true, - "license": "MIT" - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-jest": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", - "integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/ts-morph": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-17.0.1.tgz", - "integrity": "sha512-10PkHyXmrtsTvZSL+cqtJLTgFXkU43Gd0JCc0Rw6GchWbqKe0Rwgt1v3ouobTZwQzF1mGhDeAlWYBMGRV7y+3g==", - "dependencies": { - "@ts-morph/common": "~0.18.0", - "code-block-writer": "^11.0.3" - } - }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "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 - } - } - }, - "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "license": "0BSD" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", - "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/val-next": { - "resolved": "examples/next", - "link": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/lib": { - "name": "@val/lib", - "version": "1.0.0", - "devDependencies": { - "@types/jest": "^29.2.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.3" - } - }, - "packages/react": { - "name": "@val/react", - "version": "1.0.0", - "dependencies": { - "@val/lib": "1.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "packages/server": { - "name": "@val/server", - "version": "1.0.0", - "dependencies": { - "express": "^4.18.2", - "ts-morph": "^17.0.1", - "typescript": "^4.9.4" - }, - "devDependencies": { - "@types/express": "^4.17.15", - "@val/lib": "*", - "nodemon": "^2.0.20", - "ts-node": "^10.9.1" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - } - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", - "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==" - }, - "@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "dependencies": { - "@babel/parser": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz", - "integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==" - }, - "@babel/traverse": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz", - "integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.13", - "@babel/types": "^7.18.13", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/generator": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz", - "integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==", - "requires": { - "@babel/types": "^7.18.13", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@babel/types": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz", - "integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==", - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", - "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", - "requires": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", - "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", - "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz", - "integrity": "sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==", - "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", - "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", - "requires": { - "@babel/template": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "requires": { - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", - "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", - "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==" - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-replace-supers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz", - "integrity": "sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", - "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", - "requires": { - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==" - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" - }, - "@babel/helper-wrap-function": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.10.tgz", - "integrity": "sha512-95NLBP59VWdfK2lyLKe6eTMq9xg+yWKzxzxbJ1wcYNi1Auz200+83fMDADjRxBvc2QQor5zja2yTQzXGhk2GtQ==", - "requires": { - "@babel/helper-function-name": "^7.18.9", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", - "requires": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.10.tgz", - "integrity": "sha512-TYk3OA0HKL6qNryUayb5UUEhM/rkOQozIBEA5ITXh5DWrSp0TlUQXMyZmnWxG/DizSWBeeQ0Zbc5z8UGaaqoeg==" - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", - "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", - "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", - "requires": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", - "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", - "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz", - "integrity": "sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz", - "integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", - "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", - "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz", - "integrity": "sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==", - "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-validator-identifier": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", - "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.10.tgz", - "integrity": "sha512-gCy7Iikrpu3IZjYZolFE4M1Sm+nrh1/6za2Ewj77Z+XirT4TsbJcvOFOyF+fRPwU6AKKK136CZxx6L8AbSFG6A==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.18.10" - } - }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz", - "integrity": "sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==", - "requires": { - "@babel/plugin-transform-react-jsx": "^7.18.6" - } - }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz", - "integrity": "sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", - "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.9", - "babel-plugin-polyfill-corejs2": "^0.3.2", - "babel-plugin-polyfill-corejs3": "^0.5.3", - "babel-plugin-polyfill-regenerator": "^0.4.0", - "semver": "^6.3.0" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz", - "integrity": "sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.18.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.12.tgz", - "integrity": "sha512-2vjjam0cum0miPkenUbQswKowuxs/NjMwIKEq0zwegRxXk12C9YOF9STXnaUptITOtOJHKHpzvvWYOjbm6tc0w==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-typescript": "^7.18.6" - }, - "dependencies": { - "@babel/helper-create-class-features-plugin": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz", - "integrity": "sha512-hDvXp+QYxSRL+23mpAlSGxHMDyIGChm0/AwTfTAAK5Ufe40nCsyNdaYCGuK91phn/fVu9kqayImRDkvNAgdrsA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - } - } - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/preset-env": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", - "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", - "requires": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.18.10", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.9", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.18.9", - "@babel/plugin-transform-classes": "^7.18.9", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.18.9", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.18.9", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.18.9", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.18.10", - "babel-plugin-polyfill-corejs2": "^0.3.2", - "babel-plugin-polyfill-corejs3": "^0.5.3", - "babel-plugin-polyfill-regenerator": "^0.4.0", - "core-js-compat": "^3.22.1", - "semver": "^6.3.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-react": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz", - "integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-react-display-name": "^7.18.6", - "@babel/plugin-transform-react-jsx": "^7.18.6", - "@babel/plugin-transform-react-jsx-development": "^7.18.6", - "@babel/plugin-transform-react-pure-annotations": "^7.18.6" - } - }, - "@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - } - }, - "@babel/runtime": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", - "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/runtime-corejs3": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz", - "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==", - "dev": true, - "requires": { - "core-js-pure": "^3.20.2", - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/traverse": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.10.tgz", - "integrity": "sha512-J7ycxg0/K9XCtLyHf0cz2DqDihonJeIo+z+HEdRe9YuT8TY4A66i+Ab2/xZCEW7Ro60bPCBBfqqboHSamoV3+g==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.10", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "@babel/generator": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.10.tgz", - "integrity": "sha512-0+sW7e3HjQbiHbj1NeU/vN8ornohYlacAfZIaXhdoGweQqgcNy69COVciYYqEXJ/v+9OBA7Frxm4CVAuNqKeNA==", - "requires": { - "@babel/types": "^7.18.10", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - } - } - } - }, - "@babel/types": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", - "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - } - } - }, - "@eslint/eslintrc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", - "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", - "dev": true - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.3.1.tgz", - "integrity": "sha512-IRE6GD47KwcqA09RIWrabKdHPiKDGgtAL31xDxbi/RjQMsr+lY+ppxmHwY0dUEV3qvvxZzoe5Hl0RXZJOjQNUg==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.3.1.tgz", - "integrity": "sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==", - "dev": true, - "requires": { - "@jest/console": "^29.3.1", - "@jest/reporters": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.2.0", - "jest-config": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-resolve-dependencies": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "jest-watcher": "^29.3.1", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true - } - } - }, - "@jest/environment": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.3.1.tgz", - "integrity": "sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1" - } - }, - "@jest/expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==", - "dev": true, - "requires": { - "expect": "^29.3.1", - "jest-snapshot": "^29.3.1" - } - }, - "@jest/expect-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.3.1.tgz", - "integrity": "sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==", - "dev": true, - "requires": { - "jest-get-type": "^29.2.0" - } - }, - "@jest/fake-timers": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.3.1.tgz", - "integrity": "sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - } - }, - "@jest/globals": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.3.1.tgz", - "integrity": "sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/types": "^29.3.1", - "jest-mock": "^29.3.1" - } - }, - "@jest/reporters": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.3.1.tgz", - "integrity": "sha512-GhBu3YFuDrcAYW/UESz1JphEAbvUjaY2vShRZRoRY1mxpCMB3yGSJ4j9n0GxVlEOdCf7qjvUfBCrTUUqhVfbRA==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.2.0.tgz", - "integrity": "sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.3.1.tgz", - "integrity": "sha512-qeLa6qc0ddB0kuOZyZIhfN5q0e2htngokyTWsGriedsDhItisW7SDYZ7ceOe57Ii03sL988/03wAcBh3TChMGw==", - "dev": true, - "requires": { - "@jest/console": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.3.1.tgz", - "integrity": "sha512-IqYvLbieTv20ArgKoAMyhLHNrVHJfzO6ARZAbQRlY4UGWfdDnLlZEF0BvKOMd77uIiIjSZRwq3Jb3Fa3I8+2UA==", - "dev": true, - "requires": { - "@jest/test-result": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.3.1.tgz", - "integrity": "sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.3.1", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - } - } - }, - "@jest/types": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.3.1.tgz", - "integrity": "sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "@next/env": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-12.2.5.tgz", - "integrity": "sha512-vLPLV3cpPGjUPT3PjgRj7e3nio9t6USkuew3JE/jMeon/9Mvp1WyR18v3iwnCuX7eUAm1HmAbJHHLAbcu/EJcw==" - }, - "@next/eslint-plugin-next": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-12.2.5.tgz", - "integrity": "sha512-VBjVbmqEzGiOTBq4+wpeVXt/KgknnGB6ahvC/AxiIGnN93/RCSyXhFRI4uSfftM2Ba3w7ZO7076bfKasZsA0fw==", - "dev": true, - "requires": { - "glob": "7.1.7" - }, - "dependencies": { - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@preconstruct/cli": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@preconstruct/cli/-/cli-2.2.1.tgz", - "integrity": "sha512-G+sUV9o8l6Ds/82qJZYTXkCsVqPXLuD+bv+nVQeo3OL+eqzO/uAiBBFVp0DMcBJiyQYeU9nb+V8q22/PPaepDw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/core": "^7.7.7", - "@babel/helper-module-imports": "^7.10.4", - "@babel/runtime": "^7.7.7", - "@preconstruct/hook": "^0.4.0", - "@rollup/plugin-alias": "^3.1.1", - "@rollup/plugin-commonjs": "^15.0.0", - "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "builtin-modules": "^3.1.0", - "chalk": "^4.1.0", - "dataloader": "^2.0.0", - "detect-indent": "^6.0.0", - "enquirer": "^2.3.6", - "estree-walker": "^2.0.1", - "fast-deep-equal": "^2.0.1", - "fast-glob": "^3.2.4", - "fs-extra": "^9.0.1", - "is-ci": "^2.0.0", - "is-reference": "^1.2.1", - "jest-worker": "^26.3.0", - "magic-string": "^0.25.7", - "meow": "^7.1.0", - "ms": "^2.1.2", - "normalize-path": "^3.0.0", - "npm-packlist": "^2.1.2", - "p-limit": "^3.0.2", - "parse-glob": "^3.0.4", - "parse-json": "^5.1.0", - "quick-lru": "^5.1.1", - "resolve": "^1.17.0", - "resolve-from": "^5.0.0", - "rollup": "^2.32.0", - "semver": "^7.3.4", - "terser": "^5.2.1", - "v8-compile-cache": "^2.1.1" - }, - "dependencies": { - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", - "dev": true - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@preconstruct/hook": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@preconstruct/hook/-/hook-0.4.0.tgz", - "integrity": "sha512-a7mrlPTM3tAFJyz43qb4pPVpUx8j8TzZBFsNFqcKcE/sEakNXRlQAuCT4RGZRf9dQiiUnBahzSIWawU4rENl+Q==", - "dev": true, - "requires": { - "@babel/core": "^7.7.7", - "@babel/plugin-transform-modules-commonjs": "^7.7.5", - "pirates": "^4.0.1", - "source-map-support": "^0.5.16" - } - }, - "@rollup/plugin-alias": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.9.tgz", - "integrity": "sha512-QI5fsEvm9bDzt32k39wpOwZhVzRcL5ydcffUHMyLVaVaLeC70I8TJZ17F1z1eMoLu4E/UOcH9BWVkKpIKdrfiw==", - "dev": true, - "requires": { - "slash": "^3.0.0" - } - }, - "@rollup/plugin-commonjs": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-15.1.0.tgz", - "integrity": "sha512-xCQqz4z/o0h2syQ7d9LskIMvBSH4PX5PjYdpSSvgS+pQik3WahkQVNWg3D8XJeYjZoVWnIUQYDghuEMRGrmQYQ==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "commondir": "^1.0.1", - "estree-walker": "^2.0.1", - "glob": "^7.1.6", - "is-reference": "^1.2.1", - "magic-string": "^0.25.7", - "resolve": "^1.17.0" - } - }, - "@rollup/plugin-json": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", - "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8" - } - }, - "@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - } - }, - "@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "dependencies": { - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - } - } - }, - "@rushstack/eslint-patch": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz", - "integrity": "sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==", - "dev": true - }, - "@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@swc/helpers": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.3.tgz", - "integrity": "sha512-6JrF+fdUK2zbGpJIlN7G3v966PQjyx/dPt1T9km2wj+EUBqgrxCk3uX4Kct16MIm9gGxfKRcfax2hVf5jvlTzA==", - "requires": { - "tslib": "^2.4.0" - } - }, - "@ts-morph/common": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.18.1.tgz", - "integrity": "sha512-RVE+zSRICWRsfrkAw5qCAK+4ZH9kwEFv5h0+/YeHTLieWP7F4wWq4JsKFuNWG+fYh/KF+8rAtgdj5zb2mm+DVA==", - "requires": { - "fast-glob": "^3.2.12", - "minimatch": "^5.1.0", - "mkdirp": "^1.0.4", - "path-browserify": "^1.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.20", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.20.tgz", - "integrity": "sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true - }, - "@types/express": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz", - "integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==", - "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.31", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.32", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz", - "integrity": "sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.5.tgz", - "integrity": "sha512-H2cSxkKgVmqNHXP7TC2L/WUorrZu8ZigyRywfVzv6EyBlxj39n4C00hjXYQWsbwqgElaj/CiAeSRmk5GoaKTgw==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", - "dev": true - }, - "@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, - "@types/node": { - "version": "18.7.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz", - "integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "@types/react": { - "version": "18.0.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", - "integrity": "sha512-38ETy4tL+rn4uQQi7mB81G7V1g0u2ryquNmsVIOKUAEIDK+3CUjZ6rSRpdvS99dNBnkLFL83qfmtLacGOTIhwQ==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "18.0.6", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", - "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "@types/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "dev": true, - "requires": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.18", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.18.tgz", - "integrity": "sha512-eIJR1UER6ur3EpKM3d+2Pgd+ET+k6Kn9B4ZItX0oPjjVI5PrfaRjKyLT5UYendDpLuoiJMNJvovLQbEXqhsPaw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@typescript-eslint/parser": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.35.1.tgz", - "integrity": "sha512-XL2TBTSrh3yWAsMYpKseBYTVpvudNf69rPOWXWVBI08My2JVT5jR66eTt4IgQFHA/giiKJW5dUD4x/ZviCKyGg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.35.1", - "@typescript-eslint/types": "5.35.1", - "@typescript-eslint/typescript-estree": "5.35.1", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.35.1.tgz", - "integrity": "sha512-kCYRSAzIW9ByEIzmzGHE50NGAvAP3wFTaZevgWva7GpquDyFPFcmvVkFJGWJJktg/hLwmys/FZwqM9EKr2u24Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.35.1", - "@typescript-eslint/visitor-keys": "5.35.1" - } - }, - "@typescript-eslint/types": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.35.1.tgz", - "integrity": "sha512-FDaujtsH07VHzG0gQ6NDkVVhi1+rhq0qEvzHdJAQjysN+LHDCKDKCBRlZFFE0ec0jKxiv0hN63SNfExy0KrbQQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.35.1.tgz", - "integrity": "sha512-JUqE1+VRTGyoXlDWWjm6MdfpBYVq+hixytrv1oyjYIBEOZhBCwtpp5ZSvBt4wIA1MKWlnaC2UXl2XmYGC3BoQA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.35.1", - "@typescript-eslint/visitor-keys": "5.35.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.35.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.35.1.tgz", - "integrity": "sha512-cEB1DvBVo1bxbW/S5axbGPE6b7FIMAbo3w+AGq6zNDA7+NYJOIkKj/sInfTv4edxd4PxJSgdN4t6/pbvgA+n5g==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.35.1", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@val/lib": { - "version": "file:packages/lib", - "requires": { - "@types/jest": "^29.2.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.3" - } - }, - "@val/react": { - "version": "file:packages/react", - "requires": { - "@val/lib": "1.0.0" - } - }, - "@val/server": { - "version": "file:packages/server", - "requires": { - "@types/express": "^4.17.15", - "@val/lib": "*", - "express": "^4.18.2", - "nodemon": "^2.0.20", - "ts-morph": "^17.0.1", - "ts-node": "^10.9.1", - "typescript": "*" - } - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", - "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-shim-unscopables": "^1.0.0" - } - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true - }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "axe-core": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", - "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==", - "dev": true - }, - "axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", - "dev": true - }, - "babel-jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", - "integrity": "sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==", - "dev": true, - "requires": { - "@jest/transform": "^29.3.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.2.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "requires": { - "object.assign": "^4.1.0" - }, - "dependencies": { - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz", - "integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz", - "integrity": "sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==", - "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.2", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", - "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.2", - "core-js-compat": "^3.21.0" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz", - "integrity": "sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.2" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz", - "integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.2.0", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", - "requires": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" - }, - "dependencies": { - "caniuse-lite": { - "version": "1.0.30001373", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz", - "integrity": "sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ==" - } - } - }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "dependencies": { - "map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - } - } - }, - "caniuse-lite": { - "version": "1.0.30001383", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001383.tgz", - "integrity": "sha512-swMpEoTp5vDoGBZsYZX7L7nXHe6dsHxi9o6/LKf/f0LukVtnrxly5GVb/fWdCDTqi/yw6Km6tiJ0pmBacm0gbg==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", - "dev": true - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "code-block-writer": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-11.0.3.tgz", - "integrity": "sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==" - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "core-js-compat": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.24.1.tgz", - "integrity": "sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw==", - "requires": { - "browserslist": "^4.21.3", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } - }, - "core-js-pure": { - "version": "3.25.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.25.0.tgz", - "integrity": "sha512-IeHpLwk3uoci37yoI2Laty59+YqH9x5uR65/yiA0ARAJrTrN4YU0rmauLWfvqOuk77SlNJXj2rM6oT/dBD87+A==", - "dev": true - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "csstype": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" - }, - "damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true - }, - "dataloader": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.1.0.tgz", - "integrity": "sha512-qTcEYLen3r7ojZNgVUaRggOI+KM7jrKxXeSHhogh/TWxYMeONEMqY+hmkobiYQozsGIyg9OYVzO4ZIfoB4I0pQ==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "diff-sequences": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.3.1.tgz", - "integrity": "sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "electron-to-chromium": { - "version": "1.4.210", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.210.tgz", - "integrity": "sha512-kSiX4tuyZijV7Cz0MWVmGT8K2siqaOA4Z66K5dCttPPRh0HicOcOAEj1KlC8O8J1aOS/1M8rGofOzksLKaHWcQ==" - }, - "emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - } - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", - "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.1", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "@humanwhocodes/module-importer": "^1.0.1", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "eslint-config-next": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-12.2.5.tgz", - "integrity": "sha512-SOowilkqPzW6DxKp3a3SYlrfPi5Ajs9MIzp9gVfUDxxH9QFM5ElkR1hX5m/iICJuvCbWgQqFBiA3mCMozluniw==", - "dev": true, - "requires": { - "@next/eslint-plugin-next": "12.2.5", - "@rushstack/eslint-patch": "^1.1.3", - "@typescript-eslint/parser": "^5.21.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-import-resolver-typescript": "^2.7.1", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.29.4", - "eslint-plugin-react-hooks": "^4.5.0" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-import-resolver-typescript": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.1.tgz", - "integrity": "sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ==", - "dev": true, - "requires": { - "debug": "^4.3.4", - "glob": "^7.2.0", - "is-glob": "^4.0.3", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - } - }, - "eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-import": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", - "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", - "dev": true, - "requires": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "eslint-plugin-jsx-a11y": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", - "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", - "dev": true, - "requires": { - "@babel/runtime": "^7.18.9", - "aria-query": "^4.2.2", - "array-includes": "^3.1.5", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.4.3", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.2", - "language-tags": "^1.0.5", - "minimatch": "^3.1.2", - "semver": "^6.3.0" - } - }, - "eslint-plugin-react": { - "version": "7.31.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.1.tgz", - "integrity": "sha512-j4/2xWqt/R7AZzG8CakGHA6Xa/u7iR8Q3xCxY+AUghdT92bnIDOBEefV456OeH0QvBcroVc0eyvrrLSyQGYIfg==", - "dev": true, - "requires": { - "array-includes": "^3.1.5", - "array.prototype.flatmap": "^1.3.0", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.1", - "object.values": "^1.1.5", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.7" - }, - "dependencies": { - "resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - } - } - }, - "eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.3.1.tgz", - "integrity": "sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1" - } - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true - }, - "ignore-walk": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", - "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "requires": { - "is-extglob": "^2.1.1" - }, - "dependencies": { - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" - } - } - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", - "integrity": "sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==", - "dev": true, - "requires": { - "@jest/core": "^29.3.1", - "@jest/types": "^29.3.1", - "import-local": "^3.0.2", - "jest-cli": "^29.3.1" - } - }, - "jest-changed-files": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.2.0.tgz", - "integrity": "sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.3.1.tgz", - "integrity": "sha512-wpr26sEvwb3qQQbdlmei+gzp6yoSSoSL6GsLPxnuayZSMrSd5Ka7IjAvatpIernBvT2+Ic6RLTg+jSebScmasg==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/expect": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "p-limit": "^3.1.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-cli": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.3.1.tgz", - "integrity": "sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==", - "dev": true, - "requires": { - "@jest/core": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - } - }, - "jest-config": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", - "integrity": "sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.3.1", - "@jest/types": "^29.3.1", - "babel-jest": "^29.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.3.1", - "jest-environment-node": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-runner": "^29.3.1", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true - } - } - }, - "jest-diff": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.3.1.tgz", - "integrity": "sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - } - }, - "jest-docblock": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.2.0.tgz", - "integrity": "sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.3.1.tgz", - "integrity": "sha512-qrZH7PmFB9rEzCSl00BWjZYuS1BSOH8lLuC0azQE9lQrAx3PWGKHTDudQiOSwIy5dGAJh7KA0ScYlCP7JxvFYA==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "jest-util": "^29.3.1", - "pretty-format": "^29.3.1" - } - }, - "jest-environment-node": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.3.1.tgz", - "integrity": "sha512-xm2THL18Xf5sIHoU7OThBPtuH6Lerd+Y1NLYiZJlkE3hbE+7N7r8uvHIl/FkZ5ymKXJe/11SQuf3fv4v6rUMag==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-mock": "^29.3.1", - "jest-util": "^29.3.1" - } - }, - "jest-get-type": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz", - "integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==", - "dev": true - }, - "jest-haste-map": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.3.1.tgz", - "integrity": "sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.2.0", - "jest-util": "^29.3.1", - "jest-worker": "^29.3.1", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "dependencies": { - "jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-leak-detector": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", - "integrity": "sha512-3DA/VVXj4zFOPagGkuqHnSQf1GZBmmlagpguxEERO6Pla2g84Q1MaVIB3YMxgUaFIaYag8ZnTyQgiZ35YEqAQA==", - "dev": true, - "requires": { - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - } - }, - "jest-matcher-utils": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.3.1.tgz", - "integrity": "sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.3.1" - } - }, - "jest-message-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.3.1.tgz", - "integrity": "sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.3.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.3.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.3.1.tgz", - "integrity": "sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "jest-util": "^29.3.1" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "29.2.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz", - "integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==", - "dev": true - }, - "jest-resolve": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.3.1.tgz", - "integrity": "sha512-amXJgH/Ng712w3Uz5gqzFBBjxV8WFLSmNjoreBGMqxgCz5cH7swmBZzgBaCIOsvb0NbpJ0vgaSFdJqMdT+rADw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.3.1", - "jest-validate": "^29.3.1", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.3.1.tgz", - "integrity": "sha512-Vk0cYq0byRw2WluNmNWGqPeRnZ3p3hHmjJMp2dyyZeYIfiBskwq4rpiuGFR6QGAdbj58WC7HN4hQHjf2mpvrLA==", - "dev": true, - "requires": { - "jest-regex-util": "^29.2.0", - "jest-snapshot": "^29.3.1" - } - }, - "jest-runner": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.3.1.tgz", - "integrity": "sha512-oFvcwRNrKMtE6u9+AQPMATxFcTySyKfLhvso7Sdk/rNpbhg4g2GAGCopiInk1OP4q6gz3n6MajW4+fnHWlU3bA==", - "dev": true, - "requires": { - "@jest/console": "^29.3.1", - "@jest/environment": "^29.3.1", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.2.0", - "jest-environment-node": "^29.3.1", - "jest-haste-map": "^29.3.1", - "jest-leak-detector": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-resolve": "^29.3.1", - "jest-runtime": "^29.3.1", - "jest-util": "^29.3.1", - "jest-watcher": "^29.3.1", - "jest-worker": "^29.3.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "jest-worker": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.3.1.tgz", - "integrity": "sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.3.1", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.3.1.tgz", - "integrity": "sha512-jLzkIxIqXwBEOZx7wx9OO9sxoZmgT2NhmQKzHQm1xwR1kNW/dn0OjxR424VwHHf1SPN6Qwlb5pp1oGCeFTQ62A==", - "dev": true, - "requires": { - "@jest/environment": "^29.3.1", - "@jest/fake-timers": "^29.3.1", - "@jest/globals": "^29.3.1", - "@jest/source-map": "^29.2.0", - "@jest/test-result": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-mock": "^29.3.1", - "jest-regex-util": "^29.2.0", - "jest-resolve": "^29.3.1", - "jest-snapshot": "^29.3.1", - "jest-util": "^29.3.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - } - } - }, - "jest-snapshot": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.3.1.tgz", - "integrity": "sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.3.1", - "@jest/transform": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.3.1", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.3.1", - "jest-get-type": "^29.2.0", - "jest-haste-map": "^29.3.1", - "jest-matcher-utils": "^29.3.1", - "jest-message-util": "^29.3.1", - "jest-util": "^29.3.1", - "natural-compare": "^1.4.0", - "pretty-format": "^29.3.1", - "semver": "^7.3.5" - }, - "dependencies": { - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "jest-util": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.3.1.tgz", - "integrity": "sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", - "dev": true - } - } - }, - "jest-validate": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.3.1.tgz", - "integrity": "sha512-N9Lr3oYR2Mpzuelp1F8negJR3YE+L1ebk1rYA5qYo9TTY3f9OWdptLoNSPP9itOCBIRBqjt/S5XHlzYglLN67g==", - "dev": true, - "requires": { - "@jest/types": "^29.3.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.2.0", - "leven": "^3.1.0", - "pretty-format": "^29.3.1" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.3.1.tgz", - "integrity": "sha512-RspXG2BQFDsZSRKGCT/NiNa8RkQ1iKAjrO0//soTMWx/QUt+OcxMqMSBxz23PYGqUuWm2+m2mNNsmj0eIoOaFg==", - "dev": true, - "requires": { - "@jest/test-result": "^29.3.1", - "@jest/types": "^29.3.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.3.1", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "dev": true, - "requires": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true - }, - "language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "dev": true, - "requires": { - "language-subtag-registry": "~0.3.2" - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "meow": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz", - "integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "next-compose-plugins": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/next-compose-plugins/-/next-compose-plugins-2.2.1.tgz", - "integrity": "sha512-OjJ+fV15FXO2uQXQagLD4C0abYErBjyjE0I0FHpOEIB8upw0hg1ldFP6cqHTJBH1cZqy96OeR3u1dJ+Ez2D4Bg==" - }, - "next-transpile-modules": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/next-transpile-modules/-/next-transpile-modules-9.0.0.tgz", - "integrity": "sha512-VCNFOazIAnXn1hvgYYSTYMnoWgKgwlYh4lm1pKbSfiB3kj5ZYLcKVhfh3jkPOg1cnd9DP+pte9yCUocdPEUBTQ==", - "requires": { - "enhanced-resolve": "^5.7.0", - "escalade": "^3.1.1" - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" - }, - "nodemon": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz", - "integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==", - "dev": true, - "requires": { - "chokidar": "^3.5.2", - "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-bundled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", - "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", - "dev": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true - }, - "npm-packlist": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz", - "integrity": "sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg==", - "dev": true, - "requires": { - "glob": "^7.1.6", - "ignore-walk": "^3.0.3", - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.hasown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", - "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", - "dev": true, - "requires": { - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "pretty-format": { - "version": "29.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.3.1.tgz", - "integrity": "sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - } - } - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, - "react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - }, - "dependencies": { - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - } - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "react-test-renderer": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz", - "integrity": "sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw==", - "requires": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.13.6" - } - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - }, - "regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "regexpu-core": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", - "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.0.1", - "regjsgen": "^0.6.0", - "regjsparser": "^0.8.2", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - } - }, - "regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" - }, - "regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.78.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", - "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "scheduler": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz", - "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", - "dev": true, - "requires": { - "semver": "~7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - } - } - }, - "string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" - }, - "terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, - "trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true - }, - "ts-jest": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", - "integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "dependencies": { - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "ts-morph": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-17.0.1.tgz", - "integrity": "sha512-10PkHyXmrtsTvZSL+cqtJLTgFXkU43Gd0JCc0Rw6GchWbqKe0Rwgt1v3ouobTZwQzF1mGhDeAlWYBMGRV7y+3g==", - "requires": { - "@ts-morph/common": "~0.18.0", - "code-block-writer": "^11.0.3" - } - }, - "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - } - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==" - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" - }, - "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "update-browserslist-db": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", - "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "val-next": { - "version": "file:examples/next", - "requires": { - "@types/node": "18.7.13", - "@types/react": "18.0.17", - "@types/react-dom": "18.0.6", - "@val/lib": "*", - "@val/react": "*", - "@val/server": "*", - "eslint": "8.23.0", - "eslint-config-next": "12.2.5", - "next": "^12.1.6", - "next-compose-plugins": "^2.2.0", - "next-transpile-modules": "^9.0.0", - "react": "18.2.0", - "react-dom": "18.2.0", - "typescript": "^4.8.2" - }, - "dependencies": { - "@next/swc-android-arm-eabi": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.2.5.tgz", - "integrity": "sha512-cPWClKxGhgn2dLWnspW+7psl3MoLQUcNqJqOHk2BhNcou9ARDtC0IjQkKe5qcn9qg7I7U83Gp1yh2aesZfZJMA==", - "optional": true - }, - "@next/swc-android-arm64": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.2.5.tgz", - "integrity": "sha512-vMj0efliXmC5b7p+wfcQCX0AfU8IypjkzT64GiKJD9PgiA3IILNiGJr1fw2lyUDHkjeWx/5HMlMEpLnTsQslwg==", - "optional": true - }, - "@next/swc-darwin-arm64": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.2.5.tgz", - "integrity": "sha512-VOPWbO5EFr6snla/WcxUKtvzGVShfs302TEMOtzYyWni6f9zuOetijJvVh9CCTzInnXAZMtHyNhefijA4HMYLg==", - "optional": true - }, - "@next/swc-darwin-x64": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.2.5.tgz", - "integrity": "sha512-5o8bTCgAmtYOgauO/Xd27vW52G2/m3i5PX7MUYePquxXAnX73AAtqA3WgPXBRitEB60plSKZgOTkcpqrsh546A==", - "optional": true - }, - "@next/swc-freebsd-x64": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.2.5.tgz", - "integrity": "sha512-yYUbyup1JnznMtEBRkK4LT56N0lfK5qNTzr6/DEyDw5TbFVwnuy2hhLBzwCBkScFVjpFdfiC6SQAX3FrAZzuuw==", - "optional": true - }, - "@next/swc-linux-arm-gnueabihf": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.2.5.tgz", - "integrity": "sha512-2ZE2/G921Acks7UopJZVMgKLdm4vN4U0yuzvAMJ6KBavPzqESA2yHJlm85TV/K9gIjKhSk5BVtauIUntFRP8cg==", - "optional": true - }, - "@next/swc-linux-arm64-gnu": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.2.5.tgz", - "integrity": "sha512-/I6+PWVlz2wkTdWqhlSYYJ1pWWgUVva6SgX353oqTh8njNQp1SdFQuWDqk8LnM6ulheVfSsgkDzxrDaAQZnzjQ==", - "optional": true - }, - "@next/swc-linux-arm64-musl": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.2.5.tgz", - "integrity": "sha512-LPQRelfX6asXyVr59p5sTpx5l+0yh2Vjp/R8Wi4X9pnqcayqT4CUJLiHqCvZuLin3IsFdisJL0rKHMoaZLRfmg==", - "optional": true - }, - "@next/swc-linux-x64-gnu": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.2.5.tgz", - "integrity": "sha512-0szyAo8jMCClkjNK0hknjhmAngUppoRekW6OAezbEYwHXN/VNtsXbfzgYOqjKWxEx3OoAzrT3jLwAF0HdX2MEw==", - "optional": true - }, - "@next/swc-linux-x64-musl": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.2.5.tgz", - "integrity": "sha512-zg/Y6oBar1yVnW6Il1I/08/2ukWtOG6s3acdJdEyIdsCzyQi4RLxbbhkD/EGQyhqBvd3QrC6ZXQEXighQUAZ0g==", - "optional": true - }, - "@next/swc-win32-arm64-msvc": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.2.5.tgz", - "integrity": "sha512-3/90DRNSqeeSRMMEhj4gHHQlLhhKg5SCCoYfE3kBjGpE63EfnblYUqsszGGZ9ekpKL/R4/SGB40iCQr8tR5Jiw==", - "optional": true - }, - "@next/swc-win32-ia32-msvc": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.2.5.tgz", - "integrity": "sha512-hGLc0ZRAwnaPL4ulwpp4D2RxmkHQLuI8CFOEEHdzZpS63/hMVzv81g8jzYA0UXbb9pus/iTc3VRbVbAM03SRrw==", - "optional": true - }, - "@next/swc-win32-x64-msvc": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.2.5.tgz", - "integrity": "sha512-7h5/ahY7NeaO2xygqVrSG/Y8Vs4cdjxIjowTZ5W6CKoTKn7tmnuxlUc2h74x06FKmbhAd9agOjr/AOKyxYYm9Q==", - "optional": true - }, - "next": { - "version": "12.2.5", - "resolved": "https://registry.npmjs.org/next/-/next-12.2.5.tgz", - "integrity": "sha512-tBdjqX5XC/oFs/6gxrZhjmiq90YWizUYU6qOWAfat7zJwrwapJ+BYgX2PmiacunXMaRpeVT4vz5MSPSLgNkrpA==", - "requires": { - "@next/env": "12.2.5", - "@next/swc-android-arm-eabi": "12.2.5", - "@next/swc-android-arm64": "12.2.5", - "@next/swc-darwin-arm64": "12.2.5", - "@next/swc-darwin-x64": "12.2.5", - "@next/swc-freebsd-x64": "12.2.5", - "@next/swc-linux-arm-gnueabihf": "12.2.5", - "@next/swc-linux-arm64-gnu": "12.2.5", - "@next/swc-linux-arm64-musl": "12.2.5", - "@next/swc-linux-x64-gnu": "12.2.5", - "@next/swc-linux-x64-musl": "12.2.5", - "@next/swc-win32-arm64-msvc": "12.2.5", - "@next/swc-win32-ia32-msvc": "12.2.5", - "@next/swc-win32-x64-msvc": "12.2.5", - "@swc/helpers": "0.4.3", - "caniuse-lite": "^1.0.30001332", - "postcss": "8.4.14", - "styled-jsx": "5.0.4", - "use-sync-external-store": "1.2.0" - } - }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "styled-jsx": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.4.tgz", - "integrity": "sha512-sDFWLbg4zR+UkNzfk5lPilyIgtpddfxXEULxhujorr5jtePTUqiPDc5BC0v1NRqTr/WaFBGQQUoYToGlF4B2KQ==", - "requires": {} - }, - "use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "requires": {} - } - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "dependencies": { - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/package.json b/package.json index 18a472537..c823a8775 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,44 @@ { - "name": "@val/root", + "name": "@valbuild/root", "version": "0.0.1", - "private": true, - "workspaces": [ - "packages/*", - "examples/*" - ], + "private": false, + "repository": { + "type": "git", + "url": "git+https://github.com/valbuild/val.git" + }, "scripts": { - "build": "preconstruct build", - "release": "yarn build && npm publish", - "postinstall": "preconstruct dev" + "build": "preconstruct build && pnpm --filter @valbuild/ui build", + "postinstall": "preconstruct dev", + "format": "prettier --check .", + "format:fix": "prettier --write .", + "lint": "eslint .", + "test": "jest", + "version-packages": "changeset version && pnpm install --no-frozen-lockfile", + "release": "pnpm run build && changeset publish", + "dev:example-next": "concurrently -k \"cd packages/ui; pnpm run dev\" \"cd examples/next; pnpm run dev\"" }, "devDependencies": { - "@preconstruct/cli": "^2.1.5" - }, - "dependencies": { - "@types/react": "^18.0.9", - "@babel/core": "^7.18.13", - "@babel/plugin-transform-runtime": "^7.18.10", - "@babel/preset-env": "^7.18.10", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "react": "16.14.0", - "react-dom": "16.14.0", - "react-test-renderer": "16.8.6" + "@babel/core": "^7.28.6", + "@babel/plugin-transform-runtime": "^7.28.5", + "@babel/preset-env": "^7.28.6", + "@babel/preset-react": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", + "@changesets/cli": "^2.29.8", + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^9.39.2", + "@preconstruct/cli": "^2.8.12", + "@types/jest": "^30.0.0", + "@types/node": "^25.0.9", + "@typescript-eslint/eslint-plugin": "^8.53.0", + "@typescript-eslint/parser": "^8.53.0", + "babel-jest": "^30.2.0", + "concurrently": "^9.2.1", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react": "^7.37.5", + "globals": "^17.0.0", + "jest": "30.2", + "prettier": "^3.8.0" }, "preconstruct": { "packages": [ diff --git a/packages/cli/.babelrc.json b/packages/cli/.babelrc.json new file mode 100644 index 000000000..eb8b0273a --- /dev/null +++ b/packages/cli/.babelrc.json @@ -0,0 +1,5 @@ +{ + "targets": { + "node": 16 + } +} diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 000000000..af4e70fd1 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,12 @@ +# @valbuild/cli + +CLI tools for Val projects including validation, development server, and project initialization. + +## Usage + +```sh +npx @valbuild/cli val validate --fix # Validate Val content files +npx @valbuild/cli val connect # Not required: connect to project in Cloud (requires sign in) +``` + +See the [documentation](https://val.build/docs) for more information. diff --git a/packages/cli/bin.js b/packages/cli/bin.js new file mode 100755 index 000000000..f174d5be3 --- /dev/null +++ b/packages/cli/bin.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +"use strict"; +require("./cli"); diff --git a/packages/cli/cli/package.json b/packages/cli/cli/package.json new file mode 100644 index 000000000..906723485 --- /dev/null +++ b/packages/cli/cli/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/valbuild-cli-cli.cjs.js", + "module": "dist/valbuild-cli-cli.esm.js" +} diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 000000000..076200efd --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,53 @@ +{ + "name": "@valbuild/cli", + "private": false, + "version": "0.96.0", + "description": "Val CLI tools", + "repository": { + "type": "git", + "url": "git+https://github.com/valbuild/val.git" + }, + "bin": { + "val": "./bin.js" + }, + "exports": { + "./cli": { + "module": "./cli/dist/valbuild-cli-cli.esm.js", + "default": "./cli/dist/valbuild-cli-cli.cjs.js" + }, + "./package.json": "./package.json" + }, + "scripts": { + "start": "tsx src/cli.ts --", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@valbuild/core": "workspace:*", + "@valbuild/eslint-plugin": "workspace:*", + "@valbuild/server": "workspace:*", + "@valbuild/shared": "workspace:*", + "chalk": "^5.6.2", + "cors": "^2.8.5", + "fast-glob": "^3.3.3", + "meow": "^9.0.0", + "open": "^9.1.0", + "picocolors": "^1.1.1", + "zod": "^4.3.5" + }, + "peerDependencies": { + "prettier": "*", + "typescript": ">=5.0.0" + }, + "preconstruct": { + "entrypoints": [ + "cli.ts" + ] + }, + "engines": { + "node": ">=18.17.0" + }, + "devDependencies": { + "@jest/globals": "^30.2.0", + "tsx": "^4.21.0" + } +} diff --git a/packages/cli/src/__fixtures__/basic/content/basic-errors.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-errors.val.ts new file mode 100644 index 000000000..11042ad19 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-errors.val.ts @@ -0,0 +1,7 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/basic-errors.val.ts", + s.string().minLength(30), + "Hello World", +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-files.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-files.val.ts new file mode 100644 index 000000000..0e11375d7 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-files.val.ts @@ -0,0 +1,14 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/basic-files.val.ts", + s.files({ + directory: "/public/val/files", + accept: "*/*", + }), + { + "/public/val/files/tracked.txt": { + mimeType: "text/plain", + }, + }, +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-gallery-2.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-gallery-2.val.ts new file mode 100644 index 000000000..5c82ec096 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-gallery-2.val.ts @@ -0,0 +1,17 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/basic-gallery-2.val.ts", + s.images({ + directory: "/public/val/images2", + accept: "image/*", + }), + { + "/public/val/images2/image.png": { + width: 1, + height: 1, + mimeType: "image/png", + alt: null, + }, + }, +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-gallery-fail-on-non-unique-dir.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-gallery-fail-on-non-unique-dir.val.ts new file mode 100644 index 000000000..5797e2adc --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-gallery-fail-on-non-unique-dir.val.ts @@ -0,0 +1,17 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/basic-gallery-fail-on-non-unique-dir.val.ts", + s.images({ + directory: "/public/val/images", + accept: "image/*", + }), + { + "/public/val/images/image.png": { + width: 1, + height: 1, + mimeType: "image/png", + alt: null, + }, + }, +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-gallery-missing-tracked.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-gallery-missing-tracked.val.ts new file mode 100644 index 000000000..e01a26755 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-gallery-missing-tracked.val.ts @@ -0,0 +1,17 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/basic-gallery-missing-tracked.val.ts", + s.images({ + directory: "/public/val/images4", + accept: "image/*", + }), + { + "/public/val/images4/missing.png": { + width: 1, + height: 1, + mimeType: "image/png", + alt: null, + }, + }, +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-gallery-wrong-metadata.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-gallery-wrong-metadata.val.ts new file mode 100644 index 000000000..b3e96651f --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-gallery-wrong-metadata.val.ts @@ -0,0 +1,17 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/basic-gallery-wrong-metadata.val.ts", + s.images({ + directory: "/public/val/images3", + accept: "image/*", + }), + { + "/public/val/images3/image.png": { + width: 999, + height: 999, + mimeType: "image/png", + alt: null, + }, + }, +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-gallery.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-gallery.val.ts new file mode 100644 index 000000000..353bec875 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-gallery.val.ts @@ -0,0 +1,18 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/basic-gallery.val.ts", + s.images({ + directory: "/public/val/images", + accept: "image/*", + }), + + { + "/public/val/images/image.png": { + width: 1, + height: 1, + mimeType: "image/png", + alt: null, + }, + }, +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-image-from-galleries.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-image-from-galleries.val.ts new file mode 100644 index 000000000..b32ff4aa6 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-image-from-galleries.val.ts @@ -0,0 +1,15 @@ +import { c, s } from "../val.config"; +import basicGalleryVal from "./basic-gallery.val"; +import basicGallery2Val from "./basic-gallery-2.val"; + +export default c.define( + "/content/basic-image-from-galleries.val.ts", + s.object({ + image1: s.image(basicGalleryVal), + image2: s.image(basicGallery2Val), + }), + { + image1: c.image("/public/val/images/image.png"), + image2: c.image("/public/val/images2/image.png"), + }, +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-image-from-gallery.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-image-from-gallery.val.ts new file mode 100644 index 000000000..c9f595a3f --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-image-from-gallery.val.ts @@ -0,0 +1,12 @@ +import { c, s } from "../val.config"; +import basicGalleryVal from "./basic-gallery.val"; + +export default c.define( + "/content/basic-image-from-gallery.val.ts", + s.object({ + image: s.image(basicGalleryVal), + }), + { + image: c.image("/public/val/images/image.png"), + }, +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-image.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-image.val.ts new file mode 100644 index 000000000..6db197ab5 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-image.val.ts @@ -0,0 +1,7 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/basic-image.val.ts", + s.image(), + c.image("/public/val/image.png"), +); diff --git a/packages/cli/src/__fixtures__/basic/content/basic-valid.val.ts b/packages/cli/src/__fixtures__/basic/content/basic-valid.val.ts new file mode 100644 index 000000000..d56661e9f --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/content/basic-valid.val.ts @@ -0,0 +1,7 @@ +import { c, s } from "../val.config"; + +export default c.define( + "/content/basic-valid.val.ts", + s.string(), + "Hello World", +); diff --git a/packages/cli/src/__fixtures__/basic/public/val/files/tracked.txt b/packages/cli/src/__fixtures__/basic/public/val/files/tracked.txt new file mode 100644 index 000000000..93a9e06d2 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/public/val/files/tracked.txt @@ -0,0 +1 @@ +tracked diff --git a/packages/cli/src/__fixtures__/basic/public/val/files/untracked.txt b/packages/cli/src/__fixtures__/basic/public/val/files/untracked.txt new file mode 100644 index 000000000..5a72eb2ed --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/public/val/files/untracked.txt @@ -0,0 +1 @@ +untracked diff --git a/packages/cli/src/__fixtures__/basic/public/val/image.png b/packages/cli/src/__fixtures__/basic/public/val/image.png new file mode 100644 index 000000000..252d9502d Binary files /dev/null and b/packages/cli/src/__fixtures__/basic/public/val/image.png differ diff --git a/packages/cli/src/__fixtures__/basic/public/val/images/image.png b/packages/cli/src/__fixtures__/basic/public/val/images/image.png new file mode 100644 index 000000000..252d9502d Binary files /dev/null and b/packages/cli/src/__fixtures__/basic/public/val/images/image.png differ diff --git a/packages/cli/src/__fixtures__/basic/public/val/images2/image.png b/packages/cli/src/__fixtures__/basic/public/val/images2/image.png new file mode 100644 index 000000000..252d9502d Binary files /dev/null and b/packages/cli/src/__fixtures__/basic/public/val/images2/image.png differ diff --git a/packages/cli/src/__fixtures__/basic/public/val/images3/image.png b/packages/cli/src/__fixtures__/basic/public/val/images3/image.png new file mode 100644 index 000000000..252d9502d Binary files /dev/null and b/packages/cli/src/__fixtures__/basic/public/val/images3/image.png differ diff --git a/packages/cli/src/__fixtures__/basic/tsconfig.json b/packages/cli/src/__fixtures__/basic/tsconfig.json new file mode 100644 index 000000000..b3baf2701 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2017", + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node" + }, + "exclude": ["node_modules"] +} diff --git a/packages/cli/src/__fixtures__/basic/val.config.ts b/packages/cli/src/__fixtures__/basic/val.config.ts new file mode 100644 index 000000000..8db86c966 --- /dev/null +++ b/packages/cli/src/__fixtures__/basic/val.config.ts @@ -0,0 +1,5 @@ +import { initVal } from "@valbuild/core"; + +const { s, c } = initVal(); + +export { s, c }; diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts new file mode 100644 index 000000000..2be3aaa7e --- /dev/null +++ b/packages/cli/src/cli.ts @@ -0,0 +1,139 @@ +import meow from "meow"; +import { error } from "./logger"; +import { validate } from "./validate"; +import { listUnusedFiles as listUnusedFiles } from "./listUnusedFiles"; +import { getVersions } from "./getVersions"; +import { connect } from "./connect"; +import chalk from "chalk"; +import { login } from "./login"; + +async function main(): Promise { + const { input, flags, showHelp } = meow( + ` + Usage: + $ val [command] + + Options: + --help Show this message + + Commands: + validate + login + files + connect + versions + + Command: validate + Description: val-idate val modules + Options: + --root [root], -r [root] Set project root directory (default process.cwd()) + --fix [fix] Attempt to fix validation errors + + + Command: login + Description: login to admin.val.build and generate a Personal Access Token + Options: + --root [root], -r [root] Set project root directory (default process.cwd()) + + + Command: connect + Description: connect your local project to a Val Build project at admin.val.build + Options: + --root [root], -r [root] Set project root directory (default process.cwd()) + + Command: list-unused-files + Description: EXPERIMENTAL. + List files that are in public/val but not in use by any Val module. + This is useful for cleaning up unused files. + Options: + --root [root], -r [root] Set project root directory (default process.cwd()) + `, + { + flags: { + port: { + type: "number", + alias: "p", + default: 4123, + }, + root: { + type: "string", + alias: "r", + }, + fix: { + type: "boolean", + }, + noEslint: { + type: "boolean", + }, + managedDir: { + type: "string", + }, + }, + hardRejection: false, + }, + ); + + if (input.length === 0) { + return showHelp(); + } + + if (input.length !== 1) { + return error(`Unknown command "${input.join(" ")}"`); + } + + const [command] = input; + switch (command) { + case "list-unused-files": + if (flags.fix || flags.noEslint) { + return error( + `Command "list-unused-files" does not support --fix or --noEslint flags`, + ); + } + return listUnusedFiles({ + root: flags.root, + }); + case "versions": + return versions(); + case "login": + return login({ + root: flags.root, + }); + case "connect": + if (flags.fix || flags.noEslint) { + return error( + `Command "connect" does not support --fix or --noEslint flags`, + ); + } + return connect({ + root: flags.root, + }); + case "validate": + case "idate": + if (flags.managedDir) { + return error(`Command "validate" does not support --managedDir flag`); + } + return validate({ + root: flags.root, + fix: flags.fix, + }); + default: + return error(`Unknown command "${input.join(" ")}"`); + } +} + +void main().catch((err) => { + error( + err instanceof Error + ? err.message + "\n" + err.stack + : typeof err === "object" + ? JSON.stringify(err, null, 2) + : err, + ); + process.exitCode = 1; +}); + +async function versions() { + const foundVersions = getVersions(); + console.log(`${chalk.cyan("@valbuild/core")}: ${foundVersions.coreVersion}`); + console.log(`${chalk.cyan("@valbuild/next")}: ${foundVersions.nextVersion}`); +} diff --git a/packages/cli/src/connect.ts b/packages/cli/src/connect.ts new file mode 100644 index 000000000..1381911d5 --- /dev/null +++ b/packages/cli/src/connect.ts @@ -0,0 +1,115 @@ +import pc from "picocolors"; +import fs from "fs"; +import path from "path"; +import { evalValConfigFile } from "./utils/evalValConfigFile"; + +const host = process.env.VAL_BUILD_URL || "https://admin.val.build"; + +export async function connect(options: { root?: string }) { + const { root } = options; + const projectRoot = root ? path.resolve(root) : process.cwd(); + + const maybeProject = await tryGetProject(projectRoot); + const maybeGitRemote = await tryGetGitRemote(projectRoot); + const maybeGitOwnerAndRepo = + maybeGitRemote !== null ? getGitHubOwnerAndRepo(maybeGitRemote) : null; + + const params = new URLSearchParams(); + params.set("step", "create-project"); + if (maybeProject) { + params.set("org", maybeProject.orgName); + params.set("project", maybeProject.projectName); + } + if (maybeGitOwnerAndRepo) { + params.set( + "github_repo", + [maybeGitOwnerAndRepo.owner, maybeGitOwnerAndRepo.repo].join("/"), + ); + } + const url = `${host}/connect?${params.toString()}`; + + console.log( + pc.dim( + `\nFollow the instructions in your browser to complete the setup:\n${pc.cyan(url)}\n`, + ), + ); +} + +async function tryGetProject(projectRoot: string): Promise<{ + orgName: string; + projectName: string; +} | null> { + const valConfigFile = + (await evalValConfigFile(projectRoot, "val.config.ts")) || + (await evalValConfigFile(projectRoot, "val.config.js")); + + if (valConfigFile && valConfigFile.project) { + const parts = valConfigFile.project.split("/"); + if (parts.length === 2) { + return { + orgName: parts[0], + projectName: parts[1], + }; + } else { + console.error( + pc.red( + `Invalid project format in val.config file: "${valConfigFile.project}". Expected format "orgName/projectName".`, + ), + ); + process.exit(1); + } + } + return null; +} + +function getGitHubOwnerAndRepo( + gitRemote: string, +): { owner: string; repo: string } | null { + const sshMatch = gitRemote.match(/git@github\.com:(.+?)\/(.+?)(\.git)?$/); + if (sshMatch) { + return { owner: sshMatch[1], repo: sshMatch[2] }; + } + const httpsMatch = gitRemote.match( + /https:\/\/github\.com\/(.+?)\/(.+?)(\.git)?$/, + ); + if (httpsMatch) { + return { owner: httpsMatch[1], repo: httpsMatch[2] }; + } + return null; +} + +async function tryGetGitRemote(root: string): Promise { + try { + const gitConfig = tryGetGitConfig(root); + if (!gitConfig) { + return null; + } + const remoteMatch = gitConfig.match( + /\[remote "origin"\][\s\S]*?url = (.+)/, + ); + if (remoteMatch && remoteMatch[1]) { + return remoteMatch[1].trim(); + } + return null; + } catch (error) { + console.error(pc.red("Failed to read .git/config file."), error); + return null; + } +} + +function tryGetGitConfig(root: string): string | null { + let currentDir = root; + let lastDir = null; + while (currentDir !== lastDir) { + const gitConfigPath = path.join(currentDir, ".git", "config"); + if (fs.existsSync(gitConfigPath)) { + return fs.readFileSync(gitConfigPath, "utf-8"); + } + lastDir = currentDir; + currentDir = path.dirname(currentDir); + if (lastDir === currentDir) { + break; + } + } + return null; +} diff --git a/packages/cli/src/getVersions.ts b/packages/cli/src/getVersions.ts new file mode 100644 index 000000000..93b0cb817 --- /dev/null +++ b/packages/cli/src/getVersions.ts @@ -0,0 +1,25 @@ +export const getVersions = (): { + coreVersion?: string; + nextVersion?: string; +} => { + const coreVersion = (() => { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require("@valbuild/core")?.Internal?.VERSION?.core; + } catch { + return null; + } + })(); + const nextVersion = (() => { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require("@valbuild/next")?.Internal?.VERSION?.next; + } catch { + return null; + } + })(); + return { + coreVersion: coreVersion || undefined, + nextVersion: nextVersion || undefined, + }; +}; diff --git a/packages/cli/src/listUnusedFiles.ts b/packages/cli/src/listUnusedFiles.ts new file mode 100644 index 000000000..47976e574 --- /dev/null +++ b/packages/cli/src/listUnusedFiles.ts @@ -0,0 +1,85 @@ +import { + FILE_REF_PROP, + ModuleFilePath, + ModulePath, + SourcePath, + VAL_EXTENSION, +} from "@valbuild/core"; +import { createService } from "@valbuild/server"; +import { glob } from "fast-glob"; +import path from "path"; + +export async function listUnusedFiles({ root }: { root?: string }) { + const managedDir = "public/val"; + const projectRoot = root ? path.resolve(root) : process.cwd(); + + const service = await createService(projectRoot, {}); + + const valFiles: string[] = await glob("**/*.val.{js,ts}", { + ignore: ["node_modules/**"], + cwd: projectRoot, + }); + + const filesUsedByVal: string[] = []; + async function pushFilesUsedByVal(file: string) { + const moduleId = `/${file}` as ModuleFilePath; // TODO: check if this always works? (Windows?) + const valModule = await service.get(moduleId, "" as ModulePath, { + validate: true, + source: true, + schema: true, + }); + // TODO: not sure using validation is the best way to do this, but it works currently. + if (valModule.errors) { + if (valModule.errors.validation) { + for (const sourcePathS in valModule.errors.validation) { + const sourcePath = sourcePathS as SourcePath; + const validationError = valModule.errors.validation[sourcePath]; + for (const error of validationError) { + const value = error.value; + if (isFileRef(value)) { + const absoluteFilePathUsedByVal = path.join( + projectRoot, + ...value[FILE_REF_PROP].split("/"), + ); + filesUsedByVal.push(absoluteFilePathUsedByVal); + } + } + } + } + } + } + for (const file of valFiles) { + await pushFilesUsedByVal(file); + } + + const managedRoot = path.join(projectRoot, managedDir); + const allFilesInManagedDir = await glob("**/*", { + ignore: ["node_modules/**"], + cwd: managedRoot, + }); + for (const file of allFilesInManagedDir) { + const absoluteFilePath = path.join(managedRoot, file); + if (!filesUsedByVal.includes(absoluteFilePath)) { + console.log(path.join(managedRoot, file)); + } + } + + service.dispose(); + return; +} + +function isFileRef( + value: unknown, +): value is { [FILE_REF_PROP]: string; [VAL_EXTENSION]: "file" } { + if (!value) return false; + if (typeof value !== "object") return false; + if (FILE_REF_PROP in value && VAL_EXTENSION in value) { + if ( + value[VAL_EXTENSION] === "file" && + typeof value[FILE_REF_PROP] === "string" + ) { + return true; + } + } + return false; +} diff --git a/packages/cli/src/logger.ts b/packages/cli/src/logger.ts new file mode 100644 index 000000000..0ea1b12eb --- /dev/null +++ b/packages/cli/src/logger.ts @@ -0,0 +1,34 @@ +import chalk from "chalk"; + +export function error(message: string) { + console.error(chalk.red("❌Error: ") + message); +} + +export function info( + message: string, + opts: { isCodeSnippet?: true; isGood?: true } = {}, +) { + if (opts.isCodeSnippet) { + console.log(chalk.cyanBright("$ > ") + chalk.cyan(message)); + return; + } + if (opts.isGood) { + console.log(chalk.green("✅: ") + message); + return; + } + console.log(chalk.blue("️ℹ️ : ") + message); +} + +export function debugPrint(str: string) { + /*eslint-disable no-constant-condition */ + if (process.env["DEBUG"] || true) { + // TODO: remove true + console.log(`DEBUG: ${str}`); + } +} + +export function printDebuggingHelp() { + info( + `If you're having trouble, please follow the debugging steps\n🌐: https://val.build/docs/troubleshooting`, + ); +} diff --git a/packages/cli/src/login.ts b/packages/cli/src/login.ts new file mode 100644 index 000000000..9cc31b56d --- /dev/null +++ b/packages/cli/src/login.ts @@ -0,0 +1,115 @@ +import pc from "picocolors"; +import fs from "fs"; +import path from "path"; +import { getPersonalAccessTokenPath } from "@valbuild/server"; + +const host = process.env.VAL_BUILD_URL || "https://admin.val.build"; + +export async function login(options: { root?: string }) { + try { + console.log(pc.cyan("\nStarting login process...\n")); + + // Step 1: Initiate login and get token and URL + const response = await fetch(`${host}/api/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); + let token; + let url; + if (!response.headers.get("content-type")?.includes("application/json")) { + const text = await response.text(); + console.error( + pc.red( + "Unexpected failure while trying to login (content type was not JSON). ", + ), + text + ? `Server response: ${text} (status: ${response.status})` + : `Status: ${response.status}`, + ); + process.exit(1); + } + const json = await response.json(); + if (json) { + token = json.nonce; + url = json.url; + } + if (!token || !url) { + console.error(pc.red("Unexpected response from the server."), json); + process.exit(1); + } + + console.log(pc.green("Open the following URL in your browser to log in:")); + console.log(pc.underline(pc.blue(url))); + console.log(pc.dim("\nWaiting for login confirmation...\n")); + + // Step 2: Poll for login confirmation + const result = await pollForConfirmation(token); + + // Step 3: Save the token + const filePath = getPersonalAccessTokenPath(options.root || process.cwd()); + saveToken(result, filePath); + } catch (error) { + console.error( + pc.red( + "An error occurred during the login process. Check your internet connection. Details:", + ), + error instanceof Error ? error.message : JSON.stringify(error, null, 2), + ); + process.exit(1); + } +} + +const MAX_DURATION = 5 * 60 * 1000; // 5 minutes +async function pollForConfirmation(token: string): Promise<{ + profile: { email: string }; + pat: string; +}> { + const start = Date.now(); + while (Date.now() - start < MAX_DURATION) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + const response = await fetch( + `${host}/api/login?token=${token}&consume=true`, + { method: "POST" }, + ); + if (response.status === 500) { + console.error(pc.red("An error occurred on the server.")); + process.exit(1); + } + if (response.status === 200) { + const json = await response.json(); + if (json) { + if ( + typeof json.profile.email === "string" && + typeof json.pat === "string" + ) { + return json; + } else { + console.error(pc.red("Unexpected response from the server.")); + process.exit(1); + } + } + } + } + console.error(pc.red("Login confirmation timed out.")); + process.exit(1); +} + +function saveToken( + result: { + profile: { email: string }; + pat: string; + }, + filePath: string, +) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, JSON.stringify(result, null, 2)); + console.log( + pc.green( + `Token for ${pc.cyan( + result.profile.email, + )} saved to ${pc.cyan(filePath)}`, + ), + ); +} diff --git a/packages/cli/src/runValidation.test.ts b/packages/cli/src/runValidation.test.ts new file mode 100644 index 000000000..165842196 --- /dev/null +++ b/packages/cli/src/runValidation.test.ts @@ -0,0 +1,386 @@ +import { describe, test, expect, beforeEach, afterEach } from "@jest/globals"; +import path from "path"; +import fs from "fs"; +import { + DEFAULT_VAL_REMOTE_HOST, + type ModuleFilePath, + type ModulePath, +} from "@valbuild/core"; +import { createService } from "@valbuild/server"; +import { + createDefaultValFSHost, + runValidation, + ValidationEvent, + IValRemote, +} from "./runValidation"; + +const BASIC_FIXTURE = path.resolve(__dirname, "__fixtures__/basic"); + +const mockRemote: IValRemote = { + remoteHost: DEFAULT_VAL_REMOTE_HOST, + getSettings: async () => { + throw new Error("Not expected to be called"); + }, + uploadFile: async () => { + throw new Error("Not expected to be called"); + }, +}; + +describe("runValidation", () => { + let tmpDir: string; + + beforeEach(() => { + const tmpBase = path.join(__dirname, ".tmp"); + fs.mkdirSync(tmpBase, { recursive: true }); + tmpDir = fs.mkdtempSync(path.join(tmpBase, "runValidation-")); + fs.cpSync(BASIC_FIXTURE, tmpDir, { recursive: true }); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + test("returns summary-success for a valid module", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: false, + valFiles: ["content/basic-valid.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + + expect(events.at(-1)).toEqual({ type: "summary-success" }); + expect(events.filter((e) => e.type === "validation-error")).toHaveLength(0); + }); + + test("returns validation-error for a module with minLength violation", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: false, + valFiles: ["content/basic-errors.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + + expect(events.at(-1)).toEqual({ type: "summary-errors", count: 1 }); + expect(events.filter((e) => e.type === "validation-error")).toHaveLength(1); + }); + + test("applies metadata fix for image without metadata", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: true, + valFiles: ["content/basic-image.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + + expect(events.at(-1)).toEqual({ type: "summary-success" }); + expect(events.filter((e) => e.type === "validation-error")).toHaveLength(0); + expect(events.filter((e) => e.type === "fix-applied")).toHaveLength(1); + expect(events.find((e) => e.type === "fix-applied")).toMatchObject({ + type: "fix-applied", + sourcePath: "/content/basic-image.val.ts", + }); + }); + + test("reports fixable error for image without metadata when fix is false", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: false, + valFiles: ["content/basic-image.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + + expect(events.at(-1)).toEqual({ type: "summary-errors", count: 1 }); + const fixableErrors = events.filter( + (e) => e.type === "validation-fixable-error", + ); + expect(fixableErrors).toHaveLength(1); + expect(fixableErrors[0]).toMatchObject({ + type: "validation-fixable-error", + sourcePath: "/content/basic-image.val.ts", + fixable: true, + }); + }); + + test("handles module with both s.image and s.images", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: false, + valFiles: ["content/basic-image-from-gallery.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + const lastEvent = events.at(-1); + expect(["summary-success", "summary-errors"]).toContain(lastEvent?.type); + }); + + test("handles module with two gallery val files", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: false, + valFiles: [ + "content/basic-image-from-galleries.val.ts", + "content/basic-gallery.val.ts", + "content/basic-gallery-2.val.ts", + ], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + + const lastEvent = events.at(-1); + expect(["summary-success", "summary-errors"]).toContain(lastEvent?.type); + }); + + test("basic-gallery-fail-on-non-unique-dir returns error for duplicate directory", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: false, + valFiles: [ + "content/basic-gallery.val.ts", + "content/basic-gallery-fail-on-non-unique-dir.val.ts", + ], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + + expect(events.at(-1)).toEqual({ + type: "summary-errors", + count: expect.any(Number), + }); + const errors = events.filter((e) => e.type === "validation-error"); + expect(errors.length).toBeGreaterThan(0); + expect( + errors.some( + (e) => + "message" in e && + (e.message as string).includes("/public/val/images"), + ), + ).toBe(true); + }); + + test("returns validation-error for s.files gallery with untracked file in directory", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: false, + valFiles: ["content/basic-files.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + + expect(events.at(-1)).toEqual({ + type: "summary-errors", + count: expect.any(Number), + }); + const errors = events.filter((e) => e.type === "validation-error"); + expect(errors.length).toBeGreaterThan(0); + expect( + errors.some( + (e) => + "message" in e && (e.message as string).includes("untracked.txt"), + ), + ).toBe(true); + }); + + test("returns validation-error for gallery with tracked file missing from disk", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: false, + valFiles: ["content/basic-gallery-missing-tracked.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + + expect(events.at(-1)).toEqual({ + type: "summary-errors", + count: expect.any(Number), + }); + const errors = events.filter((e) => e.type === "validation-error"); + expect(errors.length).toBeGreaterThan(0); + expect( + errors.some( + (e) => "message" in e && (e.message as string).includes("missing.png"), + ), + ).toBe(true); + }); + + test("removes missing tracked file entry from gallery when fix is true", async () => { + const gen = runValidation({ + root: tmpDir, + fix: true, + valFiles: ["content/basic-gallery-missing-tracked.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + }); + let next = await gen.next(); + while (!next.done) { + next = await gen.next(); + } + + const service = await createService(tmpDir, {}, createDefaultValFSHost()); + try { + const result = await service.get( + "/content/basic-gallery-missing-tracked.val.ts" as ModuleFilePath, + "" as ModulePath, + { source: true, schema: true, validate: true }, + ); + expect(result.source).not.toHaveProperty( + "/public/val/images4/missing.png", + ); + } finally { + service.dispose(); + } + }); + + test("returns validation-fixable-error for gallery with wrong stored metadata", async () => { + const events: ValidationEvent[] = []; + + for await (const event of runValidation({ + root: tmpDir, + fix: false, + valFiles: ["content/basic-gallery-wrong-metadata.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + })) { + events.push(event); + } + + expect(events.at(-1)).toEqual({ + type: "summary-errors", + count: expect.any(Number), + }); + const fixableErrors = events.filter( + (e) => e.type === "validation-fixable-error", + ); + expect(fixableErrors.length).toBeGreaterThan(0); + expect(fixableErrors[0]).toMatchObject({ + type: "validation-fixable-error", + fixable: true, + }); + }); + + test("fixes wrong metadata for gallery entry when fix is true", async () => { + const gen = runValidation({ + root: tmpDir, + fix: true, + valFiles: ["content/basic-gallery-wrong-metadata.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + }); + let next = await gen.next(); + while (!next.done) { + next = await gen.next(); + } + + const service = await createService(tmpDir, {}, createDefaultValFSHost()); + try { + const result = await service.get( + "/content/basic-gallery-wrong-metadata.val.ts" as ModuleFilePath, + "" as ModulePath, + { source: true, schema: true, validate: true }, + ); + expect(result.source).toMatchObject({ + "/public/val/images3/image.png": { + width: 1, + height: 1, + mimeType: "image/png", + }, + }); + } finally { + service.dispose(); + } + }); + + test("image has metadata after applying fix", async () => { + const gen = runValidation({ + root: tmpDir, + fix: true, + valFiles: ["content/basic-image.val.ts"], + project: undefined, + remote: mockRemote, + fs: createDefaultValFSHost(), + }); + // consume all events to apply fixes + let next = await gen.next(); + while (!next.done) { + next = await gen.next(); + } + + const service = await createService(tmpDir, {}, createDefaultValFSHost()); + try { + const result = await service.get( + "/content/basic-image.val.ts" as ModuleFilePath, + "" as ModulePath, + { source: true, schema: true, validate: true }, + ); + // The schema always emits image:check-metadata when metadata exists + // (actual metadata verification happens in the fix handler). + // Verify no image:add-metadata errors remain (fix was applied): + if (result.errors && result.errors.validation) { + const allFixes = Object.values(result.errors.validation) + .flat() + .flatMap((e) => e.fixes ?? []); + expect(allFixes).not.toContain("image:add-metadata"); + } + expect(result.source).toMatchObject({ + metadata: { + width: 1, + height: 1, + mimeType: "image/png", + }, + }); + } finally { + service.dispose(); + } + }); +}); diff --git a/packages/cli/src/runValidation.ts b/packages/cli/src/runValidation.ts new file mode 100644 index 000000000..85e59ac94 --- /dev/null +++ b/packages/cli/src/runValidation.ts @@ -0,0 +1,1096 @@ +import path from "path"; +import { + createFixPatch, + createService, + getPersonalAccessTokenPath, + parsePersonalAccessTokenFile, + Service, + IValFSHost, +} from "@valbuild/server"; +import { + FILE_REF_PROP, + Internal, + ModuleFilePath, + ModulePath, + SerializedFileSchema, + SerializedImageSchema, + SourcePath, + ValidationFix, +} from "@valbuild/core"; +import { + filterRoutesByPatterns, + validateRoutePatterns, + type SerializedRegExpPattern, +} from "@valbuild/shared/internal"; +import { getFileExt } from "./utils/getFileExt"; +import ts from "typescript"; +import nodeFs from "fs"; + +export type { IValFSHost }; + +export type IValRemote = { + remoteHost: string; + getSettings( + projectName: string, + options: { pat: string }, + ): Promise< + | { + success: true; + data: { + publicProjectId: string; + remoteFileBuckets: { bucket: string }[]; + }; + } + | { success: false; message: string } + >; + uploadFile( + project: string, + bucket: string, + fileHash: string, + fileExt: string | undefined, + fileBuffer: Buffer, + options: { pat: string }, + ): Promise<{ success: true } | { success: false; error: string }>; +}; + +const textEncoder = new TextEncoder(); + +// Types for handler system +export type ValModule = Awaited>; + +export type ValidationError = { + message: string; + value?: unknown; + fixes?: ValidationFix[]; +}; + +// Cache types for avoiding redundant service.get() calls +export type KeyOfCache = Map< + string, // moduleFilePath + modulePath key + { source: unknown; schema: { type: string } | undefined } +>; +export type RouterModulesCache = { + loaded: boolean; + modules: Record>; +}; + +export type FixHandlerContext = { + sourcePath: SourcePath; + validationError: ValidationError; + valModule: ValModule; + projectRoot: string; + fix: boolean; + service: Service; + valFiles: string[]; + moduleFilePath: ModuleFilePath; + file: string; + fs: IValFSHost; + // Shared state + remoteFiles: Record< + SourcePath, + { ref: string; metadata?: Record } + >; + publicProjectId?: string; + remoteFileBuckets?: string[]; + remoteFilesCounter: number; + remote: IValRemote; + project: string | undefined; + // Caches for validation + keyOfCache: KeyOfCache; + routerModulesCache: RouterModulesCache; +}; + +export type FixHandlerResult = { + success: boolean; + errorMessage?: string; + shouldApplyPatch?: boolean; + // Updated shared state + publicProjectId?: string; + remoteFileBuckets?: string[]; + remoteFilesCounter?: number; + // Events to emit + events?: ValidationEvent[]; +}; + +export type FixHandler = (ctx: FixHandlerContext) => Promise; + +export type ValidationEvent = + | { type: "file-valid"; file: string; durationMs: number } + | { + type: "file-error-count"; + file: string; + errorCount: number; + durationMs: number; + } + | { type: "validation-error"; sourcePath: string; message: string } + | { + type: "validation-fixable-error"; + sourcePath: string; + message: string; + fixable: boolean; + } + | { type: "unknown-fix"; sourcePath: string; fixes: string[] } + | { type: "fix-applied"; file: string; sourcePath: string } + | { type: "fatal-error"; file: string; message: string } + | { type: "remote-uploading"; ref: string } + | { type: "remote-uploaded"; ref: string } + | { type: "remote-already-uploaded"; filePath: string } + | { type: "remote-downloading"; sourcePath: string } + | { type: "summary-errors"; count: number } + | { type: "summary-success" }; + +// Handler functions +export async function handleFileMetadata( + ctx: FixHandlerContext, +): Promise { + const [, modulePath] = Internal.splitModuleFilePathAndModulePath( + ctx.sourcePath, + ); + + if (!ctx.valModule.source || !ctx.valModule.schema) { + return { + success: false, + errorMessage: `Could not resolve source or schema for ${ctx.sourcePath}`, + }; + } + + const fileSource = Internal.resolvePath( + modulePath, + ctx.valModule.source, + ctx.valModule.schema, + ); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const fileRefProp = (fileSource.source as any)?.[FILE_REF_PROP]; + if (!fileRefProp) { + return { + success: false, + errorMessage: `Expected file to be defined at: ${ctx.sourcePath} but no file was found`, + }; + } + + const filePath = path.join(ctx.projectRoot, fileRefProp); + if (!ctx.fs.fileExists(filePath)) { + return { + success: false, + errorMessage: `File ${filePath} does not exist`, + }; + } + + return { success: true, shouldApplyPatch: true }; +} + +export async function handleKeyOfCheck( + ctx: FixHandlerContext, +): Promise { + if ( + !ctx.validationError.value || + typeof ctx.validationError.value !== "object" || + !("key" in ctx.validationError.value) || + !("sourcePath" in ctx.validationError.value) + ) { + return { + success: false, + errorMessage: `Unexpected error in ${ctx.sourcePath}: ${ctx.validationError.message} (Expected value to be an object with 'key' and 'sourcePath' properties - this is likely a bug in Val)`, + }; + } + + const { key, sourcePath } = ctx.validationError.value as { + key: unknown; + sourcePath: unknown; + }; + + if (typeof key !== "string") { + return { + success: false, + errorMessage: `Unexpected error in ${sourcePath}: ${ctx.validationError.message} (Expected value property 'key' to be a string - this is likely a bug in Val)`, + }; + } + + if (typeof sourcePath !== "string") { + return { + success: false, + errorMessage: `Unexpected error in ${sourcePath}: ${ctx.validationError.message} (Expected value property 'sourcePath' to be a string - this is likely a bug in Val)`, + }; + } + + const res = await checkKeyIsValid( + key, + sourcePath, + ctx.service, + ctx.keyOfCache, + ); + if (res.error) { + return { + success: false, + errorMessage: res.message, + }; + } + + return { success: true }; +} + +export async function handleRemoteFileUpload( + ctx: FixHandlerContext, +): Promise { + if (!ctx.fix) { + return { + success: false, + errorMessage: `Remote file ${ctx.sourcePath} needs to be uploaded (use --fix to upload)`, + }; + } + + const [, modulePath] = Internal.splitModuleFilePathAndModulePath( + ctx.sourcePath, + ); + + if (!ctx.valModule.source || !ctx.valModule.schema) { + return { + success: false, + errorMessage: `Could not resolve source or schema for ${ctx.sourcePath}`, + }; + } + + const resolvedRemoteFileAtSourcePath = Internal.resolvePath( + modulePath, + ctx.valModule.source, + ctx.valModule.schema, + ); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const fileRefProp = (resolvedRemoteFileAtSourcePath.source as any)?.[ + FILE_REF_PROP + ]; + if (!fileRefProp) { + return { + success: false, + errorMessage: `Expected file to be defined at: ${ctx.sourcePath} but no file was found`, + }; + } + + const filePath = path.join(ctx.projectRoot, fileRefProp); + if (!ctx.fs.fileExists(filePath)) { + return { + success: false, + errorMessage: `File ${filePath} does not exist`, + }; + } + + const patFile = getPersonalAccessTokenPath(ctx.projectRoot); + if (!ctx.fs.fileExists(patFile)) { + return { + success: false, + errorMessage: `File: ${path.join(ctx.projectRoot, ctx.file)} has remote images that are not uploaded and you are not logged in.\n\nFix this error by logging in:\n\t"npx val login"\n`, + }; + } + + const patFileContent = ctx.fs.readFile(patFile); + if (patFileContent === undefined) { + return { + success: false, + errorMessage: `Could not read personal access token file at ${patFile}`, + }; + } + + const parsedPatFile = parsePersonalAccessTokenFile(patFileContent); + if (!parsedPatFile.success) { + return { + success: false, + errorMessage: `Error parsing personal access token file: ${parsedPatFile.error}. You need to login again.`, + }; + } + const { pat } = parsedPatFile.data; + + if (ctx.remoteFiles[ctx.sourcePath]) { + return { + success: true, + events: [{ type: "remote-already-uploaded", filePath }], + }; + } + + if (!resolvedRemoteFileAtSourcePath.schema) { + return { + success: false, + errorMessage: `Cannot upload remote file: schema not found for ${ctx.sourcePath}`, + }; + } + + const actualRemoteFileSource = resolvedRemoteFileAtSourcePath.source; + const fileSourceMetadata = Internal.isFile(actualRemoteFileSource) + ? actualRemoteFileSource.metadata + : undefined; + const resolveRemoteFileSchema = resolvedRemoteFileAtSourcePath.schema; + + if (!resolveRemoteFileSchema) { + return { + success: false, + errorMessage: `Could not resolve schema for remote file: ${ctx.sourcePath}`, + }; + } + + const projectName = ctx.project; + let publicProjectId = ctx.publicProjectId; + let remoteFileBuckets = ctx.remoteFileBuckets; + let remoteFilesCounter = ctx.remoteFilesCounter; + + if (!publicProjectId || !remoteFileBuckets) { + if (!projectName) { + return { + success: false, + errorMessage: + "Project name not found. Add project name to val.config or set the VAL_PROJECT environment variable", + }; + } + const settingsRes = await ctx.remote.getSettings(projectName, { pat }); + if (!settingsRes.success) { + return { + success: false, + errorMessage: `Could not get public project id: ${settingsRes.message}.`, + }; + } + publicProjectId = settingsRes.data.publicProjectId; + remoteFileBuckets = settingsRes.data.remoteFileBuckets.map((b) => b.bucket); + } + + if (!publicProjectId) { + return { + success: false, + errorMessage: "Could not get public project id", + }; + } + + if (!projectName) { + return { + success: false, + errorMessage: `Could not get project. Check that your val.config has the 'project' field set, or set it using the VAL_PROJECT environment variable`, + }; + } + + if ( + resolveRemoteFileSchema.type !== "image" && + resolveRemoteFileSchema.type !== "file" + ) { + return { + success: false, + errorMessage: `The schema is the remote is neither image nor file: ${ctx.sourcePath}`, + }; + } + + remoteFilesCounter += 1; + const bucket = + remoteFileBuckets[remoteFilesCounter % remoteFileBuckets.length]; + + if (!bucket) { + return { + success: false, + errorMessage: `Internal error: could not allocate a bucket for the remote file located at ${ctx.sourcePath}`, + }; + } + + const fileBuffer = ctx.fs.readBuffer(filePath); + if (fileBuffer === undefined) { + return { + success: false, + errorMessage: `Error reading file: ${filePath}`, + }; + } + + const relativeFilePath = path + .relative(ctx.projectRoot, filePath) + .split(path.sep) + .join("/") as `public/val/${string}`; + + if (!relativeFilePath.startsWith("public/val/")) { + return { + success: false, + errorMessage: `File path must be within the public/val/ directory (e.g. public/val/path/to/file.txt). Got: ${relativeFilePath}`, + }; + } + + const fileHash = Internal.remote.getFileHash(fileBuffer); + const coreVersion = Internal.VERSION.core || "unknown"; + const fileExt = getFileExt(filePath); + const schema = resolveRemoteFileSchema as + | SerializedImageSchema + | SerializedFileSchema; + const metadata = fileSourceMetadata; + const ref = Internal.remote.createRemoteRef(ctx.remote.remoteHost, { + publicProjectId, + coreVersion, + bucket, + validationHash: Internal.remote.getValidationHash( + coreVersion, + schema, + fileExt, + metadata, + fileHash, + textEncoder, + ), + fileHash, + filePath: relativeFilePath, + }); + + const remoteFileUpload = await ctx.remote.uploadFile( + projectName, + bucket, + fileHash, + fileExt, + fileBuffer, + { pat }, + ); + + if (!remoteFileUpload.success) { + return { + success: false, + errorMessage: `Could not upload remote file: '${ref}'. Error: ${remoteFileUpload.error}`, + }; + } + + ctx.remoteFiles[ctx.sourcePath] = { + ref, + metadata: fileSourceMetadata, + }; + + return { + success: true, + shouldApplyPatch: true, + publicProjectId, + remoteFileBuckets, + remoteFilesCounter, + events: [ + { type: "remote-uploading", ref }, + { type: "remote-uploaded", ref }, + ], + }; +} + +export async function handleRemoteFileDownload( + ctx: FixHandlerContext, +): Promise { + if (ctx.fix) { + return { + success: true, + shouldApplyPatch: true, + events: [{ type: "remote-downloading", sourcePath: ctx.sourcePath }], + }; + } else { + return { + success: false, + errorMessage: `Remote file ${ctx.sourcePath} needs to be downloaded (use --fix to download)`, + }; + } +} + +export async function handleRemoteFileCheck(): Promise { + // Skip - no action needed + return { success: true, shouldApplyPatch: true }; +} + +// Helper function +export async function checkKeyIsValid( + key: string, + sourcePath: string, + service: Service, + cache: KeyOfCache, +): Promise<{ error: false } | { error: true; message: string }> { + const [moduleFilePath, modulePath] = + Internal.splitModuleFilePathAndModulePath(sourcePath as SourcePath); + + const cacheKey = `${moduleFilePath}::${modulePath}`; + let keyOfModuleSource: unknown; + let keyOfModuleSchema: { type: string } | undefined; + + const cached = cache.get(cacheKey); + if (cached) { + keyOfModuleSource = cached.source; + keyOfModuleSchema = cached.schema; + } else { + const keyOfModule = await service.get(moduleFilePath, modulePath, { + source: true, + schema: true, + validate: false, + }); + keyOfModuleSource = keyOfModule.source; + keyOfModuleSchema = keyOfModule.schema as { type: string } | undefined; + cache.set(cacheKey, { + source: keyOfModuleSource, + schema: keyOfModuleSchema, + }); + } + + if (keyOfModuleSchema && keyOfModuleSchema.type !== "record") { + return { + error: true, + message: `Expected key at ${sourcePath} to be of type 'record'`, + }; + } + if ( + keyOfModuleSource && + typeof keyOfModuleSource === "object" && + key in keyOfModuleSource + ) { + return { error: false }; + } + if (!keyOfModuleSource || typeof keyOfModuleSource !== "object") { + return { + error: true, + message: `Expected ${sourcePath} to be a truthy object`, + }; + } + const alternatives = findSimilar(key, Object.keys(keyOfModuleSource)); + return { + error: true, + message: `Key '${key}' does not exist in ${sourcePath}. Closest match: '${alternatives[0].target}'. Other similar: ${alternatives + .slice(1, 4) + .map((a) => `'${a.target}'`) + .join(", ")}${alternatives.length > 4 ? ", ..." : ""}`, + }; +} + +/** + * Check if a route is valid by scanning all router modules + * and validating against include/exclude patterns + */ +export async function checkRouteIsValid( + route: string, + include: SerializedRegExpPattern | undefined, + exclude: SerializedRegExpPattern | undefined, + service: Service, + valFiles: string[], + cache: RouterModulesCache, +): Promise<{ error: false } | { error: true; message: string }> { + // 1. Scan all val files to find modules with routers (use cache if available) + if (!cache.loaded) { + for (const file of valFiles) { + const moduleFilePath = `/${file}` as ModuleFilePath; + const valModule = await service.get(moduleFilePath, "" as ModulePath, { + source: true, + schema: true, + validate: false, + }); + + // Check if this module has a router defined + if (valModule.schema?.type === "record" && valModule.schema.router) { + if (valModule.source && typeof valModule.source === "object") { + cache.modules[moduleFilePath] = valModule.source as Record< + string, + unknown + >; + } + } + } + cache.loaded = true; + } + + const routerModules = cache.modules; + + // 2. Check if route exists in any router module + let foundInModule: string | null = null; + for (const [moduleFilePath, source] of Object.entries(routerModules)) { + if (route in source) { + foundInModule = moduleFilePath; + break; + } + } + + if (!foundInModule) { + // Route not found in any router module + let allRoutes = Object.values(routerModules).flatMap((source) => + Object.keys(source), + ); + + if (allRoutes.length === 0) { + return { + error: true, + message: `Route '${route}' could not be validated: No router modules found in the project. Use s.record(...).router(...) to define router modules.`, + }; + } + + // Filter routes by include/exclude patterns for suggestions + allRoutes = filterRoutesByPatterns(allRoutes, include, exclude); + + const alternatives = findSimilar(route, allRoutes); + + return { + error: true, + message: `Route '${route}' does not exist in any router module. ${ + alternatives.length > 0 + ? `Closest match: '${alternatives[0].target}'. Other similar: ${alternatives + .slice(1, 4) + .map((a) => `'${a.target}'`) + .join(", ")}${alternatives.length > 4 ? ", ..." : ""}` + : "No similar routes found." + }`, + }; + } + + // 3. Validate against include/exclude patterns + const patternValidation = validateRoutePatterns(route, include, exclude); + if (!patternValidation.valid) { + return { + error: true, + message: patternValidation.message, + }; + } + + return { error: false }; +} + +/** + * Handler for router:check-route validation fix + */ +export async function handleRouteCheck( + ctx: FixHandlerContext, +): Promise { + const { sourcePath, validationError, service, valFiles, routerModulesCache } = + ctx; + + // Extract route and patterns from validation error value + const value = validationError.value as + | { + route: unknown; + include?: { source: string; flags: string }; + exclude?: { source: string; flags: string }; + } + | undefined; + + if (!value || typeof value.route !== "string") { + return { + success: false, + errorMessage: `Invalid route value in validation error: ${JSON.stringify(value)}`, + }; + } + + const route = value.route; + + // Check if the route is valid + const result = await checkRouteIsValid( + route, + value.include, + value.exclude, + service, + valFiles, + routerModulesCache, + ); + + if (result.error) { + return { + success: false, + errorMessage: `${sourcePath}: ${result.message}`, + }; + } + + return { success: true }; +} + +export async function handleUniqueFolderCheck( + ctx: FixHandlerContext, +): Promise { + const value = ctx.validationError.value as + | { directory: string; type: string } + | undefined; + if (!value || typeof value.directory !== "string") { + return { + success: false, + errorMessage: `Unexpected value in unique folder check for ${ctx.sourcePath}`, + }; + } + const { directory } = value; + const conflicts: string[] = []; + for (const file of ctx.valFiles) { + const otherModuleFilePath = `/${file}` as ModuleFilePath; + if (otherModuleFilePath === ctx.moduleFilePath) continue; + const otherModule = await ctx.service.get( + otherModuleFilePath, + "" as ModulePath, + { source: false, schema: true, validate: false }, + ); + const schema = otherModule.schema as + | { type?: string; directory?: string; mediaType?: string } + | undefined; + if ( + schema?.type === "record" && + schema.directory === directory && + schema.mediaType + ) { + conflicts.push(otherModuleFilePath); + } + } + if (conflicts.length > 0) { + return { + success: false, + errorMessage: `Gallery directory '${directory}' in ${ctx.moduleFilePath} is also used by: ${conflicts.join(", ")}. Each gallery must use a unique directory.`, + }; + } + return { success: true }; +} + +export async function handleCheckAllFiles( + ctx: FixHandlerContext, +): Promise { + const value = ctx.validationError.value as + | { directory: string; type: string } + | undefined; + if (!value || typeof value.directory !== "string") { + return { + success: false, + errorMessage: `Unexpected value in check-all-files for ${ctx.sourcePath}`, + }; + } + const { directory } = value; + + const source = ctx.valModule.source; + if (!source || typeof source !== "object" || Array.isArray(source)) { + return { + success: false, + errorMessage: `Could not get source for ${ctx.sourcePath}`, + }; + } + const trackedFiles = new Set(Object.keys(source as Record)); + + // Check that all tracked files exist on disk + const missingTrackedFiles = [...trackedFiles].filter((f) => { + return !ctx.fs.fileExists(path.join(ctx.projectRoot, f)); + }); + if (missingTrackedFiles.length > 0) { + if (!ctx.fix) { + return { + success: false, + errorMessage: `Gallery in ${ctx.moduleFilePath} has tracked files that do not exist on disk: ${missingTrackedFiles.join(", ")}. Add the files or remove them from the gallery.`, + }; + } + // fix: true — let createFixPatch remove the missing entries + return { success: true, shouldApplyPatch: true }; + } + + const dirPath = path.join(ctx.projectRoot, directory); + + const filesInDir: string[] = []; + try { + const entries = ctx.fs.readDirectory(dirPath, undefined, undefined, [ + "**/*", + ]); + for (const entry of entries) { + const relPath = + "/" + path.relative(ctx.projectRoot, entry).split(path.sep).join("/"); + filesInDir.push(relPath); + } + } catch { + // directory doesn't exist — no untracked files possible + } + + const untrackedFiles = filesInDir.filter((f) => !trackedFiles.has(f)); + if (untrackedFiles.length > 0) { + return { + success: false, + errorMessage: `Gallery in ${ctx.moduleFilePath} has files not tracked: ${untrackedFiles.join(", ")}. Add these files to the gallery or remove them from the directory.`, + }; + } + + // All files accounted for — trigger metadata verification via createFixPatch + return { success: true, shouldApplyPatch: true }; +} + +// Fix handler registry +export const currentFixHandlers: Record = { + "image:check-metadata": handleFileMetadata, + "image:add-metadata": handleFileMetadata, + "file:check-metadata": handleFileMetadata, + "file:add-metadata": handleFileMetadata, + "keyof:check-keys": handleKeyOfCheck, + "router:check-route": handleRouteCheck, + "image:upload-remote": handleRemoteFileUpload, + "file:upload-remote": handleRemoteFileUpload, + "image:download-remote": handleRemoteFileDownload, + "file:download-remote": handleRemoteFileDownload, + "image:check-remote": handleRemoteFileCheck, + "images:check-remote": handleRemoteFileCheck, + "file:check-remote": handleRemoteFileCheck, + "files:check-remote": handleRemoteFileCheck, + "images:check-unique-folder": handleUniqueFolderCheck, + "files:check-unique-folder": handleUniqueFolderCheck, + "images:check-all-files": handleCheckAllFiles, + "files:check-all-files": handleCheckAllFiles, +}; +const deprecatedFixHandlers: Record = { + "image:replace-metadata": handleFileMetadata, +}; +export const fixHandlers: Record = { + ...deprecatedFixHandlers, + ...currentFixHandlers, +}; + +export function createDefaultValFSHost(): IValFSHost { + return { + ...ts.sys, + writeFile: (fileName, data, encoding) => { + nodeFs.mkdirSync(path.dirname(fileName), { recursive: true }); + nodeFs.writeFileSync( + fileName, + typeof data === "string" ? data : new Uint8Array(data), + encoding, + ); + }, + rmFile: nodeFs.rmSync, + readBuffer: (fileName) => { + try { + return nodeFs.readFileSync(fileName); + } catch { + return undefined; + } + }, + }; +} + +export async function* runValidation({ + root, + fix, + valFiles, + project, + remote, + fs, +}: { + root: string; + fix: boolean; + valFiles: string[]; + project: string | undefined; + remote: IValRemote; + fs: IValFSHost; +}): AsyncGenerator { + const projectRoot = path.resolve(root); + + const service = await createService(projectRoot, {}, fs); + + let errors = 0; + + // Create caches that persist across all file validations + const keyOfCache: KeyOfCache = new Map(); + const routerModulesCache: RouterModulesCache = { + loaded: false, + modules: {}, + }; + + async function* validateFile(file: string): AsyncGenerator { + const moduleFilePath = `/${file}` as ModuleFilePath; // TODO: check if this always works? (Windows?) + const start = Date.now(); + const valModule = await service.get(moduleFilePath, "" as ModulePath, { + source: true, + schema: true, + validate: true, + }); + const remoteFiles: Record< + SourcePath, + { ref: string; metadata?: Record } + > = {}; + let remoteFileBuckets: string[] | undefined = undefined; + let remoteFilesCounter = 0; + if (!valModule.errors) { + yield { + type: "file-valid", + file: moduleFilePath, + durationMs: Date.now() - start, + }; + return; + } else { + let fileErrors = 0; + let fixedErrors = 0; + if (valModule.errors) { + if (valModule.errors.validation) { + for (const [sourcePath, validationErrors] of Object.entries( + valModule.errors.validation, + )) { + for (const v of validationErrors) { + if (!v.fixes || v.fixes.length === 0) { + // No fixes available - just report error + fileErrors += 1; + yield { + type: "validation-error", + sourcePath, + message: v.message, + }; + continue; + } + + // Find and execute appropriate handler + const fixType = v.fixes[0]; // Take first fix + const handler = fixHandlers[fixType]; + + if (!handler) { + yield { + type: "unknown-fix", + sourcePath, + fixes: v.fixes, + }; + fileErrors += 1; + continue; + } + + // Execute handler + const result = await handler({ + sourcePath: sourcePath as SourcePath, + validationError: v, + valModule, + projectRoot, + fix: !!fix, + service, + valFiles, + moduleFilePath, + file, + fs, + remoteFiles, + publicProjectId: undefined, + remoteFileBuckets, + remoteFilesCounter, + remote, + project, + keyOfCache, + routerModulesCache, + }); + + // Yield any events from handler + if (result.events) { + for (const event of result.events) { + yield event; + } + } + + // Update shared state from handler result + if (result.remoteFileBuckets !== undefined) { + remoteFileBuckets = result.remoteFileBuckets; + } + if (result.remoteFilesCounter !== undefined) { + remoteFilesCounter = result.remoteFilesCounter; + } + + if (!result.success) { + yield { + type: "validation-error", + sourcePath, + message: result.errorMessage ?? "Unknown error", + }; + fileErrors += 1; + continue; + } + + // Apply patch if needed + if (result.shouldApplyPatch) { + const fixPatch = await createFixPatch( + { projectRoot, remoteHost: remote.remoteHost }, + !!fix, + sourcePath as SourcePath, + v, + remoteFiles, + valModule.source, + valModule.schema, + ); + + if (fix && fixPatch?.patch && fixPatch?.patch.length > 0) { + await service.patch(moduleFilePath, fixPatch.patch); + fixedErrors += 1; + yield { type: "fix-applied", file, sourcePath }; + } else if ( + !fix && + fixPatch?.patch && + fixPatch?.patch.length > 0 + ) { + fileErrors += 1; + yield { + type: "validation-fixable-error", + sourcePath, + message: v.message, + fixable: true, + }; + } + + for (const e of fixPatch?.remainingErrors ?? []) { + fileErrors += 1; + yield { + type: "validation-fixable-error", + sourcePath, + message: e.message, + fixable: !!(e.fixes && e.fixes.length), + }; + } + } + } + } + } + if ( + fixedErrors === fileErrors && + (!valModule.errors.fatal || valModule.errors.fatal.length == 0) + ) { + yield { + type: "file-valid", + file: moduleFilePath, + durationMs: Date.now() - start, + }; + } + for (const fatalError of valModule.errors.fatal || []) { + fileErrors += 1; + yield { + type: "fatal-error", + file: moduleFilePath, + message: fatalError.message, + }; + } + } else { + yield { + type: "file-valid", + file: moduleFilePath, + durationMs: Date.now() - start, + }; + } + if (fileErrors > 0) { + yield { + type: "file-error-count", + file: `/${file}`, + errorCount: fileErrors, + durationMs: Date.now() - start, + }; + } + errors += fileErrors; + } + } + + for (const file of valFiles.sort()) { + yield* validateFile(file); + } + + service.dispose(); + + if (errors > 0) { + yield { type: "summary-errors", count: errors }; + } else { + yield { type: "summary-success" }; + } +} + +// GPT generated levenshtein distance algorithm: +export const levenshtein = (a: string, b: string): number => { + const [m, n] = [a.length, b.length]; + if (!m || !n) return Math.max(m, n); + + const dp = Array.from({ length: m + 1 }, (_, i) => i); + + for (let j = 1; j <= n; j++) { + let prev = dp[0]; + dp[0] = j; + + for (let i = 1; i <= m; i++) { + const temp = dp[i]; + dp[i] = + a[i - 1] === b[j - 1] + ? prev + : Math.min(prev + 1, dp[i - 1] + 1, dp[i] + 1); + prev = temp; + } + } + + return dp[m]; +}; + +export function findSimilar(key: string, targets: string[]) { + return targets + .map((target) => ({ target, distance: levenshtein(key, target) })) + .sort((a, b) => a.distance - b.distance); +} diff --git a/packages/cli/src/utils/evalValConfigFile.ts b/packages/cli/src/utils/evalValConfigFile.ts new file mode 100644 index 000000000..b488f0aff --- /dev/null +++ b/packages/cli/src/utils/evalValConfigFile.ts @@ -0,0 +1,92 @@ +import path from "path"; +import fs from "fs/promises"; +import vm from "node:vm"; +import ts from "typescript"; // TODO: make this dependency optional (only required if the file is val.config.ts not val.config.js) +import z from "zod"; +import { ValConfig } from "@valbuild/core"; +import { createRequire } from "node:module"; + +const ValConfigSchema = z.object({ + project: z.string().optional(), + root: z.string().optional(), + files: z + .object({ + directory: z + .string() + .refine((val): val is `/public/val` => val.startsWith("/public/val"), { + message: "files.directory must start with '/public/val'", + }), + }) + .optional(), + gitCommit: z.string().optional(), + gitBranch: z.string().optional(), + defaultTheme: z.union([z.literal("light"), z.literal("dark")]).optional(), + ai: z + .object({ + commitMessages: z + .object({ + disabled: z.boolean().optional(), + }) + .optional(), + }) + .optional(), +}); + +export async function evalValConfigFile( + projectRoot: string, + configFileName: string, +): Promise { + const valConfigPath = path.join(projectRoot, configFileName); + + let code: string | null = null; + try { + code = await fs.readFile(valConfigPath, "utf-8"); + } catch { + // + } + if (!code) { + return null; + } + + const transpiled = ts.transpileModule(code, { + compilerOptions: { + target: ts.ScriptTarget.ES2020, + module: ts.ModuleKind.CommonJS, + esModuleInterop: true, + }, + fileName: valConfigPath, + }); + + const projectRootRequire = createRequire(valConfigPath); + const exportsObj = {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const sandbox: Record = { + exports: exportsObj, + module: { exports: exportsObj }, + require: projectRootRequire, // NOTE: this is a security risk, but this code is running in the users own environment at the CLI level + __filename: valConfigPath, + __dirname: projectRoot, + console, + process, + }; + sandbox.global = sandbox; + + const context = vm.createContext(sandbox); + const script = new vm.Script(transpiled.outputText, { + filename: valConfigPath, + }); + script.runInContext(context); + const valConfig = sandbox.module.exports.config; + if (!valConfig) { + throw Error( + `Val config file at path: '${valConfigPath}' must export a config object. Got: ${valConfig}`, + ); + } + const result = ValConfigSchema.safeParse(valConfig); + if (!result.success) { + throw Error( + `Val config file at path: '${valConfigPath}' has invalid schema: ${result.error.message}`, + ); + } + return result.data; +} diff --git a/packages/cli/src/utils/getFileExt.ts b/packages/cli/src/utils/getFileExt.ts new file mode 100644 index 000000000..6a8989ce1 --- /dev/null +++ b/packages/cli/src/utils/getFileExt.ts @@ -0,0 +1,4 @@ +export function getFileExt(filePath: string) { + // NOTE: We do not import the path module. This code is copied in different projects. We want the same implementation and which means that this might running in browser where path is not available). + return filePath.split(".").pop() || ""; +} diff --git a/packages/cli/src/utils/getValCoreVersion.ts b/packages/cli/src/utils/getValCoreVersion.ts new file mode 100644 index 000000000..de891c334 --- /dev/null +++ b/packages/cli/src/utils/getValCoreVersion.ts @@ -0,0 +1,5 @@ +import { getVersions } from "../getVersions"; + +export function getValCoreVersion() { + return getVersions().coreVersion || "unknown"; +} diff --git a/packages/cli/src/validate.test.ts b/packages/cli/src/validate.test.ts new file mode 100644 index 000000000..7450edfeb --- /dev/null +++ b/packages/cli/src/validate.test.ts @@ -0,0 +1,812 @@ +import { describe, test, expect, jest, beforeEach } from "@jest/globals"; +import { ModuleFilePath, SourcePath } from "@valbuild/core"; +import type { Service } from "@valbuild/server"; + +// Mock dependencies +jest.mock("@valbuild/server"); +jest.mock("fs/promises"); +jest.mock("picocolors", () => ({ + red: (s: string) => s, + green: (s: string) => s, + yellow: (s: string) => s, + greenBright: (s: string) => s, + inverse: (s: string) => s, +})); + +// Import types from validate.ts +type ValModule = { + source?: unknown; + schema?: { type: string; router?: string }; + errors?: { + validation?: Record>; + fatal?: Array<{ message: string }>; + }; +}; + +type FixHandlerContext = { + sourcePath: SourcePath; + validationError: { + message: string; + value?: unknown; + fixes?: string[]; + }; + valModule: ValModule; + projectRoot: string; + fix: boolean; + service: Service; + valFiles: string[]; + moduleFilePath: ModuleFilePath; + file: string; + remoteFiles: Record< + SourcePath, + { ref: string; metadata?: Record } + >; + publicProjectId?: string; + remoteFileBuckets?: string[]; + remoteFilesCounter: number; + valRemoteHost: string; + contentHostUrl: string; + valConfigFile?: { project?: string }; +}; + +describe("validate handlers", () => { + let mockService: jest.Mocked; + let baseContext: FixHandlerContext; + + beforeEach(() => { + mockService = { + get: jest.fn(), + patch: jest.fn(), + dispose: jest.fn(), + } as unknown as jest.Mocked; + + baseContext = { + sourcePath: "/test.val.ts" as SourcePath, + validationError: { + message: "Test error", + fixes: [], + }, + valModule: { + source: {}, + schema: { type: "string" }, + }, + projectRoot: "/test/project", + fix: false, + service: mockService, + valFiles: [], + moduleFilePath: "/test.val.ts" as ModuleFilePath, + file: "test.val.ts", + remoteFiles: {}, + remoteFilesCounter: 0, + valRemoteHost: "https://val.build", + contentHostUrl: "https://content.val.build", + }; + }); + + describe("handleKeyOfCheck", () => { + test("should return success when key exists in referenced module", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Key validation", + fixes: ["keyof:check-keys"], + value: { + key: "existingKey", + sourcePath: "/test.val.ts", + }, + }, + }; + + // We can't actually import the handler directly, so this test documents expected behavior + // The handler should: + // 1. Extract key and sourcePath from validationError.value + // 2. Call service.get to fetch the referenced module + // 3. Check if the key exists in the module source + // 4. Return success: true if key exists + + expect(ctx.validationError.value).toHaveProperty("key", "existingKey"); + expect(ctx.validationError.value).toHaveProperty( + "sourcePath", + "/test.val.ts", + ); + }); + + test("should return error when key does not exist", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Key validation", + fixes: ["keyof:check-keys"], + value: { + key: "nonExistentKey", + sourcePath: "/test.val.ts", + }, + }, + }; + + // Expected behavior: + // 1. Handler finds that nonExistentKey is not in source + // 2. Uses levenshtein distance to find similar keys + // 3. Returns success: false with helpful error message + + expect(ctx.validationError.value).toHaveProperty("key", "nonExistentKey"); + }); + + test("should return error when value format is invalid", () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Key validation", + fixes: ["keyof:check-keys"], + value: "invalid", // Should be an object with key and sourcePath + }, + }; + + // Expected behavior: + // Handler should validate that value is an object with key and sourcePath + // Should return success: false with error message about invalid format + + expect(typeof ctx.validationError.value).toBe("string"); + }); + + test("should return error when key is not a string", () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Key validation", + fixes: ["keyof:check-keys"], + value: { + key: 123, // Should be string + sourcePath: "/test.val.ts", + }, + }, + }; + + // Expected behavior: + // Handler should validate that key is a string + // Should return success: false with error about type mismatch + + expect(typeof (ctx.validationError.value as { key: unknown }).key).toBe( + "number", + ); + }); + }); + + describe("handleFileMetadata", () => { + test("should return success when file exists", () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "File metadata", + fixes: ["image:check-metadata"], + }, + valModule: { + source: { + image: { + "~$ref": "public/val/image.png", + }, + }, + schema: { type: "image" }, + }, + }; + + // Expected behavior: + // 1. Extract file path from source + // 2. Check if file exists using fs.access + // 3. Return success: true if file exists + + expect(ctx.valModule.source).toBeDefined(); + }); + + test("should return error when source or schema is missing", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "File metadata", + fixes: ["file:check-metadata"], + }, + valModule: { + source: undefined, + schema: undefined, + }, + }; + + // Expected behavior: + // Handler should check if source and schema exist + // Should return success: false with error message + + expect(ctx.valModule.source).toBeUndefined(); + expect(ctx.valModule.schema).toBeUndefined(); + }); + }); + + describe("handleRemoteFileUpload", () => { + test("should return error when fix is false", () => { + const ctx: FixHandlerContext = { + ...baseContext, + fix: false, + validationError: { + message: "Remote file upload", + fixes: ["image:upload-remote"], + }, + }; + + // Expected behavior: + // When fix=false, handler should return error telling user to use --fix + // Should return success: false with message about using --fix + + expect(ctx.fix).toBe(false); + }); + + test("should attempt upload when fix is true", () => { + const ctx: FixHandlerContext = { + ...baseContext, + fix: true, + validationError: { + message: "Remote file upload", + fixes: ["file:upload-remote"], + }, + valConfigFile: { + project: "test-project", + }, + }; + + // Expected behavior: + // 1. Check file exists + // 2. Get PAT file + // 3. Get project settings if not cached + // 4. Upload file to remote + // 5. Store reference in remoteFiles + // 6. Return success: true with shouldApplyPatch: true + + expect(ctx.fix).toBe(true); + expect(ctx.valConfigFile?.project).toBe("test-project"); + }); + + test("should return error when project config is missing", () => { + const ctx: FixHandlerContext = { + ...baseContext, + fix: true, + validationError: { + message: "Remote file upload", + fixes: ["image:upload-remote"], + }, + valConfigFile: undefined, + }; + + // Expected behavior: + // Handler should check for project config + // Should return success: false with error about missing config + + expect(ctx.valConfigFile).toBeUndefined(); + }); + + test("should use cached project settings when available", () => { + const ctx: FixHandlerContext = { + ...baseContext, + fix: true, + publicProjectId: "cached-id", + remoteFileBuckets: ["bucket1", "bucket2"], + validationError: { + message: "Remote file upload", + fixes: ["image:upload-remote"], + }, + }; + + // Expected behavior: + // Handler should reuse publicProjectId and remoteFileBuckets if available + // Should not call getSettings again + + expect(ctx.publicProjectId).toBe("cached-id"); + expect(ctx.remoteFileBuckets).toEqual(["bucket1", "bucket2"]); + }); + }); + + describe("handleRemoteFileDownload", () => { + test("should return success when fix is true", () => { + const ctx: FixHandlerContext = { + ...baseContext, + fix: true, + validationError: { + message: "Remote file download", + fixes: ["image:download-remote"], + }, + }; + + // Expected behavior: + // When fix=true, handler logs download message and returns success + // Should return success: true with shouldApplyPatch: true + + expect(ctx.fix).toBe(true); + }); + + test("should return error when fix is false", () => { + const ctx: FixHandlerContext = { + ...baseContext, + fix: false, + validationError: { + message: "Remote file download", + fixes: ["file:download-remote"], + }, + }; + + // Expected behavior: + // When fix=false, handler returns error telling user to use --fix + // Should return success: false + + expect(ctx.fix).toBe(false); + }); + }); + + describe("handleRemoteFileCheck", () => { + test("should always return success", () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Remote file check", + fixes: ["image:check-remote"], + }, + }; + + // Expected behavior: + // This handler is a no-op that always succeeds + // Should return success: true with shouldApplyPatch: true + + expect(ctx.validationError.fixes).toContain("image:check-remote"); + }); + }); + + describe("handleRouteCheck", () => { + test("should return success when route exists in router module", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Route validation required", + value: { route: "/home" }, + fixes: ["router:check-route"], + }, + valFiles: ["routes.val.ts"], + }; + + mockService.get.mockResolvedValue({ + schema: { + type: "record", + router: "next-app-router", + item: { type: "object", items: {}, opt: false }, + opt: false, + }, + source: { "/home": {}, "/about": {} }, + path: "/routes.val.ts" as SourcePath, + errors: {}, + }); + + // Expected behavior: + // 1. Extract route from validationError.value + // 2. Scan all valFiles for router modules + // 3. Check if route exists in any router module + // 4. Return success: true if found + + expect(ctx.validationError.value).toEqual({ route: "/home" }); + expect(ctx.valFiles).toContain("routes.val.ts"); + }); + + test("should return error when route does not exist", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Route validation required", + value: { route: "/notfound" }, + fixes: ["router:check-route"], + }, + valFiles: ["routes.val.ts"], + }; + + mockService.get.mockResolvedValue({ + schema: { + type: "record", + router: "next-app-router", + item: { type: "object", items: {}, opt: false }, + opt: false, + }, + source: { "/home": {}, "/about": {} }, + path: "/routes.val.ts" as SourcePath, + errors: {}, + }); + + // Expected behavior: + // Route not found, should return error with suggestions + // Should use levenshtein distance to find similar routes + + expect(ctx.validationError.value).toEqual({ route: "/notfound" }); + }); + + test("should return error when no router modules found", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Route validation required", + value: { route: "/home" }, + fixes: ["router:check-route"], + }, + valFiles: ["data.val.ts"], + }; + + mockService.get.mockResolvedValue({ + schema: { + type: "record", + router: undefined, + item: { type: "object", items: {}, opt: false }, + opt: false, + }, + source: { key: "value" }, + path: "/data.val.ts" as SourcePath, + errors: {}, + }); + + // Expected behavior: + // No router modules found, should return helpful error message + + expect(ctx.valFiles).toContain("data.val.ts"); + }); + + test("should validate include pattern", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Route validation required", + value: { + route: "/admin/users", + include: { source: "^\\/api\\/", flags: "" }, + }, + fixes: ["router:check-route"], + }, + valFiles: ["routes.val.ts"], + }; + + mockService.get.mockResolvedValue({ + schema: { + type: "record", + router: "next-app-router", + item: { type: "object", items: {}, opt: false }, + opt: false, + }, + source: { "/admin/users": {} }, + path: "/routes.val.ts" as SourcePath, + errors: {}, + }); + + // Expected behavior: + // Route exists but doesn't match include pattern + // Should return error about pattern mismatch + + const value = ctx.validationError.value as { + route: string; + include: { source: string; flags: string }; + }; + expect(value.include.source).toBe("^\\/api\\/"); + }); + + test("should validate exclude pattern", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Route validation required", + value: { + route: "/api/internal/secret", + exclude: { source: "^\\/api\\/internal\\/", flags: "" }, + }, + fixes: ["router:check-route"], + }, + valFiles: ["routes.val.ts"], + }; + + mockService.get.mockResolvedValue({ + schema: { + type: "record", + router: "next-app-router", + item: { type: "object", items: {}, opt: false }, + opt: false, + }, + source: { "/api/internal/secret": {} }, + path: "/routes.val.ts" as SourcePath, + errors: {}, + }); + + // Expected behavior: + // Route exists but matches exclude pattern + // Should return error about excluded route + + const value = ctx.validationError.value as { + route: string; + exclude: { source: string; flags: string }; + }; + expect(value.exclude.source).toBe("^\\/api\\/internal\\/"); + }); + + test("should validate both include and exclude patterns", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Route validation required", + value: { + route: "/api/users", + include: { source: "^\\/api\\/", flags: "" }, + exclude: { source: "^\\/api\\/internal\\/", flags: "" }, + }, + fixes: ["router:check-route"], + }, + valFiles: ["routes.val.ts"], + }; + + mockService.get.mockResolvedValue({ + schema: { + type: "record", + router: "next-app-router", + item: { type: "object", items: {}, opt: false }, + opt: false, + }, + source: { "/api/users": {} }, + path: "/routes.val.ts" as SourcePath, + errors: {}, + }); + + // Expected behavior: + // Route exists, matches include, doesn't match exclude + // Should return success: true + + const value = ctx.validationError.value as { + route: string; + include: { source: string; flags: string }; + exclude: { source: string; flags: string }; + }; + expect(value.include.source).toBe("^\\/api\\/"); + expect(value.exclude.source).toBe("^\\/api\\/internal\\/"); + }); + + test("should return error when route value is invalid", async () => { + const ctx: FixHandlerContext = { + ...baseContext, + validationError: { + message: "Route validation required", + value: { route: 123 }, // Invalid: should be string + fixes: ["router:check-route"], + }, + valFiles: ["routes.val.ts"], + }; + + // Expected behavior: + // Invalid route value type, should return error + + expect( + typeof (ctx.validationError.value as { route: unknown }).route, + ).toBe("number"); + }); + }); + + describe("fix handler registry", () => { + test("should have handlers for all file metadata fix types", () => { + const metadataFixTypes = [ + "image:replace-metadata", + "image:check-metadata", + "image:add-metadata", + "file:check-metadata", + "file:add-metadata", + ]; + + // Expected: All metadata fix types should map to handleFileMetadata + expect(metadataFixTypes.length).toBeGreaterThan(0); + }); + + test("should have handler for keyof:check-keys", () => { + const fixType = "keyof:check-keys"; + + // Expected: Registry should have entry for keyof:check-keys + expect(fixType).toBe("keyof:check-keys"); + }); + + test("should have handlers for remote file operations", () => { + const remoteFixTypes = [ + "image:upload-remote", + "file:upload-remote", + "image:download-remote", + "file:download-remote", + "image:check-remote", + "file:check-remote", + ]; + + // Expected: All remote fix types should have handlers + expect(remoteFixTypes.length).toBe(6); + }); + }); + + describe("validation flow", () => { + test("should handle validation with no errors", () => { + const valModule: ValModule = { + source: { test: "value" }, + schema: { type: "string" }, + errors: undefined, + }; + + // Expected behavior: + // When valModule.errors is undefined, validation succeeds immediately + // Should log success message and return 0 errors + + expect(valModule.errors).toBeUndefined(); + }); + + test("should handle validation with fixes", () => { + const valModule: ValModule = { + source: { test: "value" }, + schema: { type: "string" }, + errors: { + validation: { + "/test.val.ts": [ + { + message: "Test error", + fixes: ["keyof:check-keys"], + }, + ], + }, + }, + }; + + // Expected behavior: + // 1. Loop through validation errors + // 2. Find handler for first fix type + // 3. Execute handler + // 4. Apply patch if handler succeeds and shouldApplyPatch is true + + expect(valModule.errors?.validation).toBeDefined(); + expect(Object.keys(valModule.errors?.validation || {}).length).toBe(1); + }); + + test("should handle validation without fixes", () => { + const valModule: ValModule = { + source: { test: "value" }, + schema: { type: "string" }, + errors: { + validation: { + "/test.val.ts": [ + { + message: "Test error", + fixes: undefined, + }, + ], + }, + }, + }; + + // Expected behavior: + // When no fixes available, validation should log error and continue + // Should not attempt to find or execute a handler + + const error = valModule.errors?.validation?.["/test.val.ts"]?.[0]; + expect(error?.fixes).toBeUndefined(); + }); + + test("should handle unknown fix types", () => { + const valModule: ValModule = { + source: { test: "value" }, + schema: { type: "string" }, + errors: { + validation: { + "/test.val.ts": [ + { + message: "Test error", + fixes: ["unknown:fix-type"], + }, + ], + }, + }, + }; + + // Expected behavior: + // When fix type is not in registry, validation should log error + // Should increment error count and continue + + const error = valModule.errors?.validation?.["/test.val.ts"]?.[0]; + expect(error?.fixes?.[0]).toBe("unknown:fix-type"); + }); + + test("should handle fatal errors", () => { + const valModule: ValModule = { + source: { test: "value" }, + schema: { type: "string" }, + errors: { + fatal: [ + { + message: "Fatal error occurred", + }, + ], + }, + }; + + // Expected behavior: + // Fatal errors should be logged separately + // Should increment error count + + expect(valModule.errors?.fatal).toBeDefined(); + expect(valModule.errors?.fatal?.length).toBe(1); + }); + }); + + describe("error handling", () => { + test("should handle service.get failures gracefully", async () => { + mockService.get.mockRejectedValue(new Error("Service error")); + + // Expected behavior: + // When service.get fails, handler should catch error + // Should return or throw appropriate error + + await expect( + mockService.get( + "/test" as ModuleFilePath, + "" as unknown as never, + {} as never, + ), + ).rejects.toThrow("Service error"); + }); + + test("should handle file system errors gracefully", () => { + // Expected behavior: + // When fs operations fail, handlers should catch errors + // Should return success: false with appropriate error message + + const error = new Error("ENOENT: file not found"); + expect(error.message).toContain("ENOENT"); + }); + }); + + describe("levenshtein distance helper", () => { + test("should find similar strings", () => { + const targets = ["test", "text", "best", "rest", "toast"]; + + // Expected: Function should calculate edit distance and sort by similarity + // "test" should be first (distance 0) + // "text", "best", "rest" should be next (distance 1) + + expect(targets.includes("test")).toBe(true); + }); + + test("should handle empty arrays", () => { + const targets: string[] = []; + + // Expected: Should handle empty target array gracefully + // Should return empty array + + expect(targets.length).toBe(0); + }); + }); +}); + +describe("validate integration", () => { + test("documents expected validation workflow", () => { + // This test documents the expected flow of validation: + // + // 1. Load val config + // 2. Create service + // 3. Find all .val.ts/js files + // 4. For each file: + // a. Get module with validation + // b. If no errors, log success and continue + // c. If errors, process each error: + // i. If no fixes, log error and continue + // ii. If has fixes, find handler and execute + // iii. If handler succeeds and shouldApplyPatch, apply patch + // iv. Log appropriate success/error messages + // 5. Format files with prettier if fixes were applied + // 6. Exit with error code if any errors found + + const expectedFlow = { + step1: "Load val config", + step2: "Create service", + step3: "Find val files", + step4: "Validate each file", + step5: "Apply fixes if needed", + step6: "Format with prettier", + step7: "Report results", + }; + + expect(Object.keys(expectedFlow).length).toBe(7); + }); +}); diff --git a/packages/cli/src/validate.ts b/packages/cli/src/validate.ts new file mode 100644 index 000000000..8f37d4404 --- /dev/null +++ b/packages/cli/src/validate.ts @@ -0,0 +1,186 @@ +import path from "path"; +import picocolors from "picocolors"; +import fs from "fs/promises"; +import { glob } from "fast-glob"; +import { DEFAULT_CONTENT_HOST, DEFAULT_VAL_REMOTE_HOST } from "@valbuild/core"; +import { getSettings, uploadRemoteFile } from "@valbuild/server"; +import { evalValConfigFile } from "./utils/evalValConfigFile"; +import { createDefaultValFSHost, runValidation } from "./runValidation"; + +export async function validate({ + root, + fix, +}: { + root?: string; + fix?: boolean; +}) { + const projectRoot = root ? path.resolve(root) : process.cwd(); + + const valConfigFile = + (await evalValConfigFile(projectRoot, "val.config.ts")) || + (await evalValConfigFile(projectRoot, "val.config.js")); + + const resolvedValConfigFile = valConfigFile + ? { + ...valConfigFile, + project: process.env.VAL_PROJECT || valConfigFile.project, + } + : process.env.VAL_PROJECT + ? { project: process.env.VAL_PROJECT } + : undefined; + + console.log( + picocolors.greenBright( + `Validating project${resolvedValConfigFile?.project ? ` '${picocolors.inverse(resolvedValConfigFile.project)}'` : ""}...`, + ), + ); + + const valFiles: string[] = await glob("**/*.val.{js,ts}", { + ignore: ["node_modules/**"], + cwd: projectRoot, + }); + + console.log(picocolors.greenBright(`Found ${valFiles.length} files...`)); + + let prettier; + try { + prettier = (await import("prettier")).default; + } catch { + console.log("Prettier not found, skipping formatting"); + } + + const fixedFiles = new Set(); + let totalErrors = 0; + + for await (const event of runValidation({ + root: projectRoot, + fix: !!fix, + valFiles, + project: resolvedValConfigFile?.project, + remote: { + remoteHost: process.env.VAL_REMOTE_HOST || DEFAULT_VAL_REMOTE_HOST, + getSettings: (projectName, options) => getSettings(projectName, options), + uploadFile: (project, bucket, fileHash, fileExt, fileBuffer, options) => + uploadRemoteFile( + process.env.VAL_CONTENT_URL || DEFAULT_CONTENT_HOST, + project, + bucket, + fileHash, + fileExt ?? "", + fileBuffer, + options, + ), + }, + fs: createDefaultValFSHost(), + })) { + switch (event.type) { + case "file-valid": + console.log( + picocolors.green("✔"), + event.file, + "is valid (" + event.durationMs + "ms)", + ); + break; + case "file-error-count": + console.log( + picocolors.red("✘"), + `${event.file} contains ${event.errorCount} error${event.errorCount > 1 ? "s" : ""}`, + " (" + event.durationMs + "ms)", + ); + totalErrors += event.errorCount; + break; + case "validation-error": + console.log( + picocolors.red("✘"), + "Got error in", + `${event.sourcePath}:`, + event.message, + ); + break; + case "validation-fixable-error": + console.log( + event.fixable ? picocolors.yellow("⚠") : picocolors.red("✘"), + `Got ${event.fixable ? "fixable " : ""}error in`, + `${event.sourcePath}:`, + event.message, + ); + break; + case "unknown-fix": + console.log( + picocolors.red("✘"), + "Unknown fix", + event.fixes, + "for", + event.sourcePath, + ); + break; + case "fix-applied": + console.log( + picocolors.yellow("⚠"), + "Applied fix for", + event.sourcePath, + ); + fixedFiles.add(event.file); + break; + case "fatal-error": + console.log( + picocolors.red("✘"), + event.file, + "is invalid:", + event.message, + ); + break; + case "remote-uploading": + console.log( + picocolors.yellow("⚠"), + `Uploading remote file: '${event.ref}'...`, + ); + break; + case "remote-uploaded": + console.log( + picocolors.green("✔"), + `Completed upload of remote file: '${event.ref}'`, + ); + break; + case "remote-already-uploaded": + console.log( + picocolors.yellow("⚠"), + `Remote file ${event.filePath} already uploaded`, + ); + break; + case "remote-downloading": + console.log( + picocolors.yellow("⚠"), + `Downloading remote file in ${event.sourcePath}...`, + ); + break; + case "summary-errors": + case "summary-success": + break; + } + } + + // Run prettier on files that had fixes applied + if (prettier) { + for (const file of fixedFiles) { + const filePath = path.join(projectRoot, file); + const fileContent = await fs.readFile(filePath, "utf-8"); + const formattedContent = await prettier.format(fileContent, { + filepath: filePath, + }); + await fs.writeFile(filePath, formattedContent); + } + } + + if (totalErrors > 0) { + console.log( + picocolors.red("✘"), + "Got", + totalErrors, + "error" + (totalErrors > 1 ? "s" : ""), + ); + process.exit(1); + } else { + console.log(picocolors.green("✔"), "No validation errors found"); + } +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 000000000..1f9a7cd58 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "lib": ["es2020", "DOM"], + "strict": true, + "isolatedModules": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "skipLibCheck": true, + "target": "ESNext" + } +} diff --git a/packages/core/.gitignore b/packages/core/.gitignore new file mode 100644 index 000000000..ebb293b9f --- /dev/null +++ b/packages/core/.gitignore @@ -0,0 +1 @@ +/trace diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 000000000..8e44396d1 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,5 @@ +# @valbuild/core + +Core library for Val, providing schema definitions and content validation for type-safe, hard-coded content management. + +See the [documentation](https://val.build/docs) for more information. diff --git a/packages/core/ROADMAP.md b/packages/core/ROADMAP.md new file mode 100644 index 000000000..6f27fbefa --- /dev/null +++ b/packages/core/ROADMAP.md @@ -0,0 +1,106 @@ +# Planned features + +## i18n + +Example: + +```tsx +// file: ./blogs.val.ts + +export const schema = s.array( + s.i18n(s.object({ title: s.string(), text: s.richtext() })), +); + +export default c.define("/blogs", schema, [ + { + en_US: { + title: "Title 1", + text: [{ tag: "p", children: ["RichText"] }], + }, + nb_NO: { + title: "Tittel 1", + text: [{ tag: "p", children: ["RikTekst?"] }], + }, + }, +]); + +// file: ./components/ServerComponent.ts + +import blogsVal from "./blogs.val"; + +export async function ServerComponent({ index }: { index: number }) { + const blogs = await fetchVal(blogVal, getLocale()); + + // NOTE: automatically resolves the locale + const title = blogs[index].title; // is a string + return
{title}
; +} +``` + +Missing infrastructure: none in particular. + +## remote + +Remote makes it possible to move content to cloud storage (and back again). It uses immutable references, so local work, branches still works. + +Example: + +```tsx +// file: ./blogs.val.ts + +export const schema = s + .array(s.object({ title: s.string(), text: s.richtext() })) + .remote(); + +export default c.define( + "/blogs", + schema, + c.remote("4ba7c33b32a60be06b1b26dff8cc5d8d967660ab"), // a change in content, will result in a new reference +); + +// file: ./components/ServerComponent.ts + +import blogsVal from "./blogs.val"; + +export async function ServerComponent({ index }: { index: number }) { + const blog = await fetchVal( + blogVal[index], // only fetch the blog at index + ); + const title = blog.title; + return
{title}
; +} +``` + +Missing infrastructure: cloud support, patch support, selectors proxy needs to be able to switch between remote and source (see selectors/future), editor plugin to improve DX (refactors, ...)? + +## oneOf + +oneOf makes it possible to reference an item in an array of in another val module. + +Example: + +```ts +// file: ./employees.val.ts + +export schema = s.array(s.object({ name: s.string() })); + +export default c.define('/employees', schema, [{ + name: 'John Smith', +}]); + +// file: ./contacts.val.ts + +import employeesVal from './employees.val'; + +export schema = s.object({ + hr: s.oneOf(employeesVal), +}); + +export default c.define('/contacts', schema, { + hr: employeesVal[0] +}); + + +``` + +Missing infrastructure: need a change in how patches are applied for source files to handle selectors inside data. diff --git a/packages/core/fp/package.json b/packages/core/fp/package.json new file mode 100644 index 000000000..f295574b6 --- /dev/null +++ b/packages/core/fp/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/valbuild-core-fp.cjs.js", + "module": "dist/valbuild-core-fp.esm.js" +} diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js new file mode 100644 index 000000000..6458500a7 --- /dev/null +++ b/packages/core/jest.config.js @@ -0,0 +1,4 @@ +/** @type {import("jest").Config} */ +module.exports = { + preset: "../../jest.preset", +}; diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 000000000..cc82c1b9b --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,54 @@ +{ + "name": "@valbuild/core", + "version": "0.96.0", + "private": false, + "description": "Val - supercharged hard-coded content", + "repository": { + "type": "git", + "url": "git+https://github.com/valbuild/val.git" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "jest", + "analyze-trace": "npx tsc -p tsconfig.json --skipLibCheck --generateTrace trace; npx -p @typescript/analyze-trace analyze-trace trace; npx speedscope trace/trace.json" + }, + "main": "dist/valbuild-core.cjs.js", + "module": "dist/valbuild-core.esm.js", + "exports": { + ".": { + "module": "./dist/valbuild-core.esm.js", + "default": "./dist/valbuild-core.cjs.js" + }, + "./fp": { + "module": "./fp/dist/valbuild-core-fp.esm.js", + "default": "./fp/dist/valbuild-core-fp.cjs.js" + }, + "./patch": { + "module": "./patch/dist/valbuild-core-patch.esm.js", + "default": "./patch/dist/valbuild-core-patch.cjs.js" + }, + "./package.json": "./package.json" + }, + "types": "dist/valbuild-core.cjs.d.ts", + "preconstruct": { + "entrypoints": [ + "./index.ts", + "./fp/index.ts", + "./patch/index.ts" + ], + "exports": true + }, + "devDependencies": { + "typescript": "^5.9.3" + }, + "dependencies": { + "ts-toolbelt": "^9.6.0" + }, + "files": [ + "dist", + "fp/dist", + "fp/package.json", + "patch/dist", + "patch/package.json" + ] +} diff --git a/packages/core/patch/package.json b/packages/core/patch/package.json new file mode 100644 index 000000000..0d71b2e96 --- /dev/null +++ b/packages/core/patch/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/valbuild-core-patch.cjs.js", + "module": "dist/valbuild-core-patch.esm.js" +} diff --git a/packages/core/src/Json.ts b/packages/core/src/Json.ts new file mode 100644 index 000000000..26c27a943 --- /dev/null +++ b/packages/core/src/Json.ts @@ -0,0 +1,4 @@ +export type Json = JsonPrimitive | JsonObject | JsonArray; +export type JsonPrimitive = string | number | boolean | null; +export type JsonArray = readonly Json[]; +export type JsonObject = { readonly [key in string]: Json }; diff --git a/packages/core/src/enrichFileImageRemoteSourceWithMetadata.test.ts b/packages/core/src/enrichFileImageRemoteSourceWithMetadata.test.ts new file mode 100644 index 000000000..87e79c1df --- /dev/null +++ b/packages/core/src/enrichFileImageRemoteSourceWithMetadata.test.ts @@ -0,0 +1,480 @@ +import { initVal } from "./initVal"; +import { enrichFileImageRemoteSourceWithMetadata } from "./module"; +import { Internal } from "."; + +describe("enrichFileImageRemoteSourceWithMetadata", () => { + test("should enrich image source with metadata from images module", () => { + const { c, s } = initVal(); + + // Define an images module (like media.val.ts) + const imagesModule = c.define( + "/content/images.val.ts", + s.images({ + accept: "image/webp", + directory: "/public/val/images", + }), + { + "/public/val/images/logo.png": { + width: 800, + height: 600, + mimeType: "image/png", + alt: "An example image", + }, + }, + ); + + const testSchema = s.object({ + test: s.image(imagesModule), + }); + + const testModule = c.define("/content/test.val.ts", testSchema, { + test: c.image("/public/val/images/logo.png"), + }); + const source = Internal.getSource(testModule); + const enrichedSource = enrichFileImageRemoteSourceWithMetadata( + source, + testSchema, + ); + expect(enrichedSource).toEqual({ + test: c.image("/public/val/images/logo.png", { + width: 800, + height: 600, + mimeType: "image/png", + alt: "An example image", + }), + }); + }); + + test("should enrich deeply nested schema with multiple images, files, records, arrays, unions, and richtext", () => { + const { c, s } = initVal(); + + // Define 3 different images modules + const avatarsModule = c.define( + "/content/avatars.val.ts", + s.images({ + accept: "image/*", + directory: "/public/val/avatars", + }), + { + "/public/val/avatars/john.png": { + width: 200, + height: 200, + mimeType: "image/png", + alt: "John's avatar", + }, + "/public/val/avatars/jane.png": { + width: 150, + height: 150, + mimeType: "image/png", + alt: "Jane's avatar", + }, + }, + ); + + // Remote images module - uses c.remote() instead of c.image() + const productsModule = c.define( + "/content/products.val.ts", + s + .images({ + accept: "image/*", + directory: "/public/val/products", + }) + .remote(), + { + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/widget123/p/public/val/products/widget.jpg": + { + width: 600, + height: 400, + mimeType: "image/jpeg", + alt: "Widget product", + }, + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/gadget456/p/public/val/products/gadget.jpg": + { + width: 800, + height: 600, + mimeType: "image/jpeg", + alt: "Gadget product", + }, + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/inline789/p/public/val/products/inline-product.png": + { + width: 100, + height: 100, + mimeType: "image/png", + alt: "Inline product image", + }, + }, + ); + + const bannersModule = c.define( + "/content/banners.val.ts", + s.images({ + accept: "image/*", + directory: "/public/val/banners", + }), + { + "/public/val/banners/hero.webp": { + width: 1920, + height: 1080, + mimeType: "image/webp", + alt: "Hero banner", + }, + "/public/val/banners/promo.webp": { + width: 1200, + height: 600, + mimeType: "image/webp", + alt: "Promo banner", + }, + }, + ); + + // Define 1 files module + const documentsModule = c.define( + "/content/documents.val.ts", + s.files({ + accept: "application/pdf", + directory: "/public/val/documents", + }), + { + "/public/val/documents/manual.pdf": { + mimeType: "application/pdf", + }, + "/public/val/documents/brochure.pdf": { + mimeType: "application/pdf", + }, + }, + ); + + // Create deeply nested schema with all combinations + const deepSchema = s.object({ + // Simple nested object with image + header: s.object({ + banner: s.image(bannersModule), + }), + + // Record -> Object -> Image + users: s.record( + s.object({ + name: s.string(), + avatar: s.image(avatarsModule), + }), + ), + + // Array -> Object -> Object -> Array -> Image (deep nesting) + products: s.array( + s.object({ + name: s.string(), + details: s.object({ + description: s.string(), + gallery: s.array(s.image(productsModule)), + }), + }), + ), + + // Array -> Union with different types at each variant + contentBlocks: s.array( + s.union( + "type", + s.object({ + type: s.literal("hero"), + backgroundImage: s.image(bannersModule), + }), + s.object({ + type: s.literal("product"), + productImage: s.image(productsModule), + }), + s.object({ + type: s.literal("document"), + file: s.file(documentsModule), + }), + s.object({ + type: s.literal("article"), + body: s.richtext({ + inline: { + img: s.image(productsModule), + }, + }), + }), + ), + ), + + // Union at object level + sidebar: s.union( + "variant", + s.object({ + variant: s.literal("promo"), + promoImage: s.image(bannersModule), + }), + s.object({ + variant: s.literal("download"), + downloadFile: s.file(documentsModule), + }), + ), + + // Deep 3-level object nesting with richtext at bottom + nested: s.object({ + level1: s.object({ + level2: s.object({ + level3: s.object({ + deepImage: s.image(avatarsModule), + richContent: s.richtext({ + inline: { + img: s.image(avatarsModule), + }, + }), + }), + }), + }), + }), + }); + + // Create test data + const testModule = c.define("/content/deep-test.val.ts", deepSchema, { + header: { + banner: c.image("/public/val/banners/hero.webp"), + }, + + users: { + john: { + name: "John Doe", + avatar: c.image("/public/val/avatars/john.png"), + }, + jane: { + name: "Jane Smith", + avatar: c.image("/public/val/avatars/jane.png"), + }, + }, + + products: [ + { + name: "Widget", + details: { + description: "A useful widget", + gallery: [ + c.remote( + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/widget123/p/public/val/products/widget.jpg", + ), + c.remote( + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/gadget456/p/public/val/products/gadget.jpg", + ), + ], + }, + }, + ], + + contentBlocks: [ + { + type: "hero", + backgroundImage: c.image("/public/val/banners/hero.webp"), + }, + { + type: "product", + productImage: c.remote( + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/widget123/p/public/val/products/widget.jpg", + ), + }, + { + type: "document", + file: c.file("/public/val/documents/manual.pdf"), + }, + { + type: "article", + body: [ + { + tag: "p", + children: [ + "Check out this product: ", + { + tag: "img", + src: c.remote( + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/inline789/p/public/val/products/inline-product.png", + ), + }, + ], + }, + ], + }, + ], + + sidebar: { + variant: "promo", + promoImage: c.image("/public/val/banners/promo.webp"), + }, + + nested: { + level1: { + level2: { + level3: { + deepImage: c.image("/public/val/avatars/john.png"), + richContent: [ + { + tag: "p", + children: [ + "Deep content with image: ", + { + tag: "img", + src: c.image("/public/val/avatars/jane.png"), + }, + ], + }, + ], + }, + }, + }, + }, + }); + + const source = Internal.getSource(testModule); + const enrichedSource = enrichFileImageRemoteSourceWithMetadata( + source, + deepSchema, + ); + + // Verify header banner + expect(enrichedSource.header.banner).toEqual( + c.image("/public/val/banners/hero.webp", { + width: 1920, + height: 1080, + mimeType: "image/webp", + alt: "Hero banner", + }), + ); + + // Verify record -> object -> image + expect(enrichedSource.users.john.avatar).toEqual( + c.image("/public/val/avatars/john.png", { + width: 200, + height: 200, + mimeType: "image/png", + alt: "John's avatar", + }), + ); + expect(enrichedSource.users.jane.avatar).toEqual( + c.image("/public/val/avatars/jane.png", { + width: 150, + height: 150, + mimeType: "image/png", + alt: "Jane's avatar", + }), + ); + + // Verify array -> object -> object -> array -> remote image + expect(enrichedSource.products[0].details.gallery[0]).toEqual( + c.remote( + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/widget123/p/public/val/products/widget.jpg", + { + width: 600, + height: 400, + mimeType: "image/jpeg", + alt: "Widget product", + }, + ), + ); + expect(enrichedSource.products[0].details.gallery[1]).toEqual( + c.remote( + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/gadget456/p/public/val/products/gadget.jpg", + { + width: 800, + height: 600, + mimeType: "image/jpeg", + alt: "Gadget product", + }, + ), + ); + + // Verify array -> union variants + expect(enrichedSource.contentBlocks[0]).toEqual({ + type: "hero", + backgroundImage: c.image("/public/val/banners/hero.webp", { + width: 1920, + height: 1080, + mimeType: "image/webp", + alt: "Hero banner", + }), + }); + + expect(enrichedSource.contentBlocks[1]).toEqual({ + type: "product", + productImage: c.remote( + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/widget123/p/public/val/products/widget.jpg", + { + width: 600, + height: 400, + mimeType: "image/jpeg", + alt: "Widget product", + }, + ), + }); + + expect(enrichedSource.contentBlocks[2]).toEqual({ + type: "document", + file: c.file("/public/val/documents/manual.pdf", { + mimeType: "application/pdf", + }), + }); + + // Verify richtext inline remote image in union + expect(enrichedSource.contentBlocks[3]).toEqual({ + type: "article", + body: [ + { + tag: "p", + children: [ + "Check out this product: ", + { + tag: "img", + src: c.remote( + "https://example.com/file/p/test/b/default/v/1.0.0/h/abc123/f/inline789/p/public/val/products/inline-product.png", + { + width: 100, + height: 100, + mimeType: "image/png", + alt: "Inline product image", + }, + ), + }, + ], + }, + ], + }); + + // Verify sidebar union + expect(enrichedSource.sidebar).toEqual({ + variant: "promo", + promoImage: c.image("/public/val/banners/promo.webp", { + width: 1200, + height: 600, + mimeType: "image/webp", + alt: "Promo banner", + }), + }); + + // Verify deep nested object with image + expect(enrichedSource.nested.level1.level2.level3.deepImage).toEqual( + c.image("/public/val/avatars/john.png", { + width: 200, + height: 200, + mimeType: "image/png", + alt: "John's avatar", + }), + ); + + // Verify deep nested richtext inline image + expect(enrichedSource.nested.level1.level2.level3.richContent).toEqual([ + { + tag: "p", + children: [ + "Deep content with image: ", + { + tag: "img", + src: c.image("/public/val/avatars/jane.png", { + width: 150, + height: 150, + mimeType: "image/png", + alt: "Jane's avatar", + }), + }, + ], + }, + ]); + }); +}); diff --git a/packages/core/src/fp/array.ts b/packages/core/src/fp/array.ts new file mode 100644 index 000000000..d1fc665f6 --- /dev/null +++ b/packages/core/src/fp/array.ts @@ -0,0 +1,30 @@ +export type NonEmptyArray = [T, ...T[]]; +export type ReadonlyNonEmptyArray = readonly [T, ...T[]]; + +export function isNonEmpty(array: Array): array is NonEmptyArray; +export function isNonEmpty( + array: ReadonlyArray, +): array is ReadonlyNonEmptyArray { + return array.length > 0; +} + +export function flatten( + array: ReadonlyNonEmptyArray>, +): NonEmptyArray; +export function flatten(array: ReadonlyArray>): Array { + return array.flat(1); +} + +export function map( + fn: (value: T, index: number) => U, +): { + (array: ReadonlyArray): Array; + (array: ReadonlyNonEmptyArray): NonEmptyArray; +} { + function mapFn(array: ReadonlyArray): Array; + function mapFn(array: ReadonlyNonEmptyArray): NonEmptyArray; + function mapFn(array: ReadonlyArray): Array { + return array.map(fn); + } + return mapFn; +} diff --git a/packages/core/src/fp/index.ts b/packages/core/src/fp/index.ts new file mode 100644 index 000000000..834976138 --- /dev/null +++ b/packages/core/src/fp/index.ts @@ -0,0 +1,3 @@ +export * as result from "./result"; +export * as array from "./array"; +export * from "./util"; diff --git a/packages/core/src/fp/result.ts b/packages/core/src/fp/result.ts new file mode 100644 index 000000000..be8392f46 --- /dev/null +++ b/packages/core/src/fp/result.ts @@ -0,0 +1,213 @@ +import { isNonEmpty, NonEmptyArray } from "./array"; + +export type Ok = { + readonly kind: "ok"; + readonly value: T; +}; +export type Err = { + readonly kind: "err"; + readonly error: E; +}; + +export type Result = Ok | Err; + +/** + * Singleton instance of Ok. Used to optimize results whose Ok values are + * void. + */ +export const voidOk: Ok = Object.freeze({ + kind: "ok", + value: undefined, +}); + +export function ok(value: T): Ok { + if (value === undefined) return voidOk as Ok; + return { + kind: "ok", + value, + }; +} + +export function err(error: E): Err { + return { + kind: "err", + error, + }; +} + +export function isOk(result: Result): result is Ok { + return result === voidOk || result.kind === "ok"; +} + +export function isErr(result: Result): result is Err { + return result !== voidOk && result.kind === "err"; +} + +export type OkType = R extends Result ? T : never; +export type ErrType = R extends Result ? E : never; + +/** + * If all results are Ok (or if results is empty), returns Ok with all the Ok + * values concatenated into an array. If any result is Err, returns Err with all + * Err values concatenated into an array. + * + * @see {@link all} for use with simple array types. + */ +export function allT(results: { + readonly [P in keyof T]: Result; +}): Result> { + const values: T[number][] = []; + const errors: E[] = []; + for (const result of results) { + if (isOk(result)) { + values.push(result.value); + } else { + errors.push(result.error); + } + } + if (isNonEmpty(errors)) { + return err(errors); + } else { + return ok(values as T); + } +} + +/** + * If all results are Ok (or if results is empty), returns Ok with all the Ok + * values concatenated into an array. If any result is Err, returns Err with all + * Err values concatenated into an array. + * + * @see {@link allT} for use with tuple types. + */ +export function all( + results: readonly Result[], +): Result> { + return allT(results); +} + +/** + * If all results are Ok (or if results is empty), returns Ok. If any result is + * Err, returns Err with all Err values concatenated into an array. + */ +export function allV( + results: readonly Result[], +): Result> { + const errs: E[] = []; + for (const result of results) { + if (isErr(result)) { + errs.push(result.error); + } + } + if (isNonEmpty(errs)) { + return err(errs); + } + return voidOk; +} + +/** + * Perform a reduction over an array with a Result-returning reducer. If the + * reducer returns Ok, its value is used as the next value. If the reducer + * returns Err, it is returned immediately. + * + * flatMapReduce is a short-circuiting equivalent to: + * ``` + * arr.reduce( + * (accRes, current, currentIndex) => + * flatMap((acc) => reducer(acc, current, currentIndex))(accRes), + * ok(initVal) + * ) + * ``` + */ +export function flatMapReduce( + reducer: (acc: T, current: A, currentIndex: number) => Result, + initVal: T, +): (arr: readonly A[]) => Result { + return (arr) => { + let val: Result = ok(initVal); + for (let i = 0; i < arr.length && isOk(val); ++i) { + val = reducer(val.value, arr[i], i); + } + return val; + }; +} + +export function map( + onOk: (value: T0) => T1, +): (result: Result) => Result { + return (result) => { + if (isOk(result)) { + return ok(onOk(result.value)); + } else { + return result; + } + }; +} + +export function flatMap( + onOk: (value: T0) => Result, +): (result: Result) => Result { + return (result) => { + if (isOk(result)) { + return onOk(result.value); + } else { + return result; + } + }; +} + +export function mapErr( + onErr: (error: E0) => E1, +): (result: Result) => Result { + return (result) => { + if (isErr(result)) { + return err(onErr(result.error)); + } else { + return result; + } + }; +} + +export function fromPredicate( + refinement: (value: T0) => value is T1, + onFalse: (value: T0) => E, +): (value: T0) => Result; +export function fromPredicate( + refinement: (value: T0) => boolean, + onFalse: (value: T0) => E, +): (value: T1) => Result { + return (value) => { + if (refinement(value)) { + return ok(value); + } else { + return err(onFalse(value)); + } + }; +} + +// NOTE: Function overload resolution seems to fail when declared as overloaded +// function type, so a value with a callable type is used instead. +export const filterOrElse: { + ( + refinement: (value: T0) => value is T1, + onFalse: (value: T0) => E, + ): (result: Result) => Result; + ( + refinement: (value: T0) => boolean, + onFalse: (value: T0) => E, + ): (result: Result) => Result; +} = ( + refinement: (value: T0) => boolean, + onFalse: (value: T0) => E, +): ((result: Result) => Result) => { + return (result) => { + if (isOk(result)) { + if (refinement(result.value)) { + return result; + } else { + return err(onFalse(result.value)); + } + } else { + return result; + } + }; +}; diff --git a/packages/core/src/fp/util.ts b/packages/core/src/fp/util.ts new file mode 100644 index 000000000..d2415595e --- /dev/null +++ b/packages/core/src/fp/util.ts @@ -0,0 +1,52 @@ +export function pipe(a: A): A; +export function pipe(a: A, ab: (a: A) => B): B; +export function pipe(a: A, ab: (a: A) => B, bc: (b: B) => C): C; +export function pipe( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, +): D; +export function pipe( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, +): E; +export function pipe( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, +): F; +// "Ought to be enough for everybody" -- attributed to E. Åmdal +export function pipe( + a: A, + ab: (a: A) => B, + bc: (b: B) => C, + cd: (c: C) => D, + de: (d: D) => E, + ef: (e: E) => F, + fg: (f: F) => G, +): G; +export function pipe(a: unknown, ...fns: ((u: unknown) => unknown)[]): unknown { + let current = a; + for (const fn of fns) { + current = fn(current); + } + return current; +} + +/** + * Runs the callback with the supplied value, then returns the value. Useful for + * debugging pipes. + */ +export function tap(callback: (value: T) => void): (value: T) => T { + return (value) => { + callback(value); + return value; + }; +} diff --git a/packages/core/src/getSha256.ts b/packages/core/src/getSha256.ts new file mode 100644 index 000000000..477a18d0f --- /dev/null +++ b/packages/core/src/getSha256.ts @@ -0,0 +1,418 @@ +/** + * From: https://github.com/kawanet/sha256-uint8array/commit/a035f83824c319d01ca1e7559fdcf1632c0cd6c4 + * + * LICENSE: + * + * MIT License + * Copyright (c) 2020-2023 Yusuke Kawasaki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * sha256-uint8array.ts + */ + +// first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311 +const K = [ + 0x428a2f98 | 0, + 0x71374491 | 0, + 0xb5c0fbcf | 0, + 0xe9b5dba5 | 0, + 0x3956c25b | 0, + 0x59f111f1 | 0, + 0x923f82a4 | 0, + 0xab1c5ed5 | 0, + 0xd807aa98 | 0, + 0x12835b01 | 0, + 0x243185be | 0, + 0x550c7dc3 | 0, + 0x72be5d74 | 0, + 0x80deb1fe | 0, + 0x9bdc06a7 | 0, + 0xc19bf174 | 0, + 0xe49b69c1 | 0, + 0xefbe4786 | 0, + 0x0fc19dc6 | 0, + 0x240ca1cc | 0, + 0x2de92c6f | 0, + 0x4a7484aa | 0, + 0x5cb0a9dc | 0, + 0x76f988da | 0, + 0x983e5152 | 0, + 0xa831c66d | 0, + 0xb00327c8 | 0, + 0xbf597fc7 | 0, + 0xc6e00bf3 | 0, + 0xd5a79147 | 0, + 0x06ca6351 | 0, + 0x14292967 | 0, + 0x27b70a85 | 0, + 0x2e1b2138 | 0, + 0x4d2c6dfc | 0, + 0x53380d13 | 0, + 0x650a7354 | 0, + 0x766a0abb | 0, + 0x81c2c92e | 0, + 0x92722c85 | 0, + 0xa2bfe8a1 | 0, + 0xa81a664b | 0, + 0xc24b8b70 | 0, + 0xc76c51a3 | 0, + 0xd192e819 | 0, + 0xd6990624 | 0, + 0xf40e3585 | 0, + 0x106aa070 | 0, + 0x19a4c116 | 0, + 0x1e376c08 | 0, + 0x2748774c | 0, + 0x34b0bcb5 | 0, + 0x391c0cb3 | 0, + 0x4ed8aa4a | 0, + 0x5b9cca4f | 0, + 0x682e6ff3 | 0, + 0x748f82ee | 0, + 0x78a5636f | 0, + 0x84c87814 | 0, + 0x8cc70208 | 0, + 0x90befffa | 0, + 0xa4506ceb | 0, + 0xbef9a3f7 | 0, + 0xc67178f2 | 0, +]; + +const enum N { + inputBytes = 64, + inputWords = inputBytes / 4, + highIndex = inputWords - 2, + lowIndex = inputWords - 1, + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + workWords = 64, + allocBytes = 80, + allocWords = allocBytes / 4, + allocTotal = allocBytes * 100, +} + +const algorithms: { [algorithm: string]: number } = { + sha256: 1, +}; + +export function createHash(algorithm?: string) { + if ( + algorithm && + !algorithms[algorithm] && + !algorithms[algorithm.toLowerCase()] + ) { + throw new Error("Digest method not supported"); + } + + return new Hash(); +} + +export class Hash { + // first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 + private A = 0x6a09e667 | 0; + private B = 0xbb67ae85 | 0; + private C = 0x3c6ef372 | 0; + private D = 0xa54ff53a | 0; + private E = 0x510e527f | 0; + private F = 0x9b05688c | 0; + private G = 0x1f83d9ab | 0; + private H = 0x5be0cd19 | 0; + + private _byte: Uint8Array; + private _word: Int32Array; + private _size = 0; + private _sp = 0; // surrogate pair + + constructor() { + if (!sharedBuffer || sharedOffset >= N.allocTotal) { + sharedBuffer = new ArrayBuffer(N.allocTotal); + sharedOffset = 0; + } + + this._byte = new Uint8Array(sharedBuffer, sharedOffset, N.allocBytes); + this._word = new Int32Array(sharedBuffer, sharedOffset, N.allocWords); + sharedOffset += N.allocBytes; + } + + update(data: string, encoding?: string): this; + update(data: Uint8Array): this; + update(data: ArrayBufferView): this; + + update(data: string | Uint8Array | ArrayBufferView): this { + // data: string + if ("string" === typeof data) { + return this._utf8(data); + } + + // data: undefined + if (data == null) { + throw new TypeError("Invalid type: " + typeof data); + } + + const byteOffset = data.byteOffset; + const length = data.byteLength; + let blocks = (length / N.inputBytes) | 0; + let offset = 0; + + // longer than 1 block + if (blocks && !(byteOffset & 3) && !(this._size % N.inputBytes)) { + const block = new Int32Array( + data.buffer, + byteOffset, + blocks * N.inputWords, + ); + while (blocks--) { + this._int32(block, offset >> 2); + offset += N.inputBytes; + } + this._size += offset; + } + + // data: TypedArray | DataView + const BYTES_PER_ELEMENT = (data as Uint8Array).BYTES_PER_ELEMENT; + if (BYTES_PER_ELEMENT !== 1 && data.buffer) { + const rest = new Uint8Array( + data.buffer, + byteOffset + offset, + length - offset, + ); + return this._uint8(rest); + } + + // no more bytes + if (offset === length) return this; + + // data: Uint8Array | Int8Array + return this._uint8(data as Uint8Array, offset); + } + + private _uint8(data: Uint8Array, offset?: number) { + const { _byte, _word } = this; + const length = data.length; + offset = offset! | 0; + + while (offset < length) { + const start = this._size % N.inputBytes; + let index = start; + + while (offset < length && index < N.inputBytes) { + _byte[index++] = data[offset++]; + } + + if (index >= N.inputBytes) { + this._int32(_word); + } + + this._size += index - start; + } + + return this; + } + + private _utf8(text: string): this { + const { _byte, _word } = this; + const length = text.length; + let surrogate = this._sp; + + for (let offset = 0; offset < length; ) { + const start = this._size % N.inputBytes; + let index = start; + + while (offset < length && index < N.inputBytes) { + let code = text.charCodeAt(offset++) | 0; + if (code < 0x80) { + // ASCII characters + _byte[index++] = code; + } else if (code < 0x800) { + // 2 bytes + _byte[index++] = 0xc0 | (code >>> 6); + _byte[index++] = 0x80 | (code & 0x3f); + } else if (code < 0xd800 || code > 0xdfff) { + // 3 bytes + _byte[index++] = 0xe0 | (code >>> 12); + _byte[index++] = 0x80 | ((code >>> 6) & 0x3f); + _byte[index++] = 0x80 | (code & 0x3f); + } else if (surrogate) { + // 4 bytes - surrogate pair + code = ((surrogate & 0x3ff) << 10) + (code & 0x3ff) + 0x10000; + _byte[index++] = 0xf0 | (code >>> 18); + _byte[index++] = 0x80 | ((code >>> 12) & 0x3f); + _byte[index++] = 0x80 | ((code >>> 6) & 0x3f); + _byte[index++] = 0x80 | (code & 0x3f); + surrogate = 0; + } else { + surrogate = code; + } + } + + if (index >= N.inputBytes) { + this._int32(_word); + _word[0] = _word[N.inputWords]; + } + + this._size += index - start; + } + + this._sp = surrogate; + return this; + } + + private _int32(data: Int32Array, offset?: number): void { + let { A, B, C, D, E, F, G, H } = this; + let i = 0; + offset = offset! | 0; + + while (i < N.inputWords) { + W[i++] = swap32(data[offset++]); + } + + for (i = N.inputWords; i < N.workWords; i++) { + W[i] = (gamma1(W[i - 2]) + W[i - 7] + gamma0(W[i - 15]) + W[i - 16]) | 0; + } + + for (i = 0; i < N.workWords; i++) { + const T1 = (H + sigma1(E) + ch(E, F, G) + K[i] + W[i]) | 0; + const T2 = (sigma0(A) + maj(A, B, C)) | 0; + H = G; + G = F; + F = E; + E = (D + T1) | 0; + D = C; + C = B; + B = A; + A = (T1 + T2) | 0; + } + + this.A = (A + this.A) | 0; + this.B = (B + this.B) | 0; + this.C = (C + this.C) | 0; + this.D = (D + this.D) | 0; + this.E = (E + this.E) | 0; + this.F = (F + this.F) | 0; + this.G = (G + this.G) | 0; + this.H = (H + this.H) | 0; + } + + digest(): Uint8Array; + digest(encoding: string): string; + digest(encoding?: string) { + const { _byte, _word } = this; + let i = (this._size % N.inputBytes) | 0; + _byte[i++] = 0x80; + + // pad 0 for current word + while (i & 3) { + _byte[i++] = 0; + } + i >>= 2; + + if (i > N.highIndex) { + while (i < N.inputWords) { + _word[i++] = 0; + } + i = 0; + this._int32(_word); + } + + // pad 0 for rest words + while (i < N.inputWords) { + _word[i++] = 0; + } + + // input size + const bits64 = this._size * 8; + const low32 = (bits64 & 0xffffffff) >>> 0; + const high32 = (bits64 - low32) / 0x100000000; + if (high32) _word[N.highIndex] = swap32(high32); + if (low32) _word[N.lowIndex] = swap32(low32); + + this._int32(_word); + + return encoding === "hex" ? this._hex() : this._bin(); + } + + private _hex(): string { + const { A, B, C, D, E, F, G, H } = this; + + return ( + hex32(A) + + hex32(B) + + hex32(C) + + hex32(D) + + hex32(E) + + hex32(F) + + hex32(G) + + hex32(H) + ); + } + + private _bin(): Uint8Array { + const { A, B, C, D, E, F, G, H, _byte, _word } = this; + + _word[0] = swap32(A); + _word[1] = swap32(B); + _word[2] = swap32(C); + _word[3] = swap32(D); + _word[4] = swap32(E); + _word[5] = swap32(F); + _word[6] = swap32(G); + _word[7] = swap32(H); + + return _byte.slice(0, 32); + } +} + +type NS = (num: number) => string; +type NN = (num: number) => number; +type N3N = (x: number, y: number, z: number) => number; + +const W = new Int32Array(N.workWords); + +let sharedBuffer: ArrayBuffer; +let sharedOffset: number = 0; + +const hex32: NS = (num) => (num + 0x100000000).toString(16).substr(-8); +const swapLE: NN = (c) => + ((c << 24) & 0xff000000) | + ((c << 8) & 0xff0000) | + ((c >> 8) & 0xff00) | + ((c >> 24) & 0xff); +const swapBE: NN = (c) => c; +const swap32: NN = isBE() ? swapBE : swapLE; + +const ch: N3N = (x, y, z) => z ^ (x & (y ^ z)); +const maj: N3N = (x, y, z) => (x & y) | (z & (x | y)); + +const sigma0: NN = (x) => + ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10)); +const sigma1: NN = (x) => + ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7)); +const gamma0: NN = (x) => + ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3); +const gamma1: NN = (x) => + ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10); + +function isBE(): boolean { + const buf = new Uint8Array(new Uint16Array([0xfeff]).buffer); // BOM + return buf[0] === 0xfe; +} + +export const getSHA256Hash = (bits: Uint8Array) => { + return createHash().update(bits).digest("hex"); +}; diff --git a/packages/core/src/getSourcePathFromRoute.test.ts b/packages/core/src/getSourcePathFromRoute.test.ts new file mode 100644 index 000000000..46be6e9f4 --- /dev/null +++ b/packages/core/src/getSourcePathFromRoute.test.ts @@ -0,0 +1,114 @@ +import { getSourcePathFromRoute } from "./getSourcePathFromRoute"; +import type { ModuleFilePath } from "./val"; +import type { SerializedSchema } from "./schema"; + +const blogSchema: SerializedSchema = { + type: "record", + router: "next-app-router", + item: { type: "object", items: {}, opt: false }, + opt: false, +}; + +const pageSchema: SerializedSchema = { + type: "record", + router: "next-app-router", + item: { type: "object", items: {}, opt: false }, + opt: false, +}; + +const catchAllSchema: SerializedSchema = { + type: "record", + router: "next-app-router", + item: { type: "object", items: {}, opt: false }, + opt: false, +}; + +const externalSchema: SerializedSchema = { + type: "record", + router: "external-url-router", + item: { type: "object", items: {}, opt: false }, + opt: false, +}; + +const plainRecordSchema: SerializedSchema = { + type: "record", + item: { type: "object", items: {}, opt: false }, + opt: false, +}; + +const schemas = { + "/app/blogs/[blog]/page.val.ts": blogSchema, + "/app/page.val.ts": pageSchema, + "/app/generic/[[...path]]/page.val.ts": catchAllSchema, + "/app/external.val.ts": externalSchema, + "/content/authors.val.ts": plainRecordSchema, +} as Record; + +describe("getSourcePathFromRoute", () => { + test("matches a basic dynamic route", () => { + const result = getSourcePathFromRoute("/blogs/blog-1", schemas); + expect(result).toEqual({ + moduleFilePath: "/app/blogs/[blog]/page.val.ts", + route: "/blogs/blog-1", + sourcePath: '/app/blogs/[blog]/page.val.ts?p="/blogs/blog-1"', + }); + }); + + test("matches the root route", () => { + const result = getSourcePathFromRoute("/", schemas); + expect(result).toEqual({ + moduleFilePath: "/app/page.val.ts", + route: "/", + sourcePath: '/app/page.val.ts?p="/"', + }); + }); + + test("matches an optional catch-all route with multiple segments", () => { + const result = getSourcePathFromRoute("/generic/a/b", schemas); + expect(result).toEqual({ + moduleFilePath: "/app/generic/[[...path]]/page.val.ts", + route: "/generic/a/b", + sourcePath: '/app/generic/[[...path]]/page.val.ts?p="/generic/a/b"', + }); + }); + + test("matches an optional catch-all route with no extra segments", () => { + const result = getSourcePathFromRoute("/generic", schemas); + expect(result).toEqual({ + moduleFilePath: "/app/generic/[[...path]]/page.val.ts", + route: "/generic", + sourcePath: '/app/generic/[[...path]]/page.val.ts?p="/generic"', + }); + }); + + test("returns null for an unmatched pathname", () => { + const result = getSourcePathFromRoute("/unknown", schemas); + expect(result).toBeNull(); + }); + + test("skips external-url-router schemas", () => { + const externalOnly = { + "/app/external.val.ts": externalSchema, + } as Record; + const result = getSourcePathFromRoute( + "https://www.google.com", + externalOnly, + ); + expect(result).toBeNull(); + }); + + test("skips records without a router", () => { + const plainOnly = { + "/content/authors.val.ts": plainRecordSchema, + } as Record; + const result = getSourcePathFromRoute("/authors", plainOnly); + expect(result).toBeNull(); + }); + + test("sourcePath JSON-quotes the pathname", () => { + const result = getSourcePathFromRoute("/blogs/my-post", schemas); + expect(result?.sourcePath).toBe( + '/app/blogs/[blog]/page.val.ts?p="/blogs/my-post"', + ); + }); +}); diff --git a/packages/core/src/getSourcePathFromRoute.ts b/packages/core/src/getSourcePathFromRoute.ts new file mode 100644 index 000000000..60ea76663 --- /dev/null +++ b/packages/core/src/getSourcePathFromRoute.ts @@ -0,0 +1,37 @@ +import type { ModuleFilePath, SourcePath } from "./val"; +import type { SerializedSchema } from "./schema"; +import { parseNextJsRoutePattern, validateUrlAgainstPattern } from "./router"; + +/** + * Given a URL pathname (e.g. "/blogs/blog-1") and all serialized module schemas, + * finds the matching next-app-router module and returns the module file path + * and the source path for that route's content. + * + * External routers (e.g. external-url-router) are intentionally skipped. + * Returns null if no next-app-router module matches the pathname. + */ +export function getSourcePathFromRoute( + pathname: string, + schemas: Record, +): { + moduleFilePath: ModuleFilePath; + sourcePath: SourcePath; + route: string; +} | null { + for (const [moduleFilePath, schema] of Object.entries(schemas) as [ + ModuleFilePath, + SerializedSchema, + ][]) { + if (schema.type !== "record" || schema.router !== "next-app-router") { + continue; + } + const routePattern = parseNextJsRoutePattern(moduleFilePath); + const { isValid } = validateUrlAgainstPattern(pathname, routePattern); + if (isValid) { + const sourcePath = + `${moduleFilePath}?p=${JSON.stringify(pathname)}` as SourcePath; + return { moduleFilePath, sourcePath, route: pathname }; + } + } + return null; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 000000000..f133405b6 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,269 @@ +export { initVal, type ConfigDirectory } from "./initVal"; +export { modules, type ValModules } from "./modules"; +export type { + InitVal, + ValConfig, + ValConstructor, + ContentConstructor, +} from "./initVal"; +export { Schema, type SerializedSchema, type SelectorOfSchema } from "./schema"; +export type { ImageMetadata } from "./schema/image"; +export type { FileMetadata } from "./schema/file"; +export type { ValModule, SerializedModule, InferValModuleType } from "./module"; +export type { SourceObject, SourcePrimitive, Source } from "./source"; +export type { FileSource } from "./source/file"; +export type { RemoteSource, RemoteRef } from "./source/remote"; +export { DEFAULT_VAL_REMOTE_HOST } from "./schema/remote"; +export type { RawString } from "./schema/string"; +export type { ImageSource } from "./source/image"; +export type { + AllRichTextOptions, + Bold, + Styles, + HeadingNode, + ImageNode, + Italic, + LineThrough, + ListItemNode, + LinkNode, + OrderedListNode, + ParagraphNode, + BrNode, + RichTextNode, + RichTextOptions, + SerializedRichTextOptions, + RichTextSource, + BlockNode, + SpanNode, + UnorderedListNode, +} from "./source/richtext"; +export { + type Val, + type SerializedVal, + type ModuleFilePath, + type PatchId, + type ModulePath, + type SourcePath, + type JsonOfSource, + type ParentPatchId, +} from "./val"; +export type { Json, JsonPrimitive, JsonArray, JsonObject } from "./Json"; +export type { + ValidationError, + ValidationErrors, +} from "./schema/validation/ValidationError"; +export type { ValidationFix } from "./schema/validation/ValidationFix"; +export { FILE_REF_PROP, FILE_REF_SUBTYPE_TAG } from "./source/file"; +export { VAL_EXTENSION, type SourceArray } from "./source"; +export { derefPatch } from "./patch/deref"; +export { + type SelectorSource, + type SelectorOf, + GenericSelector, +} from "./selector"; +import { + getSource, + splitModulePath, + splitModuleFilePath, + resolvePath, + safeResolvePath, + splitModuleFilePathAndModulePath, + joinModuleFilePathAndModulePath, + parentOfSourcePath, + patchPathToModulePath, + splitJoinedSourcePaths, + type ValModule, +} from "./module"; +const ModuleFilePathSep = "?p="; +export { ModuleFilePathSep }; +import { SelectorSource, getSchema } from "./selector"; +import { ModulePath, SourcePath, getValPath, isVal } from "./val"; +import { convertFileSource } from "./schema/file"; +import { createValPathOfItem } from "./selector/SelectorProxy"; +import { getSHA256Hash } from "./getSha256"; +import { Operation } from "./patch"; +import { initSchema } from "./initSchema"; +import { + getMimeType, + mimeTypeToFileExt, + filenameToMimeType, + EXT_TO_MIME_TYPES, + MIME_TYPES_TO_EXT, +} from "./mimeType"; +import { type ImageMetadata } from "./schema/image"; +import { type FileMetadata } from "./schema/file"; +import { isFile } from "./source/file"; +import { createRemoteRef } from "./source/remote"; +import { + getValidationBasis, + getValidationHash, +} from "./remote/validationBasis"; +import { getFileHash, hashToRemoteFileHash } from "./remote/fileHash"; +import { splitRemoteRef } from "./remote/splitRemoteRef"; +import { convertRemoteSource } from "./schema/remote"; +export { type SerializedArraySchema, ArraySchema } from "./schema/array"; +export { type SerializedObjectSchema, ObjectSchema } from "./schema/object"; +export { type SerializedRecordSchema, RecordSchema } from "./schema/record"; +export { type SerializedStringSchema, StringSchema } from "./schema/string"; +export { type SerializedNumberSchema, NumberSchema } from "./schema/number"; +export { type SerializedBooleanSchema, BooleanSchema } from "./schema/boolean"; +export { type SerializedImageSchema, ImageSchema } from "./schema/image"; +export { type SerializedFileSchema, FileSchema } from "./schema/file"; +export { type SerializedDateSchema, DateSchema } from "./schema/date"; +export { type SerializedKeyOfSchema, KeyOfSchema } from "./schema/keyOf"; +export { type SerializedRouteSchema, RouteSchema } from "./schema/route"; +export { + type SerializedRichTextSchema, + RichTextSchema, +} from "./schema/richtext"; +export { + type SerializedUnionSchema, + UnionSchema, + type SerializedStringUnionSchema, + type SerializedObjectUnionSchema, +} from "./schema/union"; +export { type SerializedLiteralSchema, LiteralSchema } from "./schema/literal"; +export { deserializeSchema } from "./schema/deserialize"; +export { + type ListRecordRender, + type ListArrayRender, + type ReifiedRender, + type CodeLanguage, + type CodeRender, +} from "./render"; +export type { ValRouter, RouteValidationError } from "./router"; +export { getSourcePathFromRoute } from "./getSourcePathFromRoute"; +import { nextAppRouter, externalPageRouter } from "./router"; + +export const FATAL_ERROR_TYPES = [ + "no-schema", + "no-source", + "invalid-id", + "no-module", + "invalid-patch", +] as const; +export type FatalErrorType = (typeof FATAL_ERROR_TYPES)[number]; + +export const DEFAULT_CONTENT_HOST = "https://content.val.build"; +export const DEFAULT_APP_HOST = "https://admin.val.build"; + +const Internal = { + VERSION: { + core: ((): string | null => { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require("../package.json").version; + } catch { + return null; + } + })(), + }, + convertFileSource, + convertRemoteSource, + getSchema, + getValPath, + getSource, + resolvePath, + safeResolvePath, + splitModuleFilePathAndModulePath, + joinModuleFilePathAndModulePath, + nextAppRouter, + externalPageRouter, + remote: { + createRemoteRef, + getValidationBasis, + getValidationHash, + getFileHash, + hashToRemoteFileHash, + splitRemoteRef, + }, + validate: ( + val: ValModule, + path: SourcePath, + src: unknown, + ) => { + return ( + val && getSchema(val)?.["executeValidate"](path, src as SelectorSource) + ); + }, + isVal, + isFile, + createValPathOfItem, + getSHA256Hash, + initSchema, + getMimeType, + mimeTypeToFileExt, + filenameToMimeType, + EXT_TO_MIME_TYPES, + MIME_TYPES_TO_EXT, + ModuleFilePathSep, + notFileOp: (op: Operation) => op.op !== "file", + isFileOp: ( + op: Operation, + ): op is { + op: "file"; + path: string[]; + filePath: string; + value: string; + remote: boolean; + } => op.op === "file" && typeof op.filePath === "string", + createPatchJSONPath: (modulePath: ModulePath) => + `/${modulePath + .split(".") + .map((segment) => segment && tryJsonParse(segment)) + .join("/")}`, + createPatchPath: (modulePath: ModulePath) => { + return splitModulePath(modulePath); + }, + splitModulePath, + splitModuleFilePath, + splitJoinedSourcePaths, + parentOfSourcePath, + patchPathToModulePath, + VAL_ENABLE_COOKIE_NAME: "val_enable" as const, + VAL_STATE_COOKIE: "val_state" as const, + VAL_SESSION_COOKIE: "val_session" as const, + createFilename: ( + data: string | null, + filename: string | null, + metadata: FileMetadata | ImageMetadata | undefined, + sha256: string, + ) => { + if (!metadata) { + return filename; + } + if (!data) { + return filename; + } + const shaSuffix = sha256.slice(0, 5); + const mimeType = Internal.getMimeType(data) ?? "unknown"; + const newExt = Internal.mimeTypeToFileExt(mimeType) ?? "unknown"; // Don't trust the file extension + if (filename) { + let cleanFilename = + filename.split(".").slice(0, -1).join(".") || filename; // remove extension if it exists + const maybeShaSuffixPos = cleanFilename.lastIndexOf("_"); + const currentShaSuffix = cleanFilename.slice( + maybeShaSuffixPos + 1, + cleanFilename.length, + ); + if (currentShaSuffix === shaSuffix) { + cleanFilename = cleanFilename.slice(0, maybeShaSuffixPos); + } + const escapedFilename = encodeURIComponent(cleanFilename) + .replace(/%[0-9A-Fa-f]{2}/g, "") + .toLowerCase(); + return `${escapedFilename}_${shaSuffix}.${newExt}`; + } + return `${sha256}.${newExt}`; + }, +}; + +function tryJsonParse(str: string) { + try { + return JSON.parse(str); + } catch { + return str; + } +} + +export { Internal }; diff --git a/packages/core/src/initSchema.ts b/packages/core/src/initSchema.ts new file mode 100644 index 000000000..91ebe6a0c --- /dev/null +++ b/packages/core/src/initSchema.ts @@ -0,0 +1,270 @@ +// import type { F } from "ts-toolbelt"; +import { array } from "./schema/array"; +import { number } from "./schema/number"; +import { object } from "./schema/object"; +import { string } from "./schema/string"; +import { boolean } from "./schema/boolean"; +import { union } from "./schema/union"; +import { richtext } from "./schema/richtext"; +import { image } from "./schema/image"; +import { literal } from "./schema/literal"; +import { keyOf } from "./schema/keyOf"; +import { record } from "./schema/record"; +import { file } from "./schema/file"; +import { files } from "./schema/files"; +import { date } from "./schema/date"; +import { route } from "./schema/route"; +import { router } from "./schema/router"; +import { images } from "./schema/images"; +// import { i18n, I18n } from "./schema/future/i18n"; +// import { oneOf } from "./schema/future/oneOf"; + +export type InitSchema = { + /** + * Define a string. + * + * @example + * const schema = s.string(); + * export default c.define("/example.val.ts", schema, "test"); + * + */ + readonly string: typeof string; + /** + * Define a boolean. + * + * @example + * const schema = s.boolean(); + * export default c.define("/example.val.ts", schema, true); + * + */ + readonly boolean: typeof boolean; + /** + * Define an array. + * + * @example + * const schema = s.array(s.string()); + * export default c.define("/example.val.ts", schema, ["test", "test2"]); + * + */ + readonly array: typeof array; + /** + * Define an object. + * + * @example + * const schema = s.object({ + * text: s.string(), + * }); + * export default c.define("/example.val.ts", schema, { text: "test" }); + */ + readonly object: typeof object; + /** + * Define a number. + * + * @example + * const schema = s.number(); + * export default c.define("/example.val.ts", schema, 1); + * + */ + readonly number: typeof number; + /** + * Define a union. + * + * @example // union of string literals + * const schema = s.union(s.literal("test"), s.literal("test2")); + * export default c.define("/example.val.ts", schema, "test"); + * + * @example // union of string literals + * const schema = s.union("type", s.object({ + * type: s.literal("test"), + * value: s.string() + * }), s.object({ + * type: s.literal("test2"), + * value: s.string() + * })); + * export default c.define("/example.val.ts", schema, { + * type: "test", + * value: "test" + * }); + * + */ + readonly union: typeof union; + /** + * Define a rich text. + * + * @example + * const schema = s.richtext(); + * export default c.define("/example.val.ts", schema, [ + * { tag: "h1", children: ["Title 1"] }, + * ]); + */ + readonly richtext: typeof richtext; + /** + * Define an image. + * + * Use c.image to create an image source. + * + * @example + * const schema = s.image(); + * export default c.define("/example.val.ts", schema, c.image("/public/val/example.png", { + * width: 100, + * height: 100, + * mimeType: "image/png", + * hotspot: { + * x: 0.5, + * y: 0.5 + * } + * })); + * + */ + readonly image: typeof image; + /** + * Define a literal. + * + * @example + * const schema = s.literal("test"); + * export default c.define("/example.val.ts", schema, "test"); + * + */ + readonly literal: typeof literal; + /** + * Define a key of. + * + * @example + * import otherVal from "./other.val"; // this must be a record + * const schema = s.keyOf(otherVal); + * export default c.define("/example.val.ts", schema, "test"); + * + */ + readonly keyOf: typeof keyOf; + /** + * Define a record. + * + * @example + * const schema = s.record(s.string()); + * export default c.define("/example.val.ts", schema, { "test": "test" }); + * + */ + readonly record: typeof record; + /** + * Define a file. + * + * Use `c.file` to create a file source. + * + * @example + * const schema = s.file(); + * export default c.define("/example.val.ts", schema, c.file("/public/val/example.png")); + * + */ + readonly file: typeof file; + /** + * Define a date. + * + * @example + * const schema = s.date(); + * export default c.define("/example.val.ts", schema, "2025-01-01"); + * + */ + readonly date: typeof date; + /** + * Define a string that references a route path in your application. + * + * To create router pages you can use the s.router() function. + * + * @example + * ```typescript + * const schema = s.route(); // use .include() and .exclude() to constrain the route paths + * export default c.define("/example.val.ts", schema, "/a-page-slug"); + * ``` + */ + readonly route: typeof route; + /** + * Create a page router. + * Each key is the path of the page. + * + * The router will be used to validate the paths of the pages. + * + * If you need to link to these pages you can use the s.route() to reference page paths. + * + * @example Next.js App Router + * ```typescript + * import { s, c, nextAppRouter } from "../val.config"; + * const schema = s.object({ + * title: s.string(), + * }); + * export default c.define("/app/[slug]/page.val.ts", s.router(nextAppRouter, schema), { + * "/a-page-slug": { title: "First Page" }, + * "/another-page-slug": { title: "Second Page" }, + * }); + * ``` + * + * @param router - The router configuration (e.g., nextAppRouter) + * @param schema - The schema for each route item + * @returns A RecordSchema configured as a router + */ + readonly router: typeof router; + /** + * Define a collection of images. + * + * @example + * ```typescript + * const schema = s.images({ + * accept: "image/webp", + * directory: "/public/val/images", + * alt: s.string().minLength(4), + * }); + * export default c.define("/content/images.val.ts", schema, { + * "/public/val/images/hero.webp": { + * width: 1920, + * height: 1080, + * mimeType: "image/webp", + * alt: "Hero image", + * }, + * }); + * ``` + */ + readonly images: typeof images; + /** + * Define a collection of files. + * + * @example + * ```typescript + * const schema = s.files({ + * accept: "application/pdf", + * directory: "/public/val/documents", + * }); + * export default c.define("/content/documents.val.ts", schema, { + * "/public/val/documents/report.pdf": { + * mimeType: "application/pdf", + * }, + * }); + * ``` + */ + readonly files: typeof files; +}; +// export type InitSchemaLocalized = { +// readonly i18n: I18n; +// }; +export function initSchema() { + // locales: F.Narrow + return { + string, + boolean, + array, + object, + number, + union, + // oneOf, + richtext, + image, + literal, + keyOf, + record, + file, + files, + date, + route, + router, + images, + // i18n: i18n(locales), + }; +} diff --git a/packages/core/src/initVal.ts b/packages/core/src/initVal.ts new file mode 100644 index 000000000..873e6b96c --- /dev/null +++ b/packages/core/src/initVal.ts @@ -0,0 +1,104 @@ +import { define } from "./module"; +import { InitSchema, initSchema } from "./initSchema"; +import { getValPath as getPath } from "./val"; +import { initFile } from "./source/file"; +import { initImage } from "./source/image"; +import { initRemote } from "./source/remote"; +// import { i18n, I18n } from "./source/future/i18n"; +// import { remote } from "./source/future/remote"; + +export type ContentConstructor = { + define: typeof define; + // remote: typeof remote; + file: ReturnType; + image: ReturnType; + remote: ReturnType; +}; +export type ValConstructor = { + unstable_getPath: typeof getPath; +}; + +export type ConfigDirectory = `/public/val`; + +export type ValConfig = { + project?: string; + root?: string; + files?: { + directory: ConfigDirectory; + }; + gitCommit?: string; + gitBranch?: string; + defaultTheme?: "dark" | "light"; + ai?: { + commitMessages?: { + disabled?: boolean; + }; + chat?: { + experimental?: { + enable?: boolean; + }; + suggestions?: string[]; + title?: string; + description?: string; + }; + }; +}; +export type InitVal = { + c: ContentConstructor; + val: ValConstructor; + s: InitSchema; + config: ValConfig; +}; + +// type NarrowStrings = +// | (A extends [] ? [] : never) +// | (A extends string ? A : never) +// | { +// [K in keyof A]: NarrowStrings; +// }; + +// TODO: Rename to createValSystem (only to be used by internal things), we can then export * from '@valbuild/core' in the next package then. +export const initVal = ( + config?: ValConfig, +): // options?: { +// readonly locales?: NarrowStrings<{ +// readonly required: Locales; +// readonly default: Locales extends readonly string[] +// ? Locales[number] +// : never; +// }>; +// } +InitVal => { + // const locales = options?.locales; + const s = initSchema(); + // if (locales?.required) { + // console.error("Locales / i18n currently not implemented"); + // return { + // val: { + // content, + // i18n, + // remote, + // getPath, + // file, + // richtext, + // }, + // s, + // config: {}, + // // eslint-disable-next-line @typescript-eslint/no-explicit-any + // } as any; + // } + return { + val: { + unstable_getPath: getPath, + }, + c: { + define, + remote: initRemote(config), + file: initFile(config), + image: initImage(config), + }, + s, + config, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any; +}; diff --git a/packages/core/src/mimeType/all.ts b/packages/core/src/mimeType/all.ts new file mode 100644 index 000000000..65bfca325 --- /dev/null +++ b/packages/core/src/mimeType/all.ts @@ -0,0 +1,696 @@ +export const EXT_TO_MIME_TYPES: Record = { + x3d: "application/vnd.hzn-3d-crossword", + "3gp": "video/3gpp", + "3g2": "video/3gpp2", + mseq: "application/vnd.mseq", + pwn: "application/vnd.3m.post-it-notes", + plb: "application/vnd.3gpp.pic-bw-large", + psb: "application/vnd.3gpp.pic-bw-small", + pvb: "application/vnd.3gpp.pic-bw-var", + tcap: "application/vnd.3gpp2.tcap", + "7z": "application/x-7z-compressed", + abw: "application/x-abiword", + ace: "application/x-ace-compressed", + acc: "application/vnd.americandynamics.acc", + acu: "application/vnd.acucobol", + atc: "application/vnd.acucorp", + adp: "audio/adpcm", + aab: "application/x-authorware-bin", + aam: "application/x-authorware-map", + aas: "application/x-authorware-seg", + air: "application/vnd.adobe.air-application-installer-package+zip", + avif: "image/avif", + swf: "application/x-shockwave-flash", + fxp: "application/vnd.adobe.fxp", + pdf: "application/pdf", + ppd: "application/vnd.cups-ppd", + dir: "application/x-director", + xdp: "application/vnd.adobe.xdp+xml", + xfdf: "application/vnd.adobe.xfdf", + aac: "audio/x-aac", + ahead: "application/vnd.ahead.space", + azf: "application/vnd.airzip.filesecure.azf", + azs: "application/vnd.airzip.filesecure.azs", + azw: "application/vnd.amazon.ebook", + ami: "application/vnd.amiga.ami", + "N/A": "application/andrew-inset", + apk: "application/vnd.android.package-archive", + cii: "application/vnd.anser-web-certificate-issue-initiation", + fti: "application/vnd.anser-web-funds-transfer-initiation", + atx: "application/vnd.antix.game-component", + dmg: "application/x-apple-diskimage", + mpkg: "application/vnd.apple.installer+xml", + aw: "application/applixware", + mp3: "audio/mpeg", + les: "application/vnd.hhe.lesson-player", + swi: "application/vnd.aristanetworks.swi", + s: "text/x-asm", + atomcat: "application/atomcat+xml", + atomsvc: "application/atomsvc+xml", + atom: "application/atom+xml", + ac: "application/pkix-attr-cert", + aif: "audio/x-aiff", + avi: "video/x-msvideo", + aep: "application/vnd.audiograph", + dxf: "image/vnd.dxf", + dwf: "model/vnd.dwf", + par: "text/plain-bas", + bcpio: "application/x-bcpio", + bin: "application/octet-stream", + bmp: "image/bmp", + torrent: "application/x-bittorrent", + cod: "application/vnd.rim.cod", + mpm: "application/vnd.blueice.multipass", + bmi: "application/vnd.bmi", + sh: "application/x-sh", + btif: "image/prs.btif", + rep: "application/vnd.businessobjects", + bz: "application/x-bzip", + bz2: "application/x-bzip2", + csh: "application/x-csh", + c: "text/x-c", + cdxml: "application/vnd.chemdraw+xml", + css: "text/css", + cdx: "chemical/x-cdx", + cml: "chemical/x-cml", + csml: "chemical/x-csml", + cdbcmsg: "application/vnd.contact.cmsg", + cla: "application/vnd.claymore", + c4g: "application/vnd.clonk.c4group", + sub: "image/vnd.dvb.subtitle", + cdmia: "application/cdmi-capability", + cdmic: "application/cdmi-container", + cdmid: "application/cdmi-domain", + cdmio: "application/cdmi-object", + cdmiq: "application/cdmi-queue", + c11amc: "application/vnd.cluetrust.cartomobile-config", + c11amz: "application/vnd.cluetrust.cartomobile-config-pkg", + ras: "image/x-cmu-raster", + dae: "model/vnd.collada+xml", + csv: "text/csv", + cpt: "application/mac-compactpro", + wmlc: "application/vnd.wap.wmlc", + cgm: "image/cgm", + ice: "x-conference/x-cooltalk", + cmx: "image/x-cmx", + xar: "application/vnd.xara", + cmc: "application/vnd.cosmocaller", + cpio: "application/x-cpio", + clkx: "application/vnd.crick.clicker", + clkk: "application/vnd.crick.clicker.keyboard", + clkp: "application/vnd.crick.clicker.palette", + clkt: "application/vnd.crick.clicker.template", + clkw: "application/vnd.crick.clicker.wordbank", + wbs: "application/vnd.criticaltools.wbs+xml", + cryptonote: "application/vnd.rig.cryptonote", + cif: "chemical/x-cif", + cmdf: "chemical/x-cmdf", + cu: "application/cu-seeme", + cww: "application/prs.cww", + curl: "text/vnd.curl", + dcurl: "text/vnd.curl.dcurl", + mcurl: "text/vnd.curl.mcurl", + scurl: "text/vnd.curl.scurl", + car: "application/vnd.curl.car", + pcurl: "application/vnd.curl.pcurl", + cmp: "application/vnd.yellowriver-custom-menu", + dssc: "application/dssc+der", + xdssc: "application/dssc+xml", + deb: "application/x-debian-package", + uva: "audio/vnd.dece.audio", + uvi: "image/vnd.dece.graphic", + uvh: "video/vnd.dece.hd", + uvm: "video/vnd.dece.mobile", + uvu: "video/vnd.uvvu.mp4", + uvp: "video/vnd.dece.pd", + uvs: "video/vnd.dece.sd", + uvv: "video/vnd.dece.video", + dvi: "application/x-dvi", + seed: "application/vnd.fdsn.seed", + dtb: "application/x-dtbook+xml", + res: "application/x-dtbresource+xml", + ait: "application/vnd.dvb.ait", + svc: "application/vnd.dvb.service", + eol: "audio/vnd.digital-winds", + djvu: "image/vnd.djvu", + dtd: "application/xml-dtd", + mlp: "application/vnd.dolby.mlp", + wad: "application/x-doom", + dpg: "application/vnd.dpgraph", + dra: "audio/vnd.dra", + dfac: "application/vnd.dreamfactory", + dts: "audio/vnd.dts", + dtshd: "audio/vnd.dts.hd", + dwg: "image/vnd.dwg", + geo: "application/vnd.dynageo", + es: "application/ecmascript", + mag: "application/vnd.ecowin.chart", + mmr: "image/vnd.fujixerox.edmics-mmr", + rlc: "image/vnd.fujixerox.edmics-rlc", + exi: "application/exi", + mgz: "application/vnd.proteus.magazine", + epub: "application/epub+zip", + eml: "message/rfc822", + nml: "application/vnd.enliven", + xpr: "application/vnd.is-xpr", + xif: "image/vnd.xiff", + xfdl: "application/vnd.xfdl", + emma: "application/emma+xml", + ez2: "application/vnd.ezpix-album", + ez3: "application/vnd.ezpix-package", + fst: "image/vnd.fst", + fvt: "video/vnd.fvt", + fbs: "image/vnd.fastbidsheet", + fe_launch: "application/vnd.denovo.fcselayout-link", + f4v: "video/x-f4v", + flv: "video/x-flv", + fpx: "image/vnd.fpx", + npx: "image/vnd.net-fpx", + flx: "text/vnd.fmi.flexstor", + fli: "video/x-fli", + ftc: "application/vnd.fluxtime.clip", + fdf: "application/vnd.fdf", + f: "text/x-fortran", + mif: "application/vnd.mif", + fm: "application/vnd.framemaker", + fh: "image/x-freehand", + fsc: "application/vnd.fsc.weblaunch", + fnc: "application/vnd.frogans.fnc", + ltf: "application/vnd.frogans.ltf", + ddd: "application/vnd.fujixerox.ddd", + xdw: "application/vnd.fujixerox.docuworks", + xbd: "application/vnd.fujixerox.docuworks.binder", + oas: "application/vnd.fujitsu.oasys", + oa2: "application/vnd.fujitsu.oasys2", + oa3: "application/vnd.fujitsu.oasys3", + fg5: "application/vnd.fujitsu.oasysgp", + bh2: "application/vnd.fujitsu.oasysprs", + spl: "application/x-futuresplash", + fzs: "application/vnd.fuzzysheet", + g3: "image/g3fax", + gmx: "application/vnd.gmx", + gtw: "model/vnd.gtw", + txd: "application/vnd.genomatix.tuxedo", + ggb: "application/vnd.geogebra.file", + ggt: "application/vnd.geogebra.tool", + gdl: "model/vnd.gdl", + gex: "application/vnd.geometry-explorer", + gxt: "application/vnd.geonext", + g2w: "application/vnd.geoplan", + g3w: "application/vnd.geospace", + gsf: "application/x-font-ghostscript", + bdf: "application/x-font-bdf", + gtar: "application/x-gtar", + texinfo: "application/x-texinfo", + gnumeric: "application/x-gnumeric", + kml: "application/vnd.google-earth.kml+xml", + kmz: "application/vnd.google-earth.kmz", + gqf: "application/vnd.grafeq", + gif: "image/gif", + gv: "text/vnd.graphviz", + gac: "application/vnd.groove-account", + ghf: "application/vnd.groove-help", + gim: "application/vnd.groove-identity-message", + grv: "application/vnd.groove-injector", + gtm: "application/vnd.groove-tool-message", + tpl: "application/vnd.groove-tool-template", + vcg: "application/vnd.groove-vcard", + h261: "video/h261", + h263: "video/h263", + h264: "video/h264", + hpid: "application/vnd.hp-hpid", + hps: "application/vnd.hp-hps", + hdf: "application/x-hdf", + rip: "audio/vnd.rip", + hbci: "application/vnd.hbci", + jlt: "application/vnd.hp-jlyt", + pcl: "application/vnd.hp-pcl", + hpgl: "application/vnd.hp-hpgl", + hvs: "application/vnd.yamaha.hv-script", + hvd: "application/vnd.yamaha.hv-dic", + hvp: "application/vnd.yamaha.hv-voice", + "sfd-hdstx": "application/vnd.hydrostatix.sof-data", + stk: "application/hyperstudio", + hal: "application/vnd.hal+xml", + html: "text/html", + irm: "application/vnd.ibm.rights-management", + sc: "application/vnd.ibm.secure-container", + ics: "text/calendar", + icc: "application/vnd.iccprofile", + ico: "image/x-icon", + igl: "application/vnd.igloader", + ief: "image/ief", + ivp: "application/vnd.immervision-ivp", + ivu: "application/vnd.immervision-ivu", + rif: "application/reginfo+xml", + "3dml": "text/vnd.in3d.3dml", + spot: "text/vnd.in3d.spot", + igs: "model/iges", + i2g: "application/vnd.intergeo", + cdy: "application/vnd.cinderella", + xpw: "application/vnd.intercon.formnet", + fcs: "application/vnd.isac.fcs", + ipfix: "application/ipfix", + cer: "application/pkix-cert", + pki: "application/pkixcmp", + crl: "application/pkix-crl", + pkipath: "application/pkix-pkipath", + igm: "application/vnd.insors.igm", + rcprofile: "application/vnd.ipunplugged.rcprofile", + irp: "application/vnd.irepository.package+xml", + jad: "text/vnd.sun.j2me.app-descriptor", + jar: "application/java-archive", + class: "application/java-vm", + jnlp: "application/x-java-jnlp-file", + ser: "application/java-serialized-object", + java: "text/x-java-source,java", + js: "application/javascript", + json: "application/json", + joda: "application/vnd.joost.joda-archive", + jpm: "video/jpm", + jpeg: "image/jpeg", + jpg: "image/jpeg", + pjpeg: "image/pjpeg", + jpgv: "video/jpeg", + ktz: "application/vnd.kahootz", + mmd: "application/vnd.chipnuts.karaoke-mmd", + karbon: "application/vnd.kde.karbon", + chrt: "application/vnd.kde.kchart", + kfo: "application/vnd.kde.kformula", + flw: "application/vnd.kde.kivio", + kon: "application/vnd.kde.kontour", + kpr: "application/vnd.kde.kpresenter", + ksp: "application/vnd.kde.kspread", + kwd: "application/vnd.kde.kword", + htke: "application/vnd.kenameaapp", + kia: "application/vnd.kidspiration", + kne: "application/vnd.kinar", + sse: "application/vnd.kodak-descriptor", + lasxml: "application/vnd.las.las+xml", + latex: "application/x-latex", + lbd: "application/vnd.llamagraphics.life-balance.desktop", + lbe: "application/vnd.llamagraphics.life-balance.exchange+xml", + jam: "application/vnd.jam", + "123": "application/vnd.lotus-1-2-3", + apr: "application/vnd.lotus-approach", + pre: "application/vnd.lotus-freelance", + nsf: "application/vnd.lotus-notes", + org: "application/vnd.lotus-organizer", + scm: "application/vnd.lotus-screencam", + lwp: "application/vnd.lotus-wordpro", + lvp: "audio/vnd.lucent.voice", + m3u: "audio/x-mpegurl", + m4v: "video/x-m4v", + hqx: "application/mac-binhex40", + portpkg: "application/vnd.macports.portpkg", + mgp: "application/vnd.osgeo.mapguide.package", + mrc: "application/marc", + mrcx: "application/marcxml+xml", + mxf: "application/mxf", + nbp: "application/vnd.wolfram.player", + ma: "application/mathematica", + mathml: "application/mathml+xml", + mbox: "application/mbox", + mc1: "application/vnd.medcalcdata", + mscml: "application/mediaservercontrol+xml", + cdkey: "application/vnd.mediastation.cdkey", + mwf: "application/vnd.mfer", + mfm: "application/vnd.mfmp", + msh: "model/mesh", + mads: "application/mads+xml", + mets: "application/mets+xml", + mods: "application/mods+xml", + meta4: "application/metalink4+xml", + mcd: "application/vnd.mcd", + flo: "application/vnd.micrografx.flo", + igx: "application/vnd.micrografx.igx", + es3: "application/vnd.eszigno3+xml", + mdb: "application/x-msaccess", + asf: "video/x-ms-asf", + exe: "application/x-msdownload", + cil: "application/vnd.ms-artgalry", + cab: "application/vnd.ms-cab-compressed", + ims: "application/vnd.ms-ims", + application: "application/x-ms-application", + clp: "application/x-msclip", + mdi: "image/vnd.ms-modi", + eot: "application/vnd.ms-fontobject", + xls: "application/vnd.ms-excel", + xlam: "application/vnd.ms-excel.addin.macroenabled.12", + xlsb: "application/vnd.ms-excel.sheet.binary.macroenabled.12", + xltm: "application/vnd.ms-excel.template.macroenabled.12", + xlsm: "application/vnd.ms-excel.sheet.macroenabled.12", + chm: "application/vnd.ms-htmlhelp", + crd: "application/x-mscardfile", + lrm: "application/vnd.ms-lrm", + mvb: "application/x-msmediaview", + mny: "application/x-msmoney", + pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation", + sldx: "application/vnd.openxmlformats-officedocument.presentationml.slide", + ppsx: "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + potx: "application/vnd.openxmlformats-officedocument.presentationml.template", + xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + xltx: "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + dotx: "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + obd: "application/x-msbinder", + thmx: "application/vnd.ms-officetheme", + onetoc: "application/onenote", + pya: "audio/vnd.ms-playready.media.pya", + pyv: "video/vnd.ms-playready.media.pyv", + ppt: "application/vnd.ms-powerpoint", + ppam: "application/vnd.ms-powerpoint.addin.macroenabled.12", + sldm: "application/vnd.ms-powerpoint.slide.macroenabled.12", + pptm: "application/vnd.ms-powerpoint.presentation.macroenabled.12", + ppsm: "application/vnd.ms-powerpoint.slideshow.macroenabled.12", + potm: "application/vnd.ms-powerpoint.template.macroenabled.12", + mpp: "application/vnd.ms-project", + pub: "application/x-mspublisher", + scd: "application/x-msschedule", + xap: "application/x-silverlight-app", + stl: "application/vnd.ms-pki.stl", + cat: "application/vnd.ms-pki.seccat", + vsd: "application/vnd.visio", + vsdx: "application/vnd.visio2013", + wm: "video/x-ms-wm", + wma: "audio/x-ms-wma", + wax: "audio/x-ms-wax", + wmx: "video/x-ms-wmx", + wmd: "application/x-ms-wmd", + wpl: "application/vnd.ms-wpl", + wmz: "application/x-ms-wmz", + wmv: "video/x-ms-wmv", + wvx: "video/x-ms-wvx", + wmf: "application/x-msmetafile", + trm: "application/x-msterminal", + doc: "application/msword", + docm: "application/vnd.ms-word.document.macroenabled.12", + dotm: "application/vnd.ms-word.template.macroenabled.12", + wri: "application/x-mswrite", + wps: "application/vnd.ms-works", + xbap: "application/x-ms-xbap", + xps: "application/vnd.ms-xpsdocument", + mid: "audio/midi", + mpy: "application/vnd.ibm.minipay", + afp: "application/vnd.ibm.modcap", + rms: "application/vnd.jcp.javame.midlet-rms", + tmo: "application/vnd.tmobile-livetv", + prc: "application/x-mobipocket-ebook", + mbk: "application/vnd.mobius.mbk", + dis: "application/vnd.mobius.dis", + plc: "application/vnd.mobius.plc", + mqy: "application/vnd.mobius.mqy", + msl: "application/vnd.mobius.msl", + txf: "application/vnd.mobius.txf", + daf: "application/vnd.mobius.daf", + fly: "text/vnd.fly", + mpc: "application/vnd.mophun.certificate", + mpn: "application/vnd.mophun.application", + mj2: "video/mj2", + mpga: "audio/mpeg", + mxu: "video/vnd.mpegurl", + mpeg: "video/mpeg", + m21: "application/mp21", + mp4a: "audio/mp4", + mp4: "video/mp4", + m3u8: "application/vnd.apple.mpegurl", + mus: "application/vnd.musician", + msty: "application/vnd.muvee.style", + mxml: "application/xv+xml", + ngdat: "application/vnd.nokia.n-gage.data", + "n-gage": "application/vnd.nokia.n-gage.symbian.install", + ncx: "application/x-dtbncx+xml", + nc: "application/x-netcdf", + nlu: "application/vnd.neurolanguage.nlu", + dna: "application/vnd.dna", + nnd: "application/vnd.noblenet-directory", + nns: "application/vnd.noblenet-sealer", + nnw: "application/vnd.noblenet-web", + rpst: "application/vnd.nokia.radio-preset", + rpss: "application/vnd.nokia.radio-presets", + n3: "text/n3", + edm: "application/vnd.novadigm.edm", + edx: "application/vnd.novadigm.edx", + ext: "application/vnd.novadigm.ext", + gph: "application/vnd.flographit", + ecelp4800: "audio/vnd.nuera.ecelp4800", + ecelp7470: "audio/vnd.nuera.ecelp7470", + ecelp9600: "audio/vnd.nuera.ecelp9600", + oda: "application/oda", + ogx: "application/ogg", + oga: "audio/ogg", + ogv: "video/ogg", + dd2: "application/vnd.oma.dd2+xml", + oth: "application/vnd.oasis.opendocument.text-web", + opf: "application/oebps-package+xml", + qbo: "application/vnd.intu.qbo", + oxt: "application/vnd.openofficeorg.extension", + osf: "application/vnd.yamaha.openscoreformat", + weba: "audio/webm", + webm: "video/webm", + odc: "application/vnd.oasis.opendocument.chart", + otc: "application/vnd.oasis.opendocument.chart-template", + odb: "application/vnd.oasis.opendocument.database", + odf: "application/vnd.oasis.opendocument.formula", + odft: "application/vnd.oasis.opendocument.formula-template", + odg: "application/vnd.oasis.opendocument.graphics", + otg: "application/vnd.oasis.opendocument.graphics-template", + odi: "application/vnd.oasis.opendocument.image", + oti: "application/vnd.oasis.opendocument.image-template", + odp: "application/vnd.oasis.opendocument.presentation", + otp: "application/vnd.oasis.opendocument.presentation-template", + ods: "application/vnd.oasis.opendocument.spreadsheet", + ots: "application/vnd.oasis.opendocument.spreadsheet-template", + odt: "application/vnd.oasis.opendocument.text", + odm: "application/vnd.oasis.opendocument.text-master", + ott: "application/vnd.oasis.opendocument.text-template", + ktx: "image/ktx", + sxc: "application/vnd.sun.xml.calc", + stc: "application/vnd.sun.xml.calc.template", + sxd: "application/vnd.sun.xml.draw", + std: "application/vnd.sun.xml.draw.template", + sxi: "application/vnd.sun.xml.impress", + sti: "application/vnd.sun.xml.impress.template", + sxm: "application/vnd.sun.xml.math", + sxw: "application/vnd.sun.xml.writer", + sxg: "application/vnd.sun.xml.writer.global", + stw: "application/vnd.sun.xml.writer.template", + otf: "application/x-font-otf", + osfpvg: "application/vnd.yamaha.openscoreformat.osfpvg+xml", + dp: "application/vnd.osgi.dp", + pdb: "application/vnd.palm", + p: "text/x-pascal", + paw: "application/vnd.pawaafile", + pclxl: "application/vnd.hp-pclxl", + efif: "application/vnd.picsel", + pcx: "image/x-pcx", + psd: "image/vnd.adobe.photoshop", + prf: "application/pics-rules", + pic: "image/x-pict", + chat: "application/x-chat", + p10: "application/pkcs10", + p12: "application/x-pkcs12", + p7m: "application/pkcs7-mime", + p7s: "application/pkcs7-signature", + p7r: "application/x-pkcs7-certreqresp", + p7b: "application/x-pkcs7-certificates", + p8: "application/pkcs8", + plf: "application/vnd.pocketlearn", + pnm: "image/x-portable-anymap", + pbm: "image/x-portable-bitmap", + pcf: "application/x-font-pcf", + pfr: "application/font-tdpfr", + pgn: "application/x-chess-pgn", + pgm: "image/x-portable-graymap", + png: "image/png", + ppm: "image/x-portable-pixmap", + pskcxml: "application/pskc+xml", + pml: "application/vnd.ctc-posml", + ai: "application/postscript", + pfa: "application/x-font-type1", + pbd: "application/vnd.powerbuilder6", + pgp: "application/pgp-signature", + box: "application/vnd.previewsystems.box", + ptid: "application/vnd.pvi.ptid1", + pls: "application/pls+xml", + str: "application/vnd.pg.format", + ei6: "application/vnd.pg.osasli", + dsc: "text/prs.lines.tag", + psf: "application/x-font-linux-psf", + qps: "application/vnd.publishare-delta-tree", + wg: "application/vnd.pmi.widget", + qxd: "application/vnd.quark.quarkxpress", + esf: "application/vnd.epson.esf", + msf: "application/vnd.epson.msf", + ssf: "application/vnd.epson.ssf", + qam: "application/vnd.epson.quickanime", + qfx: "application/vnd.intu.qfx", + mov: "video/quicktime", + rar: "application/x-rar-compressed", + ram: "audio/x-pn-realaudio", + rmp: "audio/x-pn-realaudio-plugin", + rsd: "application/rsd+xml", + rm: "application/vnd.rn-realmedia", + bed: "application/vnd.realvnc.bed", + mxl: "application/vnd.recordare.musicxml", + musicxml: "application/vnd.recordare.musicxml+xml", + rnc: "application/relax-ng-compact-syntax", + rdz: "application/vnd.data-vision.rdz", + rdf: "application/rdf+xml", + rp9: "application/vnd.cloanto.rp9", + jisp: "application/vnd.jisp", + rtf: "application/rtf", + rtx: "text/richtext", + link66: "application/vnd.route66.link66+xml", + rss: "application/rss+xml", + shf: "application/shf+xml", + st: "application/vnd.sailingtracker.track", + svg: "image/svg+xml", + sus: "application/vnd.sus-calendar", + sru: "application/sru+xml", + setpay: "application/set-payment-initiation", + setreg: "application/set-registration-initiation", + sema: "application/vnd.sema", + semd: "application/vnd.semd", + semf: "application/vnd.semf", + see: "application/vnd.seemail", + snf: "application/x-font-snf", + spq: "application/scvp-vp-request", + spp: "application/scvp-vp-response", + scq: "application/scvp-cv-request", + scs: "application/scvp-cv-response", + sdp: "application/sdp", + etx: "text/x-setext", + movie: "video/x-sgi-movie", + ifm: "application/vnd.shana.informed.formdata", + itp: "application/vnd.shana.informed.formtemplate", + iif: "application/vnd.shana.informed.interchange", + ipk: "application/vnd.shana.informed.package", + tfi: "application/thraud+xml", + shar: "application/x-shar", + rgb: "image/x-rgb", + slt: "application/vnd.epson.salt", + aso: "application/vnd.accpac.simply.aso", + imp: "application/vnd.accpac.simply.imp", + twd: "application/vnd.simtech-mindmapper", + csp: "application/vnd.commonspace", + saf: "application/vnd.yamaha.smaf-audio", + mmf: "application/vnd.smaf", + spf: "application/vnd.yamaha.smaf-phrase", + teacher: "application/vnd.smart.teacher", + svd: "application/vnd.svd", + rq: "application/sparql-query", + srx: "application/sparql-results+xml", + gram: "application/srgs", + grxml: "application/srgs+xml", + ssml: "application/ssml+xml", + skp: "application/vnd.koan", + sgml: "text/sgml", + sdc: "application/vnd.stardivision.calc", + sda: "application/vnd.stardivision.draw", + sdd: "application/vnd.stardivision.impress", + smf: "application/vnd.stardivision.math", + sdw: "application/vnd.stardivision.writer", + sgl: "application/vnd.stardivision.writer-global", + sm: "application/vnd.stepmania.stepchart", + sit: "application/x-stuffit", + sitx: "application/x-stuffitx", + sdkm: "application/vnd.solent.sdkm+xml", + xo: "application/vnd.olpc-sugar", + au: "audio/basic", + wqd: "application/vnd.wqd", + sis: "application/vnd.symbian.install", + smi: "application/smil+xml", + xsm: "application/vnd.syncml+xml", + bdm: "application/vnd.syncml.dm+wbxml", + xdm: "application/vnd.syncml.dm+xml", + sv4cpio: "application/x-sv4cpio", + sv4crc: "application/x-sv4crc", + sbml: "application/sbml+xml", + tsv: "text/tab-separated-values", + tiff: "image/tiff", + tao: "application/vnd.tao.intent-module-archive", + tar: "application/x-tar", + tcl: "application/x-tcl", + tex: "application/x-tex", + tfm: "application/x-tex-tfm", + tei: "application/tei+xml", + txt: "text/plain", + dxp: "application/vnd.spotfire.dxp", + sfs: "application/vnd.spotfire.sfs", + tsd: "application/timestamped-data", + tpt: "application/vnd.trid.tpt", + mxs: "application/vnd.triscape.mxs", + t: "text/troff", + tra: "application/vnd.trueapp", + ttf: "application/x-font-ttf", + ttl: "text/turtle", + umj: "application/vnd.umajin", + uoml: "application/vnd.uoml+xml", + unityweb: "application/vnd.unity", + ufd: "application/vnd.ufdl", + uri: "text/uri-list", + utz: "application/vnd.uiq.theme", + ustar: "application/x-ustar", + uu: "text/x-uuencode", + vcs: "text/x-vcalendar", + vcf: "text/x-vcard", + vcd: "application/x-cdlink", + vsf: "application/vnd.vsf", + wrl: "model/vrml", + vcx: "application/vnd.vcx", + mts: "model/vnd.mts", + vtu: "model/vnd.vtu", + vis: "application/vnd.visionary", + viv: "video/vnd.vivo", + ccxml: "application/ccxml+xml,", + vxml: "application/voicexml+xml", + src: "application/x-wais-source", + wbxml: "application/vnd.wap.wbxml", + wbmp: "image/vnd.wap.wbmp", + wav: "audio/x-wav", + davmount: "application/davmount+xml", + woff: "application/x-font-woff", + wspolicy: "application/wspolicy+xml", + webp: "image/webp", + wtb: "application/vnd.webturbo", + wgt: "application/widget", + hlp: "application/winhlp", + wml: "text/vnd.wap.wml", + wmls: "text/vnd.wap.wmlscript", + wmlsc: "application/vnd.wap.wmlscriptc", + wpd: "application/vnd.wordperfect", + stf: "application/vnd.wt.stf", + wsdl: "application/wsdl+xml", + xbm: "image/x-xbitmap", + xpm: "image/x-xpixmap", + xwd: "image/x-xwindowdump", + der: "application/x-x509-ca-cert", + fig: "application/x-xfig", + xhtml: "application/xhtml+xml", + xml: "application/xml", + xdf: "application/xcap-diff+xml", + xenc: "application/xenc+xml", + xer: "application/patch-ops-error+xml", + rl: "application/resource-lists+xml", + rs: "application/rls-services+xml", + rld: "application/resource-lists-diff+xml", + xslt: "application/xslt+xml", + xop: "application/xop+xml", + xpi: "application/x-xpinstall", + xspf: "application/xspf+xml", + xul: "application/vnd.mozilla.xul+xml", + xyz: "chemical/x-xyz", + yaml: "text/yaml", + yang: "application/yang", + yin: "application/yin+xml", + zir: "application/vnd.zul", + zip: "application/zip", + zmm: "application/vnd.handheld-entertainment+xml", + zaz: "application/vnd.zzazz.deck+xml", +}; + +// TODO: write this out to avoid compute +export const MIME_TYPES_TO_EXT = Object.fromEntries( + Object.entries(EXT_TO_MIME_TYPES).map(([k, v]) => [v, k]), +); diff --git a/packages/core/src/mimeType/convertMimeType.ts b/packages/core/src/mimeType/convertMimeType.ts new file mode 100644 index 000000000..57740bb62 --- /dev/null +++ b/packages/core/src/mimeType/convertMimeType.ts @@ -0,0 +1,27 @@ +import { EXT_TO_MIME_TYPES, MIME_TYPES_TO_EXT } from "./all"; + +const MIME_TYPE_REGEX = /^data:(.*?);base64,/; + +export function getMimeType(base64Url: string): string | undefined { + const match = MIME_TYPE_REGEX.exec(base64Url); + if (match && match[1]) { + return match[1]; + } + return; +} + +export function mimeTypeToFileExt(mimeType: string) { + const recognizedMimeType = MIME_TYPES_TO_EXT[mimeType]; + if (recognizedMimeType) { + return recognizedMimeType; + } + return mimeType.split("/")[1]; +} + +export function filenameToMimeType(filename: string) { + const ext = filename.split(".").pop(); + const recognizedExt = ext && EXT_TO_MIME_TYPES[ext]; + if (recognizedExt) { + return recognizedExt; + } +} diff --git a/packages/core/src/mimeType/index.ts b/packages/core/src/mimeType/index.ts new file mode 100644 index 000000000..59f2b97f7 --- /dev/null +++ b/packages/core/src/mimeType/index.ts @@ -0,0 +1,2 @@ +export * from "./convertMimeType"; +export * from "./all"; diff --git a/packages/core/src/module.test.ts b/packages/core/src/module.test.ts new file mode 100644 index 000000000..6347b0f1a --- /dev/null +++ b/packages/core/src/module.test.ts @@ -0,0 +1,193 @@ +import { + resolvePath as resolveAtPath, + getSourceAtPath, + splitModulePath, + splitModuleFilePathAndModulePath, + parentOfSourcePath, + splitJoinedSourcePaths, +} from "./module"; +import { SelectorOfSchema } from "./schema"; +import { array } from "./schema/array"; +import { number } from "./schema/number"; +import { object } from "./schema/object"; +import { string, StringSchema } from "./schema/string"; +import { union } from "./schema/union"; +import { GetSource } from "./selector"; +import { newSelectorProxy } from "./selector/SelectorProxy"; +import { ModulePath, SourcePath } from "./val"; +import { literal } from "./schema/literal"; + +// import { i18n as initI18nSchema } from "./schema/i18n"; +// import { i18n as initI18nSource } from "./source/i18n"; +// const i18n = initI18nSchema(["en_US", "nb_NO"] as const); +// const val = { +// i18n: initI18nSource(["en_US", "nb_NO"] as const), +// }; +describe("module", () => { + test("parse path", () => { + expect(splitModulePath('"foo"."bar".1."zoo"' as ModulePath)).toStrictEqual([ + "foo", + "bar", + "1", + "zoo", + ]); + + expect( + splitModulePath('"foo"."bar".1."z\\"oo"' as ModulePath), + ).toStrictEqual(["foo", "bar", "1", 'z"oo']); + + expect( + splitModulePath('"foo"."b.ar".1."z\\"oo"' as ModulePath), + ).toStrictEqual(["foo", "b.ar", "1", 'z"oo']); + }); + + test("split joined paths", () => { + expect( + splitJoinedSourcePaths( + '/foo.val.ts?p="foo"."bar".1."zoo",/bar.val.ts?p="bar"."zo".1."do"' as ModulePath, + ), + ).toStrictEqual([ + '/foo.val.ts?p="foo"."bar".1."zoo"', + '/bar.val.ts?p="bar"."zo".1."do"', + ]); + }); + + test("getSourceAtPath: basic selector", () => { + const [, modulePath] = splitModuleFilePathAndModulePath( + '/app?p="foo"."bar".1."zoo"' as SourcePath, + ); + expect(modulePath).toStrictEqual('"foo"."bar".1."zoo"'); + const resolvedModuleAtPath = getSourceAtPath( + modulePath, + newSelectorProxy({ + foo: { + bar: [{ zoo: "zoo1" }, { zoo: "zoo2" }], + }, + }), + ); + expect(resolvedModuleAtPath[GetSource]).toStrictEqual("zoo2"); + }); + + test("getSourceAtPath: basic source", () => { + const resolvedModuleAtPath = getSourceAtPath( + '"foo"."bar".1."zoo"' as ModulePath, + { + foo: { + bar: [{ zoo: "zoo1" }, { zoo: "zoo2" }], + }, + }, + ); + expect(resolvedModuleAtPath).toStrictEqual("zoo2"); + }); + + test("getSourceAtPath: with dots and escaped quotes", () => { + const resolvedModuleAtPath = getSourceAtPath( + '"foo"."b.ar".1."z\\"oo"' as ModulePath, + newSelectorProxy({ + foo: { + "b.ar": [{ 'z"oo': "zoo1" }, { 'z"oo': "zoo2" }], + }, + }), + ); + expect(resolvedModuleAtPath[GetSource]).toStrictEqual("zoo2"); + }); + + test("getSchemaAtPath: array & object", () => { + const basicSchema = array( + object({ + foo: array(object({ bar: string() })), + zoo: number(), + }), + ); + const { schema, source } = resolveAtPath( + '0."foo".0."bar"' as ModulePath, + [ + { + foo: [ + { + bar: "bar1", + }, + ], + zoo: 1, + }, + ] as SelectorOfSchema, + basicSchema, + ); + expect(schema).toBeInstanceOf(StringSchema); + expect(source).toStrictEqual("bar1"); + }); + + // test("getSchemaAtPath: i18n", () => { + // const basicSchema = array( + // object({ + // foo: i18n(array(object({ bar: string() }))), + // zoo: number(), + // }) + // ); + // const res = resolveAtPath( + // '0."foo"."nb_NO".0."bar"' as ModulePath, + // [ + // { + // foo: val.i18n({ + // en_US: [ + // { + // bar: "dive", + // }, + // ], + // nb_NO: [ + // { + // bar: "brun", + // }, + // ], + // }), + // zoo: 1, + // }, + // ] as SchemaTypeOf, + // basicSchema.serialize() + // ); + // expect(res.schema).toStrictEqual(string().serialize()); + // expect(res.source).toStrictEqual("brun"); + // }); + + test("getSchemaAtPath: union", () => { + const basicSchema = array( + object({ + foo: union( + "type", + object({ type: literal("test1"), bar: object({ zoo: string() }) }), + object({ type: literal("test2"), bar: object({ zoo: number() }) }), + ), + }), + ); + const res = resolveAtPath( + '0."foo"."bar"."zoo"' as ModulePath, + [ + { + foo: { + type: "test2", + bar: { zoo: 1 }, + }, + }, + ] as SelectorOfSchema, + basicSchema["executeSerialize"](), + ); + expect(res.schema).toStrictEqual(number()["executeSerialize"]()); + expect(res.source).toStrictEqual(1); + }); + + test("parentOfSourcePath", () => { + const base = '/content/test?p="one".2."three"' as SourcePath; + expect(parentOfSourcePath(base)).toStrictEqual('/content/test?p="one".2'); + expect(parentOfSourcePath(parentOfSourcePath(base))).toStrictEqual( + '/content/test?p="one"', + ); + expect( + parentOfSourcePath(parentOfSourcePath(parentOfSourcePath(base))), + ).toStrictEqual("/content/test"); + expect( + parentOfSourcePath( + parentOfSourcePath(parentOfSourcePath(parentOfSourcePath(base))), + ), + ).toStrictEqual("/content/test"); + }); +}); diff --git a/packages/core/src/module.ts b/packages/core/src/module.ts new file mode 100644 index 000000000..9cae09e7a --- /dev/null +++ b/packages/core/src/module.ts @@ -0,0 +1,836 @@ +import { Schema, SelectorOfSchema, SerializedSchema } from "./schema"; +import { ObjectSchema, SerializedObjectSchema } from "./schema/object"; +import { + GenericSelector, + SelectorOf, + SelectorSource, + GetSource, + GetSchema, + Path, +} from "./selector"; +import { Source } from "./source"; +import { ModuleFilePath, ModulePath, SourcePath } from "./val"; +import { ArraySchema, SerializedArraySchema } from "./schema/array"; +import { UnionSchema, SerializedUnionSchema } from "./schema/union"; +import { Json } from "./Json"; +import { RichTextSchema, SerializedRichTextSchema } from "./schema/richtext"; +import { + ImageMetadata, + ImageSchema, + SerializedImageSchema, +} from "./schema/image"; +import { FILE_REF_PROP, FileSource } from "./source/file"; +import { AllRichTextOptions, RichTextSource } from "./source/richtext"; +import { RecordSchema, SerializedRecordSchema } from "./schema/record"; +import { RawString } from "./schema/string"; +import { ImageSelector } from "./selector/image"; +import { ImageSource } from "./source/image"; +import { FileMetadata, FileSchema, ModuleFilePathSep } from "."; + +const brand = Symbol("ValModule"); +export type ValModule = SelectorOf & + ValModuleBrand; + +export type ValModuleBrand = { + [brand]: "ValModule"; +}; + +export type InferValModuleType> = + T extends GenericSelector ? S : never; + +export type ReplaceRawStringWithString = + SelectorSource extends T + ? T + : T extends RawString + ? string + : T extends ImageSelector + ? ImageSource + : T extends { [key in string]: SelectorSource } + ? { + [key in keyof T]: ReplaceRawStringWithString; + } + : T extends SelectorSource[] + ? ReplaceRawStringWithString[] + : T; + +export function define>( + id: string, // TODO: `/${string}` + schema: T, + source: ReplaceRawStringWithString>, +): ValModule> { + return { + [GetSource]: source, + [GetSchema]: schema, + [Path]: id as SourcePath, + } as unknown as ValModule>; +} + +export function enrichFileImageRemoteSourceWithMetadata< + T extends SelectorSource, +>(source: T, schema: Schema): T { + const addedModules = new Set(); + let filesLookup: Record = {}; + function traverseSchema(schema: Schema) { + if (schema instanceof ImageSchema || schema instanceof FileSchema) { + const moduleMetadata = + schema instanceof ImageSchema + ? schema["moduleMetadata"] + : schema["moduleMetadata"]; + for (const [modulePath, valModule] of Object.entries( + moduleMetadata || {}, + )) { + if (addedModules.has(modulePath)) { + continue; + } + addedModules.add(modulePath); + filesLookup = { + ...filesLookup, + ...valModule, + }; + } + } else if (schema instanceof ObjectSchema) { + for (const value of Object.values(schema["items"])) { + if (value instanceof Schema) { + traverseSchema(value); + } + } + } else if (schema instanceof ArraySchema) { + traverseSchema(schema["item"]); + } else if (schema instanceof RecordSchema) { + traverseSchema(schema["item"]); + } else if (schema instanceof UnionSchema) { + for (const value of schema["items"]) { + if (value instanceof ObjectSchema) { + traverseSchema(value); + } + } + } else if (schema instanceof RichTextSchema) { + if (schema["options"]?.inline?.img instanceof ImageSchema) { + traverseSchema(schema["options"].inline.img); + } + } + } + function injectMetadataIntoSource(source: SelectorSource): SelectorSource { + if (!source) { + return source; + } + if ( + typeof source === "object" && + FILE_REF_PROP in source && + typeof source[FILE_REF_PROP] === "string" + ) { + const fileRef = source[FILE_REF_PROP]; + const metadata = filesLookup[fileRef]; + (source as { [FILE_REF_PROP]: string; metadata?: unknown }).metadata = + metadata; + } else if (typeof source === "object") { + if (Array.isArray(source)) { + for (const item of source) { + injectMetadataIntoSource(item); + } + } else { + for (const value of Object.values(source)) { + injectMetadataIntoSource(value); + } + } + } + } + traverseSchema(schema); + injectMetadataIntoSource(source); + return source; +} + +export function getSource(valModule: ValModule): T { + return valModule[GetSource] as T; +} + +export function splitModuleFilePathAndModulePath( + path: SourcePath | ModuleFilePath, +): [moduleId: ModuleFilePath, path: ModulePath] { + const pathOfSep = path.indexOf(ModuleFilePathSep); + if (pathOfSep === -1) { + return [path as unknown as ModuleFilePath, "" as ModulePath]; + } + return [ + path.slice(0, pathOfSep) as ModuleFilePath, + path.slice(pathOfSep + ModuleFilePathSep.length) as ModulePath, + ]; +} + +export function joinModuleFilePathAndModulePath( + moduleFilePath: ModuleFilePath, + modulePath: ModulePath, +): SourcePath { + if (modulePath === "") { + return moduleFilePath as unknown as SourcePath; + } + return `${moduleFilePath}${ModuleFilePathSep}${modulePath}` as SourcePath; +} + +export function getSourceAtPath( + modulePath: ModulePath, + valModule: ValModule | Source, +) { + const parts = splitModulePath(modulePath); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let current: any = valModule; + for (const part of parts) { + if (typeof current !== "object") { + throw Error(`Invalid path: ${part} is not an object`); + } + current = current[part]; + } + return current; +} + +function isObjectSchema( + schema: Schema | SerializedSchema, +): schema is + | ObjectSchema< + { [key: string]: Schema }, + { [key: string]: SelectorSource } + > + | SerializedObjectSchema { + return ( + schema instanceof ObjectSchema || + (typeof schema === "object" && "type" in schema && schema.type === "object") + ); +} + +function isRecordSchema( + schema: Schema | SerializedSchema, +): schema is + | RecordSchema< + Schema, + Schema, + Record>> + > + | SerializedRecordSchema { + return ( + schema instanceof RecordSchema || + (typeof schema === "object" && "type" in schema && schema.type === "record") + ); +} + +function isArraySchema( + schema: Schema | SerializedSchema, +): schema is + | ArraySchema, SelectorSource[]> + | SerializedArraySchema { + return ( + schema instanceof ArraySchema || + (typeof schema === "object" && "type" in schema && schema.type === "array") + ); +} + +// function isI18nSchema( +// schema: Schema | SerializedSchema +// ): schema is I18nSchema | SerializedI18nSchema { +// return ( +// schema instanceof I18nSchema || +// (typeof schema === "object" && "type" in schema && schema.type === "i18n") +// ); +// } + +function isUnionSchema( + schema: Schema | SerializedSchema, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): schema is UnionSchema | SerializedUnionSchema { + return ( + schema instanceof UnionSchema || + (typeof schema === "object" && "type" in schema && schema.type === "union") + ); +} + +function isRichTextSchema( + schema: Schema | SerializedSchema, +): schema is + | RichTextSchema> + | SerializedRichTextSchema { + return ( + schema instanceof RichTextSchema || + (typeof schema === "object" && + "type" in schema && + schema.type === "richtext") + ); +} + +function isImageSchema( + schema: Schema | SerializedSchema, +): schema is + | ImageSchema | null> + | SerializedImageSchema { + return ( + schema instanceof ImageSchema || + (typeof schema === "object" && "type" in schema && schema.type === "image") + ); +} + +// function isOneOfSchema( +// schema: Schema | SerializedSchema +// ): schema is OneOfSchema> | SerializedOneOfSchema { +// return ( +// schema instanceof OneOfSchema || +// (typeof schema === "object" && "type" in schema && schema.type === "oneOf") +// ); +// } + +export function resolvePath< + Src extends ValModule | Source, + Sch extends Schema | SerializedSchema, +>( + path: ModulePath, + valModule: Src, + schema: Sch, +): { + path: SourcePath; + schema: Sch; + source: Src; +} { + const parts = splitModulePath(path); + const origParts = [...parts]; + let resolvedSchema: Schema | SerializedSchema = schema; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let resolvedSource: any /* TODO: any */ = valModule; + while (parts.length > 0) { + const part = parts.shift(); + if (part === undefined) { + throw Error("Unexpected undefined part"); + } + if (isArraySchema(resolvedSchema)) { + if (Number.isNaN(Number(part))) { + throw Error( + `Invalid path: array schema ${JSON.stringify( + resolvedSchema, + )} must have a number as path, but got ${part}. Path: ${path}`, + ); + } + if ( + typeof resolvedSource !== "object" && + !Array.isArray(resolvedSource) + ) { + throw Error( + `Schema type error: expected source to be type of array, but got ${typeof resolvedSource}`, + ); + } + resolvedSource = resolvedSource[part]; + resolvedSchema = + resolvedSchema instanceof ArraySchema + ? resolvedSchema?.["item"] + : resolvedSchema.item; + } else if (isRecordSchema(resolvedSchema)) { + if (typeof part !== "string") { + throw Error( + `Invalid path: record schema ${resolvedSchema} must have path: ${part} as string`, + ); + } + if ( + typeof resolvedSource !== "object" && + !Array.isArray(resolvedSource) + ) { + throw Error( + `Schema type error: expected source to be type of record, but got ${typeof resolvedSource}`, + ); + } + if (!resolvedSource[part]) { + throw Error( + `Invalid path: record source did not have key ${part} from path: ${path}`, + ); + } + resolvedSource = resolvedSource[part]; + resolvedSchema = + resolvedSchema instanceof RecordSchema + ? resolvedSchema?.["item"] + : resolvedSchema.item; + } else if (isObjectSchema(resolvedSchema)) { + if (typeof resolvedSource !== "object") { + throw Error( + `Schema type error: expected source to be type of object, but got ${typeof resolvedSource}`, + ); + } + + if (resolvedSource !== null && resolvedSource[part] === undefined) { + throw Error( + `Invalid path: object source did not have key ${part} from path: ${path}`, + ); + } + resolvedSource = + resolvedSource === null ? resolvedSource : resolvedSource[part]; + resolvedSchema = + resolvedSchema instanceof ObjectSchema + ? resolvedSchema["items"][part] + : resolvedSchema.items[part]; + // } else if (isI18nSchema(resolvedSchema)) { + // if (!resolvedSchema.locales.includes(part)) { + // throw Error( + // `Invalid path: i18n schema ${resolvedSchema} supports locales ${resolvedSchema.locales.join( + // ", " + // )}, but found: ${part}` + // ); + // } + // if (!Object.keys(resolvedSource).includes(part)) { + // throw Error( + // `Schema type error: expected source to be type of i18n with locale ${part}, but got ${JSON.stringify( + // Object.keys(resolvedSource) + // )}` + // ); + // } + // resolvedSource = resolvedSource[part]; + // resolvedSchema = resolvedSchema.item; + } else if (isImageSchema(resolvedSchema)) { + return { + path: origParts + .slice(0, origParts.length - parts.length - 1) + .map((p) => JSON.stringify(p)) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + schema: resolvedSchema as Sch, + source: resolvedSource, + }; + } else if (isUnionSchema(resolvedSchema)) { + const key = resolvedSchema.key; + if (typeof key !== "string") { + return { + path: origParts + .map((p) => { + if (!Number.isNaN(Number(p))) { + return p; + } else { + return JSON.stringify(p); + } + }) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + schema: resolvedSchema as Sch, + source: resolvedSource as Src, + }; + } + const keyValue = resolvedSource[key]; + if (!keyValue) { + throw Error( + `Invalid path: union source ${resolvedSchema} did not have required key ${key} in path: ${path}`, + ); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const schemaOfUnionKey: any = (resolvedSchema.items as any).find( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (child: any) => child?.items?.[key]?.value === keyValue, + ); + if (!schemaOfUnionKey) { + throw Error( + `Invalid path: union schema ${resolvedSchema} did not have a child object with ${key} of value ${keyValue} in path: ${path}`, + ); + } + resolvedSchema = schemaOfUnionKey.items[part]; + resolvedSource = resolvedSource[part]; + } else if (isRichTextSchema(resolvedSchema)) { + if ( + "src" in resolvedSource && + "tag" in resolvedSource && + resolvedSource.tag === "img" && + parts.length === 0 + ) { + resolvedSchema = + resolvedSchema instanceof RichTextSchema + ? resolvedSchema["options"]?.inline?.img && + typeof resolvedSchema["options"]?.inline?.img !== "boolean" + ? resolvedSchema["options"].inline.img + : resolvedSchema + : resolvedSchema; + } + if ( + "href" in resolvedSource && + "tag" in resolvedSource && + resolvedSource.tag === "a" && + parts.length === 0 + ) { + resolvedSchema = + resolvedSchema instanceof RichTextSchema + ? resolvedSchema["options"]?.inline?.a && + typeof resolvedSchema["options"]?.inline?.a !== "boolean" + ? resolvedSchema["options"].inline.a + : resolvedSchema + : resolvedSchema; + } + resolvedSource = resolvedSource[part]; + } else { + throw Error( + `Invalid path: ${part} resolved to an unexpected schema ${JSON.stringify( + resolvedSchema, + )}`, + ); + } + } + if (parts.length > 0) { + throw Error(`Invalid path: ${parts.join(".")} is not a valid path`); + } + return { + path: origParts + .map((p) => { + if (!Number.isNaN(Number(p))) { + return p; + } else { + return JSON.stringify(p); + } + }) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + schema: resolvedSchema as Sch, + source: resolvedSource as Src, + }; +} + +// TODO: replace all usages of resolvePath with safeResolvePath +export function safeResolvePath< + Src extends ValModule | Source, + Sch extends Schema | SerializedSchema, +>( + path: ModulePath, + valModule: Src, + schema: Sch, +): + | { + status: "ok"; + path: SourcePath; + schema: Sch; + source: Src; + } + | { + status: "source-undefined"; + path: SourcePath; + } + | { + status: "error"; + message: string; + } { + const parts = splitModulePath(path); + const origParts = [...parts]; + let resolvedSchema: Schema | SerializedSchema = schema; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let resolvedSource: any /* TODO: any */ = valModule; + while (parts.length > 0) { + const part = parts.shift(); + if (part === undefined) { + return { + status: "error", + message: "Unexpected undefined part", + }; + } + if (isArraySchema(resolvedSchema)) { + if (Number.isNaN(Number(part))) { + return { + status: "error", + message: `Invalid path: array schema ${JSON.stringify( + resolvedSchema, + )} must have a number as path, but got ${part}. Path: ${path}`, + }; + } + if (resolvedSource === undefined) { + return { + status: "source-undefined", + path: origParts + .slice(0, origParts.length - parts.length - 1) + .map((p) => JSON.stringify(p)) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + }; + } + if ( + typeof resolvedSource !== "object" && + !Array.isArray(resolvedSource) + ) { + return { + status: "error", + message: `Schema type error: expected source to be type of array, but got ${typeof resolvedSource}`, + }; + } + if (resolvedSource[part] === undefined) { + return { + status: "source-undefined", + path: origParts + .slice(0, origParts.length - parts.length - 1) + .map((p) => JSON.stringify(p)) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + }; + } + resolvedSource = resolvedSource[part]; + resolvedSchema = + resolvedSchema instanceof ArraySchema + ? resolvedSchema?.["item"] + : resolvedSchema.item; + } else if (isRecordSchema(resolvedSchema)) { + if (typeof part !== "string") { + return { + status: "error", + message: `Invalid path: record schema ${resolvedSchema} must have path: ${part} as string`, + }; + } + if (resolvedSource === undefined) { + return { + status: "source-undefined", + path: origParts + .slice(0, origParts.length - parts.length - 1) + .map((p) => JSON.stringify(p)) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + }; + } + if ( + typeof resolvedSource !== "object" && + !Array.isArray(resolvedSource) + ) { + return { + status: "error", + message: `Schema type error: expected source to be type of record, but got ${typeof resolvedSource}`, + }; + } + if (resolvedSource[part] === undefined) { + return { + status: "source-undefined", + path: origParts + .slice(0, origParts.length - parts.length - 1) + .map((p) => JSON.stringify(p)) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + }; + } + resolvedSource = resolvedSource[part]; + resolvedSchema = + resolvedSchema instanceof RecordSchema + ? resolvedSchema?.["item"] + : resolvedSchema.item; + } else if (isObjectSchema(resolvedSchema)) { + if (resolvedSource === undefined) { + return { + status: "source-undefined", + path: origParts + .slice(0, origParts.length - parts.length - 1) + .map((p) => JSON.stringify(p)) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + }; + } + if (typeof resolvedSource !== "object") { + return { + status: "error", + message: `Schema type error: expected source to be type of object, but got ${typeof resolvedSource}`, + }; + } + if (resolvedSource !== null && resolvedSource[part] === undefined) { + return { + status: "error", + message: `Invalid path: object source did not have key ${part} from path: ${path}`, + }; + } + resolvedSource = + resolvedSource === null ? resolvedSource : resolvedSource[part]; + resolvedSchema = + resolvedSchema instanceof ObjectSchema + ? resolvedSchema["items"][part] + : resolvedSchema.items[part]; + // } else if (isI18nSchema(resolvedSchema)) { + // if (!resolvedSchema.locales.includes(part)) { + // throw Error( + // `Invalid path: i18n schema ${resolvedSchema} supports locales ${resolvedSchema.locales.join( + // ", " + // )}, but found: ${part}` + // ); + // } + // if (!Object.keys(resolvedSource).includes(part)) { + // throw Error( + // `Schema type error: expected source to be type of i18n with locale ${part}, but got ${JSON.stringify( + // Object.keys(resolvedSource) + // )}` + // ); + // } + // resolvedSource = resolvedSource[part]; + // resolvedSchema = resolvedSchema.item; + } else if (isImageSchema(resolvedSchema)) { + return { + status: "ok", + path: origParts + .slice(0, origParts.length - parts.length - 1) + .map((p) => JSON.stringify(p)) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + schema: resolvedSchema as Sch, + source: resolvedSource, + }; + } else if (isUnionSchema(resolvedSchema)) { + const key = resolvedSchema.key; + if (typeof key !== "string") { + return { + status: "ok", + path: origParts + .map((p) => { + if (!Number.isNaN(Number(p))) { + return p; + } else { + return JSON.stringify(p); + } + }) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + schema: resolvedSchema as Sch, + source: resolvedSource as Src, + }; + } + const keyValue = resolvedSource[key]; + if (!keyValue) { + return { + status: "error", + message: `Invalid path: union source ${resolvedSchema} did not have required key ${key} in path: ${path}`, + }; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const schemaOfUnionKey: any = (resolvedSchema.items as any).find( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (child: any) => child?.items?.[key]?.value === keyValue, + ); + if (!schemaOfUnionKey) { + return { + status: "error", + message: `Invalid path: union schema ${resolvedSchema} did not have a child object with ${key} of value ${keyValue} in path: ${path}`, + }; + } + resolvedSchema = schemaOfUnionKey.items[part]; + resolvedSource = resolvedSource[part]; + } else if (isRichTextSchema(resolvedSchema)) { + if ( + "src" in resolvedSource && + "tag" in resolvedSource && + resolvedSource.tag === "img" && + parts.length === 0 + ) { + resolvedSchema = + resolvedSchema instanceof RichTextSchema + ? resolvedSchema["options"]?.inline?.img && + typeof resolvedSchema["options"]?.inline?.img !== "boolean" + ? resolvedSchema["options"].inline.img + : resolvedSchema + : resolvedSchema; + } + if ( + "href" in resolvedSource && + "tag" in resolvedSource && + resolvedSource.tag === "a" && + parts.length === 0 + ) { + resolvedSchema = + resolvedSchema instanceof RichTextSchema + ? resolvedSchema["options"]?.inline?.a && + typeof resolvedSchema["options"]?.inline?.a !== "boolean" + ? resolvedSchema["options"].inline.a + : resolvedSchema + : resolvedSchema; + } + resolvedSource = resolvedSource[part]; + } else { + return { + status: "error", + message: `Invalid path: ${part} resolved to an unexpected schema ${JSON.stringify( + resolvedSchema, + )}`, + }; + } + } + if (parts.length > 0) { + return { + status: "error", + message: `Invalid path: ${parts.join(".")} is not a valid path`, + }; + } + return { + status: "ok", + path: origParts + .map((p) => { + if (!Number.isNaN(Number(p))) { + return p; + } else { + return JSON.stringify(p); + } + }) + .join(".") as SourcePath, // TODO: create a function generate path from parts (not sure if this always works) + schema: resolvedSchema as Sch, + source: resolvedSource as Src, + }; +} + +export function splitModuleFilePath(input: ModuleFilePath) { + const parts = input.split("/").slice(1); + return parts; +} + +export function splitModulePath(input: ModulePath) { + const result = []; + let i = 0; + + while (i < input.length) { + let part = ""; + + if (input[i] === '"') { + // Parse a quoted string + i++; + while (i < input.length && input[i] !== '"') { + if (input[i] === "\\" && input[i + 1] === '"') { + // Handle escaped double quotes + part += '"'; + i++; + } else { + part += input[i]; + } + i++; + } + if (input[i] !== '"') { + throw new Error( + `Invalid input (${JSON.stringify( + input, + )}): Missing closing double quote: ${ + input[i] ?? "at end of string" + } (char: ${i}; length: ${input.length})`, + ); + } + } else { + // Parse a regular string + while (i < input.length && input[i] !== ".") { + part += input[i]; + i++; + } + } + + if (part !== "") { + result.push(part); + } + + i++; + } + + return result; +} + +export function splitJoinedSourcePaths(input: string) { + // TODO: This is a very simple implementation that does not handle escaped commas + return input.split(",") as SourcePath[]; +} + +export function parentOfSourcePath(sourcePath: SourcePath): SourcePath { + const [moduleFilePath, modulePath] = + splitModuleFilePathAndModulePath(sourcePath); + const modulePathParts = splitModulePath(modulePath).slice(0, -1); + if (modulePathParts.length > 0) { + return joinModuleFilePathAndModulePath( + moduleFilePath, + patchPathToModulePath(modulePathParts), + ); + } + return moduleFilePath as unknown as SourcePath; +} + +export function patchPathToModulePath(patchPath: string[]): ModulePath { + return patchPath + .map((segment) => { + // TODO: I am worried that something is lost here: what if the segment is a string that happens to be a parsable as a number? We could make those keys illegal? + if (Number.isInteger(Number(segment))) { + return segment; + } + return JSON.stringify(segment); + }) + .join(".") as ModulePath; +} + +export type SerializedModule = { + source: Json; + schema: SerializedSchema; + path: SourcePath; +}; diff --git a/packages/core/src/modules.ts b/packages/core/src/modules.ts new file mode 100644 index 000000000..4cb1047c7 --- /dev/null +++ b/packages/core/src/modules.ts @@ -0,0 +1,40 @@ +import { ValConfig } from "./initVal"; +import { ValModule } from "./module"; +import { SelectorSource } from "./selector"; + +export type ValModules = { + config: ValConfig; + modules: { + /** + * A module definition defined as a function that returns a promise that resolves to the module. + * + * @example + * { def: () => import('./module.val') } + */ + def: () => Promise<{ + default: ValModule; + }>; + }[]; +}; + +/** + * Define the set of modules that can be edited using the Val UI. + * + * @example + * import { modules } from "@valbuild/next"; + * import { config } from "./val.config"; + * + * export default modules(config, [ + * { def: () => import("./app/page.val.ts") }, + * { def: () => import("./app/another/page.val.ts") }, + * ]); + */ +export function modules( + config: ValConfig, + modules: ValModules["modules"], +): ValModules { + return { + config, + modules, + }; +} diff --git a/packages/core/src/patch/deref.test.ts b/packages/core/src/patch/deref.test.ts new file mode 100644 index 000000000..fa9d144e8 --- /dev/null +++ b/packages/core/src/patch/deref.test.ts @@ -0,0 +1,273 @@ +import { result } from "../fp"; +import { initFile } from "../source/file"; +import { derefPatch, DerefPatchResult } from "./deref"; +import { JSONOps } from "./json"; +import { PatchError } from "./ops"; + +const file = initFile(); + +const ops = new JSONOps(); +describe("deref", () => { + test("replace image", () => { + const actual = derefPatch( + [ + { + op: "replace", + path: ["foo", "baz"], + value: 2, + }, + { + op: "replace", + path: ["foo", "$image1"], + value: "aWtrZSB25nJzdD8=", + }, + { + op: "replace", + path: ["foo", "baz"], + value: 3, + }, + { + op: "replace", + path: ["foo", "$image2"], + value: "ZnVua2VyIGRldHRlPw==", + }, + ], + { + foo: { + baz: 1, + image1: file("/public/val/File\\ Name.jpg", {}), + image2: file("/public/val/Some\\ Other\\ image.jpg", {}), + }, + }, + ops, + ); + const expected: DerefPatchResult = { + dereferencedPatch: [ + { + op: "replace", + path: ["foo", "baz"], + value: 2, + }, + { + op: "replace", + path: ["foo", "baz"], + value: 3, + }, + ], + fileUpdates: { + "/public/val/File\\ Name.jpg": "aWtrZSB25nJzdD8=", + "/public/val/Some\\ Other\\ image.jpg": "ZnVua2VyIGRldHRlPw==", + }, + }; + expect(actual).toStrictEqual(result.ok(expected)); + }); + + test("replace image sub-reference fails", () => { + const actual = derefPatch( + [ + { + op: "replace", + path: ["foo", "baz"], + value: 2, + }, + { + op: "replace", + path: ["foo", "$image1", "bar"], + value: "aWtrZSB25nJzdD8=", + }, + ], + { + foo: { + baz: 1, + image1: file("/public/val/File\\ Name.jpg", {}), + }, + }, + ops, + ); + expect(actual).toStrictEqual(result.err(expect.any(PatchError))); + }); + + test("replace image with 2 replaces on same image", () => { + const actual = derefPatch( + [ + { + op: "replace", + path: ["foo", "baz"], + value: 2, + }, + { + op: "replace", + path: ["foo", "$image1"], + value: "aWtrZSB25nJzdD8=", + }, + { + op: "replace", + path: ["foo", "baz"], + value: 3, + }, + { + op: "replace", + path: ["foo", "$image2"], + value: "ZnVua2VyIGRldHRlPw==", + }, + ], + { + foo: { + baz: 1, + image1: file("/public/val/File\\ Name.jpg", {}), + image2: file("/public/val/File\\ Name.jpg", {}), + }, + }, + ops, + ); + const expected: DerefPatchResult = { + dereferencedPatch: [ + { + op: "replace", + path: ["foo", "baz"], + value: 2, + }, + { + op: "replace", + path: ["foo", "baz"], + value: 3, + }, + ], + fileUpdates: { + "/public/val/File\\ Name.jpg": "ZnVua2VyIGRldHRlPw==", + }, + }; + expect(actual).toStrictEqual(result.ok(expected)); + }); + + // test("replace remote", () => { + // const actual = derefPatch( + // [ + // { + // op: "replace", + // path: ["foo", "baz"], + // value: 2, + // }, + // { + // op: "replace", + // path: ["foo", "$re1", "test1"], + // value: "next test1 update", + // }, + // { + // op: "replace", + // path: ["foo", "baz"], + // value: 3, + // }, + // { + // op: "replace", + // path: ["foo", "$re2", "test2", "sub-segment"], + // value: "next test2 update", + // }, + // ], + // { + // foo: { + // baz: 1, + // re1: remote("41f86df3"), + // re2: remote("96536d44"), + // }, + // }, + // ops + // ); + // const expected: DerefPatchResult = { + // dereferencedPatch: [ + // { + // op: "replace", + // path: ["foo", "baz"], + // value: 2, + // }, + // { + // op: "replace", + // path: ["foo", "baz"], + // value: 3, + // }, + // ], + // fileUpdates: {}, + // remotePatches: { + // "41f86df3": [ + // { + // op: "replace", + // path: ["test1"], + // value: "next test1 update", + // }, + // ], + // "96536d44": [ + // { + // op: "replace", + // path: ["test2", "sub-segment"], + // value: "next test2 update", + // }, + // ], + // }, + // }; + // expect(actual).toStrictEqual(result.ok(expected)); + // }); + // test("replace remote with 2 replaces on same ref", () => { + // const actual = derefPatch( + // [ + // { + // op: "replace", + // path: ["foo", "baz"], + // value: 2, + // }, + // { + // op: "replace", + // path: ["foo", "$re1", "test1"], + // value: "next test1 update", + // }, + // { + // op: "replace", + // path: ["foo", "baz"], + // value: 3, + // }, + // { + // op: "replace", + // path: ["foo", "$re1", "test1"], + // value: "next test2 update", + // }, + // ], + // { + // foo: { + // baz: 1, + // re1: remote("41f86df3"), + // re2: remote("96536d44"), + // }, + // }, + // ops + // ); + // const expected: DerefPatchResult = { + // dereferencedPatch: [ + // { + // op: "replace", + // path: ["foo", "baz"], + // value: 2, + // }, + // { + // op: "replace", + // path: ["foo", "baz"], + // value: 3, + // }, + // ], + // fileUpdates: {}, + // remotePatches: { + // "41f86df3": [ + // { + // op: "replace", + // path: ["test1"], + // value: "next test1 update", + // }, + // { + // op: "replace", + // path: ["test1"], + // value: "next test2 update", + // }, + // ], + // }, + // }; + // expect(actual).toStrictEqual(result.ok(expected)); + // }); +}); diff --git a/packages/core/src/patch/deref.ts b/packages/core/src/patch/deref.ts new file mode 100644 index 000000000..697be8b79 --- /dev/null +++ b/packages/core/src/patch/deref.ts @@ -0,0 +1,135 @@ +import { FILE_REF_PROP, isFile } from "../source/file"; +import { result } from "../fp"; +import { Ops, PatchError } from "./ops"; +import { Patch } from "./patch"; +import { Operation } from "./operation"; + +function derefPath( + path: string[], +): result.Result<[string[], string[] | null], PatchError> { + const dereffedPath: string[] = []; + let referencedPath: string[] | null = null; + for (const segment of path) { + if (segment.startsWith("$")) { + const dereffedSegment = segment.slice(1); + dereffedPath.push(dereffedSegment); + referencedPath = []; + for (const segment of path.slice(dereffedPath.length)) { + if (segment.startsWith("$")) { + return result.err( + new PatchError( + `Cannot reference within reference: ${segment}. Path: ${path.join( + "/", + )}`, + ), + ); + } + referencedPath.push(segment); + } + break; + } else { + dereffedPath.push(segment); + } + } + return result.ok([dereffedPath, referencedPath]); +} + +export type DerefPatchResult = { + dereferencedPatch: Patch; + fileUpdates: { [path: string]: string }; +}; + +export function derefPatch( + patch: Operation[], + document: D, + ops: Ops, +): result.Result { + const fileUpdates: { + [file: string]: string; + } = {}; + const dereferencedPatch: Patch = []; + for (const op of patch) { + if (op.op === "replace") { + const maybeDerefRes = derefPath(op.path); + if (result.isErr(maybeDerefRes)) { + return maybeDerefRes; + } + const [dereffedPath, referencedPath] = maybeDerefRes.value; + if (referencedPath) { + const maybeValue = ops.get(document, dereffedPath); + if (result.isOk(maybeValue)) { + const value = maybeValue.value; + if (isFile(value)) { + if (referencedPath.length > 0) { + return result.err( + new PatchError( + `Cannot sub-reference file reference at path: ${dereffedPath.join( + "/", + )}`, + ), + ); + } + if (typeof op.value !== "string") { + return result.err( + new PatchError( + `Expected base64 encoded string value for file reference, got ${JSON.stringify( + op.value, + )}`, + ), + ); + } + fileUpdates[value[FILE_REF_PROP]] = op.value; + // } else if (isRemote(value)) { + // if (!remotePatches[value[REMOTE_REF_PROP]]) { + // remotePatches[value[REMOTE_REF_PROP]] = []; + // } + // remotePatches[value[REMOTE_REF_PROP]].push({ + // op: "replace", + // path: referencedPath, + // value: op.value, + // }); + } else { + return result.err( + new PatchError( + `Unknown reference: ${JSON.stringify( + op, + )} at path: ${dereffedPath.join("/")}`, + ), + ); + } + } else { + return maybeValue; + } + } else { + dereferencedPatch.push(op); + } + } else if (op.op === "file") { + if (!op.filePath.startsWith("/public")) { + return result.err(new PatchError(`Path must start with /public`)); + } + if (typeof op.value !== "string") { + return result.err( + new PatchError( + `File operation must have a value that is typeof string. Found: ${typeof op.value}`, + ), + ); + } + fileUpdates[op.filePath] = op.value; + } else { + const maybeDerefRes = derefPath(op.path); + if (result.isErr(maybeDerefRes)) { + return maybeDerefRes; + } + const [, referencedPath] = maybeDerefRes.value; + if (referencedPath) { + throw new Error(`Unimplemented operation: ${JSON.stringify(op)}`); + } + dereferencedPatch.push(op); + } + } + + return result.ok({ + fileUpdates, + dereferencedPatch, + }); +} diff --git a/packages/core/src/patch/index.ts b/packages/core/src/patch/index.ts new file mode 100644 index 000000000..90abbe797 --- /dev/null +++ b/packages/core/src/patch/index.ts @@ -0,0 +1,27 @@ +export { JSONOps } from "./json"; +export { + type OperationJSON, + type Operation, + type FileOperation, +} from "./operation"; +export { parsePatch, parseJSONPointer, formatJSONPointer } from "./parse"; +export { + type JSONValue, + type Ops, + PatchError, + type ReadonlyJSONValue, +} from "./ops"; +export { + type PatchJSON, + type Patch, + applyPatch, + type PatchBlock, + type ParentRef, +} from "./patch"; +export { + isNotRoot, + deepEqual, + deepClone, + parseAndValidateArrayIndex, + sourceToPatchPath, +} from "./util"; diff --git a/packages/core/src/patch/json.test.ts b/packages/core/src/patch/json.test.ts new file mode 100644 index 000000000..0dd18b185 --- /dev/null +++ b/packages/core/src/patch/json.test.ts @@ -0,0 +1,589 @@ +import { JSONOps } from "./json"; +import * as result from "../fp/result"; +import { PatchError, JSONValue } from "./ops"; +import { pipe } from "../fp/util"; +import { NonEmptyArray } from "../fp/array"; +import { deepClone } from "./util"; + +describe("JSONOps", () => { + test.each<{ + name: string; + input: JSONValue; + path: string[]; + value: JSONValue; + expected: result.Result; + }>([ + { + name: "root of document", + input: { foo: "bar" }, + path: [], + value: null, + expected: result.ok(null), + }, + { + name: "defined property to object", + input: { foo: "bar" }, + path: ["foo"], + value: null, + expected: result.ok({ foo: null }), + }, + { + name: "new property to object", + input: { foo: "bar" }, + path: ["bar"], + value: null, + expected: result.ok({ foo: "bar", bar: null }), + }, + { + name: "property to object that is not an identifier (empty string)", + input: { foo: "bar" }, + path: [""], + value: "foo", + expected: result.ok({ foo: "bar", "": "foo" }), + }, + { + name: "property to object that is not an identifier (strings with whitespace)", + input: { foo: "bar" }, + path: ["foo and "], + value: "foo", + expected: result.ok({ foo: "bar", "foo and ": "foo" }), + }, + { + name: "item to array followed by other items", + input: ["foo", "bar"], + path: ["0"], + value: null, + expected: result.ok([null, "foo", "bar"]), + }, + { + name: "item to array followed by other items", + input: { nested: ["foo", "bar"] }, + path: ["nested", "0"], + value: null, + expected: result.ok({ nested: [null, "foo", "bar"] }), + }, + { + name: "item to end of array", + input: ["foo", "bar"], + path: ["2"], + value: null, + expected: result.ok(["foo", "bar", null]), + }, + { + name: "item to end of array with dash", + input: ["foo", "bar"], + path: ["-"], + value: null, + expected: result.ok(["foo", "bar", null]), + }, + { + name: "item to array with invalid index", + input: ["foo", "bar"], + path: ["baz"], + value: null, + expected: result.err(PatchError), + }, + { + name: "item at index out of bounds of array", + input: ["foo", "bar"], + path: ["3"], + value: null, + expected: result.err(PatchError), + }, + { + name: "defined property to object nested within object", + input: { foo: { bar: "baz" } }, + path: ["foo", "bar"], + value: null, + expected: result.ok({ foo: { bar: null } }), + }, + { + name: "defined property to object nested within array", + input: [{ foo: "bar" }], + path: ["0", "foo"], + value: null, + expected: result.ok([{ foo: null }]), + }, + { + name: "to undefined object/array", + input: { foo: "bar" }, + path: ["bar", "foo"], + value: null, + expected: result.err(PatchError), + }, + { + name: "to non-object/array", + input: 0, + path: ["foo"], + value: null, + expected: result.err(PatchError), + }, + ])("add $name", ({ input, path, value, expected }) => { + const ops = new JSONOps(); + expect( + pipe( + ops.add(deepClone(input), path, value), + result.mapErr(() => PatchError), + ), + ).toEqual(expected); + }); + + test.each<{ + name: string; + input: JSONValue; + path: NonEmptyArray; + expected: result.Result; + }>([ + { + name: "defined property from object", + input: { foo: "bar", bar: "baz" }, + path: ["bar"], + expected: result.ok({ foo: "bar" }), + }, + { + name: "undefined property to object", + input: { foo: "bar" }, + path: ["bar"], + expected: result.err(PatchError), + }, + { + name: "existing item from array", + input: ["foo", "bar"], + path: ["0"], + expected: result.ok(["bar"]), + }, + { + name: "item from end of array with dash", + input: ["foo", "bar"], + path: ["-"], + expected: result.err(PatchError), + }, + { + name: "item at index out of bounds of array", + input: ["foo", "bar"], + path: ["2"], + expected: result.err(PatchError), + }, + { + name: "item from array with invalid index", + input: ["foo", "bar"], + path: ["baz"], + expected: result.err(PatchError), + }, + { + name: "defined property from object nested within object", + input: { foo: { bar: "baz", baz: "bar" } }, + path: ["foo", "baz"], + expected: result.ok({ foo: { bar: "baz" } }), + }, + { + name: "defined property from object nested within array", + input: [{ foo: "bar", baz: "bar" }], + path: ["0", "baz"], + expected: result.ok([{ foo: "bar" }]), + }, + { + name: "from undefined object/array", + input: { foo: "bar" }, + path: ["bar", "foo"], + expected: result.err(PatchError), + }, + { + name: "from non-object/array", + input: 0, + path: ["foo"], + expected: result.err(PatchError), + }, + ])("remove $name", ({ input, path, expected }) => { + const ops = new JSONOps(); + expect( + pipe( + ops.remove(deepClone(input), path), + result.mapErr(() => PatchError), + ), + ).toEqual(expected); + }); + + test.each<{ + name: string; + input: JSONValue; + path: string[]; + value: JSONValue; + expected: result.Result; + }>([ + { + name: "root of document", + input: { foo: "bar" }, + path: [], + value: null, + expected: result.ok(null), + }, + { + name: "defined property of object", + input: { foo: "bar" }, + path: ["foo"], + value: null, + expected: result.ok({ foo: null }), + }, + { + name: "undefined property of object", + input: { foo: "bar" }, + path: ["bar"], + value: null, + expected: result.err(PatchError), + }, + { + name: "item of array", + input: ["foo", "bar"], + path: ["0"], + value: null, + expected: result.ok([null, "bar"]), + }, + { + name: "item of array at invalid index", + input: ["foo", "bar"], + path: ["baz"], + value: null, + expected: result.err(PatchError), + }, + { + name: "item at index out of bounds of array", + input: ["foo", "bar"], + path: ["2"], + value: null, + expected: result.err(PatchError), + }, + { + name: "defined property of object nested within object", + input: { foo: { bar: "baz" } }, + path: ["foo", "bar"], + value: null, + expected: result.ok({ foo: { bar: null } }), + }, + { + name: "defined property of object nested within array", + input: [{ foo: "bar" }], + path: ["0", "foo"], + value: null, + expected: result.ok([{ foo: null }]), + }, + { + name: "from undefined object/array", + input: { foo: "bar" }, + path: ["bar", "foo"], + value: null, + expected: result.err(PatchError), + }, + { + name: "from non-object/array", + input: 0, + path: ["foo"], + value: null, + expected: result.err(PatchError), + }, + ])("replace $name", ({ input, path, value, expected }) => { + const ops = new JSONOps(); + expect( + pipe( + ops.replace(deepClone(input), path, value), + result.mapErr(() => PatchError), + ), + ).toEqual(expected); + }); + + test.each<{ + name: string; + input: JSONValue; + from: NonEmptyArray; + path: string[]; + expected: result.Result; + }>([ + { + name: "value to root of document", + input: { foo: "bar" }, + from: ["foo"], + path: [], + expected: result.ok("bar"), + }, + { + name: "defined property to undefined property of object", + input: { foo: null, baz: 1 }, + from: ["foo"], + path: ["bar"], + expected: result.ok({ baz: 1, bar: null }), + }, + { + name: "defined property to defined property of object", + input: { foo: null, bar: "baz" }, + from: ["foo"], + path: ["bar"], + expected: result.ok({ bar: null }), + }, + { + name: "undefined property of object", + input: { foo: "bar" }, + from: ["bar"], + path: ["baz"], + expected: result.err(PatchError), + }, + { + name: "item within array", + input: ["foo", null], + from: ["1"], + path: ["0"], + expected: result.ok([null, "foo"]), + }, + { + name: "item to end of array with dash", + input: [null, "foo"], + from: ["0"], + path: ["-"], + expected: result.ok(["foo", null]), + }, + { + name: "item from invalid index of array", + input: ["foo", "bar"], + from: ["foo"], + path: ["0"], + expected: result.err(PatchError), + }, + { + name: "item to invalid index of array", + input: ["foo", "bar"], + from: ["1"], + path: ["foo"], + expected: result.err(PatchError), + }, + { + name: "item from index out of bounds of array", + input: ["foo", "bar"], + from: ["2"], + path: ["0"], + expected: result.err(PatchError), + }, + { + name: "item to index out of bounds of array", + input: ["foo", "bar"], + from: ["0"], + path: ["2"], + expected: result.err(PatchError), + }, + ])("move $name", ({ input, from, path, expected }) => { + const ops = new JSONOps(); + expect( + pipe( + ops.move(deepClone(input), from, path), + result.mapErr(() => PatchError), + ), + ).toEqual(expected); + }); + + test.each<{ + name: string; + input: JSONValue; + from: string[]; + path: string[]; + expected: result.Result; + }>([ + { + name: "object into itself", + input: { foo: "bar" }, + from: [], + path: ["foo"], + expected: result.ok({ foo: { foo: "bar" } }), + }, + { + name: "value to root of document", + input: { foo: "bar" }, + from: ["foo"], + path: [], + expected: result.ok("bar"), + }, + { + name: "defined property to undefined property of object", + input: { foo: null, baz: 1 }, + from: ["foo"], + path: ["bar"], + expected: result.ok({ foo: null, baz: 1, bar: null }), + }, + { + name: "defined property to defined property of object", + input: { foo: null, bar: "baz" }, + from: ["foo"], + path: ["bar"], + expected: result.ok({ foo: null, bar: null }), + }, + { + name: "undefined property of object", + input: { foo: "bar" }, + from: ["bar"], + path: ["baz"], + expected: result.err(PatchError), + }, + { + name: "item within array", + input: ["foo", null], + from: ["1"], + path: ["0"], + expected: result.ok([null, "foo", null]), + }, + { + name: "item to end of array with dash", + input: [null, "foo"], + from: ["0"], + path: ["-"], + expected: result.ok([null, "foo", null]), + }, + { + name: "item from invalid index of array", + input: ["foo", "bar"], + from: ["foo"], + path: ["0"], + expected: result.err(PatchError), + }, + { + name: "item to invalid index of array", + input: ["foo", "bar"], + from: ["1"], + path: ["foo"], + expected: result.err(PatchError), + }, + { + name: "item from index out of bounds of array", + input: ["foo", "bar"], + from: ["2"], + path: ["0"], + expected: result.err(PatchError), + }, + { + name: "item to index out of bounds of array", + input: ["foo", "bar"], + from: ["0"], + path: ["3"], + expected: result.err(PatchError), + }, + ])("copy $name", ({ input, from, path, expected }) => { + const ops = new JSONOps(); + expect( + pipe( + ops.copy(deepClone(input), from, path), + result.mapErr(() => PatchError), + ), + ).toEqual(expected); + }); + + test.each<{ + name: string; + input: JSONValue; + path: string[]; + value: JSONValue; + expected: result.Result; + }>([ + { + name: "object 1", + input: { foo: "bar" }, + path: [], + value: { foo: "bar" }, + expected: result.ok(true), + }, + { + name: "object 2", + input: { foo: "bar" }, + path: [], + value: null, + expected: result.ok(false), + }, + { + name: "array 1", + input: ["foo", "bar"], + path: [], + value: ["foo", "bar"], + expected: result.ok(true), + }, + { + name: "array 2", + input: ["foo", "bar"], + path: [], + value: ["bar", "foo"], + expected: result.ok(false), + }, + { + name: "defined property from object", + input: { foo: "bar", bar: "baz" }, + path: ["bar"], + value: "baz", + expected: result.ok(true), + }, + { + name: "undefined property of object", + input: { foo: "bar" }, + path: ["bar"], + value: null, + expected: result.err(PatchError), + }, + { + name: "item from array", + input: ["foo", "bar"], + path: ["0"], + value: "foo", + expected: result.ok(true), + }, + { + name: "item from end of array with dash", + input: ["foo", "bar"], + path: ["-"], + value: null, + expected: result.err(PatchError), + }, + { + name: "item at index out of bounds of array", + input: ["foo", "bar"], + path: ["2"], + value: null, + expected: result.err(PatchError), + }, + { + name: "item from array with invalid index", + input: ["foo", "bar"], + path: ["baz"], + value: null, + expected: result.err(PatchError), + }, + { + name: "complex nested objects", + input: { foo: { bar: "baz", baz: ["bar", { 0: 1 }] } }, + path: [], + value: { foo: { bar: "baz", baz: ["bar", { 0: 1 }] } }, + expected: result.ok(true), + }, + { + name: "at undefined object/array", + input: { foo: "bar" }, + path: ["bar", "foo"], + value: null, + expected: result.err(PatchError), + }, + { + name: "escaped paths", + input: { "foo/bar~/~": "baz" }, + path: ["foo/bar~/~"], + value: "baz", + expected: result.ok(true), + }, + { + name: "at non-object/array", + input: 0, + path: ["foo"], + value: null, + expected: result.err(PatchError), + }, + ])("test $name", ({ input, path, value, expected }) => { + const ops = new JSONOps(); + expect( + pipe( + ops.test(deepClone(input), path, value), + result.mapErr(() => PatchError), + ), + ).toEqual(expected); + }); +}); diff --git a/packages/core/src/patch/json.ts b/packages/core/src/patch/json.ts new file mode 100644 index 000000000..4512bff0d --- /dev/null +++ b/packages/core/src/patch/json.ts @@ -0,0 +1,304 @@ +import { result, array, pipe } from "../fp/"; +import { JSONValue, Ops, PatchError, ReadonlyJSONValue } from "./ops"; +import { + deepClone, + deepEqual, + isNotRoot, + parseAndValidateArrayIndex, +} from "./util"; + +type JSONOpsResult = result.Result; + +function parseAndValidateArrayInsertIndex( + key: string, + nodes: ReadonlyJSONValue[], +): JSONOpsResult { + if (key === "-") { + return result.ok(nodes.length); + } + + return pipe( + parseAndValidateArrayIndex(key), + result.filterOrElse( + (index: number): boolean => index <= nodes.length, + () => new PatchError("Array index out of bounds"), + ), + ); +} + +function parseAndValidateArrayInboundsIndex( + key: string, + nodes: ReadonlyJSONValue[], +): JSONOpsResult { + return pipe( + parseAndValidateArrayIndex(key), + result.filterOrElse( + (index: number): boolean => index < nodes.length, + () => new PatchError("Array index out of bounds"), + ), + ); +} + +function replaceInNode( + node: JSONValue, + key: string, + value: JSONValue, +): JSONOpsResult { + if (Array.isArray(node)) { + return pipe( + parseAndValidateArrayInboundsIndex(key, node), + result.map((index: number) => { + const replaced = node[index]; + node[index] = value; + return replaced; + }), + ); + } else if (typeof node === "object" && node !== null) { + // Prototype pollution protection + if (Object.prototype.hasOwnProperty.call(node, key)) { + const replaced = node[key]; + node[key] = value; + return result.ok(replaced); + } else { + return result.err( + new PatchError("Cannot replace object element which does not exist"), + ); + } + } + + return result.err(new PatchError("Cannot replace in non-object/array")); +} + +function replaceAtPath( + document: JSONValue, + path: string[], + value: JSONValue, +): JSONOpsResult<[document: JSONValue, replaced: JSONValue]> { + if (isNotRoot(path)) { + return pipe( + getPointerFromPath(document, path), + result.flatMap(([node, key]: Pointer) => replaceInNode(node, key, value)), + result.map((replaced: JSONValue) => [document, replaced]), + ); + } else { + return result.ok([value, document]); + } +} + +function getFromNode( + node: JSONValue, + key: string, +): JSONOpsResult { + if (Array.isArray(node)) { + return pipe( + parseAndValidateArrayIndex(key), + result.flatMap((index: number) => { + if (index >= node.length) { + return result.err(new PatchError("Array index out of bounds")); + } else { + return result.ok(node[index]); + } + }), + ); + } else if (typeof node === "object" && node !== null) { + // Prototype pollution protection + if (Object.prototype.hasOwnProperty.call(node, key)) { + return result.ok(node[key]); + } else { + return result.ok(undefined); + } + } + + return result.err(new PatchError("Cannot access non-object/array")); +} + +type Pointer = [node: JSONValue, key: string]; +function getPointerFromPath( + node: JSONValue, + path: array.NonEmptyArray, +): JSONOpsResult { + let targetNode: JSONValue = node; + let key: string = path[0]; + for (let i = 0; i < path.length - 1; ++i, key = path[i]) { + const childNode = getFromNode(targetNode, key); + if (result.isErr(childNode)) { + return childNode; + } + if (childNode.value === undefined) { + return result.err( + new PatchError("Path refers to non-existing object/array"), + ); + } + targetNode = childNode.value; + } + + return result.ok([targetNode, key]); +} + +function getAtPath(node: JSONValue, path: string[]): JSONOpsResult { + return pipe( + path, + result.flatMapReduce( + (node: JSONValue, key: string) => + pipe( + getFromNode(node, key), + result.filterOrElse( + (childNode: JSONValue | undefined): childNode is JSONValue => + childNode !== undefined, + () => new PatchError("Path refers to non-existing object/array"), + ), + ), + node, + ), + ); +} + +function removeFromNode( + node: JSONValue, + key: string, +): JSONOpsResult { + if (Array.isArray(node)) { + return pipe( + parseAndValidateArrayInboundsIndex(key, node), + result.map((index: number) => { + const [removed] = node.splice(index, 1); + return removed; + }), + ); + } else if (typeof node === "object" && node !== null) { + // Prototype pollution protection + if (Object.prototype.hasOwnProperty.call(node, key)) { + const removed = node[key]; + delete node[key]; + return result.ok(removed); + } + } + + return result.err(new PatchError("Cannot remove from non-object/array")); +} + +function removeAtPath( + document: JSONValue, + path: array.NonEmptyArray, +): JSONOpsResult { + return pipe( + getPointerFromPath(document, path), + result.flatMap(([node, key]: Pointer) => removeFromNode(node, key)), + ); +} + +function addToNode( + node: JSONValue, + key: string, + value: JSONValue, +): JSONOpsResult { + if (Array.isArray(node)) { + return pipe( + parseAndValidateArrayInsertIndex(key, node), + result.map((index: number) => { + node.splice(index, 0, value); + return undefined; + }), + ); + } else if (typeof node === "object" && node !== null) { + let replaced: JSONValue | undefined; + // Prototype pollution protection + if (Object.prototype.hasOwnProperty.call(node, key)) { + replaced = node[key]; + } + node[key] = value; + return result.ok(replaced); + } + + return result.err(new PatchError("Cannot add to non-object/array")); +} + +function addAtPath( + document: JSONValue, + path: string[], + value: JSONValue, +): JSONOpsResult<[document: JSONValue, replaced?: JSONValue]> { + if (isNotRoot(path)) { + return pipe( + getPointerFromPath(document, path), + result.flatMap(([node, key]: Pointer) => addToNode(node, key, value)), + result.map((replaced: JSONValue | undefined) => [document, replaced]), + ); + } else { + return result.ok([value, document]); + } +} + +function pickDocument< + T extends readonly [document: ReadonlyJSONValue, ...result: unknown[]], +>([document]: T): T[0] { + return document; +} + +export class JSONOps implements Ops { + get( + document: JSONValue, + path: string[], + ): result.Result { + return getAtPath(document, path); + } + add( + document: JSONValue, + path: string[], + value: JSONValue, + ): result.Result { + return pipe(addAtPath(document, path, value), result.map(pickDocument)); + } + remove( + document: JSONValue, + path: array.NonEmptyArray, + ): result.Result { + return pipe( + removeAtPath(document, path), + result.map(() => document), + ); + } + replace( + document: JSONValue, + path: string[], + value: JSONValue, + ): result.Result { + return pipe(replaceAtPath(document, path, value), result.map(pickDocument)); + } + move( + document: JSONValue, + from: array.NonEmptyArray, + path: string[], + ): result.Result { + return pipe( + removeAtPath(document, from), + result.flatMap((removed: JSONValue) => + addAtPath(document, path, removed), + ), + result.map(pickDocument), + ); + } + copy( + document: JSONValue, + from: string[], + path: string[], + ): result.Result { + return pipe( + getAtPath(document, from), + result.flatMap((value: JSONValue) => + addAtPath(document, path, deepClone(value)), + ), + result.map(pickDocument), + ); + } + test( + document: JSONValue, + path: string[], + value: JSONValue, + ): result.Result { + return pipe( + getAtPath(document, path), + result.map((documentValue: JSONValue) => deepEqual(value, documentValue)), + ); + } +} diff --git a/packages/core/src/patch/operation.ts b/packages/core/src/patch/operation.ts new file mode 100644 index 000000000..7a14281a1 --- /dev/null +++ b/packages/core/src/patch/operation.ts @@ -0,0 +1,91 @@ +import { array } from "../fp"; +import type { JSONValue } from "./ops"; + +type FileOperationBase = { + op: "file"; + metadata?: JSONValue; + /** path of the top-most element where the schema of the element points to */ + path: PathType; + /** unless remote: file path relative to project (starts with /public, e.g. /public/example.png), for remote: the whole remote ref */ + filePath: string; + /** files can be nested within an object (for richtext), in order to find the actual file element this path can be used (we use this to add the patch_id on files) */ + nestedFilePath?: string[]; + value: JSONValue; + /** true if this is a remote file */ + remote: boolean; +}; +type FileOperationJSON = FileOperationBase; +export type FileOperation = FileOperationBase; +/** + * Raw JSON patch operation. + */ +export type OperationJSON = + | { + op: "add"; + path: string; + value: JSONValue; + } + | { + op: "remove"; + path: string; + } + | { + op: "replace"; + path: string; + value: JSONValue; + } + | { + op: "move"; + from: string; + path: string; + } + | { + op: "copy"; + from: string; + path: string; + } + | { + op: "test"; + path: string; + value: JSONValue; + } + | FileOperationJSON; + +/** + * Parsed form of JSON patch operation. + */ +export type Operation = + | { + op: "add"; + path: string[]; + value: JSONValue; + } + | { + op: "remove"; + path: array.NonEmptyArray; + } + | { + op: "replace"; + path: string[]; + value: JSONValue; + } + | { + op: "move"; + /** + * Must be non-root and not a proper prefix of "path". + */ + // TODO: Replace with common prefix field + from: array.NonEmptyArray; + path: string[]; + } + | { + op: "copy"; + from: string[]; + path: string[]; + } + | { + op: "test"; + path: string[]; + value: JSONValue; + } + | FileOperation; diff --git a/packages/core/src/patch/ops.ts b/packages/core/src/patch/ops.ts new file mode 100644 index 000000000..f65c13134 --- /dev/null +++ b/packages/core/src/patch/ops.ts @@ -0,0 +1,83 @@ +import { result, array } from "../fp"; + +export class PatchError { + constructor(public message: string) {} +} + +export type ReadonlyJSONValue = + | string + | number + | boolean + | null + | readonly ReadonlyJSONValue[] + | { + readonly [key: string]: ReadonlyJSONValue; + }; + +export type JSONValue = + | string + | number + | boolean + | null + | JSONValue[] + | { + [key: string]: JSONValue; + }; + +type ToMutableJSONArray = { + [P in keyof T]: ToMutable; +}; +export type ToMutable = JSONValue extends T + ? JSONValue + : T extends readonly ReadonlyJSONValue[] + ? ToMutableJSONArray + : T extends { readonly [key: string]: ReadonlyJSONValue } + ? { -readonly [P in keyof T]: ToMutable } + : T; + +type ToReadonlyJSONArray = { + readonly [P in keyof T]: ToReadonly; +}; +export type ToReadonly = JSONValue extends T + ? ReadonlyJSONValue + : T extends readonly ReadonlyJSONValue[] + ? ToReadonlyJSONArray + : T extends { readonly [key: string]: ReadonlyJSONValue } + ? { readonly [P in keyof T]: ToReadonly } + : T; + +/** + * NOTE: MAY mutate the input document. + */ +export interface Ops { + add( + document: T, + path: string[], + value: JSONValue, + ): result.Result; + remove( + document: T, + path: array.NonEmptyArray, + ): result.Result; + replace( + document: T, + path: string[], + value: JSONValue, + ): result.Result; + move( + document: T, + from: array.NonEmptyArray, + path: string[], + ): result.Result; + copy( + document: T, + from: string[], + path: string[], + ): result.Result; + test( + document: T, + path: string[], + value: JSONValue, + ): result.Result; + get(document: T, path: string[]): result.Result; +} diff --git a/packages/core/src/patch/parse.test.ts b/packages/core/src/patch/parse.test.ts new file mode 100644 index 000000000..3157101fa --- /dev/null +++ b/packages/core/src/patch/parse.test.ts @@ -0,0 +1,202 @@ +import { + formatJSONPointer, + parseJSONPointer, + parseOperation, + StaticPatchIssue, +} from "./parse"; +import * as result from "../fp/result"; +import { Operation, OperationJSON } from "./operation"; +import { array } from "../fp"; + +describe("parseOperation", () => { + test.each<{ + name: string; + value: OperationJSON; + expected: Operation; + }>([ + { + name: "basic add operation", + value: { + op: "add", + path: "/", + value: null, + }, + expected: { + op: "add", + path: [], + value: null, + }, + }, + { + name: "basic remove operation", + value: { + op: "remove", + path: "/foo", + }, + expected: { + op: "remove", + path: ["foo"], + }, + }, + { + name: "basic replace operation", + value: { + op: "replace", + path: "/", + value: null, + }, + expected: { + op: "replace", + path: [], + value: null, + }, + }, + { + name: "basic move operation", + value: { + op: "move", + from: "/foo", + path: "/bar", + }, + expected: { + op: "move", + from: ["foo"], + path: ["bar"], + }, + }, + { + name: "basic copy operation", + value: { + op: "copy", + from: "/foo", + path: "/bar", + }, + expected: { + op: "copy", + from: ["foo"], + path: ["bar"], + }, + }, + { + name: "basic test operation", + value: { + op: "test", + path: "/", + value: null, + }, + expected: { + op: "test", + path: [], + value: null, + }, + }, + ])("$name is valid", ({ value, expected }) => { + const res = parseOperation(value); + expect(res).toEqual(result.ok(expected)); + }); + + test.each<{ + name: string; + value: OperationJSON; + errors: array.NonEmptyArray; + }>([ + { + name: "add operation with empty path", + value: { + op: "add", + path: "", + value: null, + }, + errors: [["path"]], + }, + { + name: "remove root", + value: { + op: "remove", + path: "/", + }, + errors: [["path"]], + }, + { + name: "move from root", + value: { + op: "move", + from: "/", + path: "/", + }, + errors: [["from"]], + }, + { + name: "move from prefix of path", + value: { + op: "move", + from: "/foo", + path: "/foo/bar", + }, + errors: [["from"]], + }, + ])("$name is invalid", ({ value, errors }) => { + expect(parseOperation(value)).toEqual( + result.err( + expect.arrayContaining>( + errors.map((path) => + expect.objectContaining({ + path, + message: expect.anything(), + }), + ), + ), + ), + ); + }); +}); + +const JSONPointerTestCases: { str: string; arr: string[] }[] = [ + { + str: "/", + arr: [], + }, + { + str: "/foo", + arr: ["foo"], + }, + { + str: "/foo/", + arr: ["foo", ""], + }, + { + str: "/~1", + arr: ["/"], + }, + { + str: "/~1/~1", + arr: ["/", "/"], + }, + { + str: "/~0", + arr: ["~"], + }, + { + str: "/~0/~0", + arr: ["~", "~"], + }, +]; + +describe("parseJSONPointer", () => { + test.each(JSONPointerTestCases)("valid: $str", ({ str, arr }) => { + expect(parseJSONPointer(str)).toEqual(result.ok(arr)); + }); + + test.each(["", "foo", "foo/bar", "/~2", "/~"])( + "invalid: %s", + (path: string) => { + expect(parseJSONPointer(path)).toEqual(result.err(expect.any(String))); + }, + ); +}); + +describe("formatJSONPointer", () => { + test.each(JSONPointerTestCases)("$str", ({ str, arr }) => { + expect(formatJSONPointer(arr)).toEqual(str); + }); +}); diff --git a/packages/core/src/patch/parse.ts b/packages/core/src/patch/parse.ts new file mode 100644 index 000000000..c240262b1 --- /dev/null +++ b/packages/core/src/patch/parse.ts @@ -0,0 +1,203 @@ +import { array, pipe, result } from "../fp"; +import { Operation, OperationJSON } from "./operation"; +import { Patch, PatchJSON } from "./patch"; + +function parseJSONPointerReferenceToken(value: string): string | undefined { + if (value.endsWith("~")) { + return undefined; + } + try { + return value.replace(/~./, (escaped) => { + switch (escaped) { + case "~0": + return "~"; + case "~1": + return "/"; + } + throw new Error(); + }); + } catch { + return undefined; + } +} + +export function parseJSONPointer( + pointer: string, +): result.Result { + if (pointer === "/") return result.ok([]); + if (!pointer.startsWith("/")) + return result.err("JSON pointer must start with /"); + + const tokens = pointer + .substring(1) + .split("/") + .map(parseJSONPointerReferenceToken); + if ( + tokens.every( + (token: string | undefined): token is string => token !== undefined, + ) + ) { + return result.ok(tokens); + } else { + return result.err("Invalid JSON pointer escape sequence"); + } +} + +export function formatJSONPointerReferenceToken(key: string): string { + return key.replace(/~/g, "~0").replace(/\//g, "~1"); +} + +export function formatJSONPointer(path: string[]): string { + return `/${path.map(formatJSONPointerReferenceToken).join("/")}`; +} + +/** + * A signifies an issue that makes a PatchJSON or an OperationJSON invalid. + * Unlike PatchError, a StaticPatchIssue indicates an issue with the patch + * document itself; it is independent of any document which the patch or + * might be applied to. + */ +export type StaticPatchIssue = { + path: string[]; + message: string; +}; + +export function prefixIssuePath( + prefix: string, + { path, message }: StaticPatchIssue, +): StaticPatchIssue { + return { path: [prefix, ...path], message }; +} + +function createIssueAtPath(path: string[]) { + return (message: string): StaticPatchIssue => ({ + path, + message, + }); +} + +function isProperPathPrefix(prefix: string[], path: string[]): boolean { + if (prefix.length >= path.length) { + // A proper prefix cannot be longer or have the same length as the path + return false; + } + for (let i = 0; i < prefix.length; ++i) { + if (prefix[i] !== path[i]) { + return false; + } + } + return true; +} + +export function parseOperation( + operation: OperationJSON, +): result.Result> { + const path = parseJSONPointer(operation.path); + + switch (operation.op) { + case "add": + case "replace": + case "test": + return pipe( + path, + result.mapErr( + (error: string): array.NonEmptyArray => [ + createIssueAtPath(["path"])(error), + ], + ), + result.map((path: string[]) => ({ + op: operation.op, + path, + value: operation.value, + })), + ); + case "file": + return pipe( + path, + result.mapErr( + (error: string): array.NonEmptyArray => [ + createIssueAtPath(["path"])(error), + ], + ), + result.map((path: string[]) => ({ + op: operation.op, + path, + filePath: operation.filePath, + value: operation.value, + remote: operation.remote, + })), + ); + case "remove": + return pipe( + path, + result.filterOrElse(array.isNonEmpty, () => "Cannot remove root"), + result.mapErr( + (error: string): array.NonEmptyArray => [ + createIssueAtPath(["path"])(error), + ], + ), + result.map((path: array.NonEmptyArray) => ({ + op: operation.op, + path, + })), + ); + case "move": + return pipe( + result.allT< + [from: array.NonEmptyArray, path: string[]], + StaticPatchIssue + >([ + pipe( + parseJSONPointer(operation.from), + result.filterOrElse(array.isNonEmpty, () => "Cannot move root"), + result.mapErr(createIssueAtPath(["from"])), + ), + pipe(path, result.mapErr(createIssueAtPath(["path"]))), + ]), + result.filterOrElse( + ([from, path]) => !isProperPathPrefix(from, path), + (): array.NonEmptyArray => [ + createIssueAtPath(["from"])("Cannot be a proper prefix of path"), + ], + ), + result.map(([from, path]) => ({ + op: operation.op, + from, + path, + })), + ); + case "copy": + return pipe( + result.allT<[from: string[], path: string[]], StaticPatchIssue>([ + pipe( + parseJSONPointer(operation.from), + result.mapErr(createIssueAtPath(["from"])), + ), + pipe(path, result.mapErr(createIssueAtPath(["path"]))), + ]), + result.map(([from, path]) => ({ + op: operation.op, + from, + path, + })), + ); + } +} + +export function parsePatch( + patch: PatchJSON, +): result.Result> { + return pipe( + patch + .map(parseOperation) + .map( + result.mapErr( + array.map((error: StaticPatchIssue, index: number) => + prefixIssuePath(index.toString(), error), + ), + ), + ), + result.all, + result.mapErr(array.flatten), + ); +} diff --git a/packages/core/src/patch/patch.ts b/packages/core/src/patch/patch.ts new file mode 100644 index 000000000..d1e6ec87d --- /dev/null +++ b/packages/core/src/patch/patch.ts @@ -0,0 +1,59 @@ +import { result, pipe } from "../fp"; +import { Ops, PatchError } from "./ops"; +import { Operation, OperationJSON } from "./operation"; +import { PatchId } from "../val"; + +export type Patch = Operation[]; +export type PatchJSON = OperationJSON[]; + +export type ParentRef = + | { type: "head"; headBaseSha: string } + | { type: "patch"; patchId: PatchId }; + +export type PatchBlock = { + patch: Patch; + parentRef: ParentRef; +}; + +function apply( + document: T, + ops: Ops, + op: Operation, +): result.Result { + switch (op.op) { + case "add": + return ops.add(document, op.path, op.value); + case "remove": + return ops.remove(document, op.path); + case "replace": + return ops.replace(document, op.path, op.value); + case "move": + return ops.move(document, op.from, op.path); + case "copy": + return ops.copy(document, op.from, op.path); + case "test": { + if (!ops.test(document, op.path, op.value)) { + return result.err(new PatchError("Test failed")); + } + return result.ok(document); + } + case "file": { + return result.err(new PatchError("Cannot apply a file patch here")); + } + } +} + +export function applyPatch( + document: T, + ops: Ops, + patch: Operation[], +): result.Result { + return pipe( + patch, + result.flatMapReduce( + (doc: T, op: Operation): result.Result => + apply(doc, ops, op), + document, + ), + ); +} diff --git a/packages/core/src/patch/util.ts b/packages/core/src/patch/util.ts new file mode 100644 index 000000000..56707ccd6 --- /dev/null +++ b/packages/core/src/patch/util.ts @@ -0,0 +1,75 @@ +import { array, result } from "../fp"; +import { PatchError, ReadonlyJSONValue, ToMutable } from "./ops"; +import { splitModuleFilePathAndModulePath } from "../module"; +import { SourcePath } from "../val"; + +export function isNotRoot(path: string[]): path is array.NonEmptyArray { + return array.isNonEmpty(path); +} + +export function deepEqual(a: ReadonlyJSONValue, b: ReadonlyJSONValue) { + if (a === b) { + return true; + } + + if ( + typeof a === "object" && + typeof b === "object" && + a !== null && + b !== null + ) { + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false; + + for (let i = 0; i < a.length; ++i) { + if (!deepEqual(a[i], b[i])) return false; + } + + return true; + } else if (!Array.isArray(a) && !Array.isArray(b)) { + const aEntries = Object.entries(a); + // If the objects have a different amount of keys, they cannot be equal + if (aEntries.length !== Object.keys(b).length) return false; + + for (const [key, aValue] of aEntries) { + // b must be a JSON object, so the only way for the bValue to be + // undefined is if the key is unset + const bValue: ReadonlyJSONValue | undefined = ( + b as { readonly [P in string]: ReadonlyJSONValue } + )[key]; + if (bValue === undefined) return false; + if (!deepEqual(aValue, bValue)) return false; + } + return true; + } + } + + return false; +} + +export function deepClone(value: T): ToMutable { + if (Array.isArray(value)) { + return value.map(deepClone) as ToMutable; + } else if (typeof value === "object" && value !== null) { + return Object.fromEntries( + Object.entries(value).map(([key, value]) => [key, deepClone(value)]), + ) as ToMutable; + } else { + return value as ToMutable; + } +} + +export function parseAndValidateArrayIndex( + value: string, +): result.Result { + if (!/^(0|[1-9][0-9]*)$/g.test(value)) { + return result.err(new PatchError(`Invalid array index "${value}"`)); + } + return result.ok(Number(value)); +} + +export function sourceToPatchPath(sourcePath: SourcePath) { + const [, modulePath] = splitModuleFilePathAndModulePath(sourcePath); + if (!modulePath) return []; + return modulePath.split(".").map((p) => JSON.parse(p).toString()); +} diff --git a/packages/core/src/remote/fileHash.ts b/packages/core/src/remote/fileHash.ts new file mode 100644 index 000000000..eb72f4777 --- /dev/null +++ b/packages/core/src/remote/fileHash.ts @@ -0,0 +1,12 @@ +import { getSHA256Hash } from "../getSha256"; +import { Buffer } from "buffer"; + +export function hashToRemoteFileHash(hash: string) { + return hash.slice( + 0, + 12, // 12 hex characters = 6 bytes = 48 bits = 2^48 = 281474976710656 possibilities or 1 in 281474976710656 or using birthday problem estimate with 10K files: p = (k, n) => (k*k)/(2x2**n) and p(10_000,12*4) = 1.7763568394002505e-7 chance of collision which should be good enough + ); +} +export function getFileHash(text: Buffer) { + return hashToRemoteFileHash(getSHA256Hash(new Uint8Array(text))); +} diff --git a/packages/core/src/remote/splitRemoteRef.test.ts b/packages/core/src/remote/splitRemoteRef.test.ts new file mode 100644 index 000000000..2c97a53dd --- /dev/null +++ b/packages/core/src/remote/splitRemoteRef.test.ts @@ -0,0 +1,75 @@ +import { splitRemoteRef } from "./splitRemoteRef"; + +describe("splitRemoteRef", () => { + it("should return success with parsed values for a valid remote ref", () => { + const ref = + "https://remote.val.build/file/p/project123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/test.png"; + const result = splitRemoteRef(ref); + + expect(result).toEqual({ + status: "success", + remoteHost: "https://remote.val.build", + projectId: "project123", + bucket: "01", + version: "1.0.0", + validationHash: "abc123", + fileHash: "def456", + filePath: "public/val/test.png", + }); + }); + + it("should return an error for an invalid remote ref", () => { + const ref = "invalid-remote-ref"; + const result = splitRemoteRef(ref); + + expect(result).toEqual({ + status: "error", + error: "Invalid remote ref: " + ref, + }); + }); + + it("should return an error if the remote ref is missing required parts", () => { + const ref = + "https://remote.val.build/file/p/project123/b/01//v/1.0.0/h/abc123/f/def456"; + const result = splitRemoteRef(ref); + + expect(result).toEqual({ + status: "error", + error: "Invalid remote ref: " + ref, + }); + }); + + it("should handle a remote ref with a complex file path", () => { + const ref = + "https://remote.val.build/file/p/project123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/dir/subdir/file.png"; + const result = splitRemoteRef(ref); + + expect(result).toEqual({ + status: "success", + remoteHost: "https://remote.val.build", + projectId: "project123", + bucket: "01", + version: "1.0.0", + validationHash: "abc123", + fileHash: "def456", + filePath: "public/val/dir/subdir/file.png", + }); + }); + + it("should handle a remote ref with an HTTP host", () => { + const ref = + "http://example.com/file/p/project123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/test.png"; + const result = splitRemoteRef(ref); + + expect(result).toEqual({ + status: "success", + remoteHost: "http://example.com", + projectId: "project123", + bucket: "01", + version: "1.0.0", + validationHash: "abc123", + fileHash: "def456", + filePath: "public/val/test.png", + }); + }); +}); diff --git a/packages/core/src/remote/splitRemoteRef.ts b/packages/core/src/remote/splitRemoteRef.ts new file mode 100644 index 000000000..13078db2f --- /dev/null +++ b/packages/core/src/remote/splitRemoteRef.ts @@ -0,0 +1,49 @@ +const RegEx = + /^(https?:\/\/[^/]+)\/file\/p\/([^/]+)\/b\/([^/]+)\/v\/([^/]+)\/h\/([^/]+)\/f\/([^/]+)\/p\/(.+)$/; + +export function splitRemoteRef(ref: string): + | { + status: "success"; + remoteHost: string; + bucket: string; + projectId: string; + version: string; + validationHash: string; + fileHash: string; + filePath: `public/val/${string}`; + } + | { + status: "error"; + error: string; + } { + if (ref[0] === "/") { + return { + status: "error", + error: "Not a remote ref: " + ref, + }; + } + const match = ref.match(RegEx); + if (!match) { + return { + status: "error", + error: "Invalid remote ref: " + ref, + }; + } + if (match[7].indexOf("public/val/") !== 0) { + return { + status: "error", + error: "Invalid remote ref: " + ref, + }; + } + + return { + status: "success", + remoteHost: match[1], + projectId: match[2], + bucket: match[3], + version: match[4], + validationHash: match[5], + fileHash: match[6], + filePath: match[7] as `public/val/${string}`, + }; +} diff --git a/packages/core/src/remote/validationBasis.ts b/packages/core/src/remote/validationBasis.ts new file mode 100644 index 000000000..36ccf448e --- /dev/null +++ b/packages/core/src/remote/validationBasis.ts @@ -0,0 +1,53 @@ +import { getSHA256Hash } from "../getSha256"; +import { SerializedFileSchema } from "../schema/file"; +import { SerializedImageSchema } from "../schema/image"; + +/** + * The validation basis is used in remote refs to determine if the remote content needs to be re-validated. + * + * If the validation basis changes, we need to re-validate the remote content. + * + * NOTE: We do not care if the file path is different as long as the extension is the same. + * This way we can rename a file, without having to re-validate it. + * The version is outside of the validation hash, so that it is possible to manually fix the version without having to re-validate. + */ +export function getValidationBasis( + coreVersion: string, + schema: SerializedImageSchema | SerializedFileSchema, + fileExt: string, + metadata: Record | undefined, + fileHash: string, +) { + const metadataValidationBasis = `${metadata?.width || ""}${metadata?.height || ""}${metadata?.mimeType}`; + const schemaValidationBasis = { + type: schema.type, + opt: schema.opt, + options: { + ...schema.options, + // Ignore options that are does not affect the validation basis below: + // Currently we do not have any options that can be ignored. + }, + }; + return ( + coreVersion + + JSON.stringify(schemaValidationBasis) + + fileExt + + metadataValidationBasis + + fileHash + ); +} + +export function getValidationHash( + coreVersion: string, + schema: SerializedImageSchema | SerializedFileSchema, + fileExt: string, + metadata: Record | undefined, + fileHash: string, + textEncoder: TextEncoder, +) { + return getSHA256Hash( + textEncoder.encode( + getValidationBasis(coreVersion, schema, fileExt, metadata, fileHash), + ), + ).slice(0, 4); // we do not need a lot of bits for the validation hash, since it is only used to identify the validation basis +} diff --git a/packages/core/src/render.ts b/packages/core/src/render.ts new file mode 100644 index 000000000..bd65e0392 --- /dev/null +++ b/packages/core/src/render.ts @@ -0,0 +1,90 @@ +import { Schema } from "./schema"; +import { ImageMetadata } from "./schema/image"; +import { SelectorSource } from "./selector"; +import { ImageSource } from "./source/image"; +import { RemoteSource } from "./source/remote"; +import { ModuleFilePath, SourcePath } from "./val"; + +// TODO: we want to change layout -> as to be more consistent across the board +export type ListRecordRender = { + layout: "list"; + parent: "record"; + items: [ + key: string, + value: { + title: string; + subtitle?: string | null; + image?: ImageSource | RemoteSource | null; + }, + ][]; +}; + +export type ListArrayRender = { + layout: "list"; + parent: "array"; + items: { + title: string; + subtitle?: string | null; + image?: ImageSource | RemoteSource | null; + }[]; +}; + +export type TextareaRender = { + layout: "textarea"; +}; + +export type CodeLanguage = + | "typescript" + | "javascript" + | "javascriptreact" + | "typescriptreact" + | "json" + | "java" + | "html" + | "css" + | "xml" + | "markdown" + | "sql" + | "python" + | "rust" + | "php" + | "go" + | "cpp" + | "sass" + | "vue" + | "angular"; +export type CodeRender = { + layout: "code"; + language: CodeLanguage; +}; + +// Main render type: +type RenderTypes = + | ListRecordRender + | ListArrayRender + | TextareaRender + | CodeRender; +// + +type WithStatus = + | { + status: "error"; + message: string; + } + | { + // TODO: loading doesn't really belong in core - however this is used in other places where it does make sense and we figured... Why not just add it here? + status: "loading"; + data?: T; + } + | { + status: "success"; + data: T; + }; +export type ReifiedRender = Record< + SourcePath | ModuleFilePath, + WithStatus +>; + +// TODO: improve this so that we do not get RawString and string, only string. Are there other things? +export type RenderSelector> = + T extends Schema ? S : never; diff --git a/packages/core/src/router.test.ts b/packages/core/src/router.test.ts new file mode 100644 index 000000000..e8b7f6a5a --- /dev/null +++ b/packages/core/src/router.test.ts @@ -0,0 +1,158 @@ +import { parseNextJsRoutePattern } from "./router"; + +describe("parseNextJsRoutePattern", () => { + describe("App Router patterns", () => { + test("basic dynamic route", () => { + expect(parseNextJsRoutePattern("/app/blogs/[blog]/page.val.ts")).toEqual([ + "blogs", + "[blog]", + ]); + }); + + test("src/app directory structure", () => { + expect( + parseNextJsRoutePattern("/src/app/blogs/[blog]/page.val.ts"), + ).toEqual(["blogs", "[blog]"]); + }); + + test("with .tsx extension", () => { + expect(parseNextJsRoutePattern("/app/blogs/[blog]/page.tsx")).toEqual([ + "blogs", + "[blog]", + ]); + }); + + test("src/app with .tsx extension", () => { + expect(parseNextJsRoutePattern("/src/app/blogs/[blog]/page.tsx")).toEqual( + ["blogs", "[blog]"], + ); + }); + + test("optional catch-all segments", () => { + expect( + parseNextJsRoutePattern("/app/posts/[[...category]]/page.val.ts"), + ).toEqual(["posts", "[[...category]]"]); + }); + + test("required catch-all segments", () => { + expect( + parseNextJsRoutePattern("/app/docs/[...slug]/page.val.ts"), + ).toEqual(["docs", "[...slug]"]); + }); + + test("static segments", () => { + expect( + parseNextJsRoutePattern("/app/admin/users/[id]/page.val.ts"), + ).toEqual(["admin", "users", "[id]"]); + }); + + test("root route", () => { + expect(parseNextJsRoutePattern("/app/page.val.ts")).toEqual([]); + }); + + test("src/app root route", () => { + expect(parseNextJsRoutePattern("/src/app/page.val.ts")).toEqual([]); + }); + }); + + describe("Group segments", () => { + test("simple group", () => { + expect( + parseNextJsRoutePattern("/app/(marketing)/blogs/[blog]/page.val.ts"), + ).toEqual(["blogs", "[blog]"]); + }); + + test("multiple groups", () => { + expect( + parseNextJsRoutePattern( + "/app/(marketing)/(public)/blogs/[blog]/page.val.ts", + ), + ).toEqual(["blogs", "[blog]"]); + }); + + test("group with src/app", () => { + expect( + parseNextJsRoutePattern("/src/app/(admin)/users/[id]/page.val.ts"), + ).toEqual(["users", "[id]"]); + }); + }); + + describe("Interception routes", () => { + test("simple interception", () => { + expect(parseNextJsRoutePattern("/app/(.)feed/page.val.ts")).toEqual([ + "feed", + ]); + }); + + test("interception with group", () => { + expect( + parseNextJsRoutePattern("/app/(..)(dashboard)/feed/[id]/page.val.ts"), + ).toEqual(["feed", "[id]"]); + }); + + test("interception with multiple dots", () => { + expect( + parseNextJsRoutePattern("/app/(...)(dashboard)/feed/[id]/page.val.ts"), + ).toEqual(["feed", "[id]"]); + }); + + test("interception with src/app", () => { + expect(parseNextJsRoutePattern("/src/app/(.)feed/page.val.ts")).toEqual([ + "feed", + ]); + }); + }); + + describe("Edge cases", () => { + test("invalid file path", () => { + expect(parseNextJsRoutePattern("/invalid/path/file.ts")).toEqual([]); + }); + + test("empty string", () => { + expect(parseNextJsRoutePattern("")).toEqual([]); + }); + + test("null/undefined", () => { + expect(parseNextJsRoutePattern(null as unknown as string)).toEqual([]); + expect(parseNextJsRoutePattern(undefined as unknown as string)).toEqual( + [], + ); + }); + + test("file path without page", () => { + expect(parseNextJsRoutePattern("/app/blogs/[blog]/layout.tsx")).toEqual( + [], + ); + }); + + test("file path with other extensions", () => { + expect(parseNextJsRoutePattern("/app/blogs/[blog]/page.js")).toEqual([]); + }); + }); + + describe("Complex combinations", () => { + test("group + interception + dynamic segments", () => { + expect( + parseNextJsRoutePattern( + "/app/(admin)/(..)(dashboard)/users/[id]/posts/[postId]/page.val.ts", + ), + ).toEqual(["users", "[id]", "posts", "[postId]"]); + }); + + test("multiple groups and segments", () => { + expect( + parseNextJsRoutePattern( + "/app/(marketing)/(public)/blog/(category)/[slug]/page.val.ts", + ), + ).toEqual(["blog", "[slug]"]); + }); + + test("interception with catch-all", () => { + expect( + parseNextJsRoutePattern( + "/app/(..)(dashboard)/docs/[...slug]/page.val.ts", + ), + ).toEqual(["docs", "[...slug]"]); + }); + }); +}); diff --git a/packages/core/src/router.ts b/packages/core/src/router.ts new file mode 100644 index 000000000..dd5d2c0ea --- /dev/null +++ b/packages/core/src/router.ts @@ -0,0 +1,231 @@ +import { ModuleFilePath } from "./val"; + +export const externalPageRouter: ValRouter = { + getRouterId: () => "external-url-router", + validate: (_moduleFilePath, urlPaths): RouteValidationError[] => { + const errors: RouteValidationError[] = []; + for (const urlPath of urlPaths) { + if (!(urlPath.startsWith("https://") || urlPath.startsWith("http://"))) { + errors.push({ + error: { + message: `URL path "${urlPath}" does not start with "https://" or "http://"`, + expectedPath: null, + urlPath, + }, + }); + } + } + return []; + }, +}; + +export type RouteValidationError = { + error: { + message: string; + urlPath: string; + expectedPath: string | null; + }; +}; + +// Helper function to validate a URL path against a route pattern +export function validateUrlAgainstPattern( + urlPath: string, + routePattern: string[], +): { isValid: boolean; expectedPath?: string } { + // Remove leading slash and split URL path + const urlSegments = urlPath.startsWith("/") + ? urlPath.slice(1).split("/") + : urlPath.split("/"); + + // Handle empty patterns (root route) + if (routePattern.length === 0) { + return { + isValid: + urlSegments.length === 0 || + (urlSegments.length === 1 && urlSegments[0] === ""), + expectedPath: "/", + }; + } + + // Check if segment counts match (accounting for optional segments and catch-all) + let minSegments = 0; + let maxSegments = 0; + let hasCatchAll = false; + let catchAllIndex = -1; + + for (let i = 0; i < routePattern.length; i++) { + const segment = routePattern[i]; + if (segment.startsWith("[[") && segment.endsWith("]]")) { + // Optional catch-all segment + hasCatchAll = true; + catchAllIndex = i; + maxSegments = Infinity; + } else if (segment.startsWith("[...") && segment.endsWith("]")) { + // Required catch-all segment + hasCatchAll = true; + catchAllIndex = i; + minSegments++; + maxSegments = Infinity; + } else if (segment.startsWith("[[") && segment.endsWith("]")) { + // Optional segment + maxSegments++; + } else if (segment.startsWith("[") && segment.endsWith("]")) { + // Required segment + minSegments++; + maxSegments++; + } else { + // Static segment + minSegments++; + maxSegments++; + } + } + + // Check segment count + if ( + urlSegments.length < minSegments || + (!hasCatchAll && urlSegments.length > maxSegments) + ) { + const expectedSegments = routePattern + .map((seg) => { + if (seg.startsWith("[[") && seg.endsWith("]]")) { + return `[optional:${seg.slice(2, -2)}]`; + } else if (seg.startsWith("[...") && seg.endsWith("]")) { + return `[...${seg.slice(4, -1)}]`; + } else if (seg.startsWith("[[") && seg.endsWith("]")) { + return `[optional:${seg.slice(2, -1)}]`; + } else if (seg.startsWith("[") && seg.endsWith("]")) { + return `[${seg.slice(1, -1)}]`; + } + return seg; + }) + .join("/"); + return { + isValid: false, + expectedPath: `/${expectedSegments}`, + }; + } + + // Validate each segment up to the catch-all or the end of the pattern + const segmentsToValidate = hasCatchAll ? catchAllIndex : routePattern.length; + for (let i = 0; i < segmentsToValidate; i++) { + const patternSegment = routePattern[i]; + const urlSegment = urlSegments[i]; + + // Handle optional segments + if (patternSegment.startsWith("[[") && patternSegment.endsWith("]]")) { + // Optional segment - can be empty or match + if (urlSegment !== "" && urlSegment !== undefined) { + // If provided, validate it's not empty + if (urlSegment === "") { + return { + isValid: false, + expectedPath: `/${routePattern.join("/")}`, + }; + } + } + } else if (patternSegment.startsWith("[") && patternSegment.endsWith("]")) { + // Required dynamic segment - just check it's not empty + if (urlSegment === "" || urlSegment === undefined) { + return { + isValid: false, + expectedPath: `/${routePattern.join("/")}`, + }; + } + } else { + // Static segment - must match exactly + if (patternSegment !== urlSegment) { + return { + isValid: false, + expectedPath: `/${routePattern.join("/")}`, + }; + } + } + } + + return { isValid: true }; +} + +// This router should not be in core package +export const nextAppRouter: ValRouter = { + getRouterId: () => "next-app-router", + validate: (moduleFilePath, urlPaths) => { + const routePattern = parseNextJsRoutePattern(moduleFilePath); + const errors: RouteValidationError[] = []; + + for (const urlPath of urlPaths) { + const validation = validateUrlAgainstPattern(urlPath, routePattern); + + if (!validation.isValid) { + errors.push({ + error: { + message: `URL path "${urlPath}" does not match the route pattern for "${moduleFilePath}"`, + urlPath, + expectedPath: validation.expectedPath || null, + }, + }); + } + } + + return errors; + }, +}; + +/** + * Parse Next.js route pattern from file path + * Support multiple Next.js app directory structures: + * - /app/blogs/[blog]/page.val.ts -> ["blogs", "[blog]"] + * - /src/app/blogs/[blog]/page.val.ts -> ["blogs", "[blog]"] + * - /pages/blogs/[blog].tsx -> ["blogs", "[blog]"] (Pages Router) + * - /app/(group)/blogs/[blog]/page.val.ts -> ["blogs", "[blog]"] (with groups) + * - /app/(.)feed/page.val.ts -> ["feed"] (interception route) + * - /app/(..)(dashboard)/feed/page.val.ts -> ["feed"] (interception route) + */ +export function parseNextJsRoutePattern(moduleFilePath: string): string[] { + if (!moduleFilePath || typeof moduleFilePath !== "string") { + return []; + } + + // Try App Router patterns first + const appRouterPatterns = [ + /\/app\/(.+)\/page\.val\.ts$/, // /app/... + /\/src\/app\/(.+)\/page\.val\.ts$/, // /src/app/... + /\/app\/(.+)\/page\.tsx?$/, // /app/... with .tsx + /\/src\/app\/(.+)\/page\.tsx?$/, // /src/app/... with .tsx + ]; + + for (const pattern of appRouterPatterns) { + const match = moduleFilePath.match(pattern); + if (match) { + const routePath = match[1]; + // Remove group and interception segments + // Group: (group), Interception: (.), (..), (..)(dashboard), etc. + return routePath.split("/").flatMap((segment) => { + // Remove group segments (but not interception segments) + if ( + segment.startsWith("(") && + segment.endsWith(")") && + !segment.includes(".") + ) + return []; + // Interception segments: (.)feed, (..)(dashboard)/feed, etc. + // If segment starts with (.) or (..), strip the interception marker and keep the rest + const interceptionMatch = segment.match(/^(\([.]+\)(\(.+\))?)(.*)$/); + if (interceptionMatch) { + const rest = interceptionMatch[3] || interceptionMatch[4]; + return rest ? [rest] : []; + } + return [segment]; + }); + } + } + + return []; +} + +export interface ValRouter { + getRouterId(): string; + validate( + moduleFilePath: ModuleFilePath, + urlPaths: string[], + ): RouteValidationError[]; +} diff --git a/packages/core/src/schema/array.test.ts b/packages/core/src/schema/array.test.ts new file mode 100644 index 000000000..dce67cc9a --- /dev/null +++ b/packages/core/src/schema/array.test.ts @@ -0,0 +1,20 @@ +import { SourcePath } from "../val"; +import { array } from "./array"; +import { number } from "./number"; + +describe("ArraySchema", () => { + test("assert: should return success if src is an array", () => { + const schema = array(number()); + expect(schema["executeAssert"]("path" as SourcePath, [])).toEqual({ + success: true, + data: [], + }); + }); + + test("assert: should return error if src is string", () => { + const schema = array(number()); + expect(schema["executeAssert"]("path" as SourcePath, "").success).toEqual( + false, + ); + }); +}); diff --git a/packages/core/src/schema/array.ts b/packages/core/src/schema/array.ts new file mode 100644 index 000000000..302c452f9 --- /dev/null +++ b/packages/core/src/schema/array.ts @@ -0,0 +1,215 @@ +import { + Schema, + SchemaAssertResult, + SelectorOfSchema, + SerializedSchema, +} from "."; +import { RenderSelector, ReifiedRender } from "../render"; +import { SelectorSource } from "../selector"; +import { unsafeCreateSourcePath } from "../selector/SelectorProxy"; +import { ImageSource } from "../source/image"; +import { ModuleFilePath, SourcePath } from "../val"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; + +export type SerializedArraySchema = { + type: "array"; + item: SerializedSchema; + opt: boolean; + customValidate?: boolean; +}; + +export class ArraySchema< + T extends Schema, + Src extends SelectorOfSchema[] | null, +> extends Schema { + constructor( + private readonly item: T, + private readonly opt: boolean = false, + private readonly customValidateFunctions: (( + src: Src, + ) => false | string)[] = [], + ) { + super(); + } + + validate( + validationFunction: (src: Src) => false | string, + ): ArraySchema { + return new ArraySchema(this.item, this.opt, [ + ...this.customValidateFunctions, + validationFunction, + ]); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const assertRes = this.executeAssert(path, src); + if (!assertRes.success) { + return assertRes.errors; + } + if (assertRes.data === null) { + return false; + } + let error: Record = {}; + for (const [idx, i] of Object.entries(assertRes.data)) { + const subPath = unsafeCreateSourcePath(path, Number(idx)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const subError = this.item["executeValidate"](subPath, i as any); + if (subError) { + error = { + ...subError, + ...error, + }; + } + } + + if (Object.keys(error).length === 0) { + return false; + } + return error; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (src === null && this.opt) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { message: "Expected 'array', got 'null'", typeError: true }, + ], + }, + }; + } + if (typeof src !== "object") { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'object', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } else if (!Array.isArray(src)) { + return { + success: false, + errors: { + [path]: [ + { message: `Expected object of type 'array'`, typeError: true }, + ], + }, + }; + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } + + nullable(): ArraySchema { + return new ArraySchema(this.item, true); + } + + protected executeSerialize(): SerializedArraySchema { + return { + type: "array", + item: this.item["executeSerialize"](), + opt: this.opt, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + } + + private renderInput: { + as: "list"; + select: (input: { val: RenderSelector }) => { + title: string; + subtitle?: string | null; + image?: ImageSource | null; + }; + } | null = null; + + protected override executeRender( + sourcePath: SourcePath | ModuleFilePath, + src: Src, + ): ReifiedRender { + const res: ReifiedRender = {}; + if (src === null) { + return res; + } + for (let i = 0; i < src.length; i++) { + const key = i; + const itemSrc = src[key]; + if (itemSrc === null || itemSrc === undefined) { + continue; + } + const subPath = unsafeCreateSourcePath(sourcePath, key); + const itemResult = this.item["executeRender"](subPath, itemSrc); + for (const keyS in itemResult) { + const key = keyS as SourcePath | ModuleFilePath; + res[key] = itemResult[key]; + } + } + if (this.renderInput) { + const { select, as: layout } = this.renderInput; + if (layout !== "list") { + res[sourcePath] = { + status: "error", + message: "Unknown layout type: " + layout, + }; + } + try { + res[sourcePath] = { + status: "success", + data: { + layout: "list", + parent: "array", + items: src.map((val) => { + // NB NB: display is actually defined by the user + const { title, subtitle, image } = select({ val }); + return { title, subtitle, image }; + }), + }, + }; + } catch (e) { + res[sourcePath] = { + status: "error", + message: e instanceof Error ? e.message : "Unknown error", + }; + } + } + return res; + } + + render(input: { + as: "list"; + select: (input: { val: RenderSelector }) => { + title: string; + subtitle?: string | null; + image?: ImageSource | null; + }; + }) { + this.renderInput = input; + return this; + } +} + +export const array = >( + schema: S, +): ArraySchema[]> => { + return new ArraySchema(schema); +}; diff --git a/packages/core/src/schema/boolean.test.ts b/packages/core/src/schema/boolean.test.ts new file mode 100644 index 000000000..8b9c8d8b5 --- /dev/null +++ b/packages/core/src/schema/boolean.test.ts @@ -0,0 +1,19 @@ +import { SourcePath } from "../val"; +import { boolean } from "./boolean"; + +describe("BooleanSchema", () => { + test("assert: should return success if src is a boolean", () => { + const schema = boolean(); + expect(schema["executeAssert"]("path" as SourcePath, true)).toEqual({ + success: true, + data: true, + }); + }); + + test("assert: should return error if src is string", () => { + const schema = boolean(); + expect(schema["executeAssert"]("path" as SourcePath, "").success).toEqual( + false, + ); + }); +}); diff --git a/packages/core/src/schema/boolean.ts b/packages/core/src/schema/boolean.ts new file mode 100644 index 000000000..2a70a26de --- /dev/null +++ b/packages/core/src/schema/boolean.ts @@ -0,0 +1,111 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + CustomValidateFunction, + Schema, + SchemaAssertResult, + SerializedSchema, +} from "."; +import { ReifiedRender } from "../render"; +import { ModuleFilePath, SourcePath } from "../val"; +import { ValidationErrors } from "./validation/ValidationError"; + +export type SerializedBooleanSchema = { + type: "boolean"; + opt: boolean; + customValidate?: boolean; +}; + +export class BooleanSchema extends Schema { + constructor( + private readonly opt: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + ) { + super(); + } + + validate( + validationFunction: (src: Src) => false | string, + ): BooleanSchema { + return new BooleanSchema(this.opt, [ + ...this.customValidateFunctions, + validationFunction, + ]); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + if (this.opt && (src === null || src === undefined)) { + return false; + } + if (typeof src !== "boolean") { + return { + [path]: [ + { message: `Expected 'boolean', got '${typeof src}'`, value: src }, + ], + } as ValidationErrors; + } + return false; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { + message: "Expected 'boolean', got 'null'", + typeError: true, + }, + ], + }, + }; + } + if (typeof src !== "boolean") { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'boolean', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } + + nullable(): BooleanSchema { + return new BooleanSchema(true); + } + protected executeSerialize(): SerializedSchema { + return { + type: "boolean", + opt: this.opt, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + } + + protected executeRender(): ReifiedRender { + return {}; + } +} + +export const boolean = (): BooleanSchema => { + return new BooleanSchema(); +}; diff --git a/packages/core/src/schema/date.test.ts b/packages/core/src/schema/date.test.ts new file mode 100644 index 000000000..7a0e97bdb --- /dev/null +++ b/packages/core/src/schema/date.test.ts @@ -0,0 +1,24 @@ +import { SourcePath } from "../val"; +import { date } from "./date"; + +describe("DateSchema", () => { + test("assert: should return success if src is a string", () => { + const schema = date(); + expect(schema["executeAssert"]("path" as SourcePath, "2012-12-12")).toEqual( + { + success: true, + data: "2012-12-12", + }, + ); + }); + + test("assert: should return success if src is a date / string", () => { + const schema = date(); + expect( + schema["executeAssert"]("path" as SourcePath, "something else"), + ).toEqual({ + success: true, + data: "something else", + }); + }); +}); diff --git a/packages/core/src/schema/date.ts b/packages/core/src/schema/date.ts new file mode 100644 index 000000000..40e44f1d9 --- /dev/null +++ b/packages/core/src/schema/date.ts @@ -0,0 +1,193 @@ +import { + CustomValidateFunction, + Schema, + SchemaAssertResult, + SerializedSchema, +} from "."; +import { ReifiedRender } from "../render"; +import { SourcePath } from "../val"; +import { RawString } from "./string"; +import { ValidationErrors } from "./validation/ValidationError"; + +type DateOptions = { + /** + * Validate that the date is this date or after (inclusive). + * + * @example + * 2021-01-01 + */ + from?: string; + /** + * Validate that the date is this date or before (inclusive). + * + * @example + * 2021-01-01 + */ + to?: string; +}; + +export type SerializedDateSchema = { + type: "date"; + options?: DateOptions; + opt: boolean; + customValidate?: boolean; +}; + +export class DateSchema extends Schema { + constructor( + private readonly options?: DateOptions, + private readonly opt: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + ) { + super(); + } + + validate(validationFunction: (src: Src) => false | string): DateSchema { + return new DateSchema(this.options, this.opt, [ + ...this.customValidateFunctions, + validationFunction, + ]); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + if (this.opt && (src === null || src === undefined)) { + return false; + } + if (typeof src !== "string") { + return { + [path]: [ + { message: `Expected 'string', got '${typeof src}'`, value: src }, + ], + } as ValidationErrors; + } + if (this.options?.from && this.options?.to) { + if (this.options.from > this.options.to) { + return { + [path]: [ + { + message: `From date ${this.options.from} is after to date ${this.options.to}`, + value: src, + typeError: true, + }, + ], + } as ValidationErrors; + } + if (src < this.options.from || src > this.options.to) { + return { + [path]: [ + { + message: `Date is not between ${this.options.from} and ${this.options.to}`, + value: src, + }, + ], + } as ValidationErrors; + } + } else if (this.options?.from) { + if (src < this.options.from) { + return { + [path]: [ + { + message: `Date is before the minimum date ${this.options.from}`, + value: src, + }, + ], + } as ValidationErrors; + } + } else if (this.options?.to) { + if (src > this.options.to) { + return { + [path]: [ + { + message: `Date is after the maximum date ${this.options.to}`, + value: src, + }, + ], + } as ValidationErrors; + } + } + // const errors = []; + + // if (errors.length > 0) { + // return { + // [path]: errors, + // } as ValidationErrors; + // } + return false; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { + message: "Expected 'string', got 'null'", + typeError: true, + }, + ], + }, + }; + } + if (typeof src !== "string") { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'string', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + + return { + success: true, + data: src, + } as SchemaAssertResult; + } + + from(from: string): DateSchema { + return new DateSchema({ ...this.options, from }, this.opt); + } + + to(to: string): DateSchema { + return new DateSchema({ ...this.options, to }, this.opt); + } + + nullable(): DateSchema { + return new DateSchema(this.options, true); + } + + protected executeSerialize(): SerializedSchema { + return { + type: "date", + opt: this.opt, + options: this.options, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + } + + protected executeRender(): ReifiedRender { + return {}; + } +} + +export const date = ( + options?: Record, +): DateSchema => { + return new DateSchema(options); +}; diff --git a/packages/core/src/schema/deserialize.ts b/packages/core/src/schema/deserialize.ts new file mode 100644 index 000000000..4a5b99417 --- /dev/null +++ b/packages/core/src/schema/deserialize.ts @@ -0,0 +1,177 @@ +import { SerializedSchema, Schema } from "."; +import { SelectorSource } from "../selector"; +import { ImageSource } from "../source/image"; +import { RemoteSource } from "../source/remote"; +import { SourcePath } from "../val"; +import { ArraySchema } from "./array"; +import { BooleanSchema } from "./boolean"; +import { DateSchema } from "./date"; +import { FileSchema } from "./file"; +import { ImageMetadata, ImageSchema } from "./image"; +import { KeyOfSchema } from "./keyOf"; +import { LiteralSchema } from "./literal"; +import { NumberSchema } from "./number"; +import { ObjectSchema } from "./object"; +import { RecordSchema } from "./record"; +import { RichTextSchema } from "./richtext"; +import { RouteSchema } from "./route"; +import { StringSchema } from "./string"; +import { UnionSchema } from "./union"; + +export function deserializeSchema( + serialized: SerializedSchema, +): Schema { + switch (serialized.type) { + case "string": + return new StringSchema( + { + ...serialized.options, + regexp: + serialized.options?.regexp && + new RegExp( + serialized.options.regexp.source, + serialized.options.regexp.flags, + ), + regExpMessage: serialized.options?.regexp?.message, + }, + serialized.opt, + serialized.raw, + ); + case "literal": + return new LiteralSchema(serialized.value, serialized.opt); + case "boolean": + return new BooleanSchema(serialized.opt); + case "number": + return new NumberSchema(serialized.options, serialized.opt); + case "object": + return new ObjectSchema( + Object.fromEntries( + Object.entries(serialized.items).map(([key, item]) => { + return [key, deserializeSchema(item)]; + }), + ), + serialized.opt, + ); + case "array": + return new ArraySchema( + deserializeSchema(serialized.item), + serialized.opt, + ); + case "union": + return new UnionSchema( + typeof serialized.key === "string" + ? serialized.key + : // eslint-disable-next-line @typescript-eslint/no-explicit-any + (deserializeSchema(serialized.key) as any), // TODO: we do not really need any here - right? + // eslint-disable-next-line @typescript-eslint/no-explicit-any + serialized.items.map(deserializeSchema) as any, // TODO: we do not really need any here - right? + serialized.opt, + ); + case "richtext": { + const deserializedOptions = { + ...(serialized.options || {}), + inline: + typeof serialized.options?.inline?.img === "object" || + typeof serialized.options?.inline?.a === "object" + ? { + a: + typeof serialized.options?.inline?.a === "object" + ? (deserializeSchema(serialized.options.inline.a) as + | RouteSchema + | StringSchema) + : serialized.options?.inline?.a, + img: + typeof serialized.options?.inline?.img === "object" + ? (deserializeSchema( + serialized.options.inline.img, + ) as ImageSchema< + ImageSource | RemoteSource + >) + : serialized.options?.inline?.img, + } + : (serialized.options?.inline as + | undefined + | { + a: boolean | undefined; + img: boolean | undefined; + }), + }; + return new RichTextSchema(deserializedOptions, serialized.opt); + } + case "record": + return new RecordSchema( + deserializeSchema(serialized.item), + serialized.opt, + [], + null, + serialized.key + ? (deserializeSchema(serialized.key) as Schema) + : null, + serialized.mediaType + ? { + type: serialized.mediaType, + accept: serialized.accept ?? "*/*", + directory: serialized.directory ?? "/public/val", + remote: serialized.remote ?? false, + altSchema: serialized.alt + ? deserializeSchema(serialized.alt) + : undefined, + } + : undefined, + ); + case "keyOf": + return new KeyOfSchema( + serialized.schema, + serialized.path as SourcePath, + serialized.opt, + ); + case "route": { + const routeOptions = serialized.options + ? { + include: serialized.options.include + ? new RegExp( + serialized.options.include.source, + serialized.options.include.flags, + ) + : undefined, + exclude: serialized.options.exclude + ? new RegExp( + serialized.options.exclude.source, + serialized.options.exclude.flags, + ) + : undefined, + } + : undefined; + return new RouteSchema(routeOptions, serialized.opt); + } + case "file": + return new FileSchema( + serialized.options, + serialized.opt, + serialized.remote, + ); + case "image": + return new ImageSchema( + serialized.options, + serialized.opt, + serialized.remote, + ); + case "date": + return new DateSchema(serialized.options, serialized.opt); + default: { + const exhaustiveCheck: never = serialized; + const unknownSerialized: unknown = exhaustiveCheck; + if ( + unknownSerialized && + typeof unknownSerialized === "object" && + "type" in unknownSerialized + ) { + throw new Error(`Unknown schema type: ${unknownSerialized.type}`); + } else { + throw new Error( + `Unknown schema: ${JSON.stringify(unknownSerialized, null, 2)}`, + ); + } + } + } +} diff --git a/packages/core/src/schema/file.test.ts b/packages/core/src/schema/file.test.ts new file mode 100644 index 000000000..abb8dd363 --- /dev/null +++ b/packages/core/src/schema/file.test.ts @@ -0,0 +1,17 @@ +import { initFile } from "../source/file"; +import { SourcePath } from "../val"; +import { file } from "./file"; + +const sourceFile = initFile(); + +describe("FileSchema", () => { + test("assert: should return success if src is a file", () => { + const schema = file(); + const src = sourceFile("/public/val/features.pdf"); + const res = schema["executeAssert"]("path" as SourcePath, src); + expect(res).toEqual({ + success: true, + data: src, + }); + }); +}); diff --git a/packages/core/src/schema/file.ts b/packages/core/src/schema/file.ts new file mode 100644 index 000000000..523efa043 --- /dev/null +++ b/packages/core/src/schema/file.ts @@ -0,0 +1,403 @@ +import { Json } from "../Json"; +import { FILE_REF_PROP, FileSource } from "../source/file"; + +import { + CustomValidateFunction, + Schema, + SchemaAssertResult, + SerializedSchema, +} from "."; +import { VAL_EXTENSION } from "../source"; +import { getValPath, ModulePath, SourcePath } from "../val"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; +import { Internal, RemoteSource, ValModule } from ".."; +import { ReifiedRender } from "../render"; +import { FilesEntryMetadata } from "./files"; +import { getSource } from "../module"; + +export type FileOptions = { + accept?: string; +}; + +export type SerializedFileSchema = { + type: "file"; + options?: FileOptions; + remote?: boolean; + opt: boolean; + customValidate?: boolean; + referencedModule?: string; +}; + +export type FileMetadata = { + mimeType?: string; +}; +export class FileSchema< + Src extends + | FileSource + | RemoteSource + | null, +> extends Schema { + constructor( + private readonly options?: FileOptions, + private readonly opt: boolean = false, + protected readonly isRemote: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + private readonly moduleMetadata: Record< + ModulePath, + Record + > = {}, + ) { + super(); + } + + remote(): FileSchema> { + return new FileSchema( + this.options, + this.opt, + true, + this.customValidateFunctions, + this.moduleMetadata, + ) as FileSchema>; + } + + validate(validationFunction: CustomValidateFunction): FileSchema { + return new FileSchema( + this.options, + this.opt, + this.isRemote, + [...this.customValidateFunctions, validationFunction], + this.moduleMetadata, + ); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + if (this.opt && (src === null || src === undefined)) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + if (src === null || src === undefined) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Non-optional file was null or undefined.`, + value: src, + }, + ], + } as ValidationErrors; + } + if (typeof src[FILE_REF_PROP] !== "string") { + return { + [path]: [ + ...customValidationErrors, + { + message: `File did not have a file reference string. Got: ${typeof src[ + FILE_REF_PROP + ]}`, + value: src, + }, + ], + } as ValidationErrors; + } + if (this.isRemote && src[VAL_EXTENSION] !== "remote") { + return { + [path]: [ + ...customValidationErrors, + { + message: `Expected a remote file, but got a local file.`, + value: src, + fixes: ["file:upload-remote"], + }, + ], + } as ValidationErrors; + } + if (this.isRemote && src[VAL_EXTENSION] === "remote") { + return { + [path]: [ + ...customValidationErrors, + { + message: `Remote file was not checked.`, + value: src, + fixes: ["file:check-remote"], + }, + ], + } as ValidationErrors; + } + if (!this.isRemote && src[VAL_EXTENSION] === "remote") { + return { + [path]: [ + ...customValidationErrors, + { + message: `Expected locale file, but found remote.`, + value: src, + fixes: ["file:download-remote"], + }, + ], + } as ValidationErrors; + } + + if (src[VAL_EXTENSION] !== "file") { + return { + [path]: [ + ...customValidationErrors, + { + message: `File did not have the valid file extension type. Got: ${src[VAL_EXTENSION]}`, + value: src, + }, + ], + } as ValidationErrors; + } + + const { accept } = this.options || {}; + const { mimeType } = src.metadata || {}; + + if (accept && mimeType && !mimeType.includes("/")) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Invalid mime type format. Got: ${mimeType}`, + value: src, + }, + ], + } as ValidationErrors; + } + + if (accept && mimeType && mimeType.includes("/")) { + const acceptedTypes = accept.split(",").map((type) => type.trim()); + + const isValidMimeType = acceptedTypes.some((acceptedType) => { + if (acceptedType === "*/*") { + return true; + } + if (acceptedType.endsWith("/*")) { + const baseType = acceptedType.slice(0, -2); + return mimeType.startsWith(baseType); + } + return acceptedType === mimeType; + }); + + if (!isValidMimeType) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Mime type mismatch. Found '${mimeType}' but schema accepts '${accept}'`, + value: src, + }, + ], + } as ValidationErrors; + } + } + + const fileMimeType = Internal.filenameToMimeType(src[FILE_REF_PROP]); + if (!fileMimeType) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Could not determine mime type from file extension. Got: ${src[FILE_REF_PROP]}`, + value: src, + }, + ], + } as ValidationErrors; + } + + if (fileMimeType !== mimeType) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Mime type and file extension not matching. Mime type is '${mimeType}' but file extension is '${fileMimeType}'`, + value: src, + }, + ], + } as ValidationErrors; + } + + if (src.metadata) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Found metadata, but it could not be validated. File metadata must be an object with the mimeType.`, // These validation errors will have to be picked up by logic outside of this package and revalidated. Reasons: 1) we have to read files to verify the metadata, which is handled differently in different runtimes (Browser, QuickJS, Node.js); 2) we want to keep this package dependency free. + value: src, + fixes: ["file:check-metadata"], + }, + ], + } as ValidationErrors; + } + + return { + [path]: [ + ...customValidationErrors, + { + message: `Missing File metadata.`, + value: src, + fixes: ["file:add-metadata"], + }, + ], + } as ValidationErrors; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { message: `Expected 'object', got 'null'`, typeError: true }, + ], + }, + }; + } + if (typeof src !== "object") { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected object, got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + if (!(FILE_REF_PROP in src)) { + return { + success: false, + errors: { + [path]: [ + { + message: `Value of this schema must use: 'c.file' (error type: missing_ref_prop)`, + typeError: true, + }, + ], + }, + }; + } + if (!(VAL_EXTENSION in src && src[VAL_EXTENSION] === "file")) { + return { + success: false, + errors: { + [path]: [ + { + message: `Value of this schema must use: 'c.file' (error type: missing_file_extension)`, + typeError: true, + }, + ], + }, + }; + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } + + nullable(): FileSchema { + return new FileSchema( + this.options, + true, + this.isRemote, + this.customValidateFunctions as CustomValidateFunction[], + this.moduleMetadata, + ); + } + + protected executeSerialize(): SerializedSchema { + const modulePaths = this.moduleMetadata + ? Object.keys(this.moduleMetadata) + : []; + return { + type: "file", + options: this.options, + opt: this.opt, + remote: this.isRemote, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + referencedModule: + modulePaths.length > 0 ? (modulePaths[0] as string) : undefined, + }; + } + + protected executeRender(): ReifiedRender { + return {}; + } +} + +export const file = ( + options?: FileOptions | ValModule>, +): FileSchema> => { + const isModule = + !!options && + !!Internal.getValPath( + options as ValModule>, + ); + if (isModule) { + const allModules: Record> = {}; + for (const valModule of [ + options as ValModule>, + ]) { + const modulePath = getValPath(valModule) as ModulePath | undefined; + if (modulePath === undefined) { + throw new Error( + `Invalid argument passed to s.file(). Expected a ValModule constructed through c.define, but got an object without a valid module path.`, + ); + } + allModules[modulePath] = getSource(valModule) as Record< + string, + FilesEntryMetadata + >; + } + return new FileSchema({}, false, false, [], allModules); + } + return new FileSchema(options as FileOptions); +}; + +export function convertFileSource< + Metadata extends { readonly [key: string]: Json } | undefined = + | { readonly [key: string]: Json } + | undefined, +>(src: FileSource): { url: string; metadata?: Metadata } { + // TODO: /public should be configurable + if (!src[FILE_REF_PROP].startsWith("/public")) { + return { + url: + src[FILE_REF_PROP] + + (src.patch_id ? `?patch_id=${src["patch_id"]}` : ""), + metadata: src.metadata, + }; + } + + if (src.patch_id) { + return { + url: + "/api/val/files" + src[FILE_REF_PROP] + `?patch_id=${src["patch_id"]}`, + metadata: src.metadata, + }; + } + return { + url: src[FILE_REF_PROP].slice("/public".length), + metadata: src.metadata, + }; +} diff --git a/packages/core/src/schema/files.test.ts b/packages/core/src/schema/files.test.ts new file mode 100644 index 000000000..0d18e1db3 --- /dev/null +++ b/packages/core/src/schema/files.test.ts @@ -0,0 +1,545 @@ +import { SourcePath } from "../val"; +import { files, FilesEntryMetadata, SerializedFilesSchema } from "./files"; + +// Strip deferred-check errors (require CLI/filesystem context, not schema validation) +function filterCheckErrors( + result: + | false + | Record + | undefined, +) { + if (!result) return result; + const checkFixes = ["files:check-unique-folder", "files:check-all-files"]; + const filtered: Record = {}; + for (const [key, errors] of Object.entries(result)) { + const nonCheck = errors.filter( + (e) => !e.fixes?.some((f) => checkFixes.includes(f)), + ); + if (nonCheck.length > 0) filtered[key] = nonCheck; + } + return Object.keys(filtered).length > 0 ? filtered : false; +} + +describe("FilesSchema", () => { + describe("assert", () => { + test("should return success if src is a valid files object", () => { + const schema = files({ accept: "application/pdf" }); + const src: Record = { + "/public/val/document.pdf": { + mimeType: "application/pdf", + }, + }; + expect(schema["executeAssert"]("path" as SourcePath, src)).toEqual({ + success: true, + data: src, + }); + }); + + test("should return error if src is null (non-nullable)", () => { + const schema = files({ accept: "application/pdf" }); + const result = schema["executeAssert"]("path" as SourcePath, null); + expect(result.success).toEqual(false); + }); + + test("should return success if src is null (nullable)", () => { + const schema = files({ accept: "application/pdf" }).nullable(); + expect(schema["executeAssert"]("path" as SourcePath, null)).toEqual({ + success: true, + data: null, + }); + }); + + test("should return error if src is not an object", () => { + const schema = files({ accept: "application/pdf" }); + const result = schema["executeAssert"]("path" as SourcePath, "test"); + expect(result.success).toEqual(false); + }); + + test("should return error if src is an array", () => { + const schema = files({ accept: "application/pdf" }); + const result = schema["executeAssert"]("path" as SourcePath, []); + expect(result.success).toEqual(false); + }); + }); + + describe("validate", () => { + test("should validate directory prefix", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public/val/documents", + }); + const src: Record = { + "/public/val/wrong/document.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const allErrors = Object.values(result as object).flat(); + const dirError = allErrors.find((e: { message: string }) => + e.message.includes("must be within"), + ); + expect(dirError).toBeTruthy(); + expect((dirError as { message: string }).message).toContain( + "must be within the /public/val/documents/ directory", + ); + }); + + test("should accept valid directory prefix", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public/val/documents", + }); + const src: Record = { + "/public/val/documents/report.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have directory error + const filteredResult0 = filterCheckErrors(result); + if (filteredResult0) { + const errors = Object.values(filteredResult0 as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(false); + } + }); + + test("should validate mimeType against accept pattern", () => { + const schema = files({ accept: "application/pdf" }); + const src: Record = { + "/public/val/document.docx": { + mimeType: + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const allErrors = Object.values(result as object).flat(); + const mimeError = allErrors.find((e: { message: string }) => + e.message.includes("Mime type mismatch"), + ); + expect(mimeError).toBeTruthy(); + expect((mimeError as { message: string }).message).toContain( + "Mime type mismatch", + ); + }); + + test("should accept wildcard mimeType patterns", () => { + const schema = files({ accept: "application/*" }); + const src: Record = { + "/public/val/document.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have mime type error + if (result) { + const errors = Object.values(result as object).flat(); + const hasMimeError = errors.some((e: { message: string }) => + e.message.includes("Mime type mismatch"), + ); + expect(hasMimeError).toBe(false); + } + }); + + test("should accept any mimeType with */*", () => { + const schema = files({ accept: "*/*" }); + const src: Record = { + "/public/val/anything.xyz": { + mimeType: "application/octet-stream", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have mime type error + if (result) { + const errors = Object.values(result as object).flat(); + const hasMimeError = errors.some((e: { message: string }) => + e.message.includes("Mime type mismatch"), + ); + expect(hasMimeError).toBe(false); + } + }); + + test("should use default directory /public/val", () => { + const schema = files({ accept: "application/pdf" }); + const src: Record = { + "/public/val/document.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have directory error + const filteredResult1 = filterCheckErrors(result); + if (filteredResult1) { + const errors = Object.values(filteredResult1 as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(false); + } + }); + + test("should accept /public as directory", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public", + }); + const src: Record = { + "/public/document.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have directory error + const filteredResult2 = filterCheckErrors(result); + if (filteredResult2) { + const errors = Object.values(filteredResult2 as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(false); + } + }); + + test("should validate mimeType is a string", () => { + const schema = files({ accept: "application/pdf" }); + const src = { + "/public/val/document.pdf": { + mimeType: 123, + }, + }; + const result = schema["executeValidate"]( + "path" as SourcePath, + src as unknown as Record, + ); + expect(result).toBeTruthy(); + }); + }); + + describe("serialization", () => { + test("should serialize with correct type", () => { + const schema = files({ accept: "application/pdf" }); + const serialized = schema["executeSerialize"](); + expect(serialized.type).toBe("record"); + expect((serialized as SerializedFilesSchema).mediaType).toBe("files"); + expect(serialized.accept).toBe("application/pdf"); + expect(serialized.directory).toBe("/public/val"); + expect(serialized.opt).toBe(false); + expect(serialized.remote).toBe(false); + }); + + test("should serialize with custom directory", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public/val/custom", + }); + const serialized = schema["executeSerialize"](); + expect(serialized.directory).toBe("/public/val/custom"); + }); + + test("should serialize remote flag", () => { + const schema = files({ accept: "application/pdf" }).remote(); + const serialized = schema["executeSerialize"](); + expect(serialized.remote).toBe(true); + }); + + test("should serialize nullable flag", () => { + const schema = files({ accept: "application/pdf" }).nullable(); + const serialized = schema["executeSerialize"](); + expect(serialized.opt).toBe(true); + }); + }); + + describe("remote", () => { + test("should create remote variant", () => { + const schema = files({ accept: "application/pdf" }); + const remoteSchema = schema.remote(); + expect(remoteSchema["executeSerialize"]().remote).toBe(true); + }); + + test("should reject remote URLs when remote is not enabled", () => { + const schema = files({ accept: "application/pdf" }); + const src: Record = { + "https://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/document.pdf": + { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasRemoteError = errors.some((e: { message: string }) => + e.message.includes("Remote URLs are not allowed"), + ); + expect(hasRemoteError).toBe(true); + }); + + test("should accept remote URLs when remote is enabled", () => { + const schema = files({ accept: "application/pdf" }).remote(); + const src: Record = { + "https://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/document.pdf": + { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(filterCheckErrors(result)).toBeFalsy(); + }); + + test("should accept local paths when remote is enabled", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public/val/documents", + }).remote(); + const src: Record = { + "/public/val/documents/local.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have path errors + const filteredResult3 = filterCheckErrors(result); + if (filteredResult3) { + const errors = Object.values(filteredResult3 as object).flat(); + const hasPathError = errors.some( + (e: { message: string }) => + e.message.includes("directory") || e.message.includes("Remote"), + ); + expect(hasPathError).toBe(false); + } + }); + + test("should accept mixed remote and local when remote is enabled", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public/val/documents", + }).remote(); + const src: Record = { + "/public/val/documents/local.pdf": { + mimeType: "application/pdf", + }, + "https://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/documents/remote.pdf": + { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(filterCheckErrors(result)).toBeFalsy(); + }); + + test("should reject invalid remote URLs", () => { + const schema = files({ accept: "application/pdf" }).remote(); + const src: Record = { + "not-a-valid-url": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasUrlError = errors.some((e: { message: string }) => + e.message.includes("Expected a remote URL"), + ); + expect(hasUrlError).toBe(true); + }); + + test("should reject paths outside directory when remote is enabled but path is not a URL", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public/val/documents", + }).remote(); + const src: Record = { + "/public/other/document.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasError = errors.some((e: { message: string }) => + e.message.includes("Expected a remote URL"), + ); + expect(hasError).toBe(true); + }); + + test("should accept http URLs when remote is enabled", () => { + const schema = files({ accept: "application/pdf" }).remote(); + const src: Record = { + "http://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/document.pdf": + { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(filterCheckErrors(result)).toBeFalsy(); + }); + + test("should reject non-Val remote URLs", () => { + const schema = files({ accept: "application/pdf" }).remote(); + const src: Record = { + "https://example.com/document.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasInvalidFormatError = errors.some((e: { message: string }) => + e.message.includes("Invalid remote URL format"), + ); + expect(hasInvalidFormatError).toBe(true); + }); + + test("should reject remote URLs with wrong directory in path", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public/val/documents", + }).remote(); + const src: Record = { + // Remote URL with public/val/other instead of public/val/documents + "https://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/other/document.pdf": + { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasDirectoryError = errors.some((e: { message: string }) => + e.message.includes("not in expected directory"), + ); + expect(hasDirectoryError).toBe(true); + }); + }); + + describe("directory validation", () => { + test("should reject paths with wrong prefix", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public/val/documents", + }); + const src: Record = { + "/wrong/path/document.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(true); + }); + + test("should accept paths with exact directory match", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public", + }); + const src: Record = { + "/public/document.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have directory error + const filteredResult4 = filterCheckErrors(result); + if (filteredResult4) { + const errors = Object.values(filteredResult4 as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(false); + } + }); + + test("should accept paths in subdirectories", () => { + const schema = files({ + accept: "application/pdf", + directory: "/public/val", + }); + const src: Record = { + "/public/val/nested/deep/document.pdf": { + mimeType: "application/pdf", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have directory error + const filteredResult5 = filterCheckErrors(result); + if (filteredResult5) { + const errors = Object.values(filteredResult5 as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(false); + } + }); + }); + + describe("custom validation", () => { + test("should support custom validation function", () => { + const schema = files({ accept: "application/pdf" }).validate((src) => { + if (Object.keys(src ?? {}).length === 0) { + return "At least one file is required"; + } + return false; + }); + const src: Record = {}; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasCustomError = errors.some((e: { message: string }) => + e.message.includes("At least one file is required"), + ); + expect(hasCustomError).toBe(true); + }); + }); + + describe("accept patterns", () => { + test("should accept comma-separated mime types", () => { + const schema = files({ accept: "application/pdf, application/msword" }); + const src1: Record = { + "/public/val/document.pdf": { + mimeType: "application/pdf", + }, + }; + const src2: Record = { + "/public/val/document.doc": { + mimeType: "application/msword", + }, + }; + const result1 = schema["executeValidate"]("path" as SourcePath, src1); + const result2 = schema["executeValidate"]("path" as SourcePath, src2); + // Neither should have mime type errors + [result1, result2].forEach((result) => { + if (result) { + const errors = Object.values(result as object).flat(); + const hasMimeError = errors.some((e: { message: string }) => + e.message.includes("Mime type mismatch"), + ); + expect(hasMimeError).toBe(false); + } + }); + }); + + test("should reject mime types not in accept list", () => { + const schema = files({ accept: "application/pdf, application/msword" }); + const src: Record = { + "/public/val/document.txt": { + mimeType: "text/plain", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasMimeError = errors.some((e: { message: string }) => + e.message.includes("Mime type mismatch"), + ); + expect(hasMimeError).toBe(true); + }); + }); +}); diff --git a/packages/core/src/schema/files.ts b/packages/core/src/schema/files.ts new file mode 100644 index 000000000..ef645ac76 --- /dev/null +++ b/packages/core/src/schema/files.ts @@ -0,0 +1,74 @@ +import { Schema } from "."; +import type { SerializedRecordSchema } from "./record"; +import { RecordSchema } from "./record"; +import { ObjectSchema } from "./object"; +import { StringSchema } from "./string"; + +/** + * Options for s.files() + */ +export type FilesOptions = { + /** + * The accepted mime type pattern (e.g., "application/pdf", "text/*", "*\/*") + */ + accept: string; + /** + * The directory where files should be stored. + * Must start with "/public" (e.g., "/public/val/files") + * @default "/public/val" + */ + directory?: "/public" | `/public/${string}`; + /** + * Whether remote files are allowed + * @default false + */ + remote?: boolean; +}; + +/** + * Metadata for a file entry in the files record + */ +export type FilesEntryMetadata = { + mimeType: string; +}; + +export type SerializedFilesSchema = SerializedRecordSchema; + +type FilesItemProps = { mimeType: StringSchema }; +type FilesItemSrc = { mimeType: string }; + +/** + * Define a collection of files. + * + * @example + * ```typescript + * const schema = s.files({ + * accept: "application/pdf", + * directory: "/public/val/documents", + * }); + * export default c.define("/content/documents.val.ts", schema, { + * "/public/val/documents/report.pdf": { + * mimeType: "application/pdf", + * }, + * }); + * ``` + */ +export const files = ( + options: FilesOptions, +): RecordSchema< + ObjectSchema, + Schema, + Record +> => { + const directory = options.directory ?? "/public/val"; + const itemSchema = new ObjectSchema( + { mimeType: new StringSchema({}, false) }, + false, + ); + return new RecordSchema(itemSchema, false, [], null, null, { + type: "files", + accept: options.accept, + directory, + remote: options.remote ?? false, + }); +}; diff --git a/packages/core/src/schema/image.test.ts b/packages/core/src/schema/image.test.ts new file mode 100644 index 000000000..e46e6b499 --- /dev/null +++ b/packages/core/src/schema/image.test.ts @@ -0,0 +1,23 @@ +import { initFile } from "../source/file"; +import { SourcePath } from "../val"; +import { image } from "./image"; + +const sourceFile = initFile(); +describe("ImageSchema", () => { + test("assert: should return success if src is a file", () => { + const schema = image(); + const src = sourceFile("/public/val/features.png"); + expect(schema["executeAssert"]("path" as SourcePath, src)).toEqual({ + success: true, + data: src, + }); + }); + + test("assert: should return error if src is string", () => { + const schema = image(); + const src = "test"; + expect(schema["executeAssert"]("path" as SourcePath, src).success).toEqual( + false, + ); + }); +}); diff --git a/packages/core/src/schema/image.ts b/packages/core/src/schema/image.ts new file mode 100644 index 000000000..c956aaa2f --- /dev/null +++ b/packages/core/src/schema/image.ts @@ -0,0 +1,418 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + CustomValidateFunction, + Schema, + SchemaAssertResult, + SerializedSchema, +} from "."; +import { VAL_EXTENSION } from "../source"; +import { FileSource, FILE_REF_PROP } from "../source/file"; +import { ImageSource } from "../source/image"; +import { getValPath, ModulePath, SourcePath } from "../val"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; +import { FileMetadata, Internal, ValModule } from ".."; +import { RemoteSource } from "../source/remote"; +import { ReifiedRender } from "../render"; +import { ImagesEntryMetadata } from "./images"; +import { getSource } from "../module"; + +export type ImageOptions = { + ext?: ["jpg"] | ["webp"]; + directory?: string; + prefix?: string; + accept?: string; +}; + +export type SerializedImageSchema = { + type: "image"; + options?: ImageOptions; + opt: boolean; + remote?: boolean; + customValidate?: boolean; + referencedModule?: string; +}; + +export type ImageMetadata = { + width?: number; + height?: number; + mimeType?: string; + alt?: string; + hotspot?: { + x: number; + y: number; + }; +}; +export class ImageSchema< + Src extends + | FileSource + | RemoteSource + | null, +> extends Schema { + constructor( + private readonly options?: ImageOptions, + private readonly opt: boolean = false, + protected readonly isRemote: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + private readonly moduleMetadata: Record< + ModulePath, + Record + > = {}, + ) { + super(); + } + + remote(): ImageSchema> { + return new ImageSchema( + this.options, + this.opt, + true, + this.customValidateFunctions, + this.moduleMetadata, + ) as ImageSchema>; + } + + validate(validationFunction: CustomValidateFunction): ImageSchema { + return new ImageSchema( + this.options, + this.opt, + this.isRemote, + [...this.customValidateFunctions, validationFunction], + this.moduleMetadata, + ); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + if (this.opt && (src === null || src === undefined)) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + if (src === null || src === undefined) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Non-optional image was null or undefined.`, + value: src, + }, + ], + } as ValidationErrors; + } + if (this.isRemote && src[VAL_EXTENSION] !== "remote") { + return { + [path]: [ + ...customValidationErrors, + { + message: `Expected a remote image, but got a local image.`, + value: src, + fixes: ["image:upload-remote"], + }, + ], + } as ValidationErrors; + } + if (this.isRemote && src[VAL_EXTENSION] === "remote") { + return { + [path]: [ + ...customValidationErrors, + { + message: `Remote image was not checked.`, + value: src, + fixes: ["image:check-remote"], + }, + ], + } as ValidationErrors; + } + if (!this.isRemote && src[VAL_EXTENSION] === "remote") { + return { + [path]: [ + ...customValidationErrors, + { + message: `Expected locale image, but found remote.`, + value: src, + fixes: ["image:download-remote"], + }, + ], + } as ValidationErrors; + } + + if (typeof src[FILE_REF_PROP] !== "string") { + return { + [path]: [ + ...customValidationErrors, + { + message: `Image did not have a file reference string. Got: ${typeof src[ + FILE_REF_PROP + ]}`, + value: src, + }, + ], + } as ValidationErrors; + } + + if (src[VAL_EXTENSION] !== "file") { + return { + [path]: [ + ...customValidationErrors, + { + message: `Image did not have the valid file extension type. Got: ${src[VAL_EXTENSION]}`, + value: src, + }, + ], + } as ValidationErrors; + } + + const { accept } = this.options || {}; + const mimeType = src.metadata?.mimeType ?? ""; + + if (accept && mimeType && !mimeType.includes("/")) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Invalid mime type format. Got: '${mimeType}'`, + value: src, + fixes: ["image:check-metadata"], + }, + ], + } as ValidationErrors; + } + + if (accept && mimeType && mimeType.includes("/")) { + const acceptedTypes = accept.split(",").map((type) => type.trim()); + + const isValidMimeType = acceptedTypes.some((acceptedType) => { + if (acceptedType === "*/*") { + return true; + } + if (acceptedType.endsWith("/*")) { + const baseType = acceptedType.slice(0, -2); + return mimeType.startsWith(baseType); + } + return acceptedType === mimeType; + }); + + if (!isValidMimeType) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Mime type mismatch. Found '${mimeType}' but schema accepts '${accept}'`, + value: src, + fixes: ["image:check-metadata"], + }, + ], + } as ValidationErrors; + } + } + + const fileMimeType = Internal.filenameToMimeType(src[FILE_REF_PROP]); + if (!fileMimeType) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Could not determine mime type from file extension. Got: ${src[FILE_REF_PROP]}`, + value: src, + fixes: ["image:check-metadata"], + }, + ], + } as ValidationErrors; + } + + if (fileMimeType && mimeType && fileMimeType !== mimeType) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Mime type and file extension not matching. Mime type is '${mimeType}' but file extension is '${fileMimeType}'`, + value: src, + fixes: ["image:check-metadata"], + }, + ], + } as ValidationErrors; + } + + if (src.metadata) { + if (src.metadata.hotspot) { + if ( + typeof src.metadata.hotspot !== "object" || + typeof src.metadata.hotspot.x !== "number" || + typeof src.metadata.hotspot.y !== "number" + ) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Hotspot must be an object with x and y as numbers.`, + value: src, + }, + ], + } as ValidationErrors; + } + } + return { + [path]: [ + ...customValidationErrors, + { + message: `Found metadata, but it could not be validated. Image metadata must be an object with the required props: width (positive number), height (positive number) and the mime type.`, // These validation errors will have to be picked up by logic outside of this package and revalidated. Reasons: 1) we have to read files to verify the metadata, which is handled differently in different runtimes (Browser, QuickJS, Node.js); 2) we want to keep this package dependency free. + value: src, + fixes: ["image:check-metadata"], + }, + ], + } as ValidationErrors; + } + + const isReferencedModule = Object.keys(this.moduleMetadata).length > 0; + if (src.metadata === undefined && isReferencedModule) { + if (customValidationErrors.length === 0) { + return false; + } + return { + [path]: [...customValidationErrors], + } as ValidationErrors; + } + + return { + [path]: [ + ...customValidationErrors, + { + message: `Could not validate Image metadata.`, + value: src, + fixes: ["image:add-metadata"], + }, + ], + } as ValidationErrors; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { message: `Expected 'object', got 'null'`, typeError: true }, + ], + }, + }; + } + if (typeof src !== "object") { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'object', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + if (!(FILE_REF_PROP in src)) { + return { + success: false, + errors: { + [path]: [ + { + message: `Value of this schema must use: 'c.image' (error type: missing_ref_prop)`, + typeError: true, + }, + ], + }, + }; + } + if (!(VAL_EXTENSION in src && src[VAL_EXTENSION] === "file")) { + return { + success: false, + errors: { + [path]: [ + { + message: `Value of this schema must use: 'c.image' (error type: missing_file_extension)`, + typeError: true, + }, + ], + }, + }; + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } + + nullable(): ImageSchema { + return new ImageSchema( + this.options, + true, + this.isRemote, + this.customValidateFunctions as CustomValidateFunction[], + this.moduleMetadata, + ); + } + + protected executeSerialize(): SerializedSchema { + const modulePaths = this.moduleMetadata + ? Object.keys(this.moduleMetadata) + : []; + return { + type: "image", + options: this.options, + opt: this.opt, + remote: this.isRemote, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + referencedModule: + modulePaths.length > 0 ? (modulePaths[0] as string) : undefined, + }; + } + + protected executeRender(): ReifiedRender { + return {}; + } +} + +export const image = ( + options?: ImageOptions | ValModule>, +): ImageSchema> => { + const isModule = + !!options && + !!Internal.getValPath( + options as ValModule>, + ); + if (isModule) { + const allModules: Record> = {}; + for (const valModule of [ + options as ValModule>, + ]) { + const modulePath = getValPath(valModule) as ModulePath | undefined; + if (modulePath === undefined) { + throw new Error( + `Invalid argument passed to s.image(). Expected a ValModule constructed through c.define, but got an object without a valid module path.`, + ); + } + allModules[modulePath] = getSource(valModule) as Record< + string, + ImagesEntryMetadata + >; + } + return new ImageSchema({}, false, false, [], allModules); + } + return new ImageSchema(options as ImageOptions); +}; diff --git a/packages/core/src/schema/images.test.ts b/packages/core/src/schema/images.test.ts new file mode 100644 index 000000000..f5af9d3e4 --- /dev/null +++ b/packages/core/src/schema/images.test.ts @@ -0,0 +1,579 @@ +import { SourcePath } from "../val"; +import { images, ImagesEntryMetadata, SerializedImagesSchema } from "./images"; +import { string } from "./string"; + +// Strip deferred-check errors (require CLI/filesystem context, not schema validation) +function filterCheckErrors( + result: + | false + | Record + | undefined, +) { + if (!result) return result; + const checkFixes = ["images:check-unique-folder", "images:check-all-files"]; + const filtered: Record = {}; + for (const [key, errors] of Object.entries(result)) { + const nonCheck = errors.filter( + (e) => !e.fixes?.some((f) => checkFixes.includes(f)), + ); + if (nonCheck.length > 0) filtered[key] = nonCheck; + } + return Object.keys(filtered).length > 0 ? filtered : false; +} + +describe("ImagesSchema", () => { + describe("assert", () => { + test("should return success if src is a valid images object", () => { + const schema = images({ accept: "image/webp" }); + const src: Record = { + "/public/val/test.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Test image", + }, + }; + expect(schema["executeAssert"]("path" as SourcePath, src)).toEqual({ + success: true, + data: src, + }); + }); + + test("should return error if src is null (non-nullable)", () => { + const schema = images({ accept: "image/webp" }); + const result = schema["executeAssert"]("path" as SourcePath, null); + expect(result.success).toEqual(false); + }); + + test("should return success if src is null (nullable)", () => { + const schema = images({ accept: "image/webp" }).nullable(); + expect(schema["executeAssert"]("path" as SourcePath, null)).toEqual({ + success: true, + data: null, + }); + }); + + test("should return error if src is not an object", () => { + const schema = images({ accept: "image/webp" }); + const result = schema["executeAssert"]("path" as SourcePath, "test"); + expect(result.success).toEqual(false); + }); + + test("should return error if src is an array", () => { + const schema = images({ accept: "image/webp" }); + const result = schema["executeAssert"]("path" as SourcePath, []); + expect(result.success).toEqual(false); + }); + }); + + describe("validate", () => { + test("should validate directory prefix", () => { + const schema = images({ + accept: "image/webp", + directory: "/public/val/images", + }); + const src: Record = { + "/public/val/wrong/test.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Test image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const allErrors = Object.values(result as object).flat(); + const dirError = allErrors.find((e: { message: string }) => + e.message.includes("must be within"), + ); + expect(dirError).toBeTruthy(); + expect((dirError as { message: string }).message).toContain( + "must be within the /public/val/images/ directory", + ); + }); + + test("should accept valid directory prefix", () => { + const schema = images({ + accept: "image/webp", + directory: "/public/val/images", + }); + const src: Record = { + "/public/val/images/test.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Test image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have directory error since path is valid + const filteredResult0 = filterCheckErrors(result); + if (filteredResult0) { + const errors = Object.values(filteredResult0 as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(false); + } + }); + + test("should validate mimeType against accept pattern", () => { + const schema = images({ accept: "image/webp" }); + const src: Record = { + "/public/val/test.png": { + width: 800, + height: 600, + mimeType: "image/png", + alt: "Test image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const allErrors = Object.values(result as object).flat(); + const mimeError = allErrors.find((e: { message: string }) => + e.message.includes("Mime type mismatch"), + ); + expect(mimeError).toBeTruthy(); + expect((mimeError as { message: string }).message).toContain( + "Mime type mismatch", + ); + }); + + test("should accept wildcard mimeType patterns", () => { + const schema = images({ accept: "image/*" }); + const src: Record = { + "/public/val/test.png": { + width: 800, + height: 600, + mimeType: "image/png", + alt: "Test image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have mime type error (but may have metadata check error) + if (result) { + const errors = Object.values(result as object).flat(); + const hasMimeError = errors.some((e: { message: string }) => + e.message.includes("Mime type mismatch"), + ); + expect(hasMimeError).toBe(false); + } + }); + + test("should validate required width and height", () => { + const schema = images({ accept: "image/webp" }); + const src = { + "/public/val/test.webp": { + mimeType: "image/webp", + alt: "Test image", + }, + }; + const result = schema["executeValidate"]( + "path" as SourcePath, + src as unknown as Record, + ); + expect(result).toBeTruthy(); + }); + + test("should validate alt with custom alt schema", () => { + const schema = images({ + accept: "image/webp", + alt: string().minLength(10), + }); + const src: Record = { + "/public/val/test.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Short", // Less than 10 chars + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + }); + + test("should allow null alt when using nullable alt schema", () => { + const schema = images({ + accept: "image/webp", + alt: string().nullable(), + }); + const src: Record = { + "/public/val/test.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: null, + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have alt-related errors + if (result) { + const errors = Object.values(result as object).flat(); + const hasAltError = errors.some( + (e: { message: string }) => + e.message.includes("alt") || e.message.includes("string"), + ); + expect(hasAltError).toBe(false); + } + }); + + test("should use default directory /public/val", () => { + const schema = images({ accept: "image/webp" }); + const src: Record = { + "/public/val/test.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Test", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have directory error + const filteredResult1 = filterCheckErrors(result); + if (filteredResult1) { + const errors = Object.values(filteredResult1 as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(false); + } + }); + + test("should validate hotspot if present", () => { + const schema = images({ accept: "image/webp" }); + const src = { + "/public/val/test.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Test", + hotspot: { x: "invalid", y: 0.5 }, + }, + }; + const result = schema["executeValidate"]( + "path" as SourcePath, + src as unknown as Record, + ); + expect(result).toBeTruthy(); + }); + }); + + describe("serialization", () => { + test("should serialize with correct type", () => { + const schema = images({ accept: "image/webp" }); + const serialized = schema["executeSerialize"](); + expect(serialized.type).toBe("record"); + expect((serialized as SerializedImagesSchema).mediaType).toBe("images"); + expect(serialized.accept).toBe("image/webp"); + expect(serialized.directory).toBe("/public/val"); + expect(serialized.opt).toBe(false); + expect(serialized.remote).toBe(false); + }); + + test("should serialize with custom directory", () => { + const schema = images({ + accept: "image/png", + directory: "/public/val/custom", + }); + const serialized = schema["executeSerialize"](); + expect(serialized.directory).toBe("/public/val/custom"); + }); + + test("should serialize remote flag", () => { + const schema = images({ accept: "image/webp" }).remote(); + const serialized = schema["executeSerialize"](); + expect(serialized.remote).toBe(true); + }); + + test("should serialize nullable flag", () => { + const schema = images({ accept: "image/webp" }).nullable(); + const serialized = schema["executeSerialize"](); + expect(serialized.opt).toBe(true); + }); + }); + + describe("remote", () => { + test("should create remote variant", () => { + const schema = images({ accept: "image/webp" }); + const remoteSchema = schema.remote(); + expect(remoteSchema["executeSerialize"]().remote).toBe(true); + }); + + test("should reject remote URLs when remote is not enabled", () => { + const schema = images({ accept: "image/webp" }); + const src: Record = { + "https://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/image.webp": + { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Remote image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasRemoteError = errors.some((e: { message: string }) => + e.message.includes("Remote URLs are not allowed"), + ); + expect(hasRemoteError).toBe(true); + }); + + test("should accept remote URLs when remote is enabled", () => { + const schema = images({ accept: "image/webp" }).remote(); + const src: Record = { + "https://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/image.webp": + { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Remote image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(filterCheckErrors(result)).toBeFalsy(); + }); + + test("should accept local paths when remote is enabled", () => { + const schema = images({ + accept: "image/webp", + directory: "/public/val/images", + }).remote(); + const src: Record = { + "/public/val/images/local.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Local image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have path errors + const filteredResult2 = filterCheckErrors(result); + if (filteredResult2) { + const errors = Object.values(filteredResult2 as object).flat(); + const hasPathError = errors.some( + (e: { message: string }) => + e.message.includes("directory") || e.message.includes("Remote"), + ); + expect(hasPathError).toBe(false); + } + }); + + test("should accept mixed remote and local when remote is enabled", () => { + const schema = images({ + accept: "image/webp", + directory: "/public/val/images", + }).remote(); + const src: Record = { + "/public/val/images/local.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Local image", + }, + "https://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/images/remote.webp": + { + width: 1920, + height: 1080, + mimeType: "image/webp", + alt: "Remote image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(filterCheckErrors(result)).toBeFalsy(); + }); + + test("should reject invalid remote URLs", () => { + const schema = images({ accept: "image/webp" }).remote(); + const src: Record = { + "not-a-valid-url": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Invalid URL", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasUrlError = errors.some((e: { message: string }) => + e.message.includes("Expected a remote URL"), + ); + expect(hasUrlError).toBe(true); + }); + + test("should reject paths outside directory when remote is enabled but path is not a URL", () => { + const schema = images({ + accept: "image/webp", + directory: "/public/val/images", + }).remote(); + const src: Record = { + "/public/other/image.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Wrong directory", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasError = errors.some((e: { message: string }) => + e.message.includes("Expected a remote URL"), + ); + expect(hasError).toBe(true); + }); + + test("should accept http URLs when remote is enabled", () => { + const schema = images({ accept: "image/webp" }).remote(); + const src: Record = { + "http://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/image.webp": + { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "HTTP image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(filterCheckErrors(result)).toBeFalsy(); + }); + + test("should reject non-Val remote URLs", () => { + const schema = images({ accept: "image/webp" }).remote(); + const src: Record = { + "https://example.com/image.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "External image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasInvalidFormatError = errors.some((e: { message: string }) => + e.message.includes("Invalid remote URL format"), + ); + expect(hasInvalidFormatError).toBe(true); + }); + + test("should reject remote URLs with wrong directory in path", () => { + const schema = images({ + accept: "image/webp", + directory: "/public/val/images", + }).remote(); + const src: Record = { + // Remote URL with public/val/other instead of public/val/images + "https://remote.val.build/file/p/proj123/b/01/v/1.0.0/h/abc123/f/def456/p/public/val/other/image.webp": + { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Wrong directory in remote", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasDirectoryError = errors.some((e: { message: string }) => + e.message.includes("not in expected directory"), + ); + expect(hasDirectoryError).toBe(true); + }); + }); + + describe("directory validation", () => { + test("should reject paths with wrong prefix", () => { + const schema = images({ + accept: "image/webp", + directory: "/public/val/images", + }); + const src: Record = { + "/wrong/path/image.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Wrong path", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(true); + }); + + test("should accept paths with exact directory match", () => { + const schema = images({ + accept: "image/webp", + directory: "/public", + }); + const src: Record = { + "/public/image.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Public root image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have directory error + const filteredResult3 = filterCheckErrors(result); + if (filteredResult3) { + const errors = Object.values(filteredResult3 as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(false); + } + }); + + test("should accept paths in subdirectories", () => { + const schema = images({ + accept: "image/webp", + directory: "/public/val", + }); + const src: Record = { + "/public/val/nested/deep/image.webp": { + width: 800, + height: 600, + mimeType: "image/webp", + alt: "Nested image", + }, + }; + const result = schema["executeValidate"]("path" as SourcePath, src); + // Should not have directory error + const filteredResult4 = filterCheckErrors(result); + if (filteredResult4) { + const errors = Object.values(filteredResult4 as object).flat(); + const hasDirError = errors.some((e: { message: string }) => + e.message.includes("directory"), + ); + expect(hasDirError).toBe(false); + } + }); + }); + + describe("custom validation", () => { + test("should support custom validation function", () => { + const schema = images({ accept: "image/webp" }).validate((src) => { + if (Object.keys(src ?? {}).length === 0) { + return "At least one image is required"; + } + return false; + }); + const src: Record = {}; + const result = schema["executeValidate"]("path" as SourcePath, src); + expect(result).toBeTruthy(); + const errors = Object.values(result as object).flat(); + const hasCustomError = errors.some((e: { message: string }) => + e.message.includes("At least one image is required"), + ); + expect(hasCustomError).toBe(true); + }); + }); +}); diff --git a/packages/core/src/schema/images.ts b/packages/core/src/schema/images.ts new file mode 100644 index 000000000..38b308002 --- /dev/null +++ b/packages/core/src/schema/images.ts @@ -0,0 +1,119 @@ +import { Schema } from "."; +import type { SerializedRecordSchema } from "./record"; +import { RecordSchema } from "./record"; +import { ObjectSchema } from "./object"; +import { StringSchema, string } from "./string"; +import { NumberSchema } from "./number"; + +/** + * Alt schema type - can be a string, nullable string, or a record of locale to string + */ +export type AltSchema = + | StringSchema + | StringSchema + | RecordSchema, Schema, Record>; + +/** + * Options for s.images() + */ +export type ImagesOptions = { + /** + * The accepted mime type pattern. Must be an image type (e.g., "image/png", "image/webp", "image/*") + */ + accept: Accept; + /** + * The directory where images should be stored. + * Must start with "/public" (e.g., "/public/val/images") + * @default "/public/val" + */ + directory?: "/public" | `/public/${string}`; + /** + * Alt text schema. Can be: + * - s.string() for required alt text + * - s.string().nullable() for optional alt text (default) + * - s.record(s.string(), s.string()) for locale-based alt text + */ + alt?: AltSchema; + /** + * Whether remote images are allowed + * @default false + */ + remote?: boolean; +}; + +/** + * Metadata for an image entry in the images record + */ +export type ImagesEntryMetadata = { + width: number; + height: number; + mimeType: string; + alt: string | null; + hotspot?: { + x: number; + y: number; + }; +}; + +export type SerializedImagesSchema = SerializedRecordSchema; + +// Item schema types for images (alt simplified to string | null for typing) +type ImagesItemProps = { + width: NumberSchema; + height: NumberSchema; + mimeType: StringSchema; + alt: StringSchema; +}; +type ImagesItemSrc = { + width: number; + height: number; + mimeType: string; + alt: string | null; +}; + +/** + * Define a collection of images. + * + * @example + * ```typescript + * const schema = s.images({ + * accept: "image/webp", + * directory: "/public/val/images", + * alt: s.string().minLength(4), + * }); + * export default c.define("/content/images.val.ts", schema, { + * "/public/val/images/hero.webp": { + * width: 1920, + * height: 1080, + * mimeType: "image/webp", + * alt: "Hero image", + * }, + * }); + * ``` + */ +export const images = ( + options: ImagesOptions, +): RecordSchema< + ObjectSchema, + Schema, + Record +> => { + const directory = options.directory ?? "/public/val"; + const altSchema = options.alt ?? string().nullable(); + const itemSchema = new ObjectSchema( + { + width: new NumberSchema(undefined, false), + height: new NumberSchema(undefined, false), + mimeType: new StringSchema({}, false), + alt: altSchema, + }, + false, + ) as ObjectSchema; + return new RecordSchema(itemSchema, false, [], null, null, { + type: "images", + accept: options.accept, + directory, + remote: options.remote ?? false, + altSchema, + }); +}; diff --git a/packages/core/src/schema/index.ts b/packages/core/src/schema/index.ts new file mode 100644 index 000000000..4443d7a72 --- /dev/null +++ b/packages/core/src/schema/index.ts @@ -0,0 +1,166 @@ +// import { RemoteCompatibleSource, RemoteSource } from "../source/remote"; +import { SelectorSource } from "../selector"; +import { ModuleFilePath, SourcePath } from "../val"; +import { SerializedArraySchema } from "./array"; +import { SerializedBooleanSchema } from "./boolean"; +import { SerializedFileSchema } from "./file"; +import { SerializedImageSchema } from "./image"; +import { SerializedKeyOfSchema } from "./keyOf"; +import { SerializedLiteralSchema } from "./literal"; +import { SerializedNumberSchema } from "./number"; +import { SerializedObjectSchema } from "./object"; +import { SerializedRecordSchema } from "./record"; +import { SerializedRichTextSchema } from "./richtext"; +import { RawString, SerializedStringSchema } from "./string"; +import { SerializedUnionSchema } from "./union"; +import { SerializedDateSchema } from "./date"; +import { SerializedRouteSchema } from "./route"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; +import { FileSource } from "../source/file"; +import { GenericRichTextSourceNode, RichTextSource } from "../source/richtext"; +import { ReifiedRender } from "../render"; +// import { SerializedI18nSchema } from "./future/i18n"; +// import { SerializedOneOfSchema } from "./future/oneOf"; + +export type SerializedSchema = + // | SerializedOneOfSchema + // | SerializedI18nSchema + | SerializedStringSchema + | SerializedLiteralSchema + | SerializedBooleanSchema + | SerializedNumberSchema + | SerializedObjectSchema + | SerializedArraySchema + | SerializedUnionSchema + | SerializedRichTextSchema + | SerializedRecordSchema + | SerializedKeyOfSchema + | SerializedFileSchema + | SerializedDateSchema + | SerializedRouteSchema + | SerializedImageSchema; + +type Primitives = number | string | boolean | null | FileSource; +export type AssertError = + | { + message: string; + schemaError: true; + } + | { + message: string; + typeError: true; + } + | { + message: string; + internalError: true; + }; +export type SchemaAssertResult = + | { + // It would be more elegant if we derived this in the individual schema classes, however we must support the case when the abstract class is the only thing available (Schema does not dispatch on type-level to ArraySchema) + data: Src extends RawString + ? string + : // eslint-disable-next-line @typescript-eslint/no-empty-object-type + Src extends RichTextSource<{}> + ? GenericRichTextSourceNode[] + : Src extends Primitives + ? Src + : Src extends Array + ? SelectorSource[] + : Src extends { [key: string]: SelectorSource } + ? { [key in keyof Src]: SelectorSource } + : never; + success: true; + } + | { success: false; errors: Record }; +export type CustomValidateFunction = ( + src: Src, + ctx: { path: SourcePath }, +) => false | string; +export abstract class Schema { + /** Validate the value of source content */ + protected abstract executeValidate( + path: SourcePath, + src: Src, + ): ValidationErrors; + protected executeCustomValidateFunctions( + src: Src, + customValidateFunctions: CustomValidateFunction[], + ctx: { path: SourcePath }, + ): ValidationError[] { + const errors: ValidationError[] = []; + for (const customValidateFunction of customValidateFunctions) { + try { + const result = customValidateFunction(src, ctx); + if (result) { + errors.push({ message: result, value: src }); + } + } catch (err) { + errors.push({ + message: `Error in custom validate function: ${err instanceof Error ? err.message : String(err)}`, + value: src, + schemaError: true, + }); + } + } + return errors; + } + /** + * Check if the **root** **type** of source is correct. + * + * The difference between assert and validate is: + * - assert verifies that the root **type** of the source is correct (it does not recurse down). Therefore, assert can be used as a runtime type check. + * - validate checks the **value** of the source in addition to the type. It recurses down the source. + * + * For example assert fails for a StringSchema if the source is not a string, + * it will not fail if the length is not correct. + * Validate will check the length and all other constraints. + * + * Assert is useful if you have a generic schema and need to make sure the root type is valid. + * When using assert, you must assert recursively if you want to verify the entire source. + * For example, if you have an object schema, you must assert each key / value pair manually. + */ + protected abstract executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult; // TODO: rename to parse? or _assert / _parse to indicate it is private? Or make protected (requires us to have some sort of calling it in the UX Val code) + abstract nullable(): Schema; + protected abstract executeSerialize(): SerializedSchema; + protected abstract executeRender( + sourcePath: SourcePath | ModuleFilePath, + src: Src, + ): ReifiedRender; + // remote(): Src extends RemoteCompatibleSource + // ? Schema> + // : never { + // // TODO: Schema + // throw new Error("You need Val Ultra to use .remote()"); + // } + + /** MUTATES! since internal and perf sensitive */ + protected appendValidationError( + current: ValidationErrors, + path: SourcePath, + message: string, + value: unknown, + schemaError?: boolean, + ): ValidationErrors { + if (current) { + if (current[path]) { + current[path].push({ message, value, schemaError }); + } else { + current[path] = [{ message, value, schemaError }]; + } + return current; + } else { + return { + [path]: [{ message, value, schemaError }], + } as ValidationErrors; + } + } +} + +export type SelectorOfSchema> = + T extends Schema ? Src : never; // TODO: SourceError<"Could not determine type of Schema"> diff --git a/packages/core/src/schema/keyOf.test.ts b/packages/core/src/schema/keyOf.test.ts new file mode 100644 index 000000000..e83768b02 --- /dev/null +++ b/packages/core/src/schema/keyOf.test.ts @@ -0,0 +1,22 @@ +import { define } from "../module"; +import { SourcePath } from "../val"; +import { keyOf } from "./keyOf"; +import { object } from "./object"; +import { record } from "./record"; +import { string } from "./string"; + +describe("KeyOfSchema", () => { + test("assert: should return success if src is a keyOf value", () => { + const schema = keyOf( + define("/path2", record(object({ key: string() })), { + one: { key: "test" }, + }), + ); + const src = "one"; + const res = schema["executeAssert"]("path" as SourcePath, src); + expect(res).toEqual({ + success: true, + data: src, + }); + }); +}); diff --git a/packages/core/src/schema/keyOf.ts b/packages/core/src/schema/keyOf.ts new file mode 100644 index 000000000..04240c330 --- /dev/null +++ b/packages/core/src/schema/keyOf.ts @@ -0,0 +1,346 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + CustomValidateFunction, + Schema, + SchemaAssertResult, + SerializedSchema, +} from "../schema"; +import { ValModuleBrand } from "../module"; +import { GenericSelector, GetSchema } from "../selector"; +import { Source, SourceObject } from "../source"; +import { SourcePath, getValPath } from "../val"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; +import { RawString } from "./string"; +import { ReifiedRender } from "../render"; +import { ObjectSchema } from "./object"; +import { RecordSchema } from "./record"; + +export type SerializedKeyOfSchema = { + type: "keyOf"; + path: SourcePath; + schema?: SerializedRefSchema | undefined; + opt: boolean; + values: "string" | string[]; + customValidate?: boolean; +}; +type SerializedRefSchema = + | { + type: "object"; + keys: string[]; + opt?: boolean | undefined; + } + | { + type: "record"; + opt?: boolean | undefined; + }; + +type KeyOfSelector> = + Sel extends GenericSelector + ? S extends readonly Source[] + ? number + : S extends SourceObject + ? string extends keyof S + ? RawString + : keyof S + : S extends Record // do we need record? + ? RawString + : never + : never; + +export class KeyOfSchema< + Sel extends GenericSelector, + Src extends KeyOfSelector | null, +> extends Schema { + constructor( + private readonly schema?: SerializedRefSchema, + private readonly sourcePath?: SourcePath, + private readonly opt: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + ) { + super(); + } + + validate( + validationFunction: (src: Src) => false | string, + ): KeyOfSchema { + return new KeyOfSchema(this.schema, this.sourcePath, this.opt, [ + ...this.customValidateFunctions, + validationFunction, + ]); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + if (this.opt && (src === null || src === undefined)) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + if (!this.schema) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Schema not found for module. keyOf must be used with a Val Module`, + }, + ], + }; + } + const serializedSchema = this.schema; + + if ( + !( + serializedSchema.type === "object" || serializedSchema.type === "record" + ) + ) { + return { + [path]: [ + ...customValidationErrors, + { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + message: `Schema in keyOf must be an 'object' or 'record'. Found '${(serializedSchema as any).type || "unknown"}'`, + }, + ], + }; + } + if (serializedSchema.opt && (src === null || src === undefined)) { + return false; + } + if (serializedSchema.type === "record" && typeof src !== "string") { + return { + [path]: [ + ...customValidationErrors, + { + message: "Type of value in keyof (record) must be 'string'", + }, + ], + }; + } + if (serializedSchema.type === "object") { + const keys = serializedSchema.keys; + if (!keys.includes(src as string)) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Value of keyOf (object) must be: ${keys.join( + ", ", + )}. Found: ${src}`, + }, + ], + }; + } + } + if (serializedSchema.type === "record") { + if (typeof src !== "string") { + return { + [path]: [ + ...customValidationErrors, + { + message: `Value of keyOf (record) must be 'string'. Found: ${typeof src}`, + }, + ], + }; + } + return { + [path]: [ + ...customValidationErrors, + { + fixes: ["keyof:check-keys"], + message: `Did not validate keyOf (record). This error (keyof:check-keys) should typically be processed by Val internally. Seeing this error most likely means you have a Val version mismatch.`, + value: { + key: src, + sourcePath: this.sourcePath, + }, + }, + ], + }; + } + if (customValidationErrors.length > 0) { + return { + [path]: customValidationErrors, + }; + } + return false; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'string', got 'null'`, + typeError: true, + }, + ], + }, + }; + } + const schema = this.schema; + if (!schema) { + return { + success: false, + errors: { + [path]: [ + { + message: `Neither key nor schema was found. keyOf is missing an argument.`, + typeError: true, + }, + ], + }, + }; + } + const serializedSchema = schema; + + if ( + !( + serializedSchema.type === "object" || serializedSchema.type === "record" + ) + ) { + return { + success: false, + errors: { + [path]: [ + { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + message: `Schema of first argument must be either: 'array', 'object' or 'record'. Found '${(serializedSchema as any).type || "unknown"}'`, + typeError: true, + }, + ], + }, + }; + } + if (serializedSchema.type === "record" && typeof src !== "string") { + return { + success: false, + errors: { + [path]: [ + { + message: `Value of keyOf (record) must be 'string', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + // We check actual value here, since TypeScript also does this. Literals are used in other types (unions), + // and there it also makes sense to check the actual string values (i.e. that it is not just a string) since + // missing one would lead to a runtime error. At least this is what we are thinking currently. + if (serializedSchema.type === "object") { + const keys = serializedSchema.keys; + if (!keys.includes(src as string)) { + return { + success: false, + errors: { + [path]: [ + { + message: `Value of keyOf (object) must be: ${keys.join( + ", ", + )}. Found: ${src}`, + typeError: true, + }, + ], + }, + }; + } + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } + + nullable(): KeyOfSchema { + return new KeyOfSchema(this.schema, this.sourcePath, true); + } + + protected executeSerialize(): SerializedSchema { + const path = this.sourcePath; + if (!path) { + throw new Error( + "Cannot serialize keyOf schema with empty selector. TIP: keyOf must be used with a Val Module of record schema.", + ); + } + const serializedSchema = this.schema; + if (!serializedSchema) { + throw new Error("Cannot serialize keyOf schema with empty selector."); + } + + let values: SerializedKeyOfSchema["values"]; + switch (serializedSchema.type) { + case "record": + values = "string"; + break; + case "object": + values = serializedSchema.keys; + break; + default: + throw new Error( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + `Cannot serialize keyOf schema with selector of type '${(serializedSchema as any).type || "unknown"}'`, + ); + } + return { + type: "keyOf", + path: path, + schema: serializedSchema, + opt: this.opt, + values, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + } satisfies SerializedKeyOfSchema; + } + + protected executeRender(): ReifiedRender { + return {}; + } +} + +export const keyOf = < + Src extends GenericSelector & ValModuleBrand, // ValModuleBrand enforces call site to pass in a val module - selectors are not allowed. The reason is that this should make it easier to patch. We might be able to relax this constraint in the future +>( + valModule: Src, +): KeyOfSchema> => { + const refSchema = valModule?.[GetSchema]; + let serializedRefSchema: SerializedRefSchema | undefined = undefined; + if (refSchema instanceof ObjectSchema) { + let keys: string[] = []; + try { + keys = Object.keys(refSchema?.["items"] || {}); + } catch (e) { + // ignore this error here + } + serializedRefSchema = { + type: "object", + keys, + opt: refSchema?.["opt"], + }; + } else if (refSchema instanceof RecordSchema) { + serializedRefSchema = { + type: "record", + opt: refSchema?.["opt"], + }; + } + return new KeyOfSchema( + serializedRefSchema, + getValPath(valModule), + ) as KeyOfSchema>; +}; diff --git a/packages/core/src/schema/literal.test.ts b/packages/core/src/schema/literal.test.ts new file mode 100644 index 000000000..ec1126478 --- /dev/null +++ b/packages/core/src/schema/literal.test.ts @@ -0,0 +1,13 @@ +import { SourcePath } from "../val"; +import { literal } from "./literal"; + +describe("LiteralSchema", () => { + test("assert: should return success if src is a literal", () => { + const schema = literal("val"); + const src = "val"; + expect(schema["executeAssert"]("path" as SourcePath, src)).toEqual({ + success: true, + data: src, + }); + }); +}); diff --git a/packages/core/src/schema/literal.ts b/packages/core/src/schema/literal.ts new file mode 100644 index 000000000..289d52d82 --- /dev/null +++ b/packages/core/src/schema/literal.ts @@ -0,0 +1,140 @@ +import { + CustomValidateFunction, + Schema, + SchemaAssertResult, + SerializedSchema, +} from "."; +import { ReifiedRender } from "../render"; +import { SourcePath } from "../val"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; + +export type SerializedLiteralSchema = { + type: "literal"; + value: string; + opt: boolean; + customValidate?: boolean; +}; + +export class LiteralSchema extends Schema { + constructor( + private readonly value: string, + private readonly opt: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + ) { + super(); + } + + validate( + validationFunction: (src: Src) => false | string, + ): LiteralSchema { + return new LiteralSchema(this.value, this.opt, [ + ...this.customValidateFunctions, + validationFunction, + ]); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + if (this.opt && (src === null || src === undefined)) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + if (typeof src !== "string") { + return { + [path]: [ + ...customValidationErrors, + { message: `Expected 'string', got '${typeof src}'`, value: src }, + ], + } as ValidationErrors; + } + if (src !== this.value) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Expected literal '${this.value}', got '${src}'`, + value: src, + }, + ], + } as ValidationErrors; + } + if (customValidationErrors.length > 0) { + return { + [path]: customValidationErrors, + }; + } + return false; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'string', got 'null'`, + typeError: true, + }, + ], + }, + }; + } + if (typeof src === "string" && src === this.value) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + return { + success: false, + errors: { + [path]: [ + { + message: `Expected literal '${this.value}', got '${src}'`, + typeError: true, + }, + ], + }, + }; + } + + nullable(): LiteralSchema { + return new LiteralSchema(this.value, true); + } + + protected executeSerialize(): SerializedSchema { + return { + type: "literal", + value: this.value, + opt: this.opt, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + } + + protected executeRender(): ReifiedRender { + return {}; + } +} + +export const literal = (value: T): LiteralSchema => { + return new LiteralSchema(value); +}; diff --git a/packages/core/src/schema/number.test.ts b/packages/core/src/schema/number.test.ts new file mode 100644 index 000000000..a5369a60e --- /dev/null +++ b/packages/core/src/schema/number.test.ts @@ -0,0 +1,67 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { SourcePath } from "../val"; +import { number } from "./number"; + +describe("NumberSchema", () => { + test("assert: should return success if src is a number", () => { + const schema = number().nullable(); + expect(schema["executeAssert"]("foo" as SourcePath, 1)).toEqual({ + success: true, + data: 1, + }); + }); + test("assert: should return errors if src is a string", () => { + const schema = number(); + expect( + schema["executeAssert"]("foo" as SourcePath, "1" as any).success, + ).toEqual(false); + }); + + test("validate: should return success if src is a number", () => { + const schema = number().nullable(); + const result = schema["executeValidate"]("foo" as SourcePath, 1 as any); + expect(result).toEqual(false); + }); + + test("validate: should return success if src is within min and max", () => { + const schema = number().min(10).max(20); + const result = schema["executeValidate"]("foo" as SourcePath, 15 as any); + expect(result).toEqual(false); + }); + + test("validate: should return errors if src is greater than max", () => { + const schema = number().max(10); + const result = schema["executeValidate"]("foo" as SourcePath, 11 as any); + expect(result).toMatchObject({ + foo: [ + { + value: 11, + }, + ], + }); + }); + + test("validate: should return errors if src is less than min", () => { + const schema = number().min(10); + const result = schema["executeValidate"]("foo" as SourcePath, 9 as any); + expect(result).toMatchObject({ + foo: [ + { + value: 9, + }, + ], + }); + }); + + test("validate: should return errors if src is not a number", () => { + const schema = number(); + const result = schema["executeValidate"]("foo" as SourcePath, "1" as any); + expect(result).toMatchObject({ + foo: [ + { + value: "1", + }, + ], + }); + }); +}); diff --git a/packages/core/src/schema/number.ts b/packages/core/src/schema/number.ts new file mode 100644 index 000000000..435450c06 --- /dev/null +++ b/packages/core/src/schema/number.ts @@ -0,0 +1,164 @@ +import { + CustomValidateFunction, + Schema, + SchemaAssertResult, + SerializedSchema, +} from "."; +import { ReifiedRender } from "../render"; +import { SourcePath } from "../val"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; + +type NumberOptions = { + max?: number; + min?: number; +}; + +export type SerializedNumberSchema = { + type: "number"; + options?: NumberOptions; + opt: boolean; + customValidate?: boolean; +}; + +export class NumberSchema extends Schema { + constructor( + private readonly options?: NumberOptions, + private readonly opt: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + ) { + super(); + } + + validate( + validationFunction: (src: Src) => false | string, + ): NumberSchema { + return new NumberSchema(this.options, this.opt, [ + ...this.customValidateFunctions, + validationFunction, + ]); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + if (this.opt && (src === null || src === undefined)) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + if (typeof src !== "number") { + return { + [path]: [ + ...customValidationErrors, + { message: `Expected 'number', got '${typeof src}'`, value: src }, + ], + } as ValidationErrors; + } + if (this.options?.max && src > this.options.max) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Expected 'number' less than ${this.options.max}`, + value: src, + }, + ], + } as ValidationErrors; + } + if (this.options?.min && src < this.options.min) { + return { + [path]: [ + ...customValidationErrors, + { + message: `Expected 'number' greater than ${this.options.min}`, + value: src, + }, + ], + } as ValidationErrors; + } + if (customValidationErrors.length > 0) { + return { + [path]: customValidationErrors, + }; + } + return false; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { + message: "Expected 'number', got 'null'", + typeError: true, + }, + ], + }, + }; + } + if (typeof src === "number") { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'number', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + + nullable(): NumberSchema { + return new NumberSchema(this.options, true); + } + + max(max: number): NumberSchema { + return new NumberSchema({ ...this.options, max }, this.opt); + } + + min(min: number): NumberSchema { + return new NumberSchema({ ...this.options, min }, this.opt); + } + + protected executeSerialize(): SerializedSchema { + return { + type: "number", + options: this.options, + opt: this.opt, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + } + + protected executeRender(): ReifiedRender { + return {}; + } +} + +export const number = (options?: NumberOptions): NumberSchema => { + return new NumberSchema(options) as NumberSchema; +}; diff --git a/packages/core/src/schema/object.test.ts b/packages/core/src/schema/object.test.ts new file mode 100644 index 000000000..fbbb100e0 --- /dev/null +++ b/packages/core/src/schema/object.test.ts @@ -0,0 +1,44 @@ +import { SourcePath } from "../val"; +import { number } from "./number"; +import { object } from "./object"; + +describe("ObjectSchema", () => { + test("assert: should return success if object with keys are correct", () => { + const schema = object({ + test: number().nullable(), + }).nullable(); + const src = { + test: 1, + }; + expect(schema["executeAssert"]("foo" as SourcePath, src)).toEqual({ + success: true, + data: src, + }); + }); + + test("assert: should return success even if object has superfluous keys", () => { + const schema = object({ + test: number().nullable(), + }).nullable(); + const src = { + test: null, + ops: 1, + }; + expect(schema["executeAssert"]("foo" as SourcePath, src)).toEqual({ + success: true, + data: src, + }); + }); + + test("assert: should return errors if object is missing keys", () => { + const schema = object({ + test: number().nullable(), + }).nullable(); + const src = { + ops: 1, + }; + expect(schema["executeAssert"]("foo" as SourcePath, src).success).toEqual( + false, + ); + }); +}); diff --git a/packages/core/src/schema/object.ts b/packages/core/src/schema/object.ts new file mode 100644 index 000000000..0db8fe99b --- /dev/null +++ b/packages/core/src/schema/object.ts @@ -0,0 +1,265 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + AssertError, + CustomValidateFunction, + Schema, + SchemaAssertResult, + SelectorOfSchema, + SerializedSchema, +} from "."; +import { ReifiedRender } from "../render"; +import { SelectorSource } from "../selector"; +import { + createValPathOfItem, + unsafeCreateSourcePath, +} from "../selector/SelectorProxy"; +import { ModuleFilePath, SourcePath } from "../val"; +import { string } from "./string"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; + +export type SerializedObjectSchema = { + type: "object"; + items: Record; + opt: boolean; + customValidate?: boolean; +}; + +type ObjectSchemaProps = { [key: string]: Schema } & { + /** Cannot create object with key: valPath. It is a reserved name */ + valPath?: never; + /** Cannot create object with key: val. It is a reserved name */ + val?: never; + /** Cannot create object with key: _type. It is a reserved name */ + _type?: never; + /** Cannot create object with key: _ref. It is a reserved name */ + _ref?: never; + // The ones below we might want to allow (they are no longer intended to be used): + /** Cannot create object with key: andThen. It is a reserved name */ + andThen?: never; + /** Cannot create object with key: assert. It is a reserved name */ + assert?: never; + /** Cannot create object with key: fold. It is a reserved name */ + fold?: never; + /** Cannot create object with key: patch_id. It is a reserved name */ + patch_id?: never; +}; +type ObjectSchemaSrcOf = { + [key in keyof Props]: SelectorOfSchema; +}; + +export class ObjectSchema< + Props extends ObjectSchemaProps, + Src extends ObjectSchemaSrcOf | null, +> extends Schema { + constructor( + private readonly items: Props, + private readonly opt: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + ) { + super(); + } + + validate( + validationFunction: (src: Src) => false | string, + ): ObjectSchema { + return new ObjectSchema(this.items, this.opt, [ + ...this.customValidateFunctions, + validationFunction, + ]); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + let error: ValidationErrors = false; + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + if (this.opt && (src === null || src === undefined)) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + if (src === null) { + return { + [path]: [{ message: `Expected 'object', got 'null'` }], + } as ValidationErrors; + } + + if (typeof src !== "object") { + return { + [path]: [{ message: `Expected 'object', got '${typeof src}'` }], + } as ValidationErrors; + } else if (Array.isArray(src)) { + return { + [path]: [{ message: `Expected 'object', got 'array'` }], + } as ValidationErrors; + } + for (const customValidationError of customValidationErrors) { + error = this.appendValidationError( + error, + path, + customValidationError.message, + src, + customValidationError.schemaError, + ); + } + for (const [key, schema] of Object.entries(this.items)) { + const subPath = createValPathOfItem(path, key); + if (!subPath) { + error = this.appendValidationError( + error, + path, + `Internal error: could not create path at ${ + !path && typeof path === "string" ? "" : path + } at key ${key}`, // Should! never happen + src, + ); + } else { + const subError = schema["executeValidate"](subPath, src[key]); + if (subError && error) { + error = { + ...subError, + ...error, + }; + } else if (subError) { + error = subError; + } + } + } + + return error; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { message: `Expected 'object', got 'null'`, typeError: true }, + ], + }, + }; + } + + if (typeof src !== "object") { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'object', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } else if (Array.isArray(src)) { + return { + success: false, + errors: { + [path]: [ + { message: `Expected 'object', got 'array'`, typeError: true }, + ], + }, + }; + } + + const errorsAtPath: AssertError[] = []; + for (const key of Object.keys(this.items)) { + const subPath = createValPathOfItem(path, key); + if (!subPath) { + errorsAtPath.push({ + message: `Internal error: could not create path at ${ + !path && typeof path === "string" ? "" : path + } at key ${key}`, // Should! never happen + internalError: true, + }); + } else if (!(key in src)) { + errorsAtPath.push({ + message: `Expected key '${key}' not found in object`, + typeError: true, + }); + } + } + if (errorsAtPath.length > 0) { + return { + success: false, + errors: { + [path]: errorsAtPath, + }, + }; + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } + + nullable(): ObjectSchema { + return new ObjectSchema(this.items, true); + } + + protected executeSerialize(): SerializedSchema { + return { + type: "object", + items: Object.fromEntries( + Object.entries(this.items).map(([key, schema]) => [ + key, + schema["executeSerialize"](), + ]), + ), + opt: this.opt, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + } + + protected executeRender( + sourcePath: SourcePath | ModuleFilePath, + src: Src, + ): ReifiedRender { + const res: ReifiedRender = {}; + if (src === null) { + return res; + } + for (const key in this.items) { + const itemSrc = src[key]; + if (itemSrc === null || itemSrc === undefined) { + continue; + } + const subPath = unsafeCreateSourcePath(sourcePath, key); + const itemResult = this.items[key]["executeRender"](subPath, itemSrc); + for (const keyS in itemResult) { + const key = keyS as SourcePath | ModuleFilePath; + res[key] = itemResult[key]; + } + } + return res; + } +} + +export const object = ( + schema: Props, +): ObjectSchema> => { + return new ObjectSchema(schema); +}; + +const a = object({ + get test() { + return string(); + }, +}); diff --git a/packages/core/src/schema/record.test.ts b/packages/core/src/schema/record.test.ts new file mode 100644 index 000000000..74fbbf02a --- /dev/null +++ b/packages/core/src/schema/record.test.ts @@ -0,0 +1,546 @@ +import { nextAppRouter, externalPageRouter } from "../router"; +import { SourcePath } from "../val"; +import { deserializeSchema } from "./deserialize"; +import { number } from "./number"; +import { object } from "./object"; +import { record } from "./record"; +import { string } from "./string"; + +describe("RecordSchema", () => { + test("assert: basic record", () => { + const schema = record(number().nullable()); + expect(schema["executeAssert"]("foo" as SourcePath, { bar: 1 })).toEqual({ + success: true, + data: { bar: 1 }, + }); + }); + + test("record: nested renders", () => { + const schema = record( + object({ + title: string(), + bar: record( + object({ + baz: string().nullable(), + }), + ).render({ + as: "list", + select: ({ val }) => { + return { + title: val.baz || "No baz", + }; + }, + }), + }).nullable(), + ).render({ + as: "list", + select: ({ val }) => { + return { + title: val?.title || "No item", + }; + }, + }); + const src = { + "upper-key": { + title: "test", + bar: { + test1: { + baz: "baz", + }, + }, + }, + "nullable-key": null, + }; + const res = schema["executeRender"]("/test.val.ts" as SourcePath, src); + expect(res).toStrictEqual({ + '/test.val.ts?p="upper-key"."bar"': { + status: "success", + data: { + layout: "list", + parent: "record", + items: [ + ["test1", { title: "baz", subtitle: undefined, image: undefined }], + ], + }, + }, + "/test.val.ts": { + status: "success", + data: { + layout: "list", + parent: "record", + items: [ + [ + "upper-key", + { title: "test", subtitle: undefined, image: undefined }, + ], + [ + "nullable-key", + { title: "No item", subtitle: undefined, image: undefined }, + ], + ], + }, + }, + }); + }); + + test("record: router", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + expect( + schema["executeValidate"]("/app/blogs/[blog]/page.val.ts" as SourcePath, { + "/blogs/test": { title: "Test" }, + }), + ).toBe(false); // No validation errors for valid path + }); + + test("record: externalPageRouter router", () => { + const schema = record(object({ title: string() })).router( + externalPageRouter, + ); + expect( + schema["executeValidate"]("/external.val.ts" as SourcePath, { + "https://www.google.com": { title: "Test" }, + }), + ).toBe(false); // No validation errors for valid path + }); + + test("router validation: src/app directory structure", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]( + "/src/app/blogs/[blog]/page.val.ts" as SourcePath, + { + "/blogs/test": { title: "Test" }, // Valid + "/blog/test": { title: "Invalid" }, // Wrong path + }, + ); + + expect(result).not.toBe(false); + if (result !== false) { + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/blog/test")), + ), + ).toBe(true); + const error = Object.values(result).find((errors) => + errors.find((error) => error.value === "/blog/test"), + )?.[0]; + expect(error?.value).toStrictEqual("/blog/test"); + expect(error?.keyError).toBe(true); + } + }); + + test("router validation: with groups", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]( + "/app/(marketing)/blogs/[blog]/page.val.ts" as SourcePath, + { + "/blogs/test": { title: "Test" }, // Valid - group is ignored in URL + "/blog/test": { title: "Invalid" }, // Wrong path + }, + ); + + expect(result).not.toBe(false); + if (result !== false) { + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/blog/test")), + ), + ).toBe(true); + } + }); + + test("router validation: pages router", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]( + "/pages/blogs/[blog].tsx" as SourcePath, + { + "/blogs/test": { title: "Test" }, // Valid + "/blog/test": { title: "Invalid" }, // Wrong path + }, + ); + + expect(result).not.toBe(false); + if (result !== false) { + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/blog/test")), + ), + ).toBe(true); + } + }); + + test("router validation: basic dynamic route", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]( + "/app/blogs/[blog]/page.val.ts" as SourcePath, + { + "/blogs/test": { title: "Test" }, + "/blog/test": { title: "Invalid" }, // Wrong path + "/blogs/test/extra": { title: "Too many segments" }, // Too many segments + }, + ); + + expect(result).not.toBe(false); + if (result !== false) { + expect(Object.keys(result)).toHaveLength(2); + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/blog/test")), + ), + ).toBe(true); + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/blogs/test/extra")), + ), + ).toBe(true); + } + }); + + test("router validation: optional catch-all segments", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]( + "/app/posts/[[...category]]/page.val.ts" as SourcePath, + { + "/posts": { title: "All posts" }, // Valid - optional catch-all omitted + "/posts/tech": { title: "Tech posts" }, // Valid + "/posts/tech/extra": { title: "Extra" }, // Valid + }, + ); + + expect(result).toBe(false); // No validation errors + }); + + test("router validation: required segment", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]( + "/app/posts/[category]/page.val.ts" as SourcePath, + { + "/posts": { title: "All posts" }, // Invalid + "/posts/tech": { title: "Tech posts" }, // Valid + "/posts/tech/extra": { title: "Extra" }, // Invalid + }, + ); + + expect(result).not.toBe(false); + if (result !== false) { + expect(Object.keys(result)).toHaveLength(2); + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/posts")), + ), + ).toBe(true); + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/posts/tech/extra")), + ), + ).toBe(true); + } + }); + + test("router validation: catch-all segments", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]( + "/app/docs/[...slug]/page.val.ts" as SourcePath, + { + "/docs": { title: "Docs" }, // Invalid - catch-all requires at least one segment + "/docs/getting-started": { title: "Getting Started" }, // Valid + "/docs/getting-started/installation": { title: "Installation" }, // Valid + "/docs/getting-started/installation/advanced": { title: "Advanced" }, // Valid + }, + ); + + expect(result).not.toBe(false); + if (result !== false) { + expect(Object.keys(result)).toHaveLength(1); + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/docs")), + ), + ).toBe(true); + } + }); + + test("router validation: static segments", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]( + "/app/admin/users/[id]/page.val.ts" as SourcePath, + { + "/admin/users/123": { title: "User 123" }, // Valid + "/admin/users": { title: "Users list" }, // Invalid - missing required segment + "/admin/other/123": { title: "Wrong path" }, // Invalid - wrong static segment + }, + ); + + expect(result).not.toBe(false); + if (result !== false) { + expect(Object.keys(result)).toHaveLength(2); + } + }); + + test("router validation: root route", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]("/app/page.val.ts" as SourcePath, { + "/": { title: "Home" }, // Valid + "/about": { title: "About" }, // Invalid - root route only + }); + + expect(result).not.toBe(false); + if (result !== false) { + expect(Object.keys(result)).toHaveLength(1); + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/about")), + ), + ).toBe(true); + } + }); + + test("router validation: interception route", () => { + const schema = record(object({ title: string() })).router(nextAppRouter); + const result = schema["executeValidate"]( + "/app/(..)(dashboard)/feed/[id]/page.val.ts" as SourcePath, + { + "/feed/123": { title: "Feed 123" }, // Valid + "/dashboard/feed/123": { title: "Invalid" }, // Invalid, interception segment not in URL + }, + ); + + expect(result).not.toBe(false); + if (result !== false) { + expect( + Object.values(result).some((errors) => + errors.some((error) => error.message.includes("/dashboard/feed/123")), + ), + ).toBe(true); + } + }); + + describe("Key validation", () => { + test("record with key schema: valid keys", () => { + const schema = record( + string().validate((key) => { + if (key.startsWith("test-")) { + return false; + } + return "Key must start with 'test-'"; + }), + number(), + ); + + const result = schema["executeValidate"]("/test.val.ts" as SourcePath, { + "test-1": 1, + "test-2": 2, + }); + + expect(result).toBe(false); // No validation errors + }); + + test("record with key schema: invalid keys", () => { + const schema = record( + string().validate((key) => { + if (key.startsWith("test-")) { + return false; + } + return "Key must start with 'test-'"; + }), + number(), + ); + + const result = schema["executeValidate"]("/test.val.ts" as SourcePath, { + "test-1": 1, + invalid: 2, + "bad-key": 3, + }); + + expect(result).not.toBe(false); + if (result !== false) { + expect(Object.keys(result).length).toBeGreaterThan(0); + // should have keyError set to true for key validation errors + expect( + Object.values(result).reduce( + (acc, errors) => + errors.filter((error) => error.keyError).length + acc, + 0, + ), + ).toBe(2); + } + }); + + test("record with key schema: multiple validation rules", () => { + const schema = record( + string() + .validate((key) => { + if (key.length >= 3) { + return false; + } + return "Key must be at least 3 characters long"; + }) + .validate((key) => { + if (/^[a-z-]+$/.test(key)) { + return false; + } + return "Key must only contain lowercase letters and hyphens"; + }), + string(), + ); + + const result = schema["executeValidate"]("/test.val.ts" as SourcePath, { + "valid-key": "value1", + ab: "value2", // Too short + Invalid: "value3", // Has uppercase + "valid-123": "value4", // Has numbers + }); + + expect(result).not.toBe(false); + if (result !== false) { + expect(Object.keys(result).length).toBeGreaterThan(0); + } + }); + + test("record without key schema: no key validation", () => { + const schema = record(number()); + + const result = schema["executeValidate"]("/test.val.ts" as SourcePath, { + "any-key": 1, + ANY_KEY: 2, + "123": 3, + "!@#$": 4, + }); + + expect(result).toBe(false); // No validation errors - keys are not validated + }); + + test("record with key schema: nullable values", () => { + const schema = record( + string().validate((key) => { + if (key.startsWith("item-")) { + return false; + } + return "Key must start with 'item-'"; + }), + string().nullable(), + ); + + const result = schema["executeValidate"]("/test.val.ts" as SourcePath, { + "item-1": "value1", + "item-2": null, + wrong: "value3", + }); + + expect(result).not.toBe(false); + if (result !== false) { + // Should have error for 'wrong' key + expect(Object.keys(result).length).toBeGreaterThan(0); + } + }); + + test("record with key schema: complex value types", () => { + const schema = record( + string().validate((key) => { + if (/^[a-z]+$/.test(key)) { + return false; + } + return "Key must contain only lowercase letters"; + }), + object({ + title: string(), + count: number(), + }), + ); + + const result = schema["executeValidate"]("/test.val.ts" as SourcePath, { + validkey: { title: "Valid", count: 1 }, + "invalid-key": { title: "Invalid", count: 2 }, + }); + + expect(result).not.toBe(false); + if (result !== false) { + expect(Object.keys(result).length).toBeGreaterThan(0); + } + }); + + test("record with key schema: nullable record", () => { + const schema = record( + string().validate((key) => { + if (key.length > 0) { + return false; + } + return "Key must not be empty"; + }), + string(), + ).nullable(); + + const result = schema["executeValidate"]( + "/test.val.ts" as SourcePath, + null, + ); + + expect(result).toBe(false); // Null is valid for nullable record + }); + + test("record with key schema: serialize includes keySchema", () => { + const keySchema = string().validate((key) => { + if (key.startsWith("test-")) { + return false; + } + return "Key must start with 'test-'"; + }); + const schema = record(keySchema, number()); + + const serialized = schema["executeSerialize"](); + + expect(serialized.type).toBe("record"); + expect(serialized.item).toBeDefined(); + expect(serialized.key).toBeDefined(); + expect(serialized.key?.type).toBe("string"); + }); + + test("record without key schema: serialize excludes keySchema", () => { + const schema = record(number()); + + const serialized = schema["executeSerialize"](); + + expect(serialized.type).toBe("record"); + expect(serialized.item).toBeDefined(); + expect(serialized.key).toBeUndefined(); + }); + }); + + test("deserialize: round-trip serialization with key schema", () => { + const keySchema = string().maxLength(3); + const schema = record(keySchema, number()); + + const serialized = schema["executeSerialize"](); + const deserialized = deserializeSchema(serialized); + + const failingRes = deserialized["executeValidate"]( + "/test.val.ts" as SourcePath, + { + failhere: 1, + andhere: 2, + }, + ); + expect(failingRes).not.toBe(false); + if (failingRes !== false) { + expect(Object.keys(failingRes).length).toBe(2); // Both keys should fail maxLength + } + + const passingRes = deserialized["executeValidate"]( + "/test.val.ts" as SourcePath, + { + ok: 1, + yes: 2, + }, + ); + expect(passingRes).toBe(false); // No validation errors + + const reserialized = deserialized["executeSerialize"](); + + // Compare structures (note: custom validate functions won't be preserved) + expect(reserialized.type).toBe("record"); + expect(reserialized.opt).toBe(serialized.opt); + if (reserialized.type === "record" && serialized.type === "record") { + expect(reserialized.item.type).toBe(serialized.item.type); + expect(reserialized.key?.type).toBe(serialized.key?.type); + } + }); +}); diff --git a/packages/core/src/schema/record.ts b/packages/core/src/schema/record.ts new file mode 100644 index 000000000..1692a6141 --- /dev/null +++ b/packages/core/src/schema/record.ts @@ -0,0 +1,716 @@ +import { + CustomValidateFunction, + Schema, + SchemaAssertResult, + SelectorOfSchema, + SerializedSchema, +} from "."; +import { RenderSelector, ReifiedRender } from "../render"; +import { splitModuleFilePathAndModulePath } from "../module"; +import { ValRouter } from "../router"; +import { SelectorSource } from "../selector"; +import { + createValPathOfItem, + unsafeCreateSourcePath, +} from "../selector/SelectorProxy"; +import { ImageSource } from "../source/image"; +import { RemoteSource } from "../source/remote"; +import { ModuleFilePath, SourcePath } from "../val"; +import { ImageMetadata } from "./image"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; +import { splitRemoteRef } from "../remote/splitRemoteRef"; + +type MediaOptions = { + type: "files" | "images"; + accept: string; + directory: string; + remote: boolean; + altSchema?: Schema; +}; + +export type SerializedRecordSchema = { + type: "record"; + item: SerializedSchema; + key?: SerializedSchema; + opt: boolean; + router?: string; + customValidate?: boolean; + // Optional media collection marker for files/images that are backed by a record + mediaType?: "files" | "images"; + accept?: string; + directory?: string; + remote?: boolean; + alt?: SerializedSchema; +}; + +export class RecordSchema< + T extends Schema, + K extends Schema, + Src extends Record, SelectorOfSchema> | null, +> extends Schema { + constructor( + private readonly item: T, + private readonly opt: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + private readonly currentRouter: ValRouter | null = null, + private readonly keySchema: Schema | null = null, + private readonly mediaOptions?: MediaOptions, + ) { + super(); + } + + validate( + validationFunction: (src: Src) => false | string, + ): RecordSchema { + return new RecordSchema( + this.item, + this.opt, + [...this.customValidateFunctions, validationFunction], + this.currentRouter, + this.keySchema, + this.mediaOptions, + ); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + let error: ValidationErrors = false; + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + if (this.opt && (src === null || src === undefined)) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + if (src === null) { + return { + [path]: [ + ...customValidationErrors, + { message: `Expected 'object', got 'null'` }, + ], + } as ValidationErrors; + } + if (typeof src !== "object") { + return { + [path]: [ + ...customValidationErrors, + { message: `Expected 'object', got '${typeof src}'` }, + ], + } as ValidationErrors; + } + if (Array.isArray(src)) { + return { + [path]: [ + ...customValidationErrors, + { message: `Expected 'object', got 'array'` }, + ], + } as ValidationErrors; + } + const routerValidations = this.getRouterValidations(path, src); + if (routerValidations) { + return routerValidations; + } + for (const customValidationError of customValidationErrors) { + error = this.appendValidationError( + error, + path, + customValidationError.message, + src, + customValidationError.schemaError, + ); + } + if (this.mediaOptions) { + const checkFix = + this.mediaOptions.type === "images" + ? ("images:check-unique-folder" as const) + : ("files:check-unique-folder" as const); + const uniqueCheckError: ValidationError = { + message: `Gallery directory '${this.mediaOptions.directory}' must be unique across all galleries`, + value: { + directory: this.mediaOptions.directory, + type: this.mediaOptions.type, + }, + fixes: [checkFix], + }; + if (error) { + if (error[path]) { + error[path] = [...error[path], uniqueCheckError]; + } else { + error = { ...error, [path]: [uniqueCheckError] }; + } + } else { + error = { [path]: [uniqueCheckError] }; + } + const allFilesCheckFix = + this.mediaOptions.type === "images" + ? ("images:check-all-files" as const) + : ("files:check-all-files" as const); + const allFilesCheckError: ValidationError = { + message: `Directory '${this.mediaOptions.directory}' may have files not tracked by this gallery`, + value: { + directory: this.mediaOptions.directory, + type: this.mediaOptions.type, + }, + fixes: [allFilesCheckFix], + }; + if (error[path]) { + error[path] = [...error[path], allFilesCheckError]; + } else { + error = { ...error, [path]: [allFilesCheckError] }; + } + } + Object.entries(src).forEach(([key, elem]) => { + if (this.keySchema) { + const keyPath = createValPathOfItem(path, key); + if (!keyPath) { + throw new Error( + `Internal error: could not create path at ${ + !path && typeof path === "string" ? "" : path + } for key validation`, // Should! never happen + ); + } + const keyError = this.keySchema["executeValidate"](keyPath, key); + if (keyError) { + keyError[keyPath] = keyError[keyPath].map((err) => ({ + ...err, + keyError: true, + })); + if (error) { + if (error[keyPath]) { + error[keyPath] = [...error[keyPath], ...keyError[keyPath]]; + } else { + error[keyPath] = keyError[keyPath]; + } + } else { + error = keyError; + } + } + } + + const subPath = createValPathOfItem(path, key); + if (!subPath) { + error = this.appendValidationError( + error, + path, + `Internal error: could not create path at ${ + !path && typeof path === "string" ? "" : path + } at key ${elem}`, // Should! never happen + src, + ); + } else if (this.mediaOptions) { + // Media collection: validate key (path/URL) and entry (metadata) + const keyErr = this.validateMediaKey(subPath, key); + if (keyErr) { + error = error ? { ...error, ...keyErr } : keyErr; + } + const entryErr = this.validateMediaEntry(subPath, elem); + if (entryErr) { + error = error ? { ...error, ...entryErr } : entryErr; + } + } else { + const subError = this.item["executeValidate"]( + subPath, + elem as SelectorSource, + ); + if (subError && error) { + error = { + ...subError, + ...error, + }; + } else if (subError) { + error = subError; + } + } + }); + return error; + } + + private isRemoteUrl(url: string): boolean { + return url.startsWith("https://") || url.startsWith("http://"); + } + + private validateMediaKey(path: SourcePath, key: string): ValidationErrors { + if (!this.mediaOptions) { + return false; + } + const { directory, remote: isRemote, type } = this.mediaOptions; + const mediaLabel = type === "images" ? "images" : "files"; + const checkRemoteFix = + type === "images" ? "images:check-remote" : "files:check-remote"; + + const isRemoteUrl = this.isRemoteUrl(key); + const isLocalPath = key === directory || key.startsWith(directory + "/"); + + if (isRemote) { + // When remote is enabled, accept either remote URLs or local paths + if (isRemoteUrl) { + // Validate remote URL format using splitRemoteRef + const remoteResult = splitRemoteRef(key); + if (remoteResult.status === "error") { + return { + [path]: [ + { + message: `Invalid remote URL format. Use Val tooling (CLI, VS Code extension, or Val Studio) to upload ${mediaLabel}. Got: ${key}`, + value: key, + fixes: [checkRemoteFix], + }, + ], + }; + } + // Check that the file path in the remote URL matches our directory constraint + const remotePath = "/" + remoteResult.filePath; + if ( + remotePath !== directory && + !remotePath.startsWith(directory + "/") + ) { + return { + [path]: [ + { + message: `Remote file path '${remotePath}' is not in expected directory '${directory}'. Use Val tooling to upload ${mediaLabel} to the correct directory.`, + value: key, + fixes: [checkRemoteFix], + }, + ], + }; + } + return false; + } + if (!isLocalPath) { + return { + [path]: [ + { + message: `Expected a remote URL (https://...) or a local path starting with ${directory}/. Got: ${key}`, + value: key, + }, + ], + }; + } + } else { + // When remote is disabled, only accept local paths + if (isRemoteUrl) { + return { + [path]: [ + { + message: `Remote URLs are not allowed. Use .remote() to enable remote ${mediaLabel}. Got: ${key}`, + value: key, + fixes: [checkRemoteFix], + }, + ], + }; + } + if (!isLocalPath) { + return { + [path]: [ + { + message: `File path must be within the ${directory}/ directory. Got: ${key}`, + value: key, + }, + ], + }; + } + } + + return false; + } + + private validateMediaEntry( + path: SourcePath, + entry: unknown, + ): ValidationErrors { + if (!this.mediaOptions) { + return false; + } + const { type, accept, altSchema } = this.mediaOptions; + + if (typeof entry !== "object" || entry === null) { + return { + [path]: [ + { message: `Expected 'object', got '${typeof entry}'`, value: entry }, + ], + }; + } + + const entryObj = entry as Record; + const errors: ValidationError[] = []; + + if (type === "images") { + // Validate width + if (typeof entryObj.width !== "number" || entryObj.width <= 0) { + errors.push({ + message: `Expected 'width' to be a positive number, got '${entryObj.width}'`, + value: entry, + }); + } + + // Validate height + if (typeof entryObj.height !== "number" || entryObj.height <= 0) { + errors.push({ + message: `Expected 'height' to be a positive number, got '${entryObj.height}'`, + value: entry, + }); + } + } + + // Validate mimeType + if (typeof entryObj.mimeType !== "string") { + errors.push({ + message: `Expected 'mimeType' to be a string, got '${typeof entryObj.mimeType}'`, + value: entry, + }); + } else { + const mimeTypeError = this.validateMediaMimeType( + entryObj.mimeType, + accept, + ); + if (mimeTypeError) { + errors.push({ message: mimeTypeError, value: entry }); + } + } + + if (type === "images") { + // Validate hotspot if present + if (entryObj.hotspot !== undefined) { + const hs = entryObj.hotspot as Record; + if ( + typeof entryObj.hotspot !== "object" || + typeof hs.x !== "number" || + typeof hs.y !== "number" + ) { + errors.push({ + message: `Hotspot must be an object with x and y as numbers.`, + value: entry, + }); + } + } + + // Validate alt using the alt schema + const altPath = createValPathOfItem(path, "alt"); + if (altPath && altSchema) { + const altError = altSchema["executeValidate"]( + altPath, + entryObj.alt as SelectorSource, + ); + if (altError) { + return errors.length > 0 ? { ...altError, [path]: errors } : altError; + } + } + } + + if (errors.length > 0) { + return { [path]: errors }; + } + + return false; + } + + private validateMediaMimeType( + mimeType: string, + accept: string, + ): string | null { + if (!mimeType.includes("/")) { + return `Invalid mime type format. Got: '${mimeType}'`; + } + + const acceptedTypes = accept.split(",").map((type) => type.trim()); + + const isValidMimeType = acceptedTypes.some((acceptedType) => { + if (acceptedType === "*/*") { + return true; + } + if (acceptedType === "image/*") { + return mimeType.startsWith("image/"); + } + if (acceptedType.endsWith("/*")) { + const baseType = acceptedType.slice(0, -2); + return mimeType.startsWith(baseType); + } + return acceptedType === mimeType; + }); + + if (!isValidMimeType) { + return `Mime type mismatch. Found '${mimeType}' but schema accepts '${accept}'`; + } + + return null; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { message: `Expected 'object', got 'null'`, typeError: true }, + ], + }, + }; + } + if (typeof src !== "object") { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'object', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + if (Array.isArray(src)) { + return { + success: false, + errors: { + [path]: [ + { message: `Expected 'object', got 'array'`, typeError: true }, + ], + }, + }; + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } + + nullable(): RecordSchema { + return new RecordSchema( + this.item, + true, + this.customValidateFunctions, + this.currentRouter, + this.keySchema, + this.mediaOptions, + ) as RecordSchema; + } + + router(router: ValRouter): RecordSchema { + return new RecordSchema( + this.item, + this.opt, + this.customValidateFunctions, + router, + this.keySchema, + this.mediaOptions, + ); + } + + remote(): RecordSchema { + return new RecordSchema( + this.item, + this.opt, + this.customValidateFunctions, + this.currentRouter, + this.keySchema, + this.mediaOptions ? { ...this.mediaOptions, remote: true } : undefined, + ); + } + + private getRouterValidations(path: SourcePath, src: Src): ValidationErrors { + if (!this.currentRouter) { + return false; + } + if (src === null) { + return false; + } + const [moduleFilePath, modulePath] = splitModuleFilePathAndModulePath(path); + if (modulePath) { + return { + [path]: [ + { + message: `This field was configured as a router, but it is not defined at the root of the module`, + schemaError: true, + }, + ], + }; + } + const routerValidationErrors = this.currentRouter.validate( + moduleFilePath, + Object.keys(src), + ); + if (routerValidationErrors.length > 0) { + return Object.fromEntries( + routerValidationErrors.map( + (validation): [SourcePath, ValidationError[]] => { + if (!validation.error.urlPath) { + return [ + path, + [ + { + message: `Router validation error: ${validation.error.message} has no url path`, + schemaError: true, + }, + ], + ]; + } + const subPath = createValPathOfItem(path, validation.error.urlPath); + if (!subPath) { + throw new Error( + `Internal error: could not create path at ${ + !path && typeof path === "string" ? "" : path + } for router validation`, // Should! never happen + ); + } + return [ + subPath, + [ + { + message: validation.error.message, + value: validation.error.urlPath, + keyError: true, + }, + ], + ]; + }, + ), + ); + } + return false; + } + + protected executeSerialize(): SerializedRecordSchema { + const result: SerializedRecordSchema = { + type: "record", + item: this.item["executeSerialize"](), + key: this.keySchema?.["executeSerialize"](), + opt: this.opt, + router: this.currentRouter?.getRouterId(), + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + if (this.mediaOptions) { + result.mediaType = this.mediaOptions.type; + result.accept = this.mediaOptions.accept; + result.directory = this.mediaOptions.directory; + result.remote = this.mediaOptions.remote; + if (this.mediaOptions.altSchema) { + result.alt = this.mediaOptions.altSchema["executeSerialize"](); + } + } + return result; + } + + private renderInput: { + layout: "list"; + select: (input: { key: string; val: RenderSelector }) => { + title: string; + subtitle?: string | null; + image?: ImageSource | RemoteSource | null; + }; + } | null = null; + + protected override executeRender( + sourcePath: SourcePath | ModuleFilePath, + src: Src, + ): ReifiedRender { + const res: ReifiedRender = {}; + if (src === null) { + return res; + } + for (const key in src) { + const itemSrc = src[key as unknown as SelectorOfSchema]; + if (itemSrc === null || itemSrc === undefined) { + continue; + } + const subPath = unsafeCreateSourcePath(sourcePath, key); + const itemResult = this.item["executeRender"](subPath, itemSrc); + for (const keyS in itemResult) { + const key = keyS as SourcePath | ModuleFilePath; + res[key] = itemResult[key]; + } + } + if (this.renderInput) { + const { select: prepare, layout: layout } = this.renderInput; + if (layout !== "list") { + res[sourcePath] = { + status: "error", + message: "Unknown layout type: " + layout, + }; + } + try { + res[sourcePath] = { + status: "success", + data: { + layout: "list", + parent: "record", + items: Object.entries(src).map(([key, val]) => { + // NB NB: display is actually defined by the user + const { title, subtitle, image } = prepare({ + key, + val: val as SelectorOfSchema, + }); + return [key, { title, subtitle, image }]; + }), + }, + }; + } catch (e) { + res[sourcePath] = { + status: "error", + message: e instanceof Error ? e.message : "Unknown error", + }; + } + } + return res; + } + + render(input: { + as: "list"; + select: (input: { key: string; val: RenderSelector }) => { + title: string; + subtitle?: string | null; + image?: ImageSource | RemoteSource | null; + }; + }) { + this.renderInput = { + layout: input.as, + select: input.select, + }; + return this; + } +} + +// Overload: with key schema +export function record< + K extends Schema, + S extends Schema, +>( + key: K, + schema: S, +): RecordSchema, SelectorOfSchema>>; + +// Overload: without key schema +export function record>( + schema: S, +): RecordSchema, Record>>; + +// Implementation +export function record< + K extends Schema, + S extends Schema, +>( + keyOrSchema: K | S, + schema?: S, +): RecordSchema, SelectorOfSchema>> { + if (schema) { + // Two-argument call: first is key schema, second is value schema + return new RecordSchema(schema, false, [], null, keyOrSchema as K); + } else { + // One-argument call: only value schema + return new RecordSchema(keyOrSchema as S, false, [], null, null); + } +} diff --git a/packages/core/src/schema/remote.ts b/packages/core/src/schema/remote.ts new file mode 100644 index 000000000..81f7190fe --- /dev/null +++ b/packages/core/src/schema/remote.ts @@ -0,0 +1,34 @@ +import { Json } from ".."; +import { splitRemoteRef } from "../remote/splitRemoteRef"; +import { FILE_REF_PROP } from "../source/file"; +import { RemoteSource } from "../source/remote"; + +export const DEFAULT_VAL_REMOTE_HOST = "https://remote.val.build"; + +export function convertRemoteSource< + Metadata extends { readonly [key: string]: Json } | undefined = + | { readonly [key: string]: Json } + | undefined, +>(src: RemoteSource): { url: string; metadata?: Metadata } { + if (src?.patch_id) { + const splitRemoteRefDataRes = splitRemoteRef(src[FILE_REF_PROP]); + if (splitRemoteRefDataRes.status === "success") { + return { + url: + "/api/val/files/" + + splitRemoteRefDataRes.filePath + + `?patch_id=${src["patch_id"]}&remote=true&ref=${encodeURIComponent(src[FILE_REF_PROP])}`, + metadata: src.metadata, + }; + } else { + console.warn( + `Internal Val error: failed to split remote ref: ${src[FILE_REF_PROP]}. The data format is different than what is expected. Check Val versions for mismatches.`, + splitRemoteRefDataRes.error, + ); + } + } + return { + url: src[FILE_REF_PROP], + metadata: src.metadata, + }; +} diff --git a/packages/core/src/schema/richtext.test.ts b/packages/core/src/schema/richtext.test.ts new file mode 100644 index 000000000..fc6e8be9c --- /dev/null +++ b/packages/core/src/schema/richtext.test.ts @@ -0,0 +1,575 @@ +import { deepEqual } from "../patch"; +import { AllRichTextOptions, RichTextSource } from "../source/richtext"; +import { SourcePath } from "../val"; +import { richtext } from "./richtext"; +import { route } from "./route"; +import { string } from "./string"; +import { ValidationErrors } from "./validation/ValidationError"; + +describe("RichTextSchema", () => { + const testValidateInput: RichTextSource = [ + { tag: "h1", children: ["What is Val?"] }, + { tag: "p", children: ["Val is a CMS, which is useful because:"] }, + { + tag: "ul", + children: [ + { + tag: "li", + children: [ + { + tag: "p", + children: [ + "It", + { tag: "span", styles: ["bold"], children: ["just"] }, + " works", + ], + }, + ], + }, + ], + }, + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "/home", children: ["Val"] }, + " for more information.", + ], + }, + ]; + + test("validate: basic green test", () => { + const schema = richtext({ + style: { + bold: true, + }, + block: { + h1: true, + ul: true, + }, + inline: { + a: string(), + }, + }); + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + testValidateInput as any, + ), + [], + ); + }); + + test("validate: basic green max / min length test", () => { + const schema = richtext({ + style: { + bold: true, + }, + block: { + h1: true, + ul: true, + }, + inline: { + a: string(), + }, + }) + .minLength(1) + .maxLength(100); + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + testValidateInput as any, + ), + [], + ); + }); + + test("validate: basic red min length test", () => { + const schema = richtext({ + style: { + bold: true, + }, + block: { + h1: true, + ul: true, + }, + inline: { + a: string(), + }, + }).minLength(100); + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + testValidateInput as any, + ), + ["/richtext.val.ts"], + ); + }); + + test("validate: basic red max length test", () => { + const schema = richtext({ + style: { + bold: true, + }, + block: { + h1: true, + ul: true, + }, + inline: { + a: string(), + }, + }).maxLength(10); + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + testValidateInput as any, + ), + ["/richtext.val.ts"], + ); + }); + + test("validate: basic red test", () => { + const schema = richtext(); + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + testValidateInput as any, + ), + [ + "/richtext.val.ts?p=0", + "/richtext.val.ts?p=2", + '/richtext.val.ts?p=2."children".0', + '/richtext.val.ts?p=2."children".0."children".0."children".0."styles".0', + '/richtext.val.ts?p=3."children".0', + ], + ); + }); + + test("validate: red test with maxLength", () => { + const schema = richtext().maxLength(10); + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + testValidateInput as any, + ), + [ + "/richtext.val.ts", + "/richtext.val.ts?p=0", + "/richtext.val.ts?p=2", + '/richtext.val.ts?p=2."children".0', + '/richtext.val.ts?p=2."children".0."children".0."children".0."styles".0', + '/richtext.val.ts?p=3."children".0', + ], + ); + }); + + test("assert: should return success if src is richtext", () => { + const schema = richtext(); + const src = [ + { tag: "p", children: ["Val is a CMS, which is useful because:"] }, + { + tag: "ul", + children: [ + { + tag: "li", + children: [ + { + tag: "p", + children: [ + "It", + { tag: "span", styles: ["bold"], children: ["just"] }, + " works", + ], + }, + ], + }, + ], + }, + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "https://val.build", children: ["Val"] }, + " for more information.", + ], + }, + ]; + expect( + schema["executeAssert"]("/richtext.val.ts" as SourcePath, src), + ).toEqual({ + success: true, + data: src, + }); + }); + + test("assert: should return errors if src is invalid richtext", () => { + const schema = richtext(); + const src = [ + { tag: "p", children: ["Val is a CMS, which is useful because:"] }, + { + tag: 1, // <- error 1 + }, + { + tag: "ul", + children: [ + { + tag: "li", + children: [ + { + tag: "p", + children: [ + "It", + 42, // <- error + { tag: "span", styles: ["bold"], children: ["just"] }, + " works", + ], + }, + ], + }, + ], + }, + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "https://val.build", children: ["Val"] }, + " for more information.", + ], + }, + ]; + const res = schema["executeAssert"]("/richtext.val.ts" as SourcePath, src); + expect(res.success).toEqual(false); + if (!res.success) { + expect(Object.keys(res.errors).sort()).toEqual( + [ + `/richtext.val.ts?p=1`, + `/richtext.val.ts?p=2."children".0."children".0."children".1`, + ].sort(), + ); + const errorAtPath = + res.errors[ + `/richtext.val.ts?p=2."children".0."children".0."children".1` as SourcePath + ]; + expect(errorAtPath).toBeDefined(); + expect(errorAtPath).toHaveLength(1); + } + }); + + // Anchor href validation tests + test("validate: a: true with valid route href (green test)", () => { + const schema = richtext({ + inline: { + a: true, + }, + }); + const input = [ + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "/blogs/blog1", children: ["Blog 1"] }, + " for more information.", + ], + }, + ]; + const errors = schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ); + // Note: a: true now defaults to route validation, which returns router:check-route error + // This is expected and will be processed by Val internally + if (errors !== false) { + expect(Object.keys(errors).length).toBeGreaterThan(0); + const firstError = errors[Object.keys(errors)[0] as SourcePath][0]; + expect(firstError.message).toContain("router:check-route"); + } + }); + + test("validate: a: route() with explicit route schema", () => { + const schema = richtext({ + inline: { + a: route(), + }, + }); + const input = [ + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "/blogs/blog1", children: ["Blog 1"] }, + " for more.", + ], + }, + ]; + const errors = schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ); + // Route validation returns router:check-route error for internal processing + if (errors !== false) { + expect(Object.keys(errors).length).toBeGreaterThan(0); + const firstError = errors[Object.keys(errors)[0] as SourcePath][0]; + expect(firstError.message).toContain("router:check-route"); + } + }); + + test("validate: a: string().maxLength(30) with valid short URL (green test)", () => { + const schema = richtext({ + inline: { + a: string().maxLength(30), + }, + }); + const input = [ + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "/short", children: ["Link"] }, + " here.", + ], + }, + ]; + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ), + [], + ); + }); + + test("validate: a: string().maxLength(30) with URL exceeding max length (red test)", () => { + const schema = richtext({ + inline: { + a: string().maxLength(30), + }, + }); + const input = [ + { + tag: "p", + children: [ + { + tag: "a", + href: "/this-is-a-very-long-url-that-exceeds-thirty-characters", + children: ["Link"], + }, + ], + }, + ]; + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ), + ['/richtext.val.ts?p=0."children".0."href"'], + ); + }); + + test("validate: a: route().include(/^\\/blog/) with matching route (green test)", () => { + const schema = richtext({ + inline: { + a: route().include(/^\/blog/), + }, + }); + const input = [ + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "/blog/post1", children: ["Blog"] }, + " here.", + ], + }, + ]; + const errors = schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ); + // Route validation returns router:check-route error for internal processing + if (errors !== false) { + const firstError = errors[Object.keys(errors)[0] as SourcePath][0]; + expect(firstError.message).toContain("router:check-route"); + } + }); + + test("validate: a: route().exclude(/^\\/admin/) with excluded route (red test)", () => { + const schema = richtext({ + inline: { + a: route().exclude(/^\/admin/), + }, + }); + const input = [ + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "/admin/dashboard", children: ["Admin"] }, + " here.", + ], + }, + ]; + const errors = schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ); + // Route validation returns router:check-route error for internal processing + if (errors !== false) { + const firstError = errors[Object.keys(errors)[0] as SourcePath][0]; + expect(firstError.message).toContain("router:check-route"); + } + }); + + test("validate: a tag without href attribute (red test)", () => { + const schema = richtext({ + inline: { + a: true, + }, + }); + const input = [ + { + tag: "p", + children: [ + { tag: "a", children: ["Link"] }, // Missing href + ], + }, + ]; + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ), + ['/richtext.val.ts?p=0."children".0'], + ); + }); + + test("validate: a: string() allows external URLs", () => { + const schema = richtext({ + inline: { + a: string(), + }, + }); + const input = [ + { + tag: "p", + children: [ + "Visit ", + { tag: "a", href: "https://example.com", children: ["External"] }, + " here.", + ], + }, + ]; + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ), + [], + ); + }); + + test("validate: unsupported tag (red test)", () => { + const schema = richtext({ + block: { + h1: true, + }, + }); + const input = [ + { tag: "h1", children: ["Title"] }, + { tag: "div", children: ["This should not be allowed"] }, + ]; + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ), + ["/richtext.val.ts?p=1"], + ); + }); + + test("validate: multiple unsupported tags (red test)", () => { + const schema = richtext({ + block: { + h1: true, + }, + }); + const input = [ + { tag: "h1", children: ["Title"] }, + { tag: "script", children: ["alert('xss')"] }, + { tag: "table", children: ["data"] }, + ]; + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input as any, + ), + ["/richtext.val.ts?p=1", "/richtext.val.ts?p=2"], + ); + }); + + test("validate: a: string().regexp() validates URL patterns", () => { + const schema = richtext({ + inline: { + a: string().regexp(/^https?:\/\//), + }, + }); + const inputValid = [ + { + tag: "p", + children: [ + { tag: "a", href: "https://example.com", children: ["Link"] }, + ], + }, + ]; + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + inputValid as any, + ), + [], + ); + + const inputInvalid = [ + { + tag: "p", + children: [{ tag: "a", href: "/relative-url", children: ["Link"] }], + }, + ]; + expectedErrorAtPaths( + schema["executeValidate"]( + "/richtext.val.ts" as SourcePath, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + inputInvalid as any, + ), + ['/richtext.val.ts?p=0."children".0."href"'], + ); + }); +}); + +function expectedErrorAtPaths(errors: ValidationErrors, paths: string[]) { + if (paths.length === 0) { + expect(errors).toEqual(false); + return; + } + const allPaths = Object.keys(errors).sort(); + if (!deepEqual(allPaths, paths)) { + console.error("Found errors", errors); + } + expect(allPaths).toEqual(paths.sort()); +} diff --git a/packages/core/src/schema/richtext.ts b/packages/core/src/schema/richtext.ts new file mode 100644 index 000000000..18c06196b --- /dev/null +++ b/packages/core/src/schema/richtext.ts @@ -0,0 +1,700 @@ +import { + AssertError, + CustomValidateFunction, + Schema, + SchemaAssertResult, + SerializedSchema, +} from "."; +import { ReifiedRender } from "../render"; +import { unsafeCreateSourcePath } from "../selector/SelectorProxy"; +import { ImageSource } from "../source/image"; +import { RemoteSource } from "../source/remote"; +import { + RichTextSource, + RichTextOptions, + SerializedRichTextOptions, +} from "../source/richtext"; +import { SourcePath } from "../val"; +import { ImageMetadata, ImageSchema, SerializedImageSchema } from "./image"; +import { RouteSchema, SerializedRouteSchema } from "./route"; +import { SerializedStringSchema, StringSchema } from "./string"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; + +type ValidationOptions = { + maxLength?: number; + minLength?: number; +}; +export type SerializedRichTextSchema = { + type: "richtext"; + opt: boolean; + options?: SerializedRichTextOptions & ValidationOptions; + customValidate?: boolean; +}; + +export class RichTextSchema< + O extends RichTextOptions, + Src extends RichTextSource | null, +> extends Schema { + constructor( + private readonly options: O & ValidationOptions, + private readonly opt: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + ) { + super(); + } + + maxLength(max: number): RichTextSchema { + return new RichTextSchema( + { + ...this.options, + maxLength: max, + }, + this.opt, + ); + } + + minLength(min: number): RichTextSchema { + return new RichTextSchema( + { + ...this.options, + minLength: min, + }, + this.opt, + ); + } + + validate( + validationFunction: (src: Src) => false | string, + ): RichTextSchema { + return new RichTextSchema(this.options, this.opt, [ + ...this.customValidateFunctions, + validationFunction, + ]); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + const assertRes = this.executeAssert(path, src); + if (!assertRes.success) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : assertRes.errors; + } + const nodes = assertRes.data; + if (nodes === null && this.opt) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + if (nodes === null) { + return { + [path]: [ + ...customValidationErrors, + { + message: "Expected 'array', got 'null'", + typeError: true, + }, + ], + }; + } + + const current = {}; + const typeErrorRes = this.internalValidate(path, nodes, current); + if (typeErrorRes) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors, ...typeErrorRes } + : typeErrorRes; + } + if (Object.keys(current).length > 0) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors, ...current } + : current; + } + if (customValidationErrors.length > 0) { + return { + [path]: customValidationErrors, + }; + } + return false; + } + + private internalValidate( + rootPath: SourcePath, + nodes: unknown[], + current: Record, + ): ValidationErrors { + let length = 0; + const recurse = ( + rootPath: SourcePath, + nodes: unknown[], + current: Record, + ): ValidationErrors => { + const addError = ( + path: SourcePath, + message: string, + typeError: boolean, + ) => { + if (!current[path]) { + current[path] = []; + } + current[path].push({ + message, + typeError, + }); + }; + for (const node of nodes) { + const path = unsafeCreateSourcePath(rootPath, nodes.indexOf(node)); + if (typeof node === "string") { + length += node.length; + continue; + } + if (typeof node !== "object") { + return { + [path]: [ + { + message: `Expected nodes of type 'object' or 'string', got '${typeof node}'`, + typeError: true, + }, + ], + }; + } + if (node === null) { + return { + [path]: [ + { + message: `Expected nodes of type 'object' or 'string', got 'null'`, + typeError: true, + }, + ], + }; + } + if (!("tag" in node)) { + return { + [path]: [ + { + message: `Expected node to either have 'tag' or be of type 'string'`, + typeError: true, + }, + ], + }; + } + if (typeof node.tag !== "string") { + return { + [path]: [ + { + message: `Expected 'string', got '${typeof node.tag}'`, + typeError: true, + }, + ], + }; + } + const supportedTags = [ + "p", + "span", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "ol", + "ul", + "li", + "a", + "img", + "br", + ]; + if (!supportedTags.includes(node.tag)) { + addError( + path, + `Tag '${node.tag}' is not supported. Supported tags: ${supportedTags.join(", ")}`, + true, + ); + } + if (node.tag === "h1" && !this.options.block?.h1) { + addError(path, `'h' block is not valid`, false); + } + if (node.tag === "h2" && !this.options.block?.h2) { + addError(path, `'h2' block is not valid`, false); + } + if (node.tag === "h3" && !this.options.block?.h3) { + addError(path, `'h3' block is not valid`, false); + } + if (node.tag === "h4" && !this.options.block?.h4) { + addError(path, `'h4' block is not valid`, false); + } + if (node.tag === "h5" && !this.options.block?.h5) { + addError(path, `'h5' block is not valid`, false); + } + if (node.tag === "h6" && !this.options.block?.h6) { + addError(path, `'h6' block is not valid`, false); + } + if (node.tag === "ol" && !this.options.block?.ol) { + addError(path, `'ol' block is not valid`, false); + } + if (node.tag === "ul" && !this.options.block?.ul) { + addError(path, `'ul' block is not valid`, false); + } + if ( + node.tag === "li" && + !this.options.block?.ul && + !this.options.block?.ol + ) { + addError( + path, + `'li' tag is invalid since neither 'ul' nor 'ol' block is not valid`, + false, + ); + } + if (node.tag === "a") { + if (!this.options.inline?.a) { + addError(path, `'a' inline is not valid`, false); + } else if (this.options.inline?.a) { + if (!("href" in node)) { + return { + [path]: [ + { + message: `Expected 'href' in 'a'`, + typeError: true, + }, + ], + }; + } + const hrefPath = unsafeCreateSourcePath(path, "href"); + const hrefSchema = + typeof this.options.inline?.a === "boolean" + ? new RouteSchema() + : this.options.inline.a; + + const executeValidate = () => { + if (hrefSchema instanceof RouteSchema) { + return hrefSchema["executeValidate"]( + hrefPath, + node.href as string, + ); + } else if (hrefSchema instanceof StringSchema) { + return hrefSchema["executeValidate"]( + hrefPath, + node.href as string, + ); + } else { + const exhaustiveCheck: never = hrefSchema; + console.error( + "Exhaustive check failed in RichText (anchor href validation)", + exhaustiveCheck, + ); + return false; + } + }; + const hrefValidationErrors = executeValidate(); + if (hrefValidationErrors) { + for (const validationErrorPathS in hrefValidationErrors) { + const validationErrorPath = validationErrorPathS as SourcePath; + if (!current[validationErrorPath]) { + current[validationErrorPath] = []; + } + current[validationErrorPath].push( + ...hrefValidationErrors[validationErrorPath], + ); + } + } + } + } + + if (node.tag === "img") { + if (!this.options.inline?.img) { + addError(path, `'img' inline is not valid`, false); + } else if (this.options.inline?.img) { + if (!("src" in node)) { + return { + [path]: [ + { + message: `Expected 'src' in 'img'`, + typeError: true, + }, + ], + }; + } + const srcPath = unsafeCreateSourcePath(path, "src"); + const imgSchema = this.options.inline?.img; + const imageValidationErrors = + typeof imgSchema === "object" + ? ( + imgSchema as ImageSchema< + ImageSource | RemoteSource + > + )["executeValidate"]( + srcPath, + node.src as + | ImageSource + | RemoteSource, + ) + : new ImageSchema({}, false, false)["executeValidate"]( + srcPath, + node.src as ImageSource, + ); + if (imageValidationErrors) { + for (const validationErrorPathS in imageValidationErrors) { + const validationErrorPath = validationErrorPathS as SourcePath; + if (!current[validationErrorPath]) { + current[validationErrorPath] = []; + } + current[validationErrorPath].push( + ...imageValidationErrors[validationErrorPath], + ); + } + } + } + } + if ("styles" in node && node.tag !== "span") { + return { + [path]: [ + { + message: `Cannot have styles on '${node.tag}'. This is only allowed on 'span'`, + typeError: true, + }, + ], + }; + } + if ("styles" in node) { + if (!Array.isArray(node.styles)) { + return { + [path]: [ + { + message: `Expected 'array', got '${typeof node.styles}'`, + typeError: true, + }, + ], + }; + } + + const stylesPath = unsafeCreateSourcePath(path, "styles"); + for (let i = 0; i < node.styles.length; i++) { + const style = node.styles[i]; + const currentStylePath = unsafeCreateSourcePath(stylesPath, i); + if (typeof style !== "string") { + return { + [currentStylePath]: [ + { + message: `Expected 'string', got '${typeof style}'`, + typeError: true, + }, + ], + }; + } + if (style === "bold" && !this.options.style?.bold) { + addError(currentStylePath, `Style 'bold' is not valid`, false); + } + if (style === "italic" && !this.options.style?.italic) { + addError(currentStylePath, `Style 'italic' is not valid`, false); + } + if (style === "lineThrough" && !this.options.style?.lineThrough) { + addError( + currentStylePath, + `Style 'lineThrough' is not valid`, + false, + ); + } + } + } + if ("children" in node) { + if (!Array.isArray(node.children)) { + return { + [path]: [ + { + message: `Expected 'array', got '${typeof node.children}'`, + typeError: true, + }, + ], + }; + } + const children = node.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (typeof child === "object") { + const childPath = unsafeCreateSourcePath(path, "children"); + const res = recurse(childPath, [child], current); + if (res) { + return res; + } + } else if (typeof child === "string") { + length += child.length; + continue; + } else { + return { + [path]: [ + { + message: `Expected 'object' or 'string', got '${typeof child}'`, + typeError: true, + }, + ], + }; + } + } + } + } + return false; + }; + + const results = recurse(rootPath, nodes, current); + const lengthErrors = []; + if (this.options.maxLength && length > this.options.maxLength) { + lengthErrors.push({ + message: `Maximum length of ${this.options.maxLength} exceeded (current length: ${length})`, + typeError: false, + }); + } + if (this.options.minLength && length < this.options.minLength) { + lengthErrors.push({ + message: `Minimum length of ${this.options.minLength} not met (current length: ${length})`, + typeError: false, + }); + } + if (results) { + if (lengthErrors.length > 0) { + if (!results[rootPath]) { + results[rootPath] = []; + } + results[rootPath].push(...lengthErrors); + } + return results; + } + if (Object.keys(current).length > 0) { + if (lengthErrors.length > 0) { + if (!current[rootPath]) { + current[rootPath] = []; + } + current[rootPath].push(...lengthErrors); + } + return current; + } + if (lengthErrors.length > 0) { + return { + [rootPath]: lengthErrors, + }; + } + return false; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null && !this.opt) { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'array', got 'null'`, + typeError: true, + }, + ], + }, + }; + } + if (!Array.isArray(src)) { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'array', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + const errors: Record = {}; + for (let i = 0; i < src.length; i++) { + this.recursiveAssert(unsafeCreateSourcePath(path, i), src[i], errors); + } + if (Object.keys(errors).length > 0) { + return { + success: false, + errors, + }; + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } + + private recursiveAssert( + path: string, + node: unknown, + errors: Record, + ) { + if (typeof node !== "object") { + if (!errors[path]) { + errors[path] = []; + } + errors[path].push({ + message: `Expected 'object', got '${typeof node}'`, + typeError: true, + }); + return; + } + if (Array.isArray(node)) { + if (!errors[path]) { + errors[path] = []; + } + errors[path].push({ + message: `Expected 'object', got 'array'`, + typeError: true, + }); + return; + } + if (node === null) { + if (!errors[path]) { + errors[path] = []; + } + errors[path].push({ + message: `Expected 'object', got 'null'`, + typeError: true, + }); + return; + } + if ("tag" in node) { + if (typeof node.tag !== "string") { + if (!errors[path]) { + errors[path] = []; + } + errors[path].push({ + message: `Expected 'string', got '${typeof node.tag}'`, + typeError: true, + }); + return; + } + } + if ("children" in node) { + if (!Array.isArray(node.children)) { + if (!errors[path]) { + errors[path] = []; + } + errors[path].push({ + message: `Expected 'array', got '${typeof node.children}'`, + typeError: true, + }); + return; + } else { + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + const pathAtError = unsafeCreateSourcePath( + unsafeCreateSourcePath(path, "children"), + i, + ); + if (typeof child === "object") { + this.recursiveAssert(pathAtError, child, errors); + } else if (typeof child === "string") { + continue; + } else { + if (!errors[pathAtError]) { + errors[pathAtError] = []; + } + errors[pathAtError].push({ + message: `Expected 'object' or 'string', got '${typeof child}'`, + typeError: true, + }); + } + } + } + } + if ("styles" in node) { + if (!Array.isArray(node.styles)) { + if (!errors[path]) { + errors[path] = []; + } + errors[path].push({ + message: `Expected 'array', got '${typeof node.styles}'`, + typeError: true, + }); + } else { + for (let i = 0; i < node.styles.length; i++) { + const style = node.styles[i]; + if (typeof style !== "string") { + const pathAtError = unsafeCreateSourcePath(path, i); + if (!errors[pathAtError]) { + errors[pathAtError] = []; + } + errors[pathAtError].push({ + message: `Expected 'string', got '${typeof style}'`, + typeError: true, + }); + } + } + } + } + } + + nullable(): RichTextSchema { + return new RichTextSchema(this.options, true); + } + + protected executeSerialize(): SerializedSchema { + const serializeAnchorSchema = (): + | SerializedRouteSchema + | SerializedStringSchema + | boolean + | undefined => { + if (this.options.inline?.a instanceof RouteSchema) { + return this.options.inline?.a[ + "executeSerialize" + ]() as SerializedRouteSchema; + } else if (this.options.inline?.a instanceof StringSchema) { + return this.options.inline?.a[ + "executeSerialize" + ]() as SerializedStringSchema; + } else { + return this.options.inline?.a; + } + }; + const serializedOptions: SerializedRichTextOptions & ValidationOptions = { + maxLength: this.options.maxLength, + minLength: this.options.minLength, + style: this.options.style, + block: this.options.block, + inline: this.options.inline && { + a: serializeAnchorSchema(), + img: + this.options.inline.img && typeof this.options.inline.img === "object" + ? (this.options.inline.img[ + "executeSerialize" + ]() as SerializedImageSchema) + : this.options.inline.img, + }, + }; + return { + type: "richtext", + opt: this.opt, + options: serializedOptions, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + } + + protected executeRender(): ReifiedRender { + return {}; + } +} + +export const richtext = ( + options?: O, +): RichTextSchema> => { + return new RichTextSchema>(options ?? ({} as O)); +}; diff --git a/packages/core/src/schema/route.test.ts b/packages/core/src/schema/route.test.ts new file mode 100644 index 000000000..df05ea395 --- /dev/null +++ b/packages/core/src/schema/route.test.ts @@ -0,0 +1,199 @@ +import { SourcePath } from "../val"; +import { route } from "./route"; + +describe("RouteSchema", () => { + test("assert: should return success if src is a route value (string)", () => { + const schema = route(); + const src = "/home"; + const res = schema["executeAssert"]("path" as SourcePath, src); + expect(res).toEqual({ + success: true, + data: src, + }); + }); + + test("assert: should return success for nullable route when src is null", () => { + const schema = route().nullable(); + const src = null; + const res = schema["executeAssert"]("path" as SourcePath, src); + expect(res).toEqual({ + success: true, + data: src, + }); + }); + + test("assert: should return error if src is not a string", () => { + const schema = route(); + const src = 123; + const res = schema["executeAssert"]("path" as SourcePath, src); + expect(res).toEqual({ + success: false, + errors: { + path: [ + { + message: `Expected 'string', got 'number'`, + typeError: true, + }, + ], + }, + }); + }); + + test("validate: should return validation error with router:check-route fix", () => { + const schema = route(); + const src = "/test"; + const res = schema["executeValidate"]("path" as SourcePath, src); + expect(res).toEqual({ + path: [ + { + fixes: ["router:check-route"], + message: `Did not validate route (router). This error (router:check-route) should typically be processed by Val internally. Seeing this error most likely means you have a Val version mismatch.`, + value: { + route: src, + sourcePath: "path", + include: undefined, + exclude: undefined, + }, + }, + ], + }); + }); + + test("validate: should return validation error with include pattern", () => { + const includePattern = /^\/(home|about)$/; + const schema = route().include(includePattern); + const src = "/contact"; + const res = schema["executeValidate"]("path" as SourcePath, src); + expect(res).toEqual({ + path: [ + { + fixes: ["router:check-route"], + message: `Did not validate route (router). This error (router:check-route) should typically be processed by Val internally. Seeing this error most likely means you have a Val version mismatch.`, + value: { + route: src, + sourcePath: "path", + include: includePattern, + exclude: undefined, + }, + }, + ], + }); + }); + + test("serialize: should serialize route schema", () => { + const schema = route(); + const res = schema["executeSerialize"](); + expect(res).toEqual({ + type: "route", + options: { + include: undefined, + exclude: undefined, + customValidate: false, + }, + opt: false, + customValidate: false, + }); + }); + + test("serialize: should serialize route schema with include pattern", () => { + const schema = route().include(/^\/(home|about)$/); + const res = schema["executeSerialize"](); + expect(res).toEqual({ + type: "route", + options: { + include: { + source: "^\\/(home|about)$", + flags: "", + }, + exclude: undefined, + customValidate: false, + }, + opt: false, + customValidate: false, + }); + }); + + test("validate: should return validation error with exclude pattern", () => { + const excludePattern = /^\/admin/; + const schema = route().exclude(excludePattern); + const src = "/admin/users"; + const res = schema["executeValidate"]("path" as SourcePath, src); + expect(res).toEqual({ + path: [ + { + fixes: ["router:check-route"], + message: `Did not validate route (router). This error (router:check-route) should typically be processed by Val internally. Seeing this error most likely means you have a Val version mismatch.`, + value: { + route: src, + sourcePath: "path", + include: undefined, + exclude: excludePattern, + }, + }, + ], + }); + }); + + test("validate: should return validation error with both include and exclude", () => { + const includePattern = /^\/api\//; + const excludePattern = /^\/api\/internal\//; + const schema = route().include(includePattern).exclude(excludePattern); + const src = "/api/internal/secret"; + const res = schema["executeValidate"]("path" as SourcePath, src); + expect(res).toEqual({ + path: [ + { + fixes: ["router:check-route"], + message: `Did not validate route (router). This error (router:check-route) should typically be processed by Val internally. Seeing this error most likely means you have a Val version mismatch.`, + value: { + route: src, + sourcePath: "path", + include: includePattern, + exclude: excludePattern, + }, + }, + ], + }); + }); + + test("serialize: should serialize route schema with exclude pattern", () => { + const schema = route().exclude(/^\/admin/); + const res = schema["executeSerialize"](); + expect(res).toEqual({ + type: "route", + options: { + include: undefined, + exclude: { + source: "^\\/admin", + flags: "", + }, + customValidate: false, + }, + opt: false, + customValidate: false, + }); + }); + + test("serialize: should serialize route schema with both include and exclude", () => { + const schema = route() + .include(/^\/api\//) + .exclude(/^\/api\/internal\//); + const res = schema["executeSerialize"](); + expect(res).toEqual({ + type: "route", + options: { + include: { + source: "^\\/api\\/", + flags: "", + }, + exclude: { + source: "^\\/api\\/internal\\/", + flags: "", + }, + customValidate: false, + }, + opt: false, + customValidate: false, + }); + }); +}); diff --git a/packages/core/src/schema/route.ts b/packages/core/src/schema/route.ts new file mode 100644 index 000000000..a029b08db --- /dev/null +++ b/packages/core/src/schema/route.ts @@ -0,0 +1,194 @@ +import { Schema, SchemaAssertResult, SerializedSchema } from "."; +import { SourcePath } from "../val"; +import { ReifiedRender } from "../render"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; + +type RouteOptions = { + include?: RegExp; + exclude?: RegExp; +}; + +export type SerializedRouteSchema = { + type: "route"; + options?: { + include?: { + source: string; + flags: string; + }; + exclude?: { + source: string; + flags: string; + }; + customValidate?: boolean; + }; + opt: boolean; + customValidate?: boolean; +}; + +export class RouteSchema extends Schema { + constructor( + private readonly options?: RouteOptions, + private readonly opt: boolean = false, + private readonly customValidateFunctions: (( + src: Src, + ) => false | string)[] = [], + ) { + super(); + } + + /** + * Specify a pattern for which routes are allowed. + * + * Semantics: + * - If only include is set: route must match include pattern + * - If only exclude is set: route must NOT match exclude pattern + * - If both are set: route must match include AND must NOT match exclude + * + * @example + * s.route().include(/^\/(home|about|contact)$/) // Only these specific routes + * s.route().include(/^\/api\//).exclude(/^\/api\/internal\//) // API routes except internal + */ + include(pattern: RegExp): RouteSchema { + return new RouteSchema( + { ...this.options, include: pattern }, + this.opt, + this.customValidateFunctions, + ); + } + + /** + * Specify a pattern for which routes should be excluded. + * + * Semantics: + * - If only include is set: route must match include pattern + * - If only exclude is set: route must NOT match exclude pattern + * - If both are set: route must match include AND must NOT match exclude + * + * @example + * s.route().exclude(/^\/admin/) // Exclude all admin routes + * s.route().include(/^\/api\//).exclude(/^\/api\/internal\//) // API routes except internal + */ + exclude(pattern: RegExp): RouteSchema { + return new RouteSchema( + { ...this.options, exclude: pattern }, + this.opt, + this.customValidateFunctions, + ); + } + + validate(validationFunction: (src: Src) => false | string): RouteSchema { + return new RouteSchema( + this.options, + this.opt, + this.customValidateFunctions.concat(validationFunction), + ); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + if (this.opt && (src === null || src === undefined)) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + if (typeof src !== "string") { + return { + [path]: [ + { message: `Expected 'string', got '${typeof src}'`, value: src }, + ], + } as ValidationErrors; + } + + return { + [path]: [ + ...customValidationErrors, + { + fixes: ["router:check-route"], + message: `Did not validate route (router). This error (router:check-route) should typically be processed by Val internally. Seeing this error most likely means you have a Val version mismatch.`, + value: { + route: src, + sourcePath: path, + include: this.options?.include, + exclude: this.options?.exclude, + }, + }, + ], + }; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (typeof src === "string") { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'string', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + + nullable(): RouteSchema { + return new RouteSchema( + this.options, + true, + this.customValidateFunctions, + ) as unknown as RouteSchema; + } + + protected executeSerialize(): SerializedSchema { + return { + type: "route", + options: { + include: this.options?.include && { + source: this.options.include.source, + flags: this.options.include.flags, + }, + exclude: this.options?.exclude && { + source: this.options.exclude.source, + flags: this.options.exclude.flags, + }, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }, + opt: this.opt, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + } + + protected executeRender(): ReifiedRender { + return {}; + } +} + +export const route = ( + options?: Record, +): RouteSchema => { + return new RouteSchema(options); +}; diff --git a/packages/core/src/schema/router.test.ts b/packages/core/src/schema/router.test.ts new file mode 100644 index 000000000..e0629c0fd --- /dev/null +++ b/packages/core/src/schema/router.test.ts @@ -0,0 +1,94 @@ +import { initSchema } from "../initSchema"; +import { router } from "./router"; +import { nextAppRouter, ValRouter } from "../router"; + +const s = initSchema(); + +describe("router", () => { + const mockRouter: ValRouter = { + getRouterId: () => "test-router", + validate: () => [], + }; + + it("should create a router record", () => { + const schema = s.object({ + title: s.string(), + }); + + const routerSchema = router(mockRouter, schema); + + const serialized = routerSchema["executeSerialize"](); + expect(serialized.type).toBe("record"); + expect(serialized.router).toBe("test-router"); + }); + + it("should be equivalent to s.record().router()", () => { + const schema = s.object({ + title: s.string(), + }); + + const routerSchema1 = router(mockRouter, schema); + const routerSchema2 = s.record(schema).router(mockRouter); + + const serialized1 = routerSchema1["executeSerialize"](); + const serialized2 = routerSchema2["executeSerialize"](); + + expect(serialized1.type).toBe(serialized2.type); + expect(serialized1.router).toBe(serialized2.router); + expect(serialized1.opt).toBe(serialized2.opt); + }); + + it("should work with nextAppRouter", () => { + const schema = s.object({ + title: s.string(), + content: s.string(), + }); + + const routerSchema = router(nextAppRouter, schema); + + const serialized = routerSchema["executeSerialize"](); + expect(serialized.type).toBe("record"); + expect(serialized.router).toBe("next-app-router"); + }); + + it("should be accessible via s.router()", () => { + const schema = s.object({ + title: s.string(), + }); + + const routerSchema = s.router(mockRouter, schema); + + expect(routerSchema).toBeDefined(); + const serialized = routerSchema["executeSerialize"](); + expect(serialized.type).toBe("record"); + expect(serialized.router).toBe("test-router"); + }); + + it("should accept valid sources", () => { + const schema = s.object({ + title: s.string(), + }); + + const routerSchema = s.router(mockRouter, schema); + + // Should create a valid router schema + expect(routerSchema).toBeDefined(); + const serialized = routerSchema["executeSerialize"](); + expect(serialized.router).toBe("test-router"); + }); + + it("should work with complex schemas", () => { + const schema = s.object({ + title: s.string(), + content: s.richtext(), + published: s.boolean(), + }); + + const routerSchema = s.router(nextAppRouter, schema); + + const serialized = routerSchema["executeSerialize"](); + expect(serialized.type).toBe("record"); + expect(serialized.router).toBe("next-app-router"); + expect(serialized.item.type).toBe("object"); + }); +}); diff --git a/packages/core/src/schema/router.ts b/packages/core/src/schema/router.ts new file mode 100644 index 000000000..052fb2d98 --- /dev/null +++ b/packages/core/src/schema/router.ts @@ -0,0 +1,20 @@ +import { Schema, SelectorOfSchema } from "."; +import { ValRouter } from "../router"; +import { SelectorSource } from "../selector"; +import { RecordSchema } from "./record"; +import { string } from "./string"; + +export function router< + T extends Schema, + Src extends Record>, +>(router: ValRouter, item: T): RecordSchema, Src> { + const keySchema = string(); + const recordSchema = new RecordSchema, Src>( + item, + false, + [], + router, + keySchema, + ); + return recordSchema; +} diff --git a/packages/core/src/schema/string.test.ts b/packages/core/src/schema/string.test.ts new file mode 100644 index 000000000..1c6033c64 --- /dev/null +++ b/packages/core/src/schema/string.test.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { SourcePath } from "../val"; +import { number } from "./number"; + +describe("NumberSchema", () => { + test("assert: should return true if src is a number", () => { + const schema = number().nullable(); + expect(schema["executeAssert"]("foo" as SourcePath, 1)).toEqual({ + success: true, + data: 1, + }); + }); + test("assert: should return false if src is a string", () => { + const schema = number(); + expect( + schema["executeAssert"]("foo" as SourcePath, "1" as any).success, + ).toEqual(false); + }); +}); diff --git a/packages/core/src/schema/string.ts b/packages/core/src/schema/string.ts new file mode 100644 index 000000000..bdca09f2b --- /dev/null +++ b/packages/core/src/schema/string.ts @@ -0,0 +1,282 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Schema, SchemaAssertResult, SerializedSchema } from "."; +import { CodeLanguage, ReifiedRender } from "../render"; +import { ModuleFilePath, SourcePath } from "../val"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; + +type StringOptions = { + maxLength?: number; + minLength?: number; + regexp?: RegExp; + regExpMessage?: string; +}; + +export type SerializedStringSchema = { + type: "string"; + options?: { + maxLength?: number; + minLength?: number; + regexp?: { + message?: string; + source: string; + flags: string; + }; + customValidate?: boolean; + }; + opt: boolean; + raw: boolean; + customValidate?: boolean; +}; + +const brand = Symbol("string"); +export type RawString = string & { readonly [brand]: "raw" }; + +export class StringSchema extends Schema { + constructor( + private readonly options?: StringOptions, + private readonly opt: boolean = false, + private readonly isRaw: boolean = false, + private readonly customValidateFunctions: (( + src: Src, + ) => false | string)[] = [], + private readonly renderInput: + | { as: "textarea" } + | { as: "code"; language: CodeLanguage } + | null = null, + ) { + super(); + } + + /** + * @deprecated Use `minLength` instead + */ + min(minLength: number): StringSchema { + return this.minLength(minLength); + } + + minLength(minLength: number): StringSchema { + return new StringSchema( + { ...this.options, minLength }, + this.opt, + this.isRaw, + this.customValidateFunctions, + this.renderInput, + ); + } + + /** + * @deprecated Use `maxLength` instead + */ + max(maxLength: number): StringSchema { + return this.maxLength(maxLength); + } + + maxLength(maxLength: number): StringSchema { + return new StringSchema( + { ...this.options, maxLength }, + this.opt, + this.isRaw, + this.customValidateFunctions, + this.renderInput, + ); + } + + regexp(regexp: RegExp, message?: string): StringSchema { + return new StringSchema( + { ...this.options, regexp, regExpMessage: message }, + this.opt, + this.isRaw, + this.customValidateFunctions, + this.renderInput, + ); + } + + validate( + validationFunction: (src: Src) => false | string, + ): StringSchema { + return new StringSchema( + this.options, + this.opt, + this.isRaw, + this.customValidateFunctions.concat(validationFunction), + this.renderInput, + ); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const errors: ValidationError[] = this.executeCustomValidateFunctions( + src, + this.customValidateFunctions, + { path }, + ); + if (this.opt && (src === null || src === undefined)) { + return errors.length > 0 ? { [path]: errors } : false; + } + if (!this.opt && (src === null || src === undefined)) { + return { + [path]: [ + { + message: `Expected 'string', got '${src === null ? "null" : "undefined"}'`, + value: src, + }, + ], + } as ValidationErrors; + } + if (typeof src !== "string") { + return { + [path]: [ + { message: `Expected 'string', got '${typeof src}'`, value: src }, + ], + } as ValidationErrors; + } + if (this.options?.maxLength && src.length > this.options.maxLength) { + errors.push({ + message: `Expected string to be at most ${this.options.maxLength} characters long, got ${src.length}`, + value: src, + }); + } + if (this.options?.minLength && src.length < this.options.minLength) { + errors.push({ + message: `Expected string to be at least ${this.options.minLength} characters long, got ${src.length}`, + value: src, + }); + } + if (this.options?.regexp && !this.options.regexp.test(src)) { + errors.push({ + message: + this.options.regExpMessage || + `Expected string to match reg exp: ${this.options.regexp.toString()}, got '${src}'`, + value: src, + }); + } + if (errors.length > 0) { + return { + [path]: errors, + } as ValidationErrors; + } + return false; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (typeof src === "string") { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'string', got '${typeof src}'`, + typeError: true, + }, + ], + }, + }; + } + + nullable(): StringSchema { + return new StringSchema( + this.options, + true, + this.isRaw, + this.customValidateFunctions, + this.renderInput, + ) as unknown as StringSchema; + } + + raw(): StringSchema { + return new StringSchema( + this.options, + this.opt, + true, + this.customValidateFunctions, + this.renderInput, + ) as unknown as StringSchema< + Src extends null ? RawString | null : RawString + >; + } + + protected executeSerialize(): SerializedSchema { + return { + type: "string", + options: { + maxLength: this.options?.maxLength, + minLength: this.options?.minLength, + regexp: this.options?.regexp && { + message: this.options.regExpMessage, + source: this.options.regexp.source, + flags: this.options.regexp.flags, + }, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }, + opt: this.opt, + raw: this.isRaw, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + }; + } + + render( + input: { as: "textarea" } | { as: "code"; language: CodeLanguage }, + ): StringSchema { + return new StringSchema( + this.options, + this.opt, + this.isRaw, + this.customValidateFunctions, + input, + ); + } + + protected executeRender( + sourcePath: SourcePath | ModuleFilePath, + src: Src, + ): ReifiedRender { + if (this.renderInput) { + if (this.renderInput.as === "code") { + return { + [sourcePath]: { + status: "success" as const, + data: { + layout: this.renderInput.as, + language: this.renderInput.language, + }, + }, + }; + } + return { + [sourcePath]: { + status: "success" as const, + data: { + layout: this.renderInput.as, + }, + }, + }; + } + return {}; + } +} + +export const string = ( + options?: Record, +): StringSchema => { + return new StringSchema(options); +}; diff --git a/packages/core/src/schema/union.test.ts b/packages/core/src/schema/union.test.ts new file mode 100644 index 000000000..be36487a7 --- /dev/null +++ b/packages/core/src/schema/union.test.ts @@ -0,0 +1,117 @@ +import { object } from "./object"; +import { union } from "./union"; +import { literal } from "./literal"; +import { ModuleFilePath, SourcePath } from "../val"; +import { string } from "./string"; +import { record } from "./record"; + +describe("UnionSchema", () => { + // tagged unions: + test("assert: tagged unions should return success for valid tagged unions", () => { + const schema = union("type", object({ type: literal("string") })); + const res = schema["executeAssert"]("foo" as SourcePath, { + type: "string", + }); + expect(res).toEqual({ + success: true, + data: { type: "string" }, + }); + }); + + test("assert: tagged unions should return success if value is a string", () => { + const schema = union("type", object({ type: literal("string") })); + const res = schema["executeAssert"]("foo" as SourcePath, { + type: "string", + }); + expect(res).toEqual({ + success: true, + data: { type: "string" }, + }); + }); + + test("assert: tagged unions should return error if value is a string", () => { + const schema = union( + "type", + object({ type: literal("string") }), + object({ type: literal("number") }), + ); + const res = schema["executeAssert"]("foo" as SourcePath, { + wrongKey: "string", + }); + expect(res.success).toEqual(false); + }); + + // string unions: + test("assert: string unions should return success for valid string unions", () => { + const schema = union(literal("one"), literal("two")); + const res = schema["executeAssert"]("foo" as SourcePath, "one"); + expect(res).toEqual({ + success: true, + data: "one", + }); + }); + + test("assert: string unions should return error for valid string unions", () => { + const schema = union(literal("one"), literal("two")); + const res = schema["executeAssert"]("foo" as SourcePath, "false"); + expect(res.success).toEqual(false); + }); + + test("render union object schema", () => { + const schema = union( + "type", + object({ + type: literal("value1"), + innerObject: record( + object({ + value: string(), + }), + ).render({ + as: "list", + select: ({ val }) => { + return { + title: val.value, + }; + }, + }), + }), + object({ type: literal("value2"), innerString: string() }), + ); + + expect( + schema["executeRender"]("/test.foo.val.ts" as ModuleFilePath, { + type: "value1", + innerObject: { + record1: { value: "test value 1" }, + record2: { value: "test value 2" }, + }, + }), + ).toStrictEqual({ + '/test.foo.val.ts?p="innerObject"': { + status: "success", + data: { + layout: "list", + parent: "record", + items: [ + [ + "record1", + { + title: "test value 1", + subtitle: undefined, + image: undefined, + }, + ], + [ + "record2", + { + title: "test value 2", + subtitle: undefined, + image: undefined, + }, + ], + ], + }, + }, + }); + }); +}); diff --git a/packages/core/src/schema/union.ts b/packages/core/src/schema/union.ts new file mode 100644 index 000000000..7989de800 --- /dev/null +++ b/packages/core/src/schema/union.ts @@ -0,0 +1,574 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + AssertError, + CustomValidateFunction, + Schema, + SchemaAssertResult, + SerializedSchema, +} from "."; +import { ReifiedRender } from "../render"; +import { + createValPathOfItem, + unsafeCreateSourcePath, +} from "../selector/SelectorProxy"; +import { SelectorSource } from "../selector/index"; +import { SourceObject } from "../source"; +import { ModuleFilePath, SourcePath } from "../val"; +import { LiteralSchema, SerializedLiteralSchema } from "./literal"; +import { ObjectSchema, SerializedObjectSchema } from "./object"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; + +export type SerializedUnionSchema = + | SerializedStringUnionSchema + | SerializedObjectUnionSchema; +export type SerializedStringUnionSchema = { + type: "union"; + key: SerializedLiteralSchema; + items: SerializedLiteralSchema[]; + opt: boolean; + customValidate?: boolean; +}; +export type SerializedObjectUnionSchema = { + type: "union"; + key: string; + items: SerializedObjectSchema[]; + opt: boolean; + customValidate?: boolean; +}; + +type SourceOf< + Key extends string | Schema, + T extends Schema< + Key extends string + ? SourceObject & { [k in Key]: string } + : Key extends Schema + ? string + : unknown + >[], +> = T extends Schema[] + ? S extends SelectorSource + ? S | (Key extends Schema ? K : never) + : never + : never; + +export class UnionSchema< + Key extends string | Schema, + T extends Schema< + Key extends string + ? SourceObject & { [k in Key]: string } + : Key extends Schema + ? string + : unknown + >[], + Src extends SourceOf | null, +> extends Schema { + validate( + validationFunction: (src: Src) => false | string, + ): UnionSchema { + return new UnionSchema( + this.key, + this.items, + this.opt, + this.customValidateFunctions.concat(validationFunction), + ); + } + + protected executeValidate(path: SourcePath, src: Src): ValidationErrors { + const customValidationErrors: ValidationError[] = + this.executeCustomValidateFunctions(src, this.customValidateFunctions, { + path, + }); + const unknownSrc = src as unknown; + if (this.opt && unknownSrc === null) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + + if (!this.key) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `Missing required first argument in union`, + schemaError: true, + }, + ], + }; + } + + const key = this.key; + if (!Array.isArray(this.items)) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `A union schema must take more than 1 schema arguments`, + schemaError: true, + }, + ], + }; + } + if (typeof key === "string") { + // tagged union + if (this.items.some((item) => !(item instanceof ObjectSchema))) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `Key is a string, so all schema items must be objects`, + schemaError: true, + }, + ], + }; + } + const objectSchemas = this.items as unknown as ObjectSchema< + { + [key: string]: Schema; + }, + { + [key: string]: SelectorSource; + } + >[]; + const serializedSchemas = objectSchemas.map((schema) => + schema["executeSerialize"](), + ); + const illegalSchemas = serializedSchemas.filter( + (schema) => + !(schema.type === "object") || + !(schema.items[key].type === "literal"), + ); + + if (illegalSchemas.length > 0) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `All schema items must be objects with a key: ${key} that is a literal schema. Found: ${JSON.stringify( + illegalSchemas, + null, + 2, + )}`, + schemaError: true, + }, + ], + }; + } + const serializedObjectSchemas = + serializedSchemas as SerializedObjectSchema[]; + const optionalLiterals = serializedObjectSchemas.filter( + (schema) => schema.items[key].opt, + ); + if (optionalLiterals.length > 1) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `Schema cannot have an optional keys: ${key}`, + schemaError: true, + }, + ], + }; + } + + if (typeof unknownSrc !== "object") { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `Expected an object`, + typeError: true, + }, + ], + }; + } + const objectSrc = unknownSrc as { [key: string]: SelectorSource }; + + if (objectSrc[key] === undefined) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `Missing required key: ${key}`, + typeError: true, + }, + ], + }; + } + + const foundSchemaLiterals: string[] = []; + for (const schema of serializedObjectSchemas) { + const schemaKey = schema.items[key]; + if (schemaKey.type === "literal") { + if (!foundSchemaLiterals.includes(schemaKey.value)) { + foundSchemaLiterals.push(schemaKey.value); + } else { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `Found duplicate key in schema: ${schemaKey.value}`, + schemaError: true, + }, + ], + }; + } + } + } + const objectSchemaAtKey = objectSchemas.find( + (schema) => + !schema["items"][key]["executeValidate"](path, objectSrc[key]), + ); + if (!objectSchemaAtKey) { + const keyPath = createValPathOfItem(path, key); + if (!keyPath) { + throw new Error( + `Internal error: could not create path at ${ + !path && typeof path === "string" ? "" : path + } at key ${key}`, + ); + } + return { + [keyPath]: [ + { + message: `Invalid key: "${key}". Value was: "${ + objectSrc[key] + }". Valid values: ${serializedObjectSchemas + .map((schema) => { + const keySchema = schema.items[key]; + if (keySchema.type === "literal" && keySchema.value) { + return `"${keySchema.value}"`; + } else { + // should not happen here, we already checked this + throw new Error( + `Expected literal schema, got ${JSON.stringify( + keySchema, + null, + 2, + )}`, + ); + } + }) + .join(", ")}`, + }, + ], + }; + } + const error = objectSchemaAtKey["executeValidate"](path, objectSrc); + if (error) { + return error; + } + } else if (key instanceof LiteralSchema) { + if (this.items.some((item) => !(item instanceof LiteralSchema))) { + return { + [path]: [ + { + message: `Key is a literal schema, so all schema items must be literals`, + typeError: true, + }, + ], + }; + } + const literalItems = [key, ...this.items] as LiteralSchema[]; + if (typeof unknownSrc === "string") { + const isMatch = literalItems.some( + (item) => !item["executeValidate"](path, unknownSrc), + ); + if (!isMatch) { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `Union must match one of the following: ${literalItems + .map((item) => `"${item["value"]}"`) + .join(", ")}`, + }, + ], + }; + } + } + } else { + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : { + [path]: [ + ...customValidationErrors, + { + message: `Expected a string or literal`, + }, + ], + }; + } + return customValidationErrors.length > 0 + ? { [path]: customValidationErrors } + : false; + } + + protected executeAssert( + path: SourcePath, + src: unknown, + ): SchemaAssertResult { + if (this.opt && src === null) { + return { + success: true, + data: src, + } as SchemaAssertResult; + } + if (src === null) { + return { + success: false, + errors: { + [path]: [ + { + message: `Expected 'object', got 'null'`, + typeError: true, + }, + ], + }, + }; + } + if (!this.key) { + return { + success: false, + errors: { + [path]: [ + { + message: `Missing required first argument in union schema`, + schemaError: true, + }, + ], + }, + }; + } + if (!Array.isArray(this.items)) { + return { + success: false, + errors: { + [path]: [ + { + message: `The schema of this value is wrong. Schema is neither a union of literals nor a tagged union (of objects)`, + schemaError: true, + }, + ], + }, + }; + } + if (this.key instanceof LiteralSchema) { + let success = false; + const errors: Record = {}; + for (const itemSchema of [this.key as Schema].concat( + ...(this.items as Schema[]), + )) { + if (!(itemSchema instanceof LiteralSchema)) { + return { + success: false, + errors: { + [path]: [ + { + message: `Schema of value is a union of string, so all schema items must be literals`, + schemaError: true, + }, + ], + }, + }; + } + if (typeof src !== "string") { + errors[path] = [ + { + message: `Expected 'string', got '${typeof src}'`, + typeError: true, + }, + ]; + continue; + } + const res = itemSchema["executeAssert"](path, src); + if (res.success) { + success = true; + break; + } else { + for (const [key, value] of Object.entries(res.errors)) { + if (!errors[key as SourcePath]) { + errors[key as SourcePath] = []; + } + errors[key as SourcePath].push(...value); + } + } + } + if (!success) { + return { + success: false, + errors, + }; + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } else if (typeof this.key === "string") { + let success = false; + const errors: Record = {}; + for (const itemSchema of this.items) { + const res = itemSchema["executeAssert"](path, src); + if (res.success) { + success = true; + break; + } else { + for (const [key, value] of Object.entries(res.errors)) { + if (!errors[key as SourcePath]) { + errors[key as SourcePath] = []; + } + errors[key as SourcePath].push(...value); // by appending all type errors, we most likely get a lot of duplicate errors. Currently we believe this is correct though, but should probably be handled in when showing the errors to users + } + } + } + if (!success) { + return { + success: false, + errors, + }; + } + return { + success: true, + data: src, + } as SchemaAssertResult; + } else { + return { + success: false, + errors: { + [path]: [ + { + message: `The schema of this value is wrong. Schema is neither a union of literals nor a tagged union (of objects)`, + schemaError: true, + }, + ], + }, + }; + } + } + + nullable(): UnionSchema { + return new UnionSchema(this.key, this.items, true); + } + + protected executeSerialize(): SerializedSchema { + if (typeof this.key === "string") { + return { + type: "union", + key: this.key, + items: this.items.map((o) => o["executeSerialize"]()), + opt: this.opt, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + } as SerializedObjectUnionSchema; + } + return { + type: "union", + key: this.key["executeSerialize"](), + items: this.items.map((o) => o["executeSerialize"]()), + opt: this.opt, + customValidate: + this.customValidateFunctions && + this.customValidateFunctions?.length > 0, + } as SerializedStringUnionSchema; + } + + constructor( + private readonly key: Key, + private readonly items: T, + private readonly opt: boolean = false, + private readonly customValidateFunctions: CustomValidateFunction[] = [], + ) { + super(); + } + + protected executeRender( + sourcePath: SourcePath | ModuleFilePath, + src: Src, + ): ReifiedRender { + const res: ReifiedRender = {}; + if (src === null) { + return res; + } + if (this.key instanceof LiteralSchema) { + return res; + } + const unionKey = this.key; + if (typeof unionKey === "string") { + const thisSchema = this.items.find( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (item): item is ObjectSchema => { + if (item instanceof ObjectSchema) { + const itemKey = item["items"][unionKey]; + if (itemKey instanceof LiteralSchema) { + return ( + typeof src === "object" && + unionKey in src && + itemKey["value"] === src[unionKey] + ); + } + } + return false; + }, + ); + if (thisSchema) { + const itemResult = thisSchema["executeRender"](sourcePath, src); + for (const keyS in itemResult) { + const key = keyS as SourcePath | ModuleFilePath; + res[key] = itemResult[key]; + } + return res; + } + res[sourcePath] = { + status: "error", + message: `Could not find a matching (object) schema for the union key: ${unionKey}`, + }; + } + res[sourcePath] = { + status: "error", + message: `The schema of this value is wrong. Expected a object union schema, but union key is not a string. Got: '${JSON.stringify( + unionKey, + null, + 2, + )}'`, + }; + return res; + } +} + +export const union = < + Key extends string | Schema, + T extends Schema< + Key extends string + ? SourceObject & { [k in Key]: string } + : Key extends Schema + ? string + : unknown + >[], +>( + key: Key, + ...objects: T +): UnionSchema> => { + return new UnionSchema(key, objects); +}; diff --git a/packages/core/src/schema/validation.test.ts b/packages/core/src/schema/validation.test.ts new file mode 100644 index 000000000..e8bd43dab --- /dev/null +++ b/packages/core/src/schema/validation.test.ts @@ -0,0 +1,498 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { SourcePath } from "../val"; +import { array } from "./array"; +import { boolean } from "./boolean"; +import { literal } from "./literal"; +import { number } from "./number"; +import { object } from "./object"; +import { string } from "./string"; +import { image } from "./image"; +import { ValidationFix } from "./validation/ValidationFix"; +import { + ValidationError, + ValidationErrors, +} from "./validation/ValidationError"; +import { richtext } from "./richtext"; +import { record } from "./record"; +import { keyOf } from "./keyOf"; +import { define } from "../module"; +import { union } from "./union"; +import { createValPathOfItem } from "../selector/SelectorProxy"; +import { date } from "./date"; +import { initFile } from "../source/file"; + +const fileVal = initFile(); +const testPath = "/test" as SourcePath; +const pathOf = (p: string | symbol | number) => { + return createValPathOfItem(testPath, p); +}; +const ValidationTestCases: { + description: string; + input: any; + schema: any; + expected: [SourcePath | undefined] | false; + fixes?: { + [path: string]: ValidationFix[]; + }; +}[] = [ + // boolean + { + description: "basic boolean (true)", + input: true, + schema: boolean(), + expected: false, + }, + { + description: "basic boolean (false)", + input: false, + schema: boolean(), + expected: false, + }, + { + description: "failing boolean (null)", + input: null, + schema: boolean(), + expected: [testPath], + }, + { + description: "nullable boolean (null)", + input: null, + schema: boolean().nullable(), + expected: false, + }, + { + description: "failing boolean", + input: "false", + schema: boolean(), + expected: [testPath], + }, + // number + { + description: "basic number (0)", + input: 0, + schema: number(), + expected: false, + }, + { + description: "basic number (-1)", + input: -1, + schema: number(), + expected: false, + }, + { + description: "basic number (1)", + input: 1, + schema: number(), + expected: false, + }, + { + description: "basic number (1)", + input: 1, + schema: number(), + expected: false, + }, + // string + { + description: "basic string", + input: "two", + schema: string(), + expected: false, + }, + { + description: "failing string", + input: 1, + schema: string(), + expected: [testPath], + }, + { + description: "basic null nullable string", + input: null, + schema: string().nullable(), + expected: false, + }, + { + description: "nested nullable string", + input: { test: null }, + schema: object({ test: string().nullable() }).nullable(), + expected: false, + }, + { + description: "basic nullable string", + input: "two", + schema: string().nullable(), + expected: false, + }, + { + description: "failing max length", + input: "three", + schema: string().maxLength(3), + expected: [testPath], + }, + { + description: "basic max length", + input: "two", + schema: string().maxLength(3), + expected: false, + }, + { + description: "failing min length", + input: "a", + schema: string().minLength(3), + expected: [testPath], + }, + { + description: "basic min length", + input: "two", + schema: string().minLength(3), + expected: false, + }, + { + description: "basic reg exp", + input: "two", + schema: string().regexp(/two|three/), + expected: false, + }, + { + description: "failing reg exp", + input: "one", + schema: string().regexp(/two|three/), + expected: [testPath], + }, + // literal + { + description: "basic literal", + input: "one", + schema: literal("one"), + expected: false, + }, + { + description: "basic literal nullable", + input: "one", + schema: literal("one").nullable(), + expected: false, + }, + { + description: "failing literal", + input: "two", + schema: literal("one"), + expected: [testPath], + }, + // array + { + description: "basic array(string)", + input: ["one", "two"], + schema: array(string()), + expected: false, + }, + { + description: "failing array(string)", + input: [true, "false"], + schema: array(string()), + expected: [pathOf(0)], + }, + // object + { + description: "basic object(string)", + input: { one: "one val", two: 2 }, + schema: object({ + one: string(), + two: number(), + }), + expected: false, + }, + { + description: "failing object(string)", + input: { one: "one val", two: 1 }, + schema: object({ + one: string(), + two: string(), + }), + expected: [pathOf("two")], + }, + // record + { + description: "basic record(string)", + input: { one: "one val", two: "two val" }, + schema: record(string()), + expected: false, + }, + { + description: "failing record(string)", + input: { one: "one val", two: 1 }, + schema: object({ + one: string(), + two: string(), + }), + expected: [pathOf("two")], + }, + { + description: "basic keyOf(record)", + input: "one", + schema: keyOf(define("/keyof-module", record(string()), { one: "" })), + expected: [testPath], + fixes: { + [testPath]: ["keyof:check-keys"], + }, + }, + { + description: "failing keyOf(record)", + input: 1, + schema: keyOf(define("/keyof-module", record(string()), {})), + expected: [testPath], + }, + { + description: "basic keyOf(object)", + input: "test1", + schema: keyOf( + define("/keyof-module", object({ test1: string(), test2: string() }), { + test1: "", + test2: "", + }), + ), + expected: false, + }, + { + description: "failing keyOf(object)", + input: "test", + schema: keyOf( + define("/keyof-module", object({ test1: string(), test2: string() }), { + test1: "", + test2: "", + }), + ), + expected: [testPath], + }, + // image / file + { + description: "nullable image", + input: null, + schema: image().nullable(), + expected: false, + }, + { + description: "failure image:: null", + input: null, + schema: image(), + expected: [testPath], + }, + { + description: "failure image: replace metadata", + input: fileVal("/public/val/test.png", { + width: 100, + height: 100, + }), + schema: image(), + expected: [testPath], + fixes: { + [testPath]: ["image:check-metadata"], + }, + }, + { + description: "failure image: check metadata", + input: fileVal("/public/val/test.png", { + width: 100, + height: 100, + }), + schema: image(), + expected: [testPath], + fixes: { + [testPath]: ["image:check-metadata"], + }, + }, + // richtext + { + description: "basic richtext", + input: [{ tag: "p", children: ["test"] }], + expected: false, + schema: richtext({ style: { bold: true } }), + }, + // TODO: more richtext cases + { + description: "basic union literals: key", + input: "one", + schema: union(literal("one"), literal("two"), literal("three")), + expected: false, + }, + { + description: "basic union literals: item", + input: "three", + schema: union(literal("one"), literal("two"), literal("three")), + expected: false, + }, + { + description: "failing union literals", + input: "four", + schema: union(literal("one"), literal("two"), literal("three")), + expected: [testPath], + }, + { + description: "basic tagged union", + input: { + type: "singleItem", + text: "test", + }, + schema: union( + "type", + object({ type: literal("singleItem"), text: string() }), + object({ + type: literal("multiItem"), + items: array(object({ type: literal("singleItem"), text: string() })), + }), + ), + expected: false, + }, + { + description: "failing tagged union: 1", + input: { + type: "multiItem", + text: "test", + }, + schema: union( + "type", + object({ type: literal("singleItem"), text: string() }), + object({ + type: literal("multiItem"), + items: array(string()), + }), + ), + expected: [pathOf("items")], + }, + { + description: "failing tagged union: 2", + input: { + type: "multiItem", + items: { test: "subItem2", text2: "" }, + }, + schema: union( + "type", + object({ type: literal("singleItem"), text: string() }), + object({ + type: literal("multiItem"), + items: union( + "test", + object({ test: literal("subItem1"), text1: string() }), + object({ test: literal("subItem2"), text2: string().minLength(2) }), + ), + }), + ), + expected: [createValPathOfItem(pathOf("items"), "text2")], + }, + { + description: "failing tagged union: 3", + input: { + type: "multiItem", + items: { test: "subItem1", text2: "" }, + }, + schema: union( + "type", + object({ type: literal("duplicateItem"), text: string() }), + object({ + type: literal("duplicateItem"), + text2: string(), + }), + ), + expected: [testPath], + }, + { + description: "failing tagged union: 4", + input: { + type: "foobar", + items: { test: "subItem1", text2: "" }, + }, + schema: union( + "type", + object({ type: literal("test1"), text: string() }), + object({ + type: literal("test2"), + text2: string(), + }), + ), + expected: [pathOf("type")], + }, + + { + description: "failing tagged union: 5", + input: { + type: "test2", + image: fileVal("/public/val/test.png"), + }, + schema: union( + "type", + object({ type: literal("test1"), text: string() }), + object({ + type: literal("test2"), + image: image(), + }), + ), + expected: [pathOf("image")], + fixes: { + [pathOf("image") as string]: ["image:add-metadata"], + }, + }, + { + description: "date validation: base case", + input: "2021-01-01", + schema: date(), + expected: false, + }, + { + description: "date validation: from error", + input: "2020-01-01", + schema: date().from("2021-01-01"), + expected: [testPath], + }, + { + description: "date validation: to error", + input: "2021-01-01", + schema: date().to("2019-12-31"), + expected: [testPath], + }, + { + description: "date validation: between", + input: "2021-01-01", + schema: date().from("2020-01-01").to("2023-12-31"), + expected: false, + }, + { + description: "date validation: between error", + input: "2021-01-01", + schema: date().from("2022-01-01").to("2022-12-31"), + expected: [testPath], + }, + { + description: "date validation: from / to is not valid in schema error", + input: "2021-01-01", + schema: date().from("2022-01-01").to("2019-12-31"), + expected: [testPath], + }, + + // TODO: oneOf + // TODO: i18n +]; + +describe("validation", () => { + test.each(ValidationTestCases)( + 'validate ($description): "$expected"', + ({ input, schema, expected, fixes }) => { + const result = schema["executeValidate"](testPath, input); + if (result) { + expect(Object.keys(result)).toStrictEqual(expected); + if (fixes) { + expect( + Object.fromEntries( + Object.entries(result as ValidationErrors).map( + ([path, errors]) => [ + path, + errors.flatMap((error: ValidationError) => error.fixes), + ], + ), + ), + ).toStrictEqual(fixes); + } + } else { + expect(result).toStrictEqual(expected); + } + }, + ); +}); diff --git a/packages/core/src/schema/validation/ValidationError.ts b/packages/core/src/schema/validation/ValidationError.ts new file mode 100644 index 000000000..361fbc660 --- /dev/null +++ b/packages/core/src/schema/validation/ValidationError.ts @@ -0,0 +1,19 @@ +import { SourcePath } from "../../val"; +import { ValidationFix } from "./ValidationFix"; + +export type ValidationError = { + message: string; + value?: unknown; + typeError?: boolean; + schemaError?: boolean; + fixes?: ValidationFix[]; + keyError?: boolean; +}; + +/** + * Equals `false` if no validation errors were found. + * Errors are indexed by the full source path. + * + * Global errors have the path `"/"`. + */ +export type ValidationErrors = false | Record; diff --git a/packages/core/src/schema/validation/ValidationFix.ts b/packages/core/src/schema/validation/ValidationFix.ts new file mode 100644 index 000000000..02a875e34 --- /dev/null +++ b/packages/core/src/schema/validation/ValidationFix.ts @@ -0,0 +1,22 @@ +export const ValidationFix = [ + "image:add-metadata", + "image:check-metadata", + "image:upload-remote", + "image:download-remote", + "image:check-remote", + "images:check-remote", + "file:add-metadata", + "file:check-metadata", + "file:upload-remote", + "file:download-remote", + "file:check-remote", + "files:check-remote", + "keyof:check-keys", + "router:check-route", + "images:check-unique-folder", + "files:check-unique-folder", + "images:check-all-files", + "files:check-all-files", +] as const; + +export type ValidationFix = (typeof ValidationFix)[number]; diff --git a/packages/core/src/selector/SelectorProxy.ts b/packages/core/src/selector/SelectorProxy.ts new file mode 100644 index 000000000..9ac6343f3 --- /dev/null +++ b/packages/core/src/selector/SelectorProxy.ts @@ -0,0 +1,269 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Path, GenericSelector, GetSource, GetSchema } from "./index"; +import { Schema } from "../schema"; +import { convertFileSource } from "../schema/file"; +import { Source, SourcePrimitive, VAL_EXTENSION } from "../source"; +import { FILE_REF_PROP } from "../source/file"; +import { isSerializedVal, ModulePath, SourcePath } from "../val"; +import { Internal } from ".."; + +function hasOwn(obj: object, prop: T): boolean { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +function andThen(f: (...args: any[]) => any, source: any, path?: SourcePath) { + if (source) { + return newSelectorProxy(f(newSelectorProxy(source, path))); + } + return newSelectorProxy(source, path); +} + +export function isSelector(source: any): source is GenericSelector { + return ( + typeof source === "object" && + source !== null && + (GetSource in source || Path in source) + ); +} + +export function newSelectorProxy( + source: any, + path?: SourcePath, + moduleSchema?: any, +): any { + if (typeof source === "object") { + if (isSelector(source)) { + return source; + } else if (isSerializedVal(source)) { + return newSelectorProxy(source.val, source.valPath); + } + } + + if (source && source[FILE_REF_PROP] && source[VAL_EXTENSION] === "file") { + const fileRef = source[FILE_REF_PROP]; + if (typeof fileRef !== "string") { + throw Error("Invalid file ref: " + fileRef); + } + return newSelectorProxy(convertFileSource(source), path, moduleSchema); + } + + switch (typeof source) { + case "function": + case "symbol": + throw Error(`Invalid selector type: ${typeof source}: ${source}`); + case "object": + // Handles both objects and arrays! + if (source !== null) { + return new Proxy(source, { + // TODO: see proxy docs if we want more traps + has(target, prop: string | symbol) { + if (prop === GetSource) { + return true; + } + if (prop === Path) { + return true; + } + if (prop === "andThen") { + return true; + } + if (prop === GetSchema) { + return true; + } + return prop in target; + }, + get(target, prop: string | symbol) { + if (prop === GetSource) { + return source; + } + if (prop === Path) { + return path; + } + if (prop === GetSchema) { + return moduleSchema; + } + if (prop === "andThen") { + return (f: any) => andThen(f, source, path); + } + if (Array.isArray(target)) { + if (prop === "filter") { + return (f: any) => { + const filtered = target + .map((a, i) => + newSelectorProxy( + a, + createValPathOfItem(path, i), + moduleSchema?.item, + ), + ) + .filter((a) => { + if (f && f instanceof Schema) { + return f["executeAssert"]( + path || ("" as SourcePath), + unValify(a), + ).success; + } else { + return unValify(f(a)); + } + }); + return newSelectorProxy(filtered, path, moduleSchema); + }; + } else if (prop === "map") { + return (f: any) => { + const filtered = target.map((a, i) => { + const valueOrSelector = f( + newSelectorProxy( + a, + createValPathOfItem(path, i), + moduleSchema?.item, + ), + newSelectorProxy(i), + ); + if (isSelector(valueOrSelector)) { + return valueOrSelector; + } + return newSelectorProxy(valueOrSelector); + }); + return newSelectorProxy(filtered, path, moduleSchema); + }; + } + } + if (Array.isArray(target) && prop === "length") { + return newSelectorProxy(target.length); + } + const reflectedValue = Reflect.get(target, prop); + + if (hasOwn(source, prop)) { + if (!Number.isNaN(Number(prop))) { + return newSelectorProxy( + reflectedValue, + createValPathOfItem(path, Number(prop)), + moduleSchema?.item, + ); + } + return newSelectorProxy( + reflectedValue, + createValPathOfItem(path, prop), + moduleSchema?.items[prop], + ); + } + return reflectedValue; + }, + }); + } + // intentional fallthrough + // eslint-disable-next-line no-fallthrough + default: + return { + eq: (other: SourcePrimitive | GenericSelector) => { + let otherValue: any = other; + if (isSelector(other)) { + otherValue = other[GetSource]; + } + return newSelectorProxy(source === otherValue, undefined); + }, + andThen: (f: any) => { + return andThen(f, source === undefined ? null : source, path); + }, + [GetSource]: source === undefined ? null : source, + [Path]: path, + [GetSchema]: moduleSchema, + }; + } +} + +function selectorAsVal(sel: any): any { + if (isSerializedVal(sel)) { + // is a serialized val + return selectorAsVal(newSelectorProxy(sel.val, sel.valPath)); + } else if ( + typeof sel === "object" && + sel && + !(GetSource in sel) && + !Array.isArray(sel) + ) { + // is object + return Object.fromEntries( + Object.entries(sel).map(([k, v]) => [k, selectorAsVal(v)]), + ); + } else if ( + typeof sel === "object" && + sel && + !(GetSource in sel) && + Array.isArray(sel) + ) { + // is array + return sel.map((v) => selectorAsVal(v)); + } else if ( + typeof sel === "object" && + sel && + (GetSource in sel || Path in sel) + ) { + return selectorAsVal(sel?.[GetSource]); + } else if (sel === undefined) { + return null; + } + return sel; +} + +export function createValPathOfItem( + arrayPath: SourcePath | ModulePath | undefined, + prop: string | number | symbol, +) { + if (typeof prop === "symbol") { + throw Error( + `Cannot create val path of array item with symbol prop: ${prop.toString()}`, + ); + } + if (!arrayPath) { + return undefined; + } + if (arrayPath.includes(Internal.ModuleFilePathSep)) { + return `${arrayPath}.${JSON.stringify(prop)}` as SourcePath; + } + return `${arrayPath}${Internal.ModuleFilePathSep}${JSON.stringify( + prop, + )}` as SourcePath; +} + +// TODO: replace createValPathOfItem everywhere with this newer implementation (that does not return undefined but throws) +export function unsafeCreateSourcePath( + path: string, + itemKey: string | number | symbol, +) { + if (typeof itemKey === "symbol") { + throw Error( + `Cannot create val path of array item with symbol prop: ${itemKey.toString()}`, + ); + } + if (!path) { + throw Error( + `Cannot create val path of array item of empty or missing path: ${path}. Item: ${itemKey}`, + ); + } + if (path.includes(Internal.ModuleFilePathSep)) { + return `${path}.${JSON.stringify(itemKey)}` as SourcePath; + } + return `${path}${Internal.ModuleFilePathSep}${JSON.stringify( + itemKey, + )}` as SourcePath; +} + +export function selectorToVal(s: any): any { + const v = selectorAsVal(s?.[GetSource]); + return { + val: v, + [Path]: s?.[Path], + }; +} + +// TODO: could we do .val on the objects instead? +function unValify(valueOrSelector: any) { + if ( + typeof valueOrSelector === "object" && + (GetSource in valueOrSelector || Path in valueOrSelector) + ) { + const selectorValue = valueOrSelector[GetSource]; + return selectorValue; + } + return valueOrSelector; +} diff --git a/packages/core/src/selector/array.ts b/packages/core/src/selector/array.ts new file mode 100644 index 000000000..6c9799984 --- /dev/null +++ b/packages/core/src/selector/array.ts @@ -0,0 +1,13 @@ +import { GenericSelector } from "."; +import { Source, SourceArray } from "../source"; + +export type UndistributedSourceArray = [T] extends [ + infer U, // infer here to avoid Type instantiation is excessively deep and possibly infinite. See: https://github.com/microsoft/TypeScript/issues/30188#issuecomment-478938437. Avoiding infer extends to keep us below TS 4.9 compat +] + ? U extends Source[] + ? Selector + : never + : never; + +// TODO: docs +export type Selector = GenericSelector; diff --git a/packages/core/src/selector/boolean.ts b/packages/core/src/selector/boolean.ts new file mode 100644 index 000000000..fb8b746b2 --- /dev/null +++ b/packages/core/src/selector/boolean.ts @@ -0,0 +1,4 @@ +import { Selector as PrimitiveSelector } from "./primitive"; + +// TODO: docs +export type Selector = PrimitiveSelector; diff --git a/packages/core/src/selector/file.ts b/packages/core/src/selector/file.ts new file mode 100644 index 000000000..513e4013a --- /dev/null +++ b/packages/core/src/selector/file.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type */ +import { Source } from "../source"; +import { FileMetadata } from "../source/file"; +import { Selector as UnknownSelector, GenericSelector } from "./index"; + +// TODO: docs +export type FileSelector = + GenericSelector<{ + url: string; + }> & { + readonly url: UnknownSelector; + } & Metadata extends undefined + ? {} + : Metadata extends Source + ? { readonly metadata: UnknownSelector } + : {}; diff --git a/packages/core/src/selector/image.ts b/packages/core/src/selector/image.ts new file mode 100644 index 000000000..025d632d4 --- /dev/null +++ b/packages/core/src/selector/image.ts @@ -0,0 +1,4 @@ +import { ImageMetadata } from "../schema/image"; +import { FileSelector } from "./file"; + +export type ImageSelector = FileSelector; diff --git a/packages/core/src/selector/index.ts b/packages/core/src/selector/index.ts new file mode 100644 index 000000000..fc3e676ae --- /dev/null +++ b/packages/core/src/selector/index.ts @@ -0,0 +1,129 @@ +import { Selector as ObjectSelector } from "./object"; +import { UndistributedSourceArray as ArraySelector } from "./array"; +import { Selector as NumberSelector } from "./number"; +import { Selector as StringSelector } from "./string"; +import { Selector as BooleanSelector } from "./boolean"; +import { Selector as PrimitiveSelector } from "./primitive"; +import { FileSelector } from "./file"; +import { SourcePath } from "../val"; +import { Source, SourceArray, SourceObject, SourcePrimitive } from "../source"; +import { Schema } from "../schema"; +import type { A } from "ts-toolbelt"; +import { FileSource } from "../source/file"; +import { AllRichTextOptions, RichTextSource } from "../source/richtext"; +import { ImageSelector } from "./image"; +import { RichTextSelector } from "./richtext"; +import { ImageSource } from "../source/image"; +import { RemoteSource } from "../source/remote"; + +export type Selector = Source extends T + ? GenericSelector + : T extends ImageSource + ? ImageSelector + : T extends FileSource + ? FileSelector + : T extends RichTextSource + ? RichTextSelector + : T extends SourceObject + ? ObjectSelector + : T extends SourceArray + ? ArraySelector + : T extends string + ? StringSelector + : T extends number + ? NumberSelector + : T extends boolean + ? BooleanSelector + : T extends null + ? PrimitiveSelector + : never; + +export type SelectorSource = + | SourcePrimitive + | undefined + | readonly SelectorSource[] + | { + [key: string]: SelectorSource; + } + | ImageSource + | FileSource + | RemoteSource + | RichTextSource + | GenericSelector; + +/** + * @internal + */ +export const GetSchema = Symbol("GetSchema"); +/** +/** + * @internal + */ +export const Path = Symbol("Path"); +/** + * @internal + */ +export const GetSource = Symbol("GetSource"); +/** + * @internal + */ +export const ValError = Symbol("ValError"); +export abstract class GenericSelector< + out T extends Source, + Error extends string | undefined = undefined, +> { + readonly [Path]: SourcePath | undefined; + readonly [GetSource]: T; + readonly [ValError]: Error | undefined; + readonly [GetSchema]: Schema | undefined; + constructor( + valOrExpr: T, + path: SourcePath | undefined, + schema?: Schema, + error?: Error, + ) { + this[Path] = path; + this[GetSource] = valOrExpr; + this[ValError] = error; + this[GetSchema] = schema; + } +} + +export type SourceOf = Source extends T + ? Source + : T extends Source + ? T + : T extends undefined + ? null + : T extends GenericSelector + ? S + : T extends readonly (infer S)[] // NOTE: the infer S instead of Selector Source here, is to avoid infinite recursion + ? S extends SelectorSource + ? { + [key in keyof T]: SourceOf>; + } + : never + : T extends { [key: string]: SelectorSource } + ? { + [key in keyof T]: SourceOf>; + } + : never; + +/** + * Use this type to convert types that accepts both Source and Selectors + * + * An example would be where literals are supported like in most higher order functions (e.g. map in array) + **/ +export type SelectorOf = Source extends U + ? GenericSelector + : SourceOf extends infer S // we need this to avoid infinite recursion + ? S extends Source + ? Selector + : GenericSelector + : GenericSelector; + +export function getSchema( + selector: Selector, +): Schema | undefined { + return selector[GetSchema]; +} diff --git a/packages/core/src/selector/number.ts b/packages/core/src/selector/number.ts new file mode 100644 index 000000000..3e35ee56a --- /dev/null +++ b/packages/core/src/selector/number.ts @@ -0,0 +1,4 @@ +import { Selector as PrimitiveSelector } from "./primitive"; + +// TODO: +export type Selector = PrimitiveSelector; diff --git a/packages/core/src/selector/object.ts b/packages/core/src/selector/object.ts new file mode 100644 index 000000000..a5ee00fa2 --- /dev/null +++ b/packages/core/src/selector/object.ts @@ -0,0 +1,5 @@ +import { GenericSelector } from "./index"; +import { SourceObject } from "../source"; + +// TODO: docs +export type Selector = GenericSelector; diff --git a/packages/core/src/selector/primitive.ts b/packages/core/src/selector/primitive.ts new file mode 100644 index 000000000..7a70d4637 --- /dev/null +++ b/packages/core/src/selector/primitive.ts @@ -0,0 +1,4 @@ +import { GenericSelector } from "./index"; +import { SourcePrimitive } from "../source"; + +export type Selector = GenericSelector; diff --git a/packages/core/src/selector/richtext.ts b/packages/core/src/selector/richtext.ts new file mode 100644 index 000000000..754a9ae56 --- /dev/null +++ b/packages/core/src/selector/richtext.ts @@ -0,0 +1,8 @@ +import { GenericSelector } from "."; +import { RichTextOptions, RichTextSource } from "../source/richtext"; + +// NOTE: We are uncertain if we want to be able to select sub-objects of RichText? +// we added this to be consistent since we need a Selector of every Source type +export type RichTextSelector = GenericSelector< + RichTextSource +>; diff --git a/packages/core/src/selector/string.ts b/packages/core/src/selector/string.ts new file mode 100644 index 000000000..ff9770ab7 --- /dev/null +++ b/packages/core/src/selector/string.ts @@ -0,0 +1,4 @@ +import { Selector as PrimitiveSelector } from "./primitive"; + +// TODO: docs +export type Selector = PrimitiveSelector; diff --git a/packages/core/src/source/file.ts b/packages/core/src/source/file.ts new file mode 100644 index 000000000..e8e00e632 --- /dev/null +++ b/packages/core/src/source/file.ts @@ -0,0 +1,96 @@ +import { VAL_EXTENSION } from "."; +import { ValConfig } from "../initVal"; +import { Json } from "../Json"; + +export const FILE_REF_PROP = "_ref" as const; +export const FILE_REF_SUBTYPE_TAG = "_tag" as const; // TODO: used earlier by c.rt.image, when we remove c.rt we can remove this + +export type FileMetadata = { + mimeType?: string; +}; + +/** + * A file source represents the path to a (local) file. + * + * It will be resolved into a Asset object. + * + */ +export type FileSource< + Metadata extends FileMetadata | undefined = FileMetadata | undefined, +> = { + readonly [FILE_REF_PROP]: string; + readonly [VAL_EXTENSION]: "file"; + readonly [FILE_REF_SUBTYPE_TAG]?: string; + readonly metadata?: Metadata; + readonly patch_id?: string; +}; + +export const initFile = (config?: ValConfig) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const fileDirectory = config?.files?.directory ?? "/public/val"; + + type FileDirectory = typeof fileDirectory; + + function file( + ref: `${FileDirectory}/${string}`, + metadata: Metadata, + ): FileSource; + + function file( + ref: `${FileDirectory}/${string}`, + metadata?: undefined, + ): FileSource; + + function file( + ref: `${FileDirectory}/${string}`, + metadata?: Metadata, + ): FileSource { + return { + [FILE_REF_PROP]: ref, + [VAL_EXTENSION]: "file", + metadata, + } as FileSource; + } + + return file; +}; + +// type Directory = +// | (typeof config extends { files: { directory: infer D } } ? D : never) +// | `/public/val`; +// console.log("path", config); +// const userSpecifiedDirectory: Directory = config ?? "/public/val"; + +// const directory = userSpecifiedDirectory; + +// export function file( +// ref: `${typeof directory}/${string}`, +// metadata: Metadata +// ): FileSource; +// export function file( +// ref: `${typeof directory}/${string}`, +// metadata?: undefined +// ): FileSource; +// export function file< +// Metadata extends { readonly [key: string]: Json } | undefined +// >( +// ref: `${typeof directory}/${string}`, +// metadata?: Metadata +// ): FileSource { +// return { +// [FILE_REF_PROP]: ref, +// [VAL_EXTENSION]: "file", +// metadata, +// } as FileSource; +// } + +export function isFile(obj: unknown): obj is FileSource { + return ( + typeof obj === "object" && + obj !== null && + VAL_EXTENSION in obj && + obj[VAL_EXTENSION] === "file" && + FILE_REF_PROP in obj && + typeof obj[FILE_REF_PROP] === "string" + ); +} diff --git a/packages/core/src/source/image.ts b/packages/core/src/source/image.ts new file mode 100644 index 000000000..465db5a6c --- /dev/null +++ b/packages/core/src/source/image.ts @@ -0,0 +1,88 @@ +import { VAL_EXTENSION } from "."; +import { ValConfig } from "../initVal"; +import { ImageMetadata } from "../schema/image"; +import { FILE_REF_PROP, FILE_REF_SUBTYPE_TAG } from "./file"; + +/** + * A image source represents the path to a (local) image. + * + */ +export type ImageSource< + Metadata extends ImageMetadata | undefined = ImageMetadata | undefined, +> = { + readonly [FILE_REF_PROP]: string; + readonly [VAL_EXTENSION]: "file"; + readonly [FILE_REF_SUBTYPE_TAG]: "image"; + readonly metadata?: Metadata; + readonly patch_id?: string; +}; + +export const initImage = (config?: ValConfig) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const fileDirectory = config?.files?.directory ?? "/public/val"; + + type FileDirectory = typeof fileDirectory; + + /** + * Define the source of an image file. + * + * @example + * c.image("/public/val/example.png", { + * width: 944, + * height: 944, + * mimeType: "image/png", + *}) + * + * @param ref /public/val/example.png + * @param metadata Image metadata: width, height, mimeType and optionally a hotspot. + */ + function image( + ref: `${FileDirectory}/${string}`, + metadata: ImageMetadata, + ): ImageSource; + + /** + * Define the source of an image file. + * + * NOTE: this will **not** validate since metadata has not been defined. + * + * Run `npx -p @valbuild/cli val validate --fix` to automatically add metadata. + * + * @example + * c.image("/public/val/example.png") + * + * @param ref /public/val/example.png + * @param metadata Image metadata: width, height, mimeType and optionally a hotspot. + */ + function image( + ref: `${FileDirectory}/${string}`, + metadata?: undefined, + ): ImageSource; + + /** + * Define the source of an image file. + * + * @example + * c.image("/public/val/example.png", { + * width: 944, + * height: 944, + * mimeType: "image/png", + *}) + * + * @param ref /public/val/example.png + * @param metadata Image metadata: width, height, mimeType and optionally a hotspot. + */ + function image( + ref: `${FileDirectory}/${string}`, + metadata?: ImageMetadata, + ): ImageSource { + return { + [FILE_REF_PROP]: ref, + [VAL_EXTENSION]: "file", + [FILE_REF_SUBTYPE_TAG]: "image", + metadata, + } as ImageSource; + } + + return image; +}; diff --git a/packages/core/src/source/index.ts b/packages/core/src/source/index.ts new file mode 100644 index 000000000..47de4448a --- /dev/null +++ b/packages/core/src/source/index.ts @@ -0,0 +1,60 @@ +import { FileSource } from "./file"; +import { ImageSource } from "./image"; +import { RemoteSource } from "./remote"; +import { RichTextOptions, RichTextSource } from "./richtext"; + +export type Source = + | SourcePrimitive + | SourceObject + | SourceArray + | RemoteSource + | FileSource + | ImageSource + | RichTextSource; + +export type SourceObject = { [key in string]: Source } & { + // TODO: update these restricted parameters: + /** Reserved name */ + fold?: never; + /** Reserved name */ + assert?: never; + /** Reserved name */ + andThen?: never; + /** Reserved name */ + _ref?: never; + /** Reserved name */ + _type?: never; + /** Reserved name */ + val?: never; + /** Reserved name */ + valPath?: never; // used when serializing vals +}; +export type SourceArray = readonly Source[]; +export type SourcePrimitive = string | number | boolean | null; + +/* Branded extension types: file, remote, i18n */ +export const VAL_EXTENSION = "_type" as const; + +export function getValExtension(source: Source) { + return ( + source && + typeof source === "object" && + VAL_EXTENSION in source && + source[VAL_EXTENSION] + ); +} + +/** + * A phantom type parameter is one that doesn't show up at runtime, but is checked statically (and only) at compile time. + * + * An example where this is useful is remote types, where the type of the remote source is known at compile time, + * but the value is not there before it is fetched. + * + * @example + * type Example = string & PhantomType; + * + **/ +declare const PhantomType: unique symbol; +export type PhantomType = { + [PhantomType]: T; +}; diff --git a/packages/core/src/source/remote.ts b/packages/core/src/source/remote.ts new file mode 100644 index 000000000..56b25130e --- /dev/null +++ b/packages/core/src/source/remote.ts @@ -0,0 +1,56 @@ +import { VAL_EXTENSION } from "."; +import { ValConfig } from "../initVal"; +import { ImageMetadata } from "../schema/image"; +import { FILE_REF_PROP, FileMetadata } from "./file"; + +/** + * A remote source represents data that is not stored locally. + */ +export type RemoteSource< + Metadata extends FileMetadata | undefined = FileMetadata | undefined, +> = { + readonly [FILE_REF_PROP]: string; + readonly [VAL_EXTENSION]: "remote"; + readonly metadata?: Metadata; + readonly patch_id?: string; +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const initRemote = (config?: ValConfig) => { + function remote( + ref: RemoteRef, + metadata?: Metadata, + ): RemoteSource { + return { + [FILE_REF_PROP]: ref, + [VAL_EXTENSION]: "remote", + metadata, + } as RemoteSource; + } + return remote; +}; + +export type RemoteRef = + `${string}/file/p/${string}/v/${string}/h/${string}/f/${string}/p/public/val/${string}`; + +export function createRemoteRef( + remoteHost: string, + { + publicProjectId, + coreVersion, + validationHash, + fileHash, + filePath, + bucket, + }: { + publicProjectId: string; + coreVersion: string; + validationHash: string; + fileHash: string; + filePath: `public/val/${string}`; + bucket: string; + }, +): RemoteRef { + // NOTE: the core version is part of the validation hash, but it is also in the uri to make it easier to understand which version the remote file was validated against. + return `${remoteHost}/file/p/${publicProjectId}/b/${bucket}/v/${coreVersion}/h/${validationHash}/f/${fileHash}/p/${filePath}`; +} diff --git a/packages/core/src/source/richtext.ts b/packages/core/src/source/richtext.ts new file mode 100644 index 000000000..efdbf1529 --- /dev/null +++ b/packages/core/src/source/richtext.ts @@ -0,0 +1,265 @@ +import { + ImageMetadata, + ImageSchema, + SerializedImageSchema, +} from "../schema/image"; +import { RouteSchema, SerializedRouteSchema } from "../schema/route"; +import { StringSchema, SerializedStringSchema } from "../schema/string"; +import { FileSource } from "./file"; +import { ImageSource } from "./image"; +import { RemoteSource } from "./remote"; + +export type RichTextOptions = Partial<{ + style: Partial<{ + bold: boolean; + italic: boolean; + lineThrough: boolean; + }>; + block: Partial<{ + h1: boolean; + h2: boolean; + h3: boolean; + h4: boolean; + h5: boolean; + h6: boolean; + ul: boolean; + ol: boolean; + // TODO: + // custom: Record>; + }>; + inline: Partial<{ + a: boolean | RouteSchema | StringSchema; + img: + | boolean + | ImageSchema + | ImageSchema> + | ImageSchema>; + // custom: Record>; + }>; +}>; +export type SerializedRichTextOptions = Partial<{ + style: Partial<{ + bold: boolean; + italic: boolean; + lineThrough: boolean; + }>; + block: Partial<{ + h1: boolean; + h2: boolean; + h3: boolean; + h4: boolean; + h5: boolean; + h6: boolean; + ul: boolean; + ol: boolean; + // TODO: + // custom: Record>; + }>; + inline: Partial<{ + a: boolean | SerializedRouteSchema | SerializedStringSchema; + img: boolean | SerializedImageSchema; + // custom: Record>; + }>; +}>; +export type AllRichTextOptions = { + style: { + bold: true; + italic: true; + lineThrough: true; + }; + block: { + h1: true; + h2: true; + h3: true; + h4: true; + h5: true; + h6: true; + ul: true; + ol: true; + }; + inline: { + a: true; + img: true; + }; +}; + +//#region Classes +export type LineThrough = NonNullable< + O["style"] +>["lineThrough"] extends true + ? "line-through" + : never; + +export type Italic = NonNullable< + O["style"] +>["italic"] extends true + ? "italic" + : never; + +export type Bold = NonNullable< + O["style"] +>["bold"] extends true + ? "bold" + : never; + +export type Styles = + | LineThrough + | Italic + | Bold; + +export type GenericRichTextSourceNode = { + tag: string; + styles?: string[]; + href?: string; + src?: ImageSource; + children?: (string | GenericRichTextSourceNode)[]; +}; + +//#region Paragraph +export type ParagraphNode = { + tag: "p"; + children: ( + | string + | SpanNode + | BrNode + | LinkNode + | ImageNode + | CustomInlineNode + )[]; +}; + +//#region Break +export type BrNode = { + tag: "br"; +}; + +//#region Span +export type SpanNode = { + tag: "span"; + styles: Styles[]; + children: [string]; +}; + +//#region Image +export type ImageNode = NonNullable< + O["inline"] +>["img"] extends true + ? { tag: "img"; src: ImageSource | RemoteSource } + : NonNullable["img"] extends ImageSchema + ? Src extends RemoteSource | FileSource | ImageSource + ? { tag: "img"; src: Src } + : never + : never; + +//#region Link +type LinkTagNode = { + tag: "a"; + href: string; + children: (string | SpanNode | ImageNode | CustomInlineNode)[]; +}; +export type LinkNode = NonNullable< + O["inline"] +>["a"] extends true + ? LinkTagNode + : NonNullable["a"] extends + | RouteSchema + | StringSchema + ? LinkTagNode + : never; + +//#region List +type ListItemTagNode = { + tag: "li"; + children: (ParagraphNode | UnorderedListNode | OrderedListNode)[]; +}; +export type ListItemNode = NonNullable< + O["block"] +>["ul"] extends true + ? ListItemTagNode + : never | NonNullable["ol"] extends true + ? ListItemTagNode + : never; + +export type UnorderedListNode = NonNullable< + O["block"] +>["ul"] extends true + ? { + tag: "ul"; + // dir?: "ltr" | "rtl"; TODO: add this + children: ListItemNode[]; + } + : never; + +export type OrderedListNode = NonNullable< + O["block"] +>["ol"] extends true + ? { + tag: "ol"; + // dir?: "ltr" | "rtl"; TODO: add this + children: ListItemNode[]; + } + : never; + +//#region Heading +export type HeadingTagOf< + S extends keyof NonNullable>, + O extends RichTextOptions, +> = NonNullable>[S] extends true + ? { + tag: S; + + children: ( + | string + | SpanNode + | CustomInlineNode + | LinkNode + | BrNode + | ImageNode + )[]; + } + : never; + +export type HeadingNode = + | HeadingTagOf<"h1", O> + | HeadingTagOf<"h2", O> + | HeadingTagOf<"h3", O> + | HeadingTagOf<"h4", O> + | HeadingTagOf<"h5", O> + | HeadingTagOf<"h6", O>; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export type CustomInlineNode = never; +// export type CustomInlineNode = NonNullable< +// NonNullable["custom"] +// >[keyof NonNullable["custom"]>] extends Schema< +// infer Src +// > +// ? ReplaceRawStringWithString +// : never; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export type CustomBlockNode = never; + +//#region Block and Inline nodes: +export type RichTextNode = + | string + | BlockNode + | ListItemNode + | BrNode + | SpanNode + | LinkNode + | ImageNode + | CustomInlineNode; + +export type BlockNode = + | HeadingNode + | ParagraphNode + | UnorderedListNode + | OrderedListNode + | CustomBlockNode; + +//#region Main types + +/** + * RichText as defined in a ValModule + **/ +export type RichTextSource = BlockNode[]; diff --git a/packages/core/src/val/array.ts b/packages/core/src/val/array.ts new file mode 100644 index 000000000..b4f2844d7 --- /dev/null +++ b/packages/core/src/val/array.ts @@ -0,0 +1,10 @@ +import { SourcePath, Val as UnknownVal } from "."; +import { JsonArray } from "../Json"; +import { Path } from "../selector/index"; + +export type Val = { + readonly [key in keyof T]: UnknownVal; +} & { + readonly [Path]: SourcePath | undefined; + readonly val: T; +}; diff --git a/packages/core/src/val/index.ts b/packages/core/src/val/index.ts new file mode 100644 index 000000000..360f15364 --- /dev/null +++ b/packages/core/src/val/index.ts @@ -0,0 +1,108 @@ +import { Source, SourceArray, SourceObject } from "../source"; +import { Val as ObjectVal } from "./object"; +import { Val as ArrayVal } from "./array"; +import { Val as PrimitiveVal } from "./primitive"; +import { Json, JsonArray, JsonObject, JsonPrimitive } from "../Json"; +import { Path, Selector } from "../selector"; +import { RemoteSource } from "../source/remote"; +import { FileSource } from "../source/file"; + +export type SerializedVal = { + val: SerializedVal | Json; + valPath: SourcePath | undefined; +}; +export function isSerializedVal(val: unknown): val is SerializedVal { + return ( + typeof val === "object" && + val !== null && + val !== undefined && + ("val" in val || "valPath" in val) + ); +} + +export type JsonOfSource = Json extends T + ? Json + : T extends RemoteSource + ? { url: string } + : T extends FileSource + ? { url: string } + : T extends SourceObject + ? { + [key in keyof T]: JsonOfSource; + } + : T extends SourceArray + ? JsonOfSource[] + : T extends JsonPrimitive + ? T + : never; + +export type Val = Json extends T + ? { + readonly [Path]: SourcePath | undefined; + readonly val: Source; + } + : T extends JsonObject + ? ObjectVal + : T extends JsonArray + ? ArrayVal + : T extends JsonPrimitive + ? PrimitiveVal + : never; + +export function isVal(val: unknown): val is Val { + return ( + typeof val === "object" && + val !== null && + val !== undefined && + Path in val && + "val" in val + ); +} + +declare const brand = "VAL_DATA_TYPE"; +/** + * The path of the source value. + * + * @example + * '/app/blogs.val.ts?p=0.text' // the text property of the first element of the /app/blogs module + */ +export type SourcePath = string & { + [brand]: "SourcePath"; +}; + +/** + * The path inside the module. + * + * @example + * '0."text"' // the text property of the first element of the module + */ +export type ModulePath = string & { + [brand]: "ModulePath"; +}; + +/** + * The path of the module. + * + * @example + * '/app/blogs.val.ts' + */ +export type ModuleFilePath = string & { + [brand]: "ModuleFilePath"; +}; + +export type PatchId = string & { + [brand]: "PatchId"; +}; + +/** + * The patchId of the parent patch, or "head" if there is no parent patch. + */ +export type ParentPatchId = string & { + [brand]: "ParentPatchId"; +}; + +export function getValPath( + valOrSelector: Val | Selector, +): SourcePath | undefined { + return valOrSelector[Path]; +} diff --git a/packages/core/src/val/object.ts b/packages/core/src/val/object.ts new file mode 100644 index 000000000..d932748f3 --- /dev/null +++ b/packages/core/src/val/object.ts @@ -0,0 +1,13 @@ +import { SourcePath, Val as UnknownVal } from "."; +import { JsonObject } from "../Json"; +import { Path } from "../selector/index"; + +export type Val = Omit< + { + readonly [key in keyof T]: UnknownVal; + }, + "valPath" | "val" +> & { + readonly [Path]: SourcePath | undefined; + readonly val: T; +}; diff --git a/packages/core/src/val/primitive.ts b/packages/core/src/val/primitive.ts new file mode 100644 index 000000000..b213c4e38 --- /dev/null +++ b/packages/core/src/val/primitive.ts @@ -0,0 +1,8 @@ +import { SourcePath } from "."; +import { JsonPrimitive } from "../Json"; +import { Path } from "../selector"; + +export type Val = { + [Path]: SourcePath | undefined; + val: T; +}; diff --git a/packages/lib/tsconfig.json b/packages/core/tsconfig.json similarity index 66% rename from packages/lib/tsconfig.json rename to packages/core/tsconfig.json index 4548acb40..c54e1bb67 100644 --- a/packages/lib/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,7 +1,9 @@ { "compilerOptions": { + "lib": ["es2020"], "strict": true, "isolatedModules": true, "noEmit": true, + "skipLibCheck": true } -} \ No newline at end of file +} diff --git a/packages/create/.babelrc.json b/packages/create/.babelrc.json new file mode 100644 index 000000000..eb8b0273a --- /dev/null +++ b/packages/create/.babelrc.json @@ -0,0 +1,5 @@ +{ + "targets": { + "node": 16 + } +} diff --git a/packages/create/CHANGELOG.md b/packages/create/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/create/README.md b/packages/create/README.md new file mode 100644 index 000000000..caba5cdb5 --- /dev/null +++ b/packages/create/README.md @@ -0,0 +1,11 @@ +# @valbuild/create + +Bootstrap a Val project from the CLI. + +## Usage + +```sh +npm create @valbuild@latest +``` + +See the [documentation](https://val.build/docs) for more information. diff --git a/packages/create/bin.js b/packages/create/bin.js new file mode 100755 index 000000000..2ef3bee07 --- /dev/null +++ b/packages/create/bin.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +"use strict"; +require("./dist/valbuild-create.cjs.prod.js"); diff --git a/packages/create/package.json b/packages/create/package.json new file mode 100644 index 000000000..798f63c0e --- /dev/null +++ b/packages/create/package.json @@ -0,0 +1,53 @@ +{ + "name": "@valbuild/create", + "version": "0.93.0", + "description": "Create a new Val project with Next.js", + "repository": { + "type": "git", + "url": "git+https://github.com/valbuild/val.git" + }, + "main": "dist/valbuild-create.cjs.js", + "module": "dist/valbuild-create.esm.js", + "bin": { + "@valbuild/create": "./bin.js" + }, + "exports": { + ".": { + "module": "./dist/valbuild-create.esm.js", + "default": "./dist/valbuild-create.cjs.js" + }, + "./package.json": "./package.json" + }, + "scripts": { + "build": "tsc", + "start": "tsx src/index.ts --", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "degit": "^2.8.4", + "@inquirer/prompts": "^3.0.2", + "chalk": "^4.1.2" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "tsx": "^4.0.0" + }, + "keywords": [ + "val", + "valbuild", + "create", + "cli", + "nextjs" + ], + "author": "Valbuild", + "license": "MIT", + "preconstruct": { + "entrypoints": [ + "index.ts" + ] + }, + "engines": { + "node": ">=18.17.0" + } +} diff --git a/packages/create/src/degit.d.ts b/packages/create/src/degit.d.ts new file mode 100644 index 000000000..a0a952d4e --- /dev/null +++ b/packages/create/src/degit.d.ts @@ -0,0 +1,12 @@ +declare module "degit" { + interface DegitOptions { + cache?: boolean; + force?: boolean; + verbose?: boolean; + } + interface DegitEmitter { + clone(dest: string): Promise; + } + function degit(repo: string, opts?: DegitOptions): DegitEmitter; + export = degit; +} diff --git a/packages/create/src/index.ts b/packages/create/src/index.ts new file mode 100644 index 000000000..9ada03adb --- /dev/null +++ b/packages/create/src/index.ts @@ -0,0 +1,313 @@ +import { execSync } from "child_process"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import { join } from "path"; +import degit from "degit"; +import { input } from "@inquirer/prompts"; +import chalk from "chalk"; + +const PKG = { + name: "@valbuild/create", + version: "0.1.0", +}; + +interface Template { + name: string; + description: string; + repo: string; + default?: boolean; +} + +const TEMPLATES: Template[] = [ + { + name: "starter", + description: + "Full-featured Next.js app with Val, TypeScript, Tailwind CSS, and examples", + repo: "valbuild/template-nextjs-starter", + default: true, + }, +]; + +const DEFAULT_PROJECT_NAME = "my-val-app"; + +function printHelp() { + console.log(` +${chalk.bold("Usage:")} + ${chalk.cyan("npm create @valbuild [project-name]")} + +${chalk.bold("Options:")} + -h, --help Show help + -v, --version Show version + --root Specify the root directory for project creation (default: current directory) +`); +} + +function printVersion() { + console.log(`${PKG.name} v${PKG.version}`); +} + +function handleExit() { + console.log(chalk.yellow("\nAborted.")); + process.exit(0); +} + +process.on("SIGINT", handleExit); + +// Timeline stepper logic +const timelineSteps = [ + "Enter project name", + "Download template", + "Install dependencies", + "Complete!", +]; + +type StepStatus = "pending" | "active" | "done" | "error"; + +function renderTimeline(currentStep: number, errorStep?: number) { + const icons = { + pending: chalk.gray("◯"), + active: chalk.cyan("◉"), + done: chalk.green("✔"), + error: chalk.red("✖"), + }; + let out = "\n"; + for (let i = 0; i < timelineSteps.length; i++) { + let status: StepStatus = "pending"; + if (errorStep !== undefined && i === errorStep) status = "error"; + else if (i < currentStep) status = "done"; + else if (i === currentStep) status = "active"; + const icon = icons[status]; + out += ` ${icon} ${timelineSteps[i]}\n`; + if (i < timelineSteps.length - 1) out += ` ${chalk.gray("│")}\n`; + } + process.stdout.write("\x1b[2J\x1b[0f"); // clear screen + displayValLogo(); + process.stdout.write(out + "\n"); +} + +function displayValLogo() { + const logo = chalk.cyan(` +########### +########### +########### @@@@ +########### @@ +########### @@ @@ @@@@@@ @ @@ +########### @@ @@ @@ @@ @@ +########### @@ @@ %@ @ @@ +#### ##### @@ @@ .@ .@ @@ +### #### @@@@ @@: @@@. @@ +#### ##### @@@@ @@@@ =@@@@@@@@@ +########### +`); + process.stdout.write(logo); +} + +function displaySuccessMessage(projectName: string) { + const nextSteps = chalk.bold(` +${chalk.cyan("Next steps:")} + ${chalk.cyan("cd")} ${chalk.white(projectName)} + ${chalk.cyan("npm run dev")} + +${chalk.bold("Optionally run:")} + ${chalk.cyan("npx -p @valbuild/cli val connect")} +${chalk.bold("to connect your project to Val Build")} + +${chalk.bold("Need help?")} Join our community on Discord: ${chalk.underline( + "https://discord.gg/cZzqPvaX8k", + )} + +${chalk.green("Happy coding! 🚀")} +`); + + process.stdout.write(nextSteps); +} + +// Template processing function +function processTemplateFiles(projectPath: string, projectName: string) { + const filesToProcess = [ + "package.json", + "README.md", + "next.config.js", + "val.config.ts", + "val.config.js", + ]; + + filesToProcess.forEach((filename) => { + const filePath = join(projectPath, filename); + if (existsSync(filePath)) { + try { + let content = readFileSync(filePath, "utf-8"); + // Replace both {{PROJECT_NAME}} and {{projectName}} placeholders + content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName); + content = content.replace(/\{\{projectName\}\}/g, projectName); + writeFileSync(filePath, content, "utf-8"); + } catch { + // Silently continue if file can't be processed + console.log(chalk.dim(`Note: Could not process ${filename}`)); + } + } + }); +} + +async function main() { + try { + const args = process.argv.slice(2); + if (args.includes("-h") || args.includes("--help")) { + printHelp(); + process.exit(0); + } + if (args.includes("-v") || args.includes("--version")) { + printVersion(); + process.exit(0); + } + + // Parse --root option + const rootIndex = args.findIndex((a) => a === "--root"); + let rootDir = process.cwd(); + if (rootIndex !== -1 && args[rootIndex + 1]) { + rootDir = args[rootIndex + 1]; + // Remove --root and its value from args for project name parsing + args.splice(rootIndex, 2); + } + + let currentStep = 0; + renderTimeline(currentStep); + + // Step 1: Enter project name + const projectName = await input({ + message: chalk.bold("What is your project named?"), + default: DEFAULT_PROJECT_NAME, + validate: (value) => { + if (!value || value.trim().length === 0) { + return "Project name cannot be empty"; + } + if (value.includes(" ")) { + return "Project name cannot contain spaces"; + } + if (!/^[a-zA-Z0-9-_]+$/.test(value)) { + return "Project name can only contain letters, numbers, hyphens, and underscores"; + } + return true; + }, + }); + currentStep++; + renderTimeline(currentStep); + + // Step 2: Select template + const selectedTemplate = TEMPLATES[0]; + currentStep++; + renderTimeline(currentStep); + + // Step 3: Download template + const projectPath = join(rootDir, projectName); + if (existsSync(projectPath)) { + renderTimeline(currentStep, currentStep); + console.error( + chalk.red(`❌ Error: Directory "${projectName}" already exists.`), + ); + console.error( + chalk.yellow( + "Please choose a different name or remove the existing directory.", + ), + ); + process.exit(1); + } + mkdirSync(projectPath, { recursive: true }); + process.stdout.write( + chalk.bold("\n📥 Downloading template from GitHub...\n") + + ` ${chalk.dim(`https://github.com/${selectedTemplate.repo}`)}\n`, + ); + + try { + const emitter = degit(selectedTemplate.repo, { + cache: false, + force: true, + verbose: false, + }); + await emitter.clone(projectPath); + } catch (error) { + renderTimeline(currentStep, currentStep); + console.error(chalk.red("❌ Failed to download template:")); + const errorMessage = + error instanceof Error ? error.message : String(error); + if (errorMessage.includes("rate limit") || errorMessage.includes("403")) { + console.error( + chalk.yellow( + "GitHub rate limit exceeded. Please try again later or authenticate with GitHub.", + ), + ); + } else if ( + errorMessage.includes("not found") || + errorMessage.includes("404") + ) { + console.error( + chalk.yellow( + `Template repository not found: ${selectedTemplate.repo}`, + ), + ); + console.error( + chalk.yellow("Please check if the repository exists and is public."), + ); + } else { + console.error( + chalk.yellow("Network error. Please check your internet connection."), + ); + } + console.error(chalk.dim("Error details:"), errorMessage); + process.exit(1); + } + + currentStep++; + renderTimeline(currentStep); + process.stdout.write( + chalk.green( + `✅ Successfully downloaded template from ${selectedTemplate.repo}!\n`, + ), + ); + + // Process template files + processTemplateFiles(projectPath, projectName); + + // Change to project directory and install dependencies + process.stdout.write(chalk.bold("\n📦 Installing dependencies...\n")); + + try { + execSync("npm install", { + cwd: projectPath, + stdio: "inherit", // Show npm output in real-time + }); + + // Clear the npm output and show success + process.stdout.write("\x1b[2J\x1b[0f"); // clear screen + displayValLogo(); + currentStep++; + renderTimeline(currentStep); + process.stdout.write( + chalk.green( + `✅ Successfully downloaded template from ${selectedTemplate.repo}!\n`, + ), + ); + process.stdout.write( + chalk.green("\n✅ Dependencies installed successfully!\n"), + ); + + // Show final success message + displaySuccessMessage(projectName); + process.stdout.write("\n"); + process.exit(0); + } catch (error) { + renderTimeline(currentStep, currentStep); + console.error( + chalk.red( + '❌ Failed to install dependencies. You can try running "npm install" manually.', + ), + ); + console.error("Error:", error); + process.exit(1); + } + } catch (error) { + console.error(chalk.red("❌ Failed to create project:"), error); + process.exit(1); + } +} + +main(); diff --git a/packages/create/tsconfig.json b/packages/create/tsconfig.json new file mode 100644 index 000000000..1f9a7cd58 --- /dev/null +++ b/packages/create/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "lib": ["es2020", "DOM"], + "strict": true, + "isolatedModules": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "skipLibCheck": true, + "target": "ESNext" + } +} diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md new file mode 100644 index 000000000..5282211fc --- /dev/null +++ b/packages/eslint-plugin/README.md @@ -0,0 +1,5 @@ +# @valbuild/eslint-plugin + +ESLint plugin for Val projects, enforcing rules for Val content files and module definitions. + +See the [documentation](https://val.build/docs) for more information. diff --git a/packages/eslint-plugin/fixtures/eslintrc.json b/packages/eslint-plugin/fixtures/eslintrc.json new file mode 100644 index 000000000..1dc905445 --- /dev/null +++ b/packages/eslint-plugin/fixtures/eslintrc.json @@ -0,0 +1,6 @@ +{ + "plugins": ["@valbuild"], + "rules": { + "@valbuild/no-illegal-module-paths": 2 + } +} diff --git a/packages/eslint-plugin/jest.config.js b/packages/eslint-plugin/jest.config.js new file mode 100644 index 000000000..6458500a7 --- /dev/null +++ b/packages/eslint-plugin/jest.config.js @@ -0,0 +1,4 @@ +/** @type {import("jest").Config} */ +module.exports = { + preset: "../../jest.preset", +}; diff --git a/packages/eslint-plugin/jsconfig.json b/packages/eslint-plugin/jsconfig.json new file mode 100644 index 000000000..c0c042e48 --- /dev/null +++ b/packages/eslint-plugin/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "strict": true, + "isolatedModules": true, + "noEmit": true, + "skipLibCheck": true + }, + "exclude": ["dist/**/*"] +} diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json new file mode 100644 index 000000000..ee409b06d --- /dev/null +++ b/packages/eslint-plugin/package.json @@ -0,0 +1,44 @@ +{ + "name": "@valbuild/eslint-plugin", + "version": "0.93.0", + "description": "ESLint rules for val", + "repository": { + "type": "git", + "url": "git+https://github.com/valbuild/val.git" + }, + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin", + "val", + "valbuild" + ], + "main": "dist/valbuild-eslint-plugin.cjs.js", + "module": "dist/valbuild-eslint-plugin.esm.js", + "exports": { + ".": { + "module": "./dist/valbuild-eslint-plugin.esm.js", + "default": "./dist/valbuild-eslint-plugin.cjs.js" + }, + "./package.json": "./package.json" + }, + "engines": { + "node": ">=18.17.0" + }, + "license": "MIT", + "peerDependencies": { + "eslint": "6 || 7 || 8 || 9", + "typescript": "5" + }, + "devDependencies": { + "@types/jest": "^30.0.0", + "@typescript-eslint/rule-tester": "^8.53.0", + "eslint": "^9.39.2", + "jest": "^30.2", + "minimatch": "^3.0.4" + }, + "scripts": { + "test": "jest", + "typecheck": "tsc -p jsconfig.json" + } +} diff --git a/packages/eslint-plugin/src/index.js b/packages/eslint-plugin/src/index.js new file mode 100644 index 000000000..af1eed0c4 --- /dev/null +++ b/packages/eslint-plugin/src/index.js @@ -0,0 +1,53 @@ +// @ts-check +/** + * @typedef {import('eslint').ESLint.Plugin} Plugin + * @typedef {import('eslint').Linter.LintMessage} LintMessage + * @typedef {import('eslint').Linter.Processor} Processor + * @typedef {import('eslint').Linter } RuleModule + */ + +import noIllegalModulePaths from "./rules/noIllegalModulePaths"; +import noIllegalImports from "./rules/noIllegalImports"; +import exportContentMustBeValid from "./rules/exportContentMustBeValid"; +import noDefineWithVariable from "./rules/noDefineWithVariable"; +import defaultExportValModule from "./rules/defaultExportValModule"; +import moduleInValModules from "./rules/moduleInValModules"; + +/** + * @type {Plugin["rules"]} + */ +export const rules = { + "no-illegal-module-paths": noIllegalModulePaths, + "no-illegal-imports": noIllegalImports, + "export-content-must-be-valid": exportContentMustBeValid, + "no-define-with-variable": noDefineWithVariable, + "default-export-val-module": defaultExportValModule, + "module-in-val-modules": moduleInValModules, +}; + +/** + * @type {Plugin["processors"]} + */ +export const processors = {}; + +/** + * @type {Plugin["configs"]} + */ +export const configs = { + recommended: { + plugins: ["@valbuild"], + rules: { + "@valbuild/no-illegal-module-paths": "error", + "@valbuild/no-illegal-imports": "error", + "@valbuild/export-content-must-be-valid": "error", + "@valbuild/no-define-with-variable": "error", + "@valbuild/default-export-val-module": "error", + "@valbuild/module-in-val-modules": "error", + }, + }, +}; + +/** + * @type {Plugin} + */ +export default { rules, processors, configs }; diff --git a/packages/eslint-plugin/src/rules/defaultExportValModule.js b/packages/eslint-plugin/src/rules/defaultExportValModule.js new file mode 100644 index 000000000..83d31ee43 --- /dev/null +++ b/packages/eslint-plugin/src/rules/defaultExportValModule.js @@ -0,0 +1,85 @@ +// @ts-check + +const message = "Val: c.define must be exported as default"; +/** + * @type {import('eslint').Rule.RuleModule} + */ +export default { + meta: { + type: "problem", + docs: { + description: "c.define must be exported as default", + recommended: true, + }, + fixable: "code", + schema: [], + }, + create: function (context) { + return { + CallExpression(node) { + if (node.callee.type === "MemberExpression") { + const memberExpression = node.callee; + if (memberExpression.object.type === "Identifier") { + const object = memberExpression.object; + if ( + object.name === "c" && + memberExpression.property.type === "Identifier" + ) { + const property = memberExpression.property; + if (property.name === "define") { + const parent = node.parent; + if (parent.type === "ExportDefaultDeclaration") { + return; + } + if (parent.type === "VariableDeclarator") { + const variableInit = parent.init; + if ( + variableInit && + parent.parent.type === "VariableDeclaration" + ) { + context.report({ + node: node, + message, + fix: function (fixer) { + return fixer.replaceText( + parent.parent, + `export default ${context.sourceCode.getText( + variableInit, + )}`, + ); + }, + }); + } else { + context.report({ + node: node, + message, + }); + } + } else if ( + parent.type === "ExpressionStatement" && + parent.parent.type === "Program" + ) { + context.report({ + node: node, + message, + fix: function (fixer) { + return fixer.replaceText( + parent, + `export default ${context.sourceCode.getText(parent)}`, + ); + }, + }); + } else { + context.report({ + node: node, + message, + }); + } + } + } + } + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin/src/rules/exportContentMustBeValid.js b/packages/eslint-plugin/src/rules/exportContentMustBeValid.js new file mode 100644 index 000000000..c3547d068 --- /dev/null +++ b/packages/eslint-plugin/src/rules/exportContentMustBeValid.js @@ -0,0 +1,49 @@ +// @ts-check + +/** + * @type {import('eslint').Rule.RuleModule} + */ +export default { + meta: { + type: "problem", + docs: { + description: "Export c.define should only happen in .val files.", + recommended: true, + }, + messages: { + "val/export-content-must-be-valid": + "Val: c.define should only be exported from .val files", + }, + schema: [], + }, + create: function (context) { + return { + ExportDefaultDeclaration(node) { + if ( + node.declaration && + node.declaration.type === "CallExpression" && + node.declaration.callee.type === "MemberExpression" && + node.declaration.callee.object.type === "Identifier" && + node.declaration.callee.object.name === "c" && + node.declaration.callee.property.type === "Identifier" && + node.declaration.callee.property.name === "define" + ) { + const filename = context.filename || context.getFilename(); + if ( + !( + filename?.endsWith(".val.ts") || + filename?.endsWith(".val.js") || + filename?.endsWith(".val.tsx") || + filename?.endsWith(".val.jsx") + ) + ) { + context.report({ + node: node.declaration.callee, + messageId: "val/export-content-must-be-valid", + }); + } + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin/src/rules/moduleInValModules.js b/packages/eslint-plugin/src/rules/moduleInValModules.js new file mode 100644 index 000000000..2d9b12550 --- /dev/null +++ b/packages/eslint-plugin/src/rules/moduleInValModules.js @@ -0,0 +1,146 @@ +// @ts-check +import fs from "fs"; +import path from "path"; +import ts from "typescript"; + +/** @type {Record} */ +const cache = {}; +/** + * @type {import('eslint').Rule.RuleModule} + */ +export default { + meta: { + type: "problem", + docs: { + description: + "Ensure all .val files with export default c.define are referenced in val.modules", + recommended: true, + }, + schema: [], // No options needed + messages: { + missingFile: + "'{{file}}' contains export default c.define but is not referenced in val.modules.ts or val.module.js.", + }, + }, + create(context) { + const projectDir = path.resolve(context.cwd || context.getCwd()); + const modulesFilePath = path.join(projectDir, "val.modules.ts"); + + let baseUrl = projectDir; + /** @type { {[key: string]: string[];} } */ + let paths = {}; + let cacheKey = baseUrl; + + const tsConfigPath = ts.findConfigFile( + projectDir, + ts.sys.fileExists, + "tsconfig.json", + ); + const jsConfigPath = ts.findConfigFile( + projectDir, + ts.sys.fileExists, + "jsconfig.json", + ); + + if (tsConfigPath) { + const tsConfig = tsConfigPath + ? JSON.parse(fs.readFileSync(tsConfigPath, "utf-8")) + : {}; + + baseUrl = tsConfig.compilerOptions?.baseUrl + ? path.resolve(projectDir, tsConfig.compilerOptions.baseUrl) + : projectDir; + paths = tsConfig.compilerOptions?.paths || {}; + cacheKey = baseUrl + "/tsconfig.json"; + if (!cache[cacheKey]) { + cache[cacheKey] = ts.createModuleResolutionCache( + baseUrl, + (x) => x, + tsConfig.compilerOptions, + ); + } + } else if (jsConfigPath) { + const jsConfig = jsConfigPath + ? JSON.parse(fs.readFileSync(jsConfigPath, "utf-8")) + : {}; + + baseUrl = jsConfig.compilerOptions?.baseUrl + ? path.resolve(projectDir, jsConfig.compilerOptions.baseUrl) + : projectDir; + paths = jsConfig.compilerOptions?.paths || {}; + cacheKey = baseUrl + "/jsconfig.json"; + if (!cache[cacheKey]) { + cache[cacheKey] = ts.createModuleResolutionCache( + baseUrl, + (x) => x, + jsConfig.compilerOptions, + ); + } + } + + // Function to resolve import paths + const resolvePath = (/** @type {string} */ importPath) => { + // Attempt to resolve using TypeScript path mapping + const resolvedModule = ts.resolveModuleName( + importPath, + path.join(baseUrl, "val.modules.ts"), + { + baseUrl, + paths, + }, + ts.sys, + cache[cacheKey], + ); + const resolved = resolvedModule.resolvedModule?.resolvedFileName; + if (resolved) { + return path.resolve(resolved); + } + // Fallback to Node.js resolution + try { + return require.resolve(importPath, { paths: [projectDir] }); + } catch { + return undefined; + } + }; + + if (!fs.existsSync(modulesFilePath)) { + return {}; + } + + const modulesFileContent = fs.readFileSync(modulesFilePath, "utf-8"); + const referencedFiles = Array.from( + modulesFileContent.matchAll(/import\(["'](.+\.val(?:\.[jt]s)?)['"]\)/g), + ) + .map((match) => { + return resolvePath(match[1]); + }) + .filter((file) => file !== undefined); + + return { + ExportDefaultDeclaration(node) { + const filename = context.filename || context.getFilename(); + if (filename?.includes(".val")) { + if ( + node.declaration && + node.declaration.type === "CallExpression" && + node.declaration.callee.type === "MemberExpression" && + node.declaration.callee.object.type === "Identifier" && + node.declaration.callee.object.name === "c" && + node.declaration.callee.property.type === "Identifier" && + node.declaration.callee.property.name === "define" && + node.declaration.arguments && + node.declaration.arguments.length > 0 + ) { + if (!referencedFiles.includes(filename)) { + context.report({ + node, + messageId: "missingFile", + data: { file: filename.replace(projectDir, "") }, + }); + } + } + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin/src/rules/noDefineWithVariable.js b/packages/eslint-plugin/src/rules/noDefineWithVariable.js new file mode 100644 index 000000000..6e0797288 --- /dev/null +++ b/packages/eslint-plugin/src/rules/noDefineWithVariable.js @@ -0,0 +1,43 @@ +// @ts-check + +/** + * @type {import('eslint').Rule.RuleModule} + */ +export default { + meta: { + type: "problem", + docs: { + description: "Cannot c.define with a variable", + recommended: true, + }, + fixable: "code", + schema: [], + }, + create: function (context) { + return { + ExportDefaultDeclaration(node) { + const decl = node.declaration; + if (decl.type === "CallExpression") { + const callee = decl.callee; + const isDefine = + callee.type === "MemberExpression" && + callee.object.type === "Identifier" && + callee.object.name === "c" && + callee.property.type === "Identifier" && + callee.property.name === "define"; + + if (isDefine) { + const args = decl.arguments; + const valueArg = args[2]; + if (valueArg.type === "Identifier") { + context.report({ + node: valueArg, + message: "Val: third argument of c.define cannot be a variable", + }); + } + } + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin/src/rules/noIllegalImports.js b/packages/eslint-plugin/src/rules/noIllegalImports.js new file mode 100644 index 000000000..16647fa99 --- /dev/null +++ b/packages/eslint-plugin/src/rules/noIllegalImports.js @@ -0,0 +1,51 @@ +// @ts-check + +/** + * @type {import('eslint').Rule.RuleModule} + */ +export default { + meta: { + type: "problem", + docs: { + description: "Check that val files only has valid imports.", + recommended: true, + }, + schema: [], + }, + create: function (context) { + return { + ImportDeclaration(node) { + const importSource = node.source.value; + const filename = context.filename || context.getFilename(); + + const isValFile = + filename.endsWith(".val.ts") || + filename.endsWith(".val.js") || + filename.endsWith(".val.tsx") || + filename.endsWith(".val.jsx"); + // only allow: .val files, @valbuild packages, and val config + if ( + isValFile && + typeof importSource === "string" && + !importSource.match(/\.val(\.ts|\.js|\.tsx|\.jsx|)$/) && + !importSource.match(/^@valbuild/) && + !importSource.match(/val\.config(\.ts|\.js|\.tsx|\.jsx|)$/) + ) { + if ( + "importKind" in node && + node["importKind"] !== "type" && + !node.specifiers.every( + (s) => "importKind" in s && s["importKind"] === "type", + ) + ) { + const message = `Val: can only 'import type' or import from source that is either: a .val.{j,t}s file, a @valbuild package, or val.config.{j,t}s.`; + context.report({ + node: node.source, + message, + }); + } + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin/src/rules/noIllegalModulePaths.js b/packages/eslint-plugin/src/rules/noIllegalModulePaths.js new file mode 100644 index 000000000..08abb95c6 --- /dev/null +++ b/packages/eslint-plugin/src/rules/noIllegalModulePaths.js @@ -0,0 +1,112 @@ +// @ts-check +import path from "path"; +import fs from "fs"; + +/** + * @type {Record} + */ +const PACKAGE_JSON_DIRS_CACHE = {}; // we cache to avoid having to do as many fs operations. however, it will fail if the user moves package.json / root directories. For now, we accept that bug since performance is considered more important. Maybe there is a way to know when that happens so we avoid those bugs. + +/** + * @type {import('eslint').Rule.RuleModule} + */ +export default { + meta: { + type: "problem", + docs: { + description: "Module path must match filename", + recommended: true, + }, + fixable: "code", + schema: [], + }, + create: function (context) { + return { + ExportDefaultDeclaration(node) { + /** + * @type {string | undefined} + */ + let expectedValue; + if (node.parent.type === "Program") { + const maybeValConfigImportDeclaration = node.parent.body.find((n) => + n.type === "ImportDeclaration" && + typeof n.source.value === "string" && + (n.source.value.endsWith("val.config") || + n.source.value.endsWith("val.config.ts") || + n.source.value.endsWith("val.config.js")) + ? n.source.value + : false, + ); + if ( + maybeValConfigImportDeclaration?.type === "ImportDeclaration" && + typeof maybeValConfigImportDeclaration.source.value === "string" + ) { + const filename = context.filename || context.getFilename(); + if ( + filename?.endsWith(".val.ts") || + filename?.endsWith(".val.tsx") || + filename?.endsWith(".val.js") || + filename?.endsWith(".val.jsx") + ) { + let packageJsonDir = + PACKAGE_JSON_DIRS_CACHE[path.dirname(filename)]; + if (!packageJsonDir) { + const runtimeRoot = path.resolve(context.cwd || process.cwd()); + packageJsonDir = path.resolve(path.dirname(filename)); + while ( + !fs.existsSync(path.join(packageJsonDir, "package.json")) && + packageJsonDir !== runtimeRoot + ) { + packageJsonDir = path.dirname(packageJsonDir); + } + PACKAGE_JSON_DIRS_CACHE[path.dirname(filename)] = + packageJsonDir; + } + const relativePath = path.relative(packageJsonDir, filename); + expectedValue = `/${relativePath}`; + } + } + } else { + console.warn("Unexpected parent type", node.parent.type); + } + if (!expectedValue) { + return; + } + if ( + node.declaration && + node.declaration.type === "CallExpression" && + node.declaration.arguments && + node.declaration.arguments.length > 0 + ) { + const firstArg = node.declaration.arguments[0]; + if (firstArg.type === "TemplateLiteral") { + context.report({ + node: firstArg, + message: "Val: c.define path should not be a template literal", + fix: (fixer) => fixer.replaceText(firstArg, `"${expectedValue}"`), + }); + } + if ( + firstArg.type === "Literal" && + typeof firstArg.value === "string" + ) { + if (firstArg.value !== expectedValue) { + const rawArg = firstArg.raw?.[0]; + if (rawArg) { + context.report({ + node: firstArg, + message: `Val: c.define path must match the filename. Expected: '${expectedValue}'. Found: '${firstArg.value}'`, + fix: (fixer) => + fixer.replaceText( + firstArg, + `${rawArg}${expectedValue}${rawArg}`, + ), + }); + } + } + } + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin/test/plugin.todo.js b/packages/eslint-plugin/test/plugin.todo.js new file mode 100644 index 000000000..cf3a932a2 --- /dev/null +++ b/packages/eslint-plugin/test/plugin.todo.js @@ -0,0 +1,93 @@ +// @ts-check +/* eslint-disable no-undef */ +import { ESLint } from "eslint"; +import path from "path"; + +/** + * @param {string} fixtureConfigName ESLint JSON config fixture filename. + */ +function initESLint(fixtureConfigName) { + return new ESLint({ + cwd: path.resolve(__dirname, "../fixtures/"), + ignore: false, + // This no longer works with Eslint 9 + // Consider fixing this (or removing this test) + overrideConfigFile: path.resolve( + __dirname, + "../fixtures/", + fixtureConfigName, + ), + }); +} + +describe("plugin", () => { + /** + * @type {ESLint} + */ + let eslint; + + beforeAll(() => { + eslint = initESLint("eslintrc.json"); + }); + + test("no illegal paths for monorepos (projects that are not at root)", async () => { + const code = `import { s, c } from "../val.config"; + +export const schema = s.string(); + +export default c.define( + "/something.val.ts", + schema, + "React Server components also works" +);`; + const results = await eslint.lintText(code, { + filePath: "./app/test.val.ts", + }); + + expect(results).toHaveLength(1); + expect(results[0].messages).toHaveLength(1); + expect(results[0].messages[0].fix?.text).toEqual('"/app/test.val.ts"'); + }); + + test("no illegal paths for monorepos (projects that are not at root) - nested", async () => { + const code = `import { s, c } from "../../../../val.config"; + +export const schema = s.string(); + +export default c.define( + "/something", + schema, + "React Server components also works" +);`; + const results = await eslint.lintText(code, { + filePath: "./content/stuff/with/all/test.val.ts", + }); + + expect(results).toHaveLength(1); + expect(results[0].messages).toHaveLength(1); + expect(results[0].messages[0].fix?.text).toEqual( + '"/content/stuff/with/all/test.val.ts"', + ); + }); + // TODO: we can't test this anymore because we do not know the root dir - perhaps the + // test("no illegal modules for monorepos (projects that are not at root) - src", async () => { + // const code = `import { s, c } from "../../../../val.config"; + + // export const schema = s.string(); + + // export default c.define( + // "/something", + // schema, + // "React Server components also works" + // );`; + // const results = await eslint.lintText(code, { + // filePath: "./src/content/stuff/with/all/test.val.ts", + // }); + + // expect(results).toHaveLength(1); + // expect(results[0].messages).toHaveLength(1); + // expect(results[0].messages[0].fix?.text).toEqual( + // '"/content/stuff/with/all/test"' + // ); + // }); +}); diff --git a/packages/eslint-plugin/test/rules/defaultExportValModule.test.js b/packages/eslint-plugin/test/rules/defaultExportValModule.test.js new file mode 100644 index 000000000..39eefbd05 --- /dev/null +++ b/packages/eslint-plugin/test/rules/defaultExportValModule.test.js @@ -0,0 +1,63 @@ +import { RuleTester } from "@typescript-eslint/rule-tester"; +import { rules as valRules } from "@valbuild/eslint-plugin"; +import path from "path"; + +const rule = valRules["default-export-val-module"]; + +const ruleTester = new RuleTester(); + +ruleTester.run("default-export-val-module", rule, { + valid: [ + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + export default c.define('/foo/test.val.ts', schema, 'String')`, + output: null, + }, + ], + invalid: [ + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + const a = c.define('/foo/test.val.ts', schema, 'String')`, + errors: [ + { + message: "Val: c.define must be exported as default", + }, + ], + output: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + export default c.define('/foo/test.val.ts', schema, 'String')`, + }, + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + { + c.define('/foo/test.val.ts', schema, 'String') + }`, + errors: [ + { + message: "Val: c.define must be exported as default", + }, + ], + output: null, + }, + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + c.define('/foo/test.val.ts', schema, 'String')`, + errors: [ + { + message: "Val: c.define must be exported as default", + }, + ], + output: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + export default c.define('/foo/test.val.ts', schema, 'String')`, + }, + ], +}); diff --git a/packages/eslint-plugin/test/rules/exportContentMustBeValid.test.js b/packages/eslint-plugin/test/rules/exportContentMustBeValid.test.js new file mode 100644 index 000000000..6b6a0c600 --- /dev/null +++ b/packages/eslint-plugin/test/rules/exportContentMustBeValid.test.js @@ -0,0 +1,48 @@ +import { RuleTester } from "@typescript-eslint/rule-tester"; +import { rules as valRules } from "@valbuild/eslint-plugin"; +import path from "path"; + +const rule = valRules["export-content-must-be-valid"]; + +const ruleTester = new RuleTester(); + +ruleTester.run("export-content-must-be-valid", rule, { + valid: [ + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: ` +import { c, s } from '../val.config'; + +export const schema = s.string(); +export default c.define('/foo/test', schema, '')`, + }, + ], + invalid: [ + { + filename: path.join(process.cwd(), "./foo/test.ts"), + code: ` +import { c, s } from '../val.config'; + +export const schema = s.string(); +export default c.define('/foo/test', schema, '')`, + errors: [ + { + message: "Val: c.define should only be exported from .val files", + }, + ], + }, + { + filename: path.join(process.cwd(), "./foo/test.js"), + code: ` +import { c, s } from '../val.config'; + +export const schema = s.string(); +export default c.define('/foo/test', schema, '')`, + errors: [ + { + message: "Val: c.define should only be exported from .val files", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/test/rules/noDefineWithVariable.test.js b/packages/eslint-plugin/test/rules/noDefineWithVariable.test.js new file mode 100644 index 000000000..1e56956fa --- /dev/null +++ b/packages/eslint-plugin/test/rules/noDefineWithVariable.test.js @@ -0,0 +1,32 @@ +import { RuleTester } from "@typescript-eslint/rule-tester"; +import { rules as valRules } from "@valbuild/eslint-plugin"; +import path from "path"; + +const rule = valRules["no-define-with-variable"]; + +const ruleTester = new RuleTester(); + +ruleTester.run("no-define-with-variable", rule, { + valid: [ + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + export default c.define('/foo/test.val.ts', schema, 'String')`, + }, + ], + invalid: [ + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + const str = 'String' + export default c.define('/foo/test.val.ts', schema, str)`, + errors: [ + { + message: "Val: third argument of c.define cannot be a variable", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/test/rules/noIllegalImports.test.js b/packages/eslint-plugin/test/rules/noIllegalImports.test.js new file mode 100644 index 000000000..0c048a6d2 --- /dev/null +++ b/packages/eslint-plugin/test/rules/noIllegalImports.test.js @@ -0,0 +1,91 @@ +import { RuleTester } from "@typescript-eslint/rule-tester"; +import { rules as valRules } from "@valbuild/eslint-plugin"; +import path from "path"; + +const rule = valRules["no-illegal-imports"]; + +const ruleTester = new RuleTester(); + +ruleTester.run("no-illegal-imports", rule, { + valid: [ + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: ` +import { c, s } from '../val.config'; +import { eventSchema } from './event.val'; + +export const schema = s.array(eventSchema); +export default c.define('/foo/test', schema, [])`, + }, + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: ` +import { c, s } from '../val.config.ts'; +import { eventSchema } from './event.val.ts'; + +export const schema = s.array(eventSchema); +export default c.define('/foo/test', schema, [])`, + }, + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: ` +import { c, s } from '../val.config.ts'; +import type { Event } from './eventSchema'; + +export const schema = s.array(s.string()); +type Test = Event; +export default c.define('/foo/test', schema, [])`, + }, + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: ` +import { c, s } from '../val.config.ts'; +import { type Event } from './eventSchema'; + +export const schema = s.array(s.string()); +type Test = Event; +export default c.define('/foo/test', schema, [])`, + }, + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: ` +import { c, s } from '../val.config.ts'; + +export const schema = s.string(); +export default c.define('/foo/test', schema, 'String')`, + }, + ], + invalid: [ + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: ` +import { c, s } from '../val.config'; +import { eventSchema } from './event'; + +export const schema = s.array(eventSchema); +export default c.define('/foo/test', schema, [])`, + errors: [ + { + message: + "Val: can only 'import type' or import from source that is either: a .val.{j,t}s file, a @valbuild package, or val.config.{j,t}s.", + }, + ], + }, + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: ` +import { c, s } from '../val.config'; +import { eventSchema, type Unused } from './event'; + +export const schema = s.array(eventSchema); +type Event = Unused; +export default c.define('/foo/test', schema, [])`, + errors: [ + { + message: + "Val: can only 'import type' or import from source that is either: a .val.{j,t}s file, a @valbuild package, or val.config.{j,t}s.", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/test/rules/noIllegalModulePaths.test.js b/packages/eslint-plugin/test/rules/noIllegalModulePaths.test.js new file mode 100644 index 000000000..57bf0f525 --- /dev/null +++ b/packages/eslint-plugin/test/rules/noIllegalModulePaths.test.js @@ -0,0 +1,64 @@ +import { RuleTester } from "@typescript-eslint/rule-tester"; +import { rules as valRules } from "@valbuild/eslint-plugin"; +import path from "path"; + +const rule = valRules["no-illegal-module-paths"]; + +const ruleTester = new RuleTester(); + +ruleTester.run("no-illegal-module-paths", rule, { + valid: [ + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + export default c.define('/foo/test.val.ts', schema, 'String')`, + }, + ], + invalid: [ + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + export default c.define('foo.val.ts', schema, 'String')`, + errors: [ + { + message: + "Val: c.define path must match the filename. Expected: '/foo/test.val.ts'. Found: 'foo.val.ts'", + }, + ], + output: `import { c, s } from '../val.config.ts'; + export const schema = s.string(); + export default c.define('/foo/test.val.ts', schema, 'String')`, + }, + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from "../val.config.ts"; + export const schema = s.string(); + export default c.define("foo.val.ts", schema, 'String')`, + errors: [ + { + message: + "Val: c.define path must match the filename. Expected: '/foo/test.val.ts'. Found: 'foo.val.ts'", + }, + ], + output: `import { c, s } from "../val.config.ts"; + export const schema = s.string(); + export default c.define("/foo/test.val.ts", schema, 'String')`, + }, + { + filename: path.join(process.cwd(), "./foo/test.val.ts"), + code: `import { c, s } from "../val.config.ts"; + export const schema = s.string(); + export default c.define(\`foo.val.ts\`, schema, 'String')`, + errors: [ + { + message: "Val: c.define path should not be a template literal", + }, + ], + output: `import { c, s } from "../val.config.ts"; + export const schema = s.string(); + export default c.define("/foo/test.val.ts", schema, 'String')`, + }, + ], +}); diff --git a/packages/init/.gitignore b/packages/init/.gitignore new file mode 100644 index 000000000..1fcb1529f --- /dev/null +++ b/packages/init/.gitignore @@ -0,0 +1 @@ +out diff --git a/packages/init/CHANGELOG.md b/packages/init/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/init/README.md b/packages/init/README.md new file mode 100644 index 000000000..ecc8c41ce --- /dev/null +++ b/packages/init/README.md @@ -0,0 +1,5 @@ +# @valbuild/init + +Initialization script for adding Val to existing projects, configuring necessary files and dependencies. + +See the [documentation](https://val.build/docs) for more information. diff --git a/packages/init/babel.config.js b/packages/init/babel.config.js new file mode 100644 index 000000000..dd242dc90 --- /dev/null +++ b/packages/init/babel.config.js @@ -0,0 +1,6 @@ +module.exports = { + presets: [ + ["@babel/preset-env", { targets: { node: "current" } }], + "@babel/preset-typescript", + ], +}; diff --git a/packages/init/bin.js b/packages/init/bin.js new file mode 100755 index 000000000..3d87db886 --- /dev/null +++ b/packages/init/bin.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +"use strict"; +require("./main"); diff --git a/packages/init/main/package.json b/packages/init/main/package.json new file mode 100644 index 000000000..da5f1f134 --- /dev/null +++ b/packages/init/main/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/valbuild-init-main.cjs.js", + "module": "dist/valbuild-init-main.esm.js" +} diff --git a/packages/init/package.json b/packages/init/package.json new file mode 100644 index 000000000..dbeeb9a32 --- /dev/null +++ b/packages/init/package.json @@ -0,0 +1,56 @@ +{ + "name": "@valbuild/init", + "version": "0.93.0", + "description": "Initialize a new val.build project", + "repository": { + "type": "git", + "url": "git+https://github.com/valbuild/val.git" + }, + "exports": { + "./main": { + "module": "./main/dist/valbuild-init-main.esm.js", + "default": "./main/dist/valbuild-init-main.cjs.js" + }, + "./package.json": "./package.json" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "jest" + }, + "bin": { + "valbuild-init": "./bin.js" + }, + "dependencies": { + "@inquirer/confirm": "^2.0.15", + "@inquirer/prompts": "^3.0.2", + "@types/diff": "^5.0.9", + "chalk": "^4.1.2", + "cors": "^2.8.5", + "diff": "^5.1.0", + "eslint": "^8.31.0", + "express": "^4.18.2", + "fast-glob": "^3.3.1", + "jscodeshift": "^0.15.1", + "meow": "^9.0.0", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "recast": "^0.23.4", + "semver": "^7.5.4", + "simple-git": "^3.22.0", + "typescript": "^5.9.3", + "zod": "^4.3.5" + }, + "preconstruct": { + "entrypoints": [ + "main.ts" + ] + }, + "devDependencies": { + "@babel/preset-env": "^7.23.8", + "@babel/preset-typescript": "^7.23.3", + "@types/jest": "^29.5.11", + "@types/jscodeshift": "^0.11.11", + "@types/semver": "^7.7.1", + "jest": "^29.7.0" + } +} diff --git a/packages/init/src/codemods.test.ts b/packages/init/src/codemods.test.ts new file mode 100644 index 000000000..a1aaf7fbb --- /dev/null +++ b/packages/init/src/codemods.test.ts @@ -0,0 +1,127 @@ +import jcs from "jscodeshift"; +import { transformNextAppRouterValProvider } from "./codemods/transformNextAppRouterValProvider"; +import { addEslintPluginToEslintMjs } from "./codemods/addEslintPluginToEslintMjs"; + +const APP_ROUTER_LAYOUT_SANS_VAL_PROVIDER = `import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} +`; + +describe("codemods", () => { + test("init ValProvider layout", async () => { + const res = transformNextAppRouterValProvider( + { + path: "./app/layout.tsx", + source: APP_ROUTER_LAYOUT_SANS_VAL_PROVIDER, + }, + { + j: jcs, + jscodeshift: jcs.withParser("tsx"), + stats: () => {}, + report: () => {}, + }, + { + configImportPath: "../val.config", + }, + ); + expect(res).toEqual(`import { ValProvider } from "@valbuild/next"; +import { config } from "../val.config"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} +`); + }); + + test("add eslint plugin to eslint.config.mjs generated by create-next-app", async () => { + const res = addEslintPluginToEslintMjs( + { + path: "./eslint.config.mjs", + source: `import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; +`, + }, + { + j: jcs, + jscodeshift: jcs, + stats: () => {}, + report: () => {}, + }, + { + configImportPath: "../val.config", + }, + ); + expect(res).toEqual(`import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript", "plugin:@valbuild/recommended"), +]; + +export default eslintConfig; +`); + }); +}); diff --git a/packages/init/src/codemods/addEslintPluginToEslintMjs.ts b/packages/init/src/codemods/addEslintPluginToEslintMjs.ts new file mode 100644 index 000000000..768ad1133 --- /dev/null +++ b/packages/init/src/codemods/addEslintPluginToEslintMjs.ts @@ -0,0 +1,22 @@ +import j from "jscodeshift"; + +export function addEslintPluginToEslintMjs( + fileInfo: j.FileInfo, + api: j.API, + options: j.Options, +) { + if (!options.configImportPath) { + throw new Error("configImportPath is required"); + } + const root = api.jscodeshift(fileInfo.source); + root + .findVariableDeclarators("eslintConfig") + .find(j.ArrayExpression) + .find(j.SpreadElement) + .find(j.CallExpression) + .find(j.Literal) + .at(-1) + .insertAfter(j.literal("plugin:@valbuild/recommended")); + + return root.toSource(); +} diff --git a/packages/init/src/codemods/index.ts b/packages/init/src/codemods/index.ts new file mode 100644 index 000000000..c02e04c3c --- /dev/null +++ b/packages/init/src/codemods/index.ts @@ -0,0 +1,2 @@ +export * from "./transformNextAppRouterValProvider"; +export * from "./addEslintPluginToEslintMjs"; diff --git a/packages/init/src/codemods/transformNextAppRouterValProvider.ts b/packages/init/src/codemods/transformNextAppRouterValProvider.ts new file mode 100644 index 000000000..009995b2e --- /dev/null +++ b/packages/init/src/codemods/transformNextAppRouterValProvider.ts @@ -0,0 +1,76 @@ +import j from "jscodeshift"; + +export function transformNextAppRouterValProvider( + fileInfo: j.FileInfo, + api: j.API, + options: j.Options, +) { + if (!options.configImportPath) { + throw new Error("configImportPath is required"); + } + const root = api.jscodeshift(fileInfo.source); + root + .find(j.ImportDeclaration) + .at(0) + .insertBefore( + j.importDeclaration( + [j.importSpecifier(j.identifier("ValProvider"))], + j.literal("@valbuild/next"), + ), + ) + .insertBefore( + j.importDeclaration( + [j.importSpecifier(j.identifier("config"))], + j.literal(options.configImportPath), + ), + ); + root + .findJSXElements("body") + .childNodes() + .forEach((el) => { + if (el.value.type === "JSXExpressionContainer") { + if ( + el.value.expression.type === "Identifier" && + el.value.expression.name === "children" + ) { + el.replace( + j.jsxElement( + { + name: { + type: "JSXIdentifier", + name: "ValProvider", + }, + type: "JSXOpeningElement", + selfClosing: false, + attributes: [ + { + type: "JSXAttribute", + name: { + type: "JSXIdentifier", + name: "config", + }, + value: { + type: "JSXExpressionContainer", + expression: { + type: "Identifier", + name: "config", + }, + }, + }, + ], + }, + { + name: { + type: "JSXIdentifier", + name: "ValProvider", + }, + type: "JSXClosingElement", + }, + [j.jsxExpressionContainer(j.identifier("children"))], + ), + ); + } + } + }); + return root.toSource(); +} diff --git a/packages/init/src/init.ts b/packages/init/src/init.ts new file mode 100644 index 000000000..716dd87a8 --- /dev/null +++ b/packages/init/src/init.ts @@ -0,0 +1,1014 @@ +import chalk from "chalk"; +import fs from "fs"; +import path from "path"; +import simpleGit, { StatusResult } from "simple-git"; +import { confirm } from "@inquirer/prompts"; +import { transformNextAppRouterValProvider } from "./codemods/transformNextAppRouterValProvider"; +import { diffLines } from "diff"; +import jcs from "jscodeshift"; +import semver from "semver"; +import packageJson from "../package.json"; +import { + BASIC_EXAMPLE, + VAL_API_ROUTER, + VAL_APP_PAGE, + VAL_CLIENT, + VAL_CONFIG, + VAL_MODULES, + VAL_RSC, + VAL_SERVER, +} from "./templates"; +import * as logger from "./logger"; +import { addEslintPluginToEslintMjs } from "./codemods"; + +const MIN_VAL_VERSION = packageJson.version; +const MIN_NEXT_VERSION = "13.4.0"; + +let maxResetLength = 0; +export async function init( + root: string = process.cwd(), + { yes: defaultAnswers }: { yes?: boolean } = {}, +) { + logger.info( + "Initializing " + + chalk.bgBlack.hex("#37cd99")("Val") + + ' in "' + + root + + '"...\n', + ); + process.stdout.write("Analyzing project..."); + const analysis = await analyze(path.resolve(root), walk(path.resolve(root))); + // reset cursor: + process.stdout.write("\x1b[0G"); + logger.info("Analysis:" + " ".repeat(maxResetLength)); + + const currentPlan = await plan(analysis, defaultAnswers); + if (currentPlan.abort) { + return; + } + + await execute(currentPlan); +} + +const sep = "/"; +function walk(dir: string, skip: RegExp = /node_modules|.git/): string[] { + if (!fs.existsSync(dir)) return []; + process.stdout.write("\x1b[0G"); + const m = + "Analyzing project... " + + (dir.length > 30 ? "..." : "") + + dir.slice(Math.max(dir.length - 30, 0)); + maxResetLength = Math.max(maxResetLength, m.length); + process.stdout.write(m + " ".repeat(maxResetLength - m.length)); + return fs.readdirSync(dir).reduce((files, fileOrDirName) => { + const fileOrDirPath = [dir, fileOrDirName].join("/"); // always use / as path separator since we are doing .endsWith("/foo/bar.ts") when checking for files and we thought this would make it easier (if you are reading this and wondering wtf, then maybe not :) - should work on windows as well? + if (fs.statSync(fileOrDirPath).isDirectory() && !skip.test(fileOrDirName)) { + return files.concat(walk(fileOrDirPath)); + } + + return files.concat(fileOrDirPath); + }, [] as string[]); +} + +type Analysis = Partial<{ + root: string; + srcDir: string; + packageJsonDir: string; + valConfigPath: string; + isTypeScript: boolean; + isJavaScript: boolean; + + // @valbuild/core package: + isValCoreInstalled: boolean; + valCoreVersion: string; + valCoreVersionIsSatisfied: boolean; + + // @valbuild/next package: + isValNextInstalled: boolean; + valNextVersion: string; + valNextVersionIsSatisfied: boolean; + + // eslint: + eslintRcJsonPath: string; + eslintRcJsonText: string; + eslintRcJsPath: string; + eslintRcJsText: string; + eslintRcMjsPath: string; + eslintRcMjsText: string; + valEslintVersion: string; + isValEslintRulesConfigured: boolean; + + // next: + nextVersion: string; + nextVersionIsSatisfied: boolean; + isNextInstalled: boolean; + pagesRouter: boolean; + appRouter: boolean; + appRouterLayoutFile: string; + appRouterPath: string; + appRouterLayoutPath: string; + + // git: + hasGit: boolean; + isGitHub: boolean; + isGitClean: boolean | "packages"; + gitIgnorePath: string; + gitIgnoreFile?: string; + gitRemote: { + owner: string; + repo: string; + }; + + // TODO: + // check if modules are used +}>; + +const analyze = async (root: string, files: string[]): Promise => { + if (!fs.existsSync(root)) { + return {}; + } + const analysis: Analysis = { root }; + const packageJsonPath = files.find( + (file) => file === [root, "package.json"].join(sep), + ); + analysis.packageJsonDir = packageJsonPath && path.dirname(packageJsonPath); + + if (packageJsonPath) { + const packageJsonText = fs.readFileSync(packageJsonPath, "utf8"); + if (packageJsonText) { + try { + const packageJson = JSON.parse(packageJsonText); + analysis.isValCoreInstalled = + !!packageJson.dependencies["@valbuild/core"]; + analysis.isValNextInstalled = + !!packageJson.dependencies["@valbuild/next"]; + analysis.isNextInstalled = !!packageJson.dependencies["next"]; + analysis.valEslintVersion = + packageJson.devDependencies["@valbuild/eslint-plugin"] || + packageJson.dependencies["@valbuild/eslint-plugin"]; + analysis.nextVersion = packageJson.dependencies["next"]; + analysis.valCoreVersion = packageJson.dependencies["@valbuild/core"]; + analysis.valNextVersion = packageJson.dependencies["@valbuild/next"]; + } catch { + throw new Error( + `Failed to parse package.json in file: ${packageJsonPath}`, + ); + } + } + } + if (analysis.nextVersion) { + const minNextVersion = semver.minVersion(analysis.nextVersion)?.version; + if (minNextVersion) { + analysis.nextVersionIsSatisfied = semver.satisfies( + minNextVersion, + ">=" + MIN_NEXT_VERSION, + ); + } + } + if (analysis.valNextVersion) { + const minValVersion = semver.minVersion(analysis.valNextVersion)?.version; + if (minValVersion) { + analysis.valNextVersionIsSatisfied = semver.satisfies( + minValVersion, + ">=" + MIN_VAL_VERSION, + ); + } + } + if (analysis.valCoreVersion) { + const minValVersion = semver.minVersion(analysis.valCoreVersion)?.version; + if (minValVersion) { + analysis.valCoreVersionIsSatisfied = semver.satisfies( + minValVersion, + ">=" + MIN_VAL_VERSION, + ); + } + } + + analysis.eslintRcJsPath = files.find((file) => file.endsWith(".eslintrc.js")); + if (analysis.eslintRcJsPath) { + analysis.eslintRcJsText = fs.readFileSync(analysis.eslintRcJsPath, "utf8"); + if (analysis.eslintRcJsText) { + // TODO: Evaluate and extract config? + analysis.isValEslintRulesConfigured = analysis.eslintRcJsText.includes( + "plugin:@valbuild/recommended", + ); + } + } + analysis.eslintRcMjsPath = files.find((file) => + file.endsWith("eslint.config.mjs"), + ); + if (analysis.eslintRcMjsPath) { + analysis.eslintRcMjsText = fs.readFileSync( + analysis.eslintRcMjsPath, + "utf8", + ); + if (analysis.eslintRcMjsText) { + // TODO: Evaluate and extract config? + analysis.isValEslintRulesConfigured = + analysis.eslintRcMjsText.includes("@valbuild"); + } + } + analysis.eslintRcJsonPath = + files.find((file) => file.endsWith(".eslintrc.json")) || + files.find((file) => file.endsWith(".eslintrc")); + if (analysis.eslintRcJsonPath) { + analysis.eslintRcJsonText = fs.readFileSync( + analysis.eslintRcJsonPath, + "utf8", + ); + if (analysis.eslintRcJsonText) { + // TODO: Parse properly + analysis.isValEslintRulesConfigured = analysis.eslintRcJsonText.includes( + "plugin:@valbuild/recommended", + ); + } + } + + const pagesRouterAppPath = + files.find((file) => file.endsWith("/pages/_app.tsx")) || + files.find((file) => file.endsWith("/pages/_app.jsx")); + analysis.pagesRouter = !!pagesRouterAppPath; + if (pagesRouterAppPath) { + analysis.isTypeScript = !!pagesRouterAppPath.endsWith(".tsx"); + analysis.isJavaScript = !!pagesRouterAppPath.endsWith(".jsx"); + analysis.srcDir = path.dirname(path.dirname(pagesRouterAppPath)); + } + + const appRouterLayoutPath = + files.find((file) => file.endsWith("/app/layout.tsx")) || + files.find((file) => file.endsWith("/app/layout.jsx")); + + if (appRouterLayoutPath) { + analysis.appRouter = true; + analysis.appRouterLayoutPath = appRouterLayoutPath; + analysis.appRouterLayoutFile = fs.readFileSync(appRouterLayoutPath, "utf8"); + analysis.isTypeScript = !!appRouterLayoutPath.endsWith(".tsx"); + analysis.isJavaScript = !!appRouterLayoutPath.endsWith(".jsx"); + analysis.appRouterPath = path.dirname(appRouterLayoutPath); + analysis.srcDir = path.dirname(analysis.appRouterPath); + } + + try { + const git = simpleGit(root); + const gitStatus = await git.status([]); + const gitRemoteOrigin = await git.remote(["-v"]); + analysis.hasGit = true; + analysis.isGitHub = gitRemoteOrigin + ? !!gitRemoteOrigin.includes("github.com") + : false; + analysis.isGitClean = getGitStatusIsClean(gitStatus); + // get owner and repo from git remote: + if (gitRemoteOrigin) { + // Split the URL by colon + const parts = gitRemoteOrigin.split(":"); + + // Extract owner and repo + const owner = parts[0].split("@")[1]; + const repo = parts[1].replace(".git", ""); // Remove .git extension if present + + analysis.gitRemote = { + owner, + repo, + }; + } + } catch { + // ignored + } + const gitIgnorePath = path.join(root, ".gitignore"); + analysis.gitIgnorePath = gitIgnorePath; + analysis.gitIgnoreFile = fs.readFileSync(gitIgnorePath, "utf-8"); + return analysis; +}; + +type FileOp = { + path: string; + source: string; +}; +type Plan = Partial<{ + root: string; + createValServer: FileOp; + createValRouter: FileOp; + createValAppPage: FileOp; + createConfigFile: FileOp; + createValRsc: false | FileOp; + createValClient: false | FileOp; + updateAppLayout: false | FileOp; + updateEslint: false | FileOp; // TODO: do this + useTypescript: boolean; + useJavascript: boolean; + abort: boolean; + ignoreGitDirty: boolean; + updateGitIgnore: false | FileOp; + gitRemote: + | false + | { + owner: string; + repo: string; + }; + includeExample: false | FileOp; + includeModules: false | FileOp; + updateVSCodeSettings: false | FileOp; +}>; + +async function plan( + analysis: Readonly, + defaultAnswers: boolean = false, +): Promise { + const plan: Plan = { root: analysis.root }; + + if (analysis.root) { + logger.info(" Root: " + analysis.root, { isGood: true }); + } else { + logger.error("Failed to find root directory"); + return { abort: true }; + } + if ( + !analysis.srcDir || + !fs.statSync(analysis.srcDir).isDirectory() || + !analysis.isNextInstalled + ) { + logger.error("Val requires a Next.js project"); + return { abort: true }; + } + if (analysis.srcDir) { + logger.info(" Source dir: " + analysis.srcDir, { isGood: true }); + } else { + logger.error("Failed to determine source directory"); + return { abort: true }; + } + if (!analysis.isNextInstalled) { + logger.error("Val requires a Next.js project"); + return { abort: true }; + } + if (!analysis.isValCoreInstalled) { + logger.error("Install @valbuild/core first"); + return { abort: true }; + } else { + if (!analysis.valCoreVersionIsSatisfied) { + logger.warn( + ` This init script expects @valbuild/core >= ${MIN_VAL_VERSION}. Found: ${analysis.valCoreVersion}`, + ); + const answer = !defaultAnswers + ? await confirm({ + message: "Continue?", + default: false, + }) + : false; + if (!answer) { + logger.error( + `Aborted: @valbuild/core version is not satisfied.\n\nInstall the @valbuild/core@${MIN_VAL_VERSION} package with your favorite package manager.\n\nExample:\n\n npm install -D @valbuild/core@${MIN_VAL_VERSION}\n`, + ); + return { abort: true }; + } + } else { + logger.info( + ` Val version: found ${analysis.valCoreVersion} >= ${MIN_VAL_VERSION}`, + { isGood: true }, + ); + } + } + if (!analysis.isValNextInstalled) { + logger.error("Install @valbuild/next first"); + return { abort: true }; + } else { + if (!analysis.valNextVersionIsSatisfied) { + logger.warn( + ` This init script expects @valbuild/next >= ${MIN_VAL_VERSION}. Found: ${analysis.valNextVersion}`, + ); + const answer = !defaultAnswers + ? await confirm({ + message: "Continue?", + default: false, + }) + : false; + if (!answer) { + logger.error( + `Aborted: @valbuild/next version is not satisfied.\n\nInstall the @valbuild/next@${MIN_VAL_VERSION} package with your favorite package manager.\n\nExample:\n\n npm install -D @valbuild/next@${MIN_VAL_VERSION}\n`, + ); + return { abort: true }; + } + } else { + logger.info( + ` Val version: found ${analysis.valNextVersion} >= ${MIN_VAL_VERSION}`, + { isGood: true }, + ); + } + } + if (!analysis.nextVersionIsSatisfied) { + logger.error( + `Val requires Next.js >= ${MIN_NEXT_VERSION}. Found: ${analysis.nextVersion}`, + ); + return { abort: true }; + } else { + logger.info( + ` Next.js version: found ${analysis.nextVersion} >= ${MIN_NEXT_VERSION}`, + { isGood: true }, + ); + } + if (analysis.isTypeScript) { + logger.info(" Use: TypeScript", { isGood: true }); + plan.useTypescript = true; + } + if (analysis.isJavaScript) { + logger.info(" Use: JavaScript", { isGood: true }); + if (!plan.useTypescript) { + plan.useJavascript = true; + } + } + if (analysis.isTypeScript) { + const tsconfigJsonPath = path.join(analysis.root, "tsconfig.json"); + if (fs.statSync(tsconfigJsonPath).isFile()) { + logger.info(" tsconfig.json: found", { isGood: true }); + } else { + logger.error("tsconfig.json: Failed to find tsconfig.json"); + return { abort: true }; + } + } else { + const jsconfigJsonPath = path.join(analysis.root, "jsconfig.json"); + if (fs.statSync(jsconfigJsonPath).isFile()) { + logger.info(" jsconfig.json: found", { isGood: true }); + } else { + logger.error(" jsconfig.json: failed to find jsconfig.json"); + return { abort: true }; + } + } + + if (analysis.valEslintVersion === undefined) { + const answer = !defaultAnswers + ? await confirm({ + message: + "The recommended Val eslint plugin (@valbuild/eslint-plugin) is not installed. Continue?", + default: false, + }) + : false; + if (!answer) { + logger.error( + "Aborted: the Val eslint plugin is not installed.\n\nInstall the @valbuild/eslint-plugin package with your favorite package manager.\n\nExample:\n\n npm install -D @valbuild/eslint-plugin\n", + ); + return { abort: true }; + } + } else { + logger.info(" @valbuild/eslint-plugin: installed", { isGood: true }); + } + if (analysis.appRouter) { + logger.info(" Use: App Router", { isGood: true }); + } + if (analysis.pagesRouter) { + logger.info(" Use: Pages Router", { isGood: true }); + } + if (analysis.isGitClean) { + if (analysis.isGitClean === "packages") { + logger.info( + " Git state: clean (only package.json / lock files modified)", + { + isGood: true, + }, + ); + } else { + logger.info(" Git state: clean", { isGood: true }); + } + } + if (!analysis.isGitClean) { + logger.warn(" Git state: dirty"); + } + + if (!analysis.isGitClean) { + while (plan.ignoreGitDirty === undefined) { + const answer = !defaultAnswers + ? await confirm({ + message: "You have uncommitted changes. Continue?", + default: false, + }) + : false; + plan.ignoreGitDirty = answer; + if (!answer) { + logger.error("Aborted: git state dirty"); + return { abort: true }; + } + } + } + + // New required files: + const valConfigPath = path.join( + analysis.root, + analysis.isTypeScript ? "val.config.ts" : "val.config.js", + ); + if (fs.existsSync(valConfigPath)) { + logger.error( + `Aborted: a Val config file: ${valConfigPath} already exists.`, + ); + return { abort: true }; + } + + plan.createConfigFile = { + path: valConfigPath, + source: VAL_CONFIG(!!analysis.isTypeScript, { + defaultTheme: "light", + }), + }; + + { + const answer = !defaultAnswers + ? await confirm({ + message: "Include example Val files?", + default: true, + }) + : true; + if (answer) { + const exampleDir = path.join(analysis.srcDir, "examples", "val"); + const examplePath = path.join( + exampleDir, + "example.val." + (analysis.isJavaScript ? "js" : "ts"), + ); + const exampleImport = path + .relative(exampleDir, valConfigPath) + .replace(".js", "") + .replace(".ts", ""); + if (!analysis.packageJsonDir) { + throw Error( + "Could not detect package.json directory! This is a Val bug.", + ); + } + const exampleModuleFilePath = `/${path.relative( + analysis.packageJsonDir, + examplePath, + )}`; + + plan.includeExample = { + path: examplePath, + source: BASIC_EXAMPLE( + exampleModuleFilePath, + exampleImport, + !!analysis.isJavaScript, + ), + }; + } + } + + const valModulesDir = analysis.root; + const valModulesImport = path + .relative(valModulesDir, valConfigPath) + .replace(".js", "") + .replace(".ts", ""); + const exampleModuleFilePath = plan.includeExample + ? plan.includeExample.path + : undefined; + const exampleModuleImport = + exampleModuleFilePath && + path + .relative(valModulesDir, exampleModuleFilePath) + .replace(".js", "") + .replace(".ts", ""); + plan.includeModules = { + path: path.join(valModulesDir, "val.modules.ts"), + source: VAL_MODULES(valModulesImport, exampleModuleImport), + }; + const valUtilsDir = path.join(analysis.srcDir, "val"); + const valModulesServerImport = path + .relative(valUtilsDir, plan.includeModules.path) + .replace(".js", "") + .replace(".ts", ""); + const valUtilsImportPath = path + .relative(valUtilsDir, valConfigPath) + .replace(".js", "") + .replace(".ts", ""); + const valServerPath = path.join( + valUtilsDir, + analysis.isTypeScript ? "val.server.ts" : "val.server.js", + ); + plan.createValServer = { + path: valServerPath, + source: VAL_SERVER(valUtilsImportPath, valModulesServerImport), + }; + + if (!analysis.appRouterPath) { + logger.warn('Creating a new "app" router'); + } + + const valAppPagePath = path.join( + analysis.appRouterPath || path.join(analysis.srcDir, "app"), + "(val)", + "val", + "[[...val]]", + analysis.isTypeScript ? "page.tsx" : "page.jsx", + ); + const valPageImportPath = path + .relative(path.dirname(valAppPagePath), valConfigPath) + .replace(".js", "") + .replace(".ts", ""); + plan.createValAppPage = { + path: valAppPagePath, + source: VAL_APP_PAGE(valPageImportPath), + }; + + const valRouterPath = path.join( + analysis.appRouterPath || path.join(analysis.srcDir, "app"), + "(val)", + "api", + "val", + "[[...val]]", + analysis.isTypeScript ? "route.ts" : "route.js", + ); + const valRouterImportPath = path + .relative(path.dirname(valRouterPath), valServerPath) + .replace(".js", "") + .replace(".ts", ""); + plan.createValRouter = { + path: valRouterPath, + source: VAL_API_ROUTER(valRouterImportPath), + }; + + // Util files: + + while (plan.createValClient === undefined) { + const answer = !defaultAnswers + ? await confirm({ + message: "Setup useVal for Client Components", + default: true, + }) + : true; + if (answer) { + plan.createValClient = { + path: path.join( + valUtilsDir, + analysis.isTypeScript ? "val.client.ts" : "val.client.js", + ), + source: VAL_CLIENT(valUtilsImportPath), + }; + } else { + plan.createValClient = false; + } + } + while (plan.createValRsc === undefined) { + const answer = !defaultAnswers + ? await confirm({ + message: "Setup fetchVal for React Server Components", + default: true, + }) + : true; + if (answer) { + plan.createValRsc = { + path: path.join( + valUtilsDir, + analysis.isTypeScript ? "val.rsc.ts" : "val.rsc.js", + ), + source: VAL_RSC(valUtilsImportPath, valModulesServerImport), + }; + } else { + plan.createValRsc = false; + } + } + + // Patches: + + const NO_PATCH_WARNING = + "Remember to add the ValProvider in your root app/layout.tsx or pages/_app.tsx file.\n"; + if (analysis.appRouterLayoutPath) { + if (!analysis.appRouterLayoutFile) { + logger.error("Failed to read app router layout file"); + return { abort: true }; + } + + const res = transformNextAppRouterValProvider( + { + path: analysis.appRouterLayoutPath, + source: analysis.appRouterLayoutFile, + }, + { + j: jcs, + jscodeshift: jcs.withParser("tsx"), + stats: () => {}, + report: () => {}, + }, + { + configImportPath: path + .relative(path.dirname(analysis.appRouterLayoutPath), valConfigPath) + .replace(".js", "") + .replace(".ts", ""), + }, + ); + + const diff = diffLines(analysis.appRouterLayoutFile, res, {}); + + let s = ""; + diff.forEach((part) => { + if (part.added) { + s += chalk.green(part.value); + } else if (part.removed) { + s += chalk.red(part.value); + } else { + s += part.value; + } + }); + const answer = !defaultAnswers + ? await confirm({ + message: `Automatically patch ${analysis.appRouterLayoutPath} file?`, + default: true, + }) + : true; + if (answer) { + const answer = !defaultAnswers + ? await confirm({ + message: `Do you accept the following patch:\n${s}\n`, + default: true, + }) + : true; + if (!answer) { + logger.warn(NO_PATCH_WARNING); + plan.updateAppLayout = false; + } else { + plan.updateAppLayout = { + path: analysis.appRouterLayoutPath, + source: res, + }; + } + } else { + logger.warn(NO_PATCH_WARNING); + } + } + if (analysis.pagesRouter) { + logger.warn(NO_PATCH_WARNING); + } + + if (analysis.valEslintVersion) { + if (analysis.isValEslintRulesConfigured) { + logger.warn(" @valbuild/eslint-plugin rules: already configured"); + } else { + if (analysis.eslintRcJsPath) { + await confirm({ + message: + ' Cannot patch eslint: found .eslintrc.js but can only patch JSON files (at the moment).\nAdd the following to your eslint config:\n\n "extends": ["plugin:@valbuild/recommended"]\n\n', + default: true, + }); + } else if (analysis.eslintRcJsonPath) { + const answer = !defaultAnswers + ? await confirm({ + message: + "Patch eslintrc.json to use the recommended Val eslint rules?", + default: true, + }) + : true; + if (answer) { + const currentEslintRc = fs.readFileSync( + analysis.eslintRcJsonPath, + "utf-8", + ); + const parsedEslint = JSON.parse(currentEslintRc); + if (typeof parsedEslint !== "object") { + logger.error( + `Could not patch eslint: ${analysis.eslintRcJsonPath} was not an object`, + ); + return { abort: true }; + } + if (typeof parsedEslint.extends === "string") { + parsedEslint.extends = [parsedEslint.extends]; + } + parsedEslint.extends = parsedEslint.extends || []; + parsedEslint.extends.push("plugin:@valbuild/recommended"); + plan.updateEslint = { + path: analysis.eslintRcJsonPath, + source: JSON.stringify(parsedEslint, null, 2) + "\n", + }; + } + } else if (analysis.eslintRcMjsPath) { + if (analysis.eslintRcMjsText) { + const res = addEslintPluginToEslintMjs( + { + path: analysis.eslintRcMjsPath, + source: analysis.eslintRcMjsText, + }, + { + j: jcs, + jscodeshift: jcs.withParser("tsx"), + stats: () => {}, + report: () => {}, + }, + { + configImportPath: path + .relative(path.dirname(analysis.eslintRcMjsPath), valConfigPath) + .replace(".js", "") + .replace(".ts", ""), + }, + ); + + const diff = diffLines(analysis.eslintRcMjsText, res, {}); + + let s = ""; + diff.forEach((part) => { + if (part.added) { + s += chalk.green(part.value); + } else if (part.removed) { + s += chalk.red(part.value); + } else { + s += part.value; + } + }); + const answer = !defaultAnswers + ? await confirm({ + message: `Automatically patch ${analysis.eslintRcMjsPath} file?`, + default: true, + }) + : true; + if (answer) { + const answer = !defaultAnswers + ? await confirm({ + message: `Do you accept the following patch:\n${s}\n`, + default: true, + }) + : true; + if (!answer) { + logger.warn(NO_PATCH_WARNING); + plan.updateEslint = false; + } else { + plan.updateEslint = { + path: analysis.eslintRcMjsPath, + source: res, + }; + } + } else { + logger.warn(NO_PATCH_WARNING); + } + } else { + logger.warn(NO_PATCH_WARNING); + } + } else { + logger.warn("Cannot patch eslint: failed to find eslint config file"); + } + } + } + + { + if (analysis.gitIgnorePath) { + const answer = !defaultAnswers + ? await confirm({ + message: "Append .gitignore entry for Val cache? (recommended)", + default: true, + }) + : true; + if (answer) { + plan.updateGitIgnore = { + path: analysis.gitIgnorePath, + source: + (analysis.gitIgnoreFile ? `${analysis.gitIgnoreFile}\n\n` : "") + + "# Val local cache\n.val\n", + }; + } else { + plan.updateGitIgnore = false; + } + } else { + plan.updateGitIgnore = false; + } + } + { + const answer = !defaultAnswers + ? await confirm({ + message: "Add the Val Build IntelliSense to .vscode/extensions.json?", + default: true, + }) + : true; + if (answer) { + const vscodeDir = path.join(analysis.root, ".vscode"); + const settingsPath = path.join(vscodeDir, "extensions.json"); + let currentSettings = {}; + + try { + const currentSettingsFile = fs.readFileSync(settingsPath, "utf-8"); + if (currentSettingsFile) { + try { + currentSettings = JSON.parse(currentSettingsFile); + } catch (err) { + logger.warn( + `Failed to parse VS Code extensions.json found here: ${settingsPath}.${ + err instanceof Error ? `Parse error: ${err.message}` : "" + }`, + ); + return { + abort: true, + }; + } + } + } catch { + // ignore - dir does not exist (most likely) + } + const currentRecommendations: string[] | undefined = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (currentSettings as any).recommendations; + const valBuildIntelliSense = "valbuild.vscode-val-build"; + if (!currentRecommendations?.includes(valBuildIntelliSense)) { + currentSettings = { + ...currentSettings, + recommendations: (currentRecommendations || []).concat( + valBuildIntelliSense, + ), + }; + } + plan.updateVSCodeSettings = { + path: settingsPath, + source: JSON.stringify(currentSettings, null, 2), + }; + } else { + plan.updateVSCodeSettings = false; + } + } + + return plan; +} + +async function execute(plan: Plan) { + if (plan.abort) { + return logger.warn("Aborted"); + } + if (!plan.root) { + return logger.error("Failed to find root directory"); + } + logger.info("Executing..."); + for (const [key, maybeFileOp] of Object.entries(plan)) { + if (isFileOp(maybeFileOp)) { + writeFile(maybeFileOp, plan.root, key.startsWith("update")); + } + } + logger.info(` + +Val was successfully initialized! + + Start the application: + + $ ${chalk.cyan("npm run dev")} + + Open (assumes http://localhost:3000): + + ${chalk.bgBlack.hex("#37cd99").underline(`http://localhost:3000/val`)} + + When you want to enable editor support, connect the project by opening the following link: + + ${chalk.bgBlack + .hex("#37cd99") + .underline( + `https://admin.val.build/connect${ + plan.gitRemote + ? `?github_repo=${encodeURIComponent( + [plan.gitRemote.repo, plan.gitRemote.owner].join("/"), + )}&org=${encodeURIComponent( + plan.gitRemote.owner, + )}&project=${encodeURIComponent(plan.gitRemote.repo)}` + : "" + }`, + )} + +`); +} + +function writeFile( + fileOp: FileOp | undefined, + rootDir: string, + isUpdate: boolean, +) { + if (fileOp) { + fs.mkdirSync(path.dirname(fileOp.path), { recursive: true }); + fs.writeFileSync(fileOp.path, fileOp.source); + logger.info( + ` ${isUpdate ? "Patched" : "Created"} file: ${fileOp.path.replace( + rootDir, + "", + )}`, + { isGood: true }, + ); + } +} +function getGitStatusIsClean(gitStatus: StatusResult): Analysis["isGitClean"] { + const filteredFiles = gitStatus.files.filter( + ({ path }) => + !( + // ignore updates to package.json and lock files + // since user might have just installed val + // TODO: check if package.json only includes val related things + ( + path === "package.json" || + // lock files: + path === "package-lock.json" || + path === "yarn.lock" || + path === "pnpm-lock.yaml" + ) + ), + ); + if (filteredFiles.length === 0) { + if (gitStatus.files.length !== 0) { + return "packages"; + } + return true; + } + return false; +} + +function isFileOp(maybeFileOp: unknown): maybeFileOp is FileOp { + return ( + typeof maybeFileOp !== "boolean" && + typeof maybeFileOp !== "string" && + typeof maybeFileOp === "object" && + !!maybeFileOp && + "path" in maybeFileOp && + "source" in maybeFileOp && + typeof maybeFileOp.path === "string" && + typeof maybeFileOp.source === "string" + ); +} diff --git a/packages/init/src/logger.ts b/packages/init/src/logger.ts new file mode 100644 index 000000000..a7248c5ee --- /dev/null +++ b/packages/init/src/logger.ts @@ -0,0 +1,32 @@ +import chalk from "chalk"; + +export function error(message: string) { + console.error(chalk.red(" ❌ ERROR: ") + message); +} + +export function warn(message: string) { + console.error(chalk.yellow(" ⚠️ WARN:" + message)); +} + +export function info( + message: string, + opts: { isCodeSnippet?: true; isGood?: true } = {}, +) { + if (opts.isCodeSnippet) { + console.log(chalk.cyanBright("$ > ") + chalk.cyan(message)); + return; + } + if (opts.isGood) { + console.log(chalk.hex("#37cd99")(" V") + message); + return; + } + console.log(message); +} + +export function debugPrint(str: string) { + /*eslint-disable no-constant-condition */ + if (process.env["DEBUG"] || true) { + // TODO: remove true + console.log(`DEBUG: ${str}`); + } +} diff --git a/packages/init/src/main.ts b/packages/init/src/main.ts new file mode 100644 index 000000000..ddcc696b7 --- /dev/null +++ b/packages/init/src/main.ts @@ -0,0 +1,49 @@ +import meow from "meow"; +import { init } from "./init"; +import { error } from "./logger"; + +async function main() { + const { flags } = meow( + ` + Usage + $ npx @valbuild/init + Description + Initialize Val in a project + + Options + --help Show this message + --root [root], -r [root] Set project root directory (default process.cwd()) + --yes [yes], -y [yes] Accept all prompts with defaults. + `, + { + flags: { + root: { + type: "string", + alias: "r", + }, + yes: { + type: "boolean", + alias: "y", + }, + }, + hardRejection: false, + }, + ); + + await init(flags.root, { yes: flags.yes }); +} + +void main().catch((err) => { + if (err.message.includes("force closed the prompt")) { + process.exitCode = 2; + return; + } + error( + err instanceof Error + ? err.message + "\n" + err.stack + : typeof err === "object" + ? JSON.stringify(err, null, 2) + : err, + ); + process.exitCode = 1; +}); diff --git a/packages/init/src/templates.ts b/packages/init/src/templates.ts new file mode 100644 index 000000000..3eff5f3f5 --- /dev/null +++ b/packages/init/src/templates.ts @@ -0,0 +1,278 @@ +export const VAL_CLIENT = (configImportPath: string) => `import "client-only"; +import { initValClient } from "@valbuild/next/client"; +import { config } from "${configImportPath}"; + +const { + useValStega: useVal, + useValRouteStega: useValRoute, + useValRouteUrl, +} = initValClient(config); + +export { useVal, useValRoute, useValRouteUrl }; +`; + +export const VAL_RSC = ( + configImportPath: string, + valModulesImportPath: string, +) => `import "server-only"; +import { initValRsc } from "@valbuild/next/rsc"; +import { config } from "${configImportPath}"; +import valModules from "${valModulesImportPath}"; +import { cookies, draftMode, headers } from "next/headers"; + +const { + fetchValStega: fetchVal, + fetchValRouteStega: fetchValRoute, + fetchValRouteUrl, +} = + initValRsc(config, valModules, { + draftMode, + headers, + cookies, + }); + +export { fetchVal, fetchValRoute, fetchValRouteUrl }; +`; + +export const VAL_SERVER = ( + configImportPath: string, + valModulesImportPath: string, +) => `import "server-only"; +import { initValServer } from "@valbuild/next/server"; +import { config } from "${configImportPath}"; +import { draftMode } from "next/headers"; +import valModules from "${valModulesImportPath}"; + +const { valNextAppRouter } = initValServer( + valModules, + { ...config }, + { + draftMode, + } +); + +export { valNextAppRouter }; +`; + +// TODO: use from @valbuild/core +type ConfigDirectory = `/public/${string}`; + +// TODO: use from @valbuild/core +type ValConfig = { + project?: string; + root?: string; + files?: { + directory: ConfigDirectory; + }; + gitCommit?: string; + gitBranch?: string; + defaultTheme?: "dark" | "light"; +}; + +export const VAL_CONFIG = ( + isTypeScript: boolean, + options: ValConfig, +) => `import { initVal } from "@valbuild/next"; + +const { s, c, val, config, nextAppRouter, externalPageRouter } = initVal(${JSON.stringify(options, null, 2)}); + +${isTypeScript ? 'export type { t } from "@valbuild/next";' : ""}; +export { s, c, val, config, nextAppRouter, externalPageRouter }; +`; + +export const VAL_API_ROUTER = ( + valServerPath: string, +) => `import { valNextAppRouter } from "${valServerPath}"; + +export const GET = valNextAppRouter; +export const POST = valNextAppRouter; +export const PATCH = valNextAppRouter; +export const DELETE = valNextAppRouter; +export const PUT = valNextAppRouter; +export const HEAD = valNextAppRouter; +`; + +export const VAL_APP_PAGE = ( + configImportPath: string, +) => `import { ValApp } from "@valbuild/next"; +import { config } from "${configImportPath}"; + +export default function Val() { + return ; +} +`; + +export const VAL_MODULES = ( + configImportPath: string, + exampleModuleImport?: string, +) => `import { modules } from "@valbuild/next"; +import { config } from "./${configImportPath}"; + +export default modules(config, [ + // Add your modules here${ + exampleModuleImport + ? ` + { def: () => import("./${exampleModuleImport}") },` + : "" + } +]); + +`; + +export const BASIC_EXAMPLE = ( + moduleFilePath: string, + configImportPath: string, + isJavaScript: boolean, +) => `${isJavaScript ? "// @ts-check\n" : ""}/** + * Val example file - generated by @valbuild/init + **/ + +import { + s /* s = schema */, + c /* c = content */,${isJavaScript ? "" : "\n type t /* t = type */,"} +} from "${configImportPath}"; + +/** + * This is the schema for the content. It defines the structure of the content and the types of each field. + * + * @docs https://val.build/docs/api/schema + */ +export const testSchema = s.object({ + /** + * Basic text field + */ + text: s.string(), + + /** + * Nullable are optional fields in the UI that can be null or not + */ + optionals: s.string().nullable(), + + arrays: s.array(s.string()), + /** + * Records are objects where entries can be added. Useful for array-like structures where you would use a key to uniquely identify each entry. + */ + records: s.record(s.string(), s.string()), + + /** + * Rich text can be used for multiline text, but also for more complex text editing capabilities like links, images, lists, etc. + * + * @docs https://val.build/docs/richtext + * + * @see ValRichText will render rich text + */ + richText: s.richtext({ + // styling: + style: { + bold: true, // enables bold + italic: true, // enables italic text + lineThrough: true, // enables line/strike-through + }, + // block-level elements: + block: { + // tags: + h1: true, // enables h1 + h2: true, + h3: true, + h4: true, + h5: true, + h6: true, + ul: true, // enables unordered lists + ol: true, // enables ordered lists + }, + // inline elements: + inline: { + a: true, + img: true, + }, + }), + + /** + * Images in Val are stored as files in the public folder. + * + * @docs https://val.build/docs/images + * + * When defining content use the following syntax: + * @example c.image('/public/myimage.png') // path to the image file, use the VS Code plugin or the \`@valbuild/cli validate --fix\` command to add metadata + * + * @see ValImage component to see how to render this in your app + */ + image: s.image().nullable(), + + /** + * String enums: presents as a dropdown in the UI + */ + stringEnum: s.union(s.literal("lit-0"), s.literal("lit-1")), + + /** + * Raw strings disables the stega (steganography) feature that automatically tags content when using the overlay. + * It is useful for slugs and other data that might be processed in code (parsed or matching for equality...) + */ + slug: s.string().raw(), + + /** + * Object unions: presents as a dropdown in the UI and the different fields + * + * @docs https://val.build/docs/api/schema/union + */ + objectUnions: s.union( + "type", + s.object({ + type: s.literal("page-type-1"), + value: s.number(), + }), + s.object({ + type: s.literal("page-type-2"), + text: s.string(), + }) + ), +}); +${ + isJavaScript + ? "" + : ` +/** + * t.inferSchema returns the type of the content. + * This pattern is useful to type props of components that use this content (partially or whole) + */ +export type TestContent = t.inferSchema; +` +} + +/** + * This is the content definition. Add your content below. + * + * NOTE: the first argument, module id, must match the path of the file. + */ +export default c.define("${moduleFilePath}", testSchema, { + text: "Basic text content", + optionals: null, + arrays: ["A string"], + records: { + "unique-key-1": "A string", + }, + richText: [ + { tag: "h1", children: ["Title 1"] }, + { + tag: "p", + children: [ + { tag: "a", href: "https://val.build/docs", children: ["Val docs"] }, + ], + }, + { + tag: "ul", + children: [ + { tag: "li", children: [{ tag: "p", children: ["List item 1"] }] }, + { tag: "li", children: [{ tag: "p", children: ["List item 2"] }] }, + ], + }, + ], + image: null, + slug: "test", + objectUnions: { + type: "page-type-2", + text: "String value", + }, + stringEnum: "lit-1", +}); +`; diff --git a/packages/init/tsconfig.json b/packages/init/tsconfig.json new file mode 100644 index 000000000..151d4a0fa --- /dev/null +++ b/packages/init/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} diff --git a/packages/lib/jest.config.js b/packages/lib/jest.config.js deleted file mode 100644 index 3abcbd946..000000000 --- a/packages/lib/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: "ts-jest", - testEnvironment: "node", -}; diff --git a/packages/lib/package.json b/packages/lib/package.json deleted file mode 100644 index d12b7205e..000000000 --- a/packages/lib/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@val/lib", - "version": "1.0.0", - "main": "dist/val-lib.cjs.js", - "devDependencies": { - "@types/jest": "^29.2.5", - "jest": "^29.3.1", - "ts-jest": "^29.0.3" - } -} diff --git a/packages/lib/src/StaticVal.ts b/packages/lib/src/StaticVal.ts deleted file mode 100644 index 76a813821..000000000 --- a/packages/lib/src/StaticVal.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Schema } from "./schema/Schema"; -import { SerializedSchema } from "./schema/SerializedSchema"; -import { ValidTypes } from "./ValidTypes"; - -/** - * @deprecated Uncertain about the name of this - */ -export class StaticVal { - constructor(private readonly val: T, public readonly schema: Schema) {} - - /** - * Get the value of this static value - * - * @internal - */ - get(): T { - return this.val; - } - - serialize(): { val: T; schema: SerializedSchema } { - return { - val: this.val, - schema: this.schema.serialize(), - }; - } -} diff --git a/packages/lib/src/Val.ts b/packages/lib/src/Val.ts deleted file mode 100644 index 4044ba98b..000000000 --- a/packages/lib/src/Val.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ValidObject, ValidTypes } from "./ValidTypes"; - -export type ValString = { - val: string; - id: string; -}; - -export type ValObject = { - [key in keyof T]: Val; -}; - -export type Val = T extends string - ? ValString - : T extends ValidObject - ? ValObject - : never; diff --git a/packages/lib/src/ValidTypes.ts b/packages/lib/src/ValidTypes.ts deleted file mode 100644 index 081b44617..000000000 --- a/packages/lib/src/ValidTypes.ts +++ /dev/null @@ -1,7 +0,0 @@ -const reservedKeys = ["_val"] as const; -export type ReservedKeys = typeof reservedKeys[number]; - -export type ValidObject = { [key: string]: ValidTypes } & { - [key in ReservedKeys]?: never; -}; -export type ValidTypes = string | ValidObject; diff --git a/packages/lib/src/content.test.ts b/packages/lib/src/content.test.ts deleted file mode 100644 index 56a0b770e..000000000 --- a/packages/lib/src/content.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { content } from "./content"; -import { object } from "./schema/object"; -import { string } from "./schema/string"; - -describe("content function", () => { - test("content initialization", () => { - expect( - content("/id", () => - object({ - foo: string(), - }).static({ - foo: "bar", - }) - ).val.get() - ).toStrictEqual({ - foo: "bar", - }); - }); -}); diff --git a/packages/lib/src/content.ts b/packages/lib/src/content.ts deleted file mode 100644 index 377d60c46..000000000 --- a/packages/lib/src/content.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { object } from "./schema/object"; -import { StaticVal } from "./StaticVal"; -import { ValidTypes } from "./ValidTypes"; - -/** - * - * @deprecated Uncertain about the name of this - */ -export class ValContent { - constructor(public readonly id: string, public readonly val: StaticVal) {} -} - -/** - * - * @deprecated Uncertain about the name of this - */ -export const content = ( - id: string, - f: () => StaticVal -): ValContent => { - return new ValContent(id, f()); -}; diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts deleted file mode 100644 index 24be3c7e4..000000000 --- a/packages/lib/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { initVal } from "./initVal"; -export type { SerializedSchema } from "./schema/SerializedSchema"; - -// DO NOT TOUCH THIS: it is used to get the Schema symbol -export type { Schema } from "./schema/Schema"; diff --git a/packages/lib/src/initVal.ts b/packages/lib/src/initVal.ts deleted file mode 100644 index a9865f29e..000000000 --- a/packages/lib/src/initVal.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { content } from "./content"; -import { object } from "./schema/object"; -import { string } from "./schema/string"; -import { useVal } from "./useVal"; - -export const initVal = () => { - return { - val: { - content, - }, - useVal, - s: { - string, - object, - }, - }; -}; diff --git a/packages/lib/src/schema/Schema.ts b/packages/lib/src/schema/Schema.ts deleted file mode 100644 index b0e64725b..000000000 --- a/packages/lib/src/schema/Schema.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { StaticVal } from "../StaticVal"; -import { ValidTypes } from "../ValidTypes"; -import { SerializedSchema } from "./SerializedSchema"; - -export abstract class Schema { - /** - * Validate a value against this schema - * - * @param input - * @internal - */ - abstract validate(input: T): false | string[]; - - static(val: T): StaticVal { - return new StaticVal(val, this); - } - - abstract serialize(): SerializedSchema; -} diff --git a/packages/lib/src/schema/SerializedSchema.ts b/packages/lib/src/schema/SerializedSchema.ts deleted file mode 100644 index d28e53709..000000000 --- a/packages/lib/src/schema/SerializedSchema.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { SerializedObjectSchema } from "./object"; -import { SerializedStringSchema } from "./string"; - -export type SerializedSchema = SerializedStringSchema | SerializedObjectSchema; diff --git a/packages/lib/src/schema/object.test.ts b/packages/lib/src/schema/object.test.ts deleted file mode 100644 index 868182e55..000000000 --- a/packages/lib/src/schema/object.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { object } from "./object"; -import { string } from "./string"; - -describe("object schema", () => { - test("object validation", () => { - expect( - object({ - foo: string({ - maxLength: 2, - }), - bar: string({ - minLength: 2, - }), - }).validate({ - foo: "Testing 123", - bar: "1", - }) - ).toHaveLength(2); - }); -}); diff --git a/packages/lib/src/schema/object.ts b/packages/lib/src/schema/object.ts deleted file mode 100644 index b0eb4460f..000000000 --- a/packages/lib/src/schema/object.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ValidObject } from "../ValidTypes"; -import { Schema } from "./Schema"; -import type { SerializedSchema } from "./SerializedSchema"; - -export type SerializedObjectSchema = { - type: "object"; - schema: Record; -}; - -class ObjectSchema extends Schema { - constructor(private readonly schema: { [key in keyof T]: Schema }) { - super(); - } - validate(input: T): false | string[] { - const errors: string[] = []; - for (const key in this.schema) { - const value = input[key]; - const schema = this.schema[key]; - const result = schema.validate(value); - if (result) { - errors.push(...result.map((error) => `[${key}]: ${error}`)); - } - } - if (errors.length > 0) { - return errors; - } - return false; - } - - serialize(): SerializedObjectSchema { - return { - type: "object", - schema: Object.fromEntries( - Object.entries(this.schema).map(([key, schema]) => [ - key, - schema.serialize(), - ]) - ), - }; - } -} -export const object = (schema: { - [key in keyof T]: Schema; -}): Schema => { - return new ObjectSchema(schema); -}; diff --git a/packages/lib/src/schema/string.test.ts b/packages/lib/src/schema/string.test.ts deleted file mode 100644 index 2bfeaa8c3..000000000 --- a/packages/lib/src/schema/string.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { string } from "./string"; - -describe("string schema", () => { - test("string validation", () => { - expect( - string({ - maxLength: 2, - }).validate("Testing 123") - ).toHaveLength(1); - }); -}); diff --git a/packages/lib/src/schema/string.ts b/packages/lib/src/schema/string.ts deleted file mode 100644 index 8d043d272..000000000 --- a/packages/lib/src/schema/string.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Schema } from "./Schema"; -import { SerializedSchema } from "./SerializedSchema"; - -type StringOptions = { - maxLength?: number; - minLength?: number; -}; - -export type SerializedStringSchema = { - type: "string"; - options: { - maxLength?: number; - minLength?: number; - }; -}; - -class StringSchema extends Schema { - constructor(private readonly options?: StringOptions) { - super(); - } - - validate(input: string): false | string[] { - const errors: string[] = []; - if (this.options?.maxLength && input.length > this.options.maxLength) { - errors.push( - `String '${input}' is too long (max ${this.options.maxLength})` - ); - } - if (this.options?.minLength && input.length < this.options.minLength) { - errors.push( - `String '${input}' is too short (min ${this.options.minLength})` - ); - } - if (errors.length > 0) { - return errors; - } - - return false; - } - - serialize(): SerializedStringSchema { - return { - type: "string", - options: { - maxLength: this.options?.maxLength, - minLength: this.options?.minLength, - }, - }; - } -} -export const string = (options?: StringOptions): Schema => { - return new StringSchema(options); -}; diff --git a/packages/lib/src/useVal.ts b/packages/lib/src/useVal.ts deleted file mode 100644 index f7cd06b19..000000000 --- a/packages/lib/src/useVal.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { content, ValContent } from "./content"; -import { object } from "./schema/object"; -import { string } from "./schema/string"; -import { StaticVal } from "./StaticVal"; -import { Val, ValObject, ValString } from "./Val"; -import { ValidTypes } from "./ValidTypes"; - -function buildVal(id: string, val: T): Val { - if (typeof val === "string") { - return { - id, - val, - } as Val; - } else if (typeof val === "object") { - // Should this be a Proxy / lazy or not? Is it serializable? - return new Proxy(val, { - get(target, prop: string) { - if (target[prop]) { - return buildVal(`${id}.${prop}`, target[prop]); - } - return undefined; - }, - }) as unknown as Val; - } - throw new Error("Not implemented"); -} - -export const useVal = ( - content: ValContent -): Val => { - const staticVal: StaticVal = content.val; - const validationError = staticVal.schema.validate(staticVal.get()); - if (validationError) { - throw new Error( - `Invalid static value. Errors:\n${validationError.join("\n")}` - ); - } - return buildVal(content.id, staticVal.get()); -}; - -// TODO: move this to tests -{ - const val: ValString = useVal(content("foo", () => string().static("bar"))); -} -{ - const val: ValObject<{ foo: string }> = useVal( - content("foo", () => object({ foo: string() }).static({ foo: "bar" })) - ); -} diff --git a/packages/next/.babelrc.json b/packages/next/.babelrc.json new file mode 100644 index 000000000..241f9b88c --- /dev/null +++ b/packages/next/.babelrc.json @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-react", + { + "runtime": "automatic" + } + ] + ] +} diff --git a/packages/next/.gitignore b/packages/next/.gitignore new file mode 100644 index 000000000..ebb293b9f --- /dev/null +++ b/packages/next/.gitignore @@ -0,0 +1 @@ +/trace diff --git a/packages/next/CHANGELOG.md b/packages/next/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/next/MANUAL_CONFIGURATION.md b/packages/next/MANUAL_CONFIGURATION.md new file mode 100644 index 000000000..955b049d9 --- /dev/null +++ b/packages/next/MANUAL_CONFIGURATION.md @@ -0,0 +1,126 @@ +# Configuring Val manually + +You can setup Val in your Next.js project manually by following the steps below: + +- Make sure your project is using TypeScript 5+, Next 14+, React 18.20.+ +- Install the Val packages using your favorite package manager. For npm, you can run: + + ```bash + npm install @valbuild/core @valbuild/next + ``` + +- Create the `/val.config.ts` file in the root of the project (where your `package.json` file is located): + + ```ts + import { initVal } from "@valbuild/next"; + + const { s, c, val, config } = initVal({ + defaultTheme: "dark", + }); + + export type { t } from "@valbuild/next"; + export { s, c, val, config }; + ``` + + **NOTE**: after you have created the config file, you can set it up to work in production for editors by following the steps described in the [remote mode section](./README.md#remote-mode) + +- Create the `/val/val.server.ts` file: + + ```ts + import "server-only"; + import { initValServer } from "@valbuild/next/server"; + import { config } from "../val.config"; + import { draftMode } from "next/headers"; + import valModules from "../val.modules"; + + const { valNextAppRouter } = initValServer( + valModules, + { ...config }, + { + draftMode, + }, + ); + + export { valNextAppRouter }; + ``` + + If you use prettier (or something else) to format your files, continue by following the steps [here](./README.md#formatting-published-content). + +- Create the `/val.modules.ts` file: + + ```ts + import { modules } from "@valbuild/next"; + import { config } from "./val.config"; + + export default modules(config, [ + // Add your modules here + ]); + ``` + +- Add the ValProvider in your root layout. Val needs this to be able to show updates when editors updates content. An example of a root layout with the ValProvider can look like this: + +```ts +import { ValProvider } from "@valbuild/next"; +import { config } from "../val.config"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {/* ValProvider with config below: */} + {children} + + + ); +} +``` + +- If you need Val in React Client Components, setup the `useVal` hook by creating the following `/val/val.client.ts` file: + + ```ts + import "client-only"; + import { initValClient } from "@valbuild/next/client"; + import { config } from "../val.config"; + + const { useValStega: useVal } = initValClient(config); + + export { useVal }; + ``` + +- If you need Val in React Server Components, setup `fetchVal` by creating the following `/val/val.rsc.ts` file: + + ```ts + import "client-only"; + import { initValClient } from "@valbuild/next/client"; + import { config } from "../val.config"; + + const { useValStega: useVal } = initValClient(config); + + export { useVal }; + ``` + +- If you want to use the recommended Val prettier rules, install the Val eslint package: + + ```bash + npm i -D @valbuild/eslint-plugin + ``` + + Then add the rule in the `extends` section in your `/eslintrc.js` file: + + ```json + "plugin:@valbuild/recommended", + ``` diff --git a/packages/next/README.md b/packages/next/README.md new file mode 100644 index 000000000..a910182f3 --- /dev/null +++ b/packages/next/README.md @@ -0,0 +1,873 @@ +

+

+

+ +## Table of contents + +- [Installation](#installation) +- [Getting started](#getting-started) +- [Schema types](#schema-types): + - [String](#string) + - [Number](#number) + - [Boolean](#boolean) + - [Nullable](#nullable) + - [Array](#array) + - [Record](#record) + - [Router](#router) + - [Object](#object) + - [Rich text](#richtext) + - [Image](#image) + - [keyOf](#keyof) + - [Route](#route) + +## Installation + +### Using the NextJS starter templates + +If you're starting from scratch, the easiest way to get up and running with Val and NextJS is to use npm (or similar) create script: + +```sh +npm create @valbuild +``` + +### Integrating into an existing project + +- Make sure you have NextJS (version 14 or higher) installed + +- Install the packages: + +```sh +npm install @valbuild/core@latest @valbuild/next@latest +``` + +- Optionally, but recommend add the eslint-plugin package: + +```sh +npm install -D @valbuild/eslint-plugin@latest +``` + +- Run the init script: + +```sh +npx @valbuild/init@latest +``` + +### Manually + +It is also possible to setup Val using the [manual configuration guide](./MANUAL_CONFIGURATION.md). + +## Additional setup + +- If you have a monorepo, or have a project where the project is located in a subdirectory relative to the github repository see the [monorepos](#monorepos) section +- See [formatting published content](#formatting-published-content) if you use prettier (or similar) Val to do it as well. +- If you want editors to update content in production, read up on how to setup [remote mode](#remote-mode). + +## Getting started + +### Create your first Val content file + +Content in Val is always defined in `.val.ts` (or `.js`) files. + +**NOTE**: the init script will generate an example Val content file (unless you opt out of it). + +Val content files are _evaluated_ by Val, therefore they need to abide a set of requirements. +If you use the eslint plugins these requirements will be enforced. You can also validate val files using the @valbuild/cli: `npx -p @valbuild/cli val validate`. + +For reference these requirements are: + +- they must export a default content definition (`c.define`) where the first argument equals the path of the file relative to the `val.config` file; and +- they must be declared in the `val.modules` file; and +- they must have a default export that is `c.define`; and +- they can only import Val related files or types (using `import type { MyType } from "./otherModule.ts"`) + +### Val content file example + +```ts +// ./examples/val/example.val.ts +import { s /* s = schema */, c /* c = content */ } from "../../val.config"; + +/** + * This is the schema for the content. It defines the structure of the content and the types of each field. + */ +export const schema = s.object({ + /** + * Basic text field + */ + text: s.string(), +}); + +/** + * This is the content definition. Add your content below. + * + * NOTE: the first argument is the path of the file. + */ +export default c.define("/examples/val/example.val.ts", schema, { + text: "Basic text content", +}); +``` + +### The `val.modules` file + +Once you have created your Val content file, it must be declared in the `val.modules.ts` (or `.js`) file in the project root folder. + +Example: + +```ts +import { modules } from "@valbuild/next"; +import { config } from "./val.config"; + +export default modules(config, [ + // Add your modules here + { def: () => import("./examples/val/example.val") }, +]); +``` + +### Using Val in Client Components + +In client components you can access your content with the `useVal` hook: + +```tsx +// ./app/page.tsx +"use client"; +import { useVal } from "../val/val.client"; +import exampleVal from "../examples/val/example.val"; + +export default function Home() { + const { text } = useVal(exampleVal); + return
{text}
; +} +``` + +### Using Val in React Server Components + +In React Server components you can access your content with the `fetchVal` function: + +```tsx +// ./app/page.tsx +"use server"; +import { fetchVal } from "../val/val.rsc"; +import exampleVal from "../examples/val/example.val"; + +export default async function Home() { + const { text } = await fetchVal(exampleVal); + return
{text}
; +} +``` + +# Remote Mode + +Enable remote mode to allow editors to update content online (outside of local development) by creating a project at [admin.val.build](https://admin.val.build). + +**NOTE**: Your content remains yours. Hosting content from your repository does not require a subscription. However, to edit content online, a subscription is needed — unless your project is a public repository or qualifies for the free tier. Visit the [pricing page](https://val.build/pricing) for details. + +**WHY**: Updating code involves creating a commit, which requires a server. We offer a hosted service for simplicity and efficiency, as self-hosted solutions takes time to setup and maintain. Additionally, the [val.build](https://val.build) team funds the ongoing development of this library. + +## Remote Mode Configuration + +Once your project is set up in [admin.val.build](https://admin.val.build), configure your application to use it by setting the following: + +### Environment Variables + +- **`VAL_API_KEY`**: This is the API key used to authenticate server side API requests. You can find it under Settings in your project on [admin.val.build](https://admin.val.build). +- **`VAL_SECRET`**: In addition to the VAL_API_KEY, you need to generate a random secret to secure communication between the UX client and your Next.js application. You can use any random string for this, but if you have openssl installed you can run the following command: `openssl rand -hex 16` + +### `val.config` Properties + +Set these properties in the `val.config` file: + +- **`project`**: The fully qualified name of your project, formatted as `/`. +- **`gitBranch`**: The Git branch your application uses. For Vercel, use `VERCEL_GIT_COMMIT_REF`. +- **`gitCommit`**: The current Git commit your application is running on. For Vercel, use `VERCEL_GIT_COMMIT_SHA`. +- **`root`**: Optional. The path to the `val.config` file. Typically empty or undefined. If the project folder is under `web`, root would be: `/web`. + +### Example `val.config.ts` + +```ts +import { initVal } from "@valbuild/next"; + +const { s, c, val, config } = initVal({ + project: "myteam/myproject", + //root: "/subdir", // only required for monorepos. Use the path where val.config is located. The path should start with / + gitBranch: process.env.VERCEL_GIT_COMMIT_REF, + gitCommit: process.env.VERCEL_GIT_COMMIT_SHA, +}); + +export type { t } from "@valbuild/next"; +export { s, c, val, config }; +``` + +# Formatting published content + +If you are using `prettier` or another code formatting tool, it is recommended to setup formatting of code after changes have been applied. + +## Setting up formatting using Prettier + +- Install `prettier` as **RUNTIME** dependency, by moving the `prettier` dependency from `devDependencies` to `dependencies`. The reason you need to do this, is that Val will be using it at runtime in production, and it has to be part of your build for this to work. +- Optionally create a `.prettierrc.json` file unless you have one already. We recommend doing this, so that you can be sure that formatting is applied consistently in both your development environment and by Val. You can set this to be an empty object, if you are want to keep using `prettier`s defaults: + + ```json + {} + ``` + +- Add a formatter to the `/val/val.server`: + + ```ts + formatter: (code: string, filePath: string) => { + return prettier.format(code, { + filepath: filePath, + ...prettierOptions, // <- use the same rules as in development + } as prettier.Options); + }, + ``` + + Unless you have any modifications in your `val.server` file, the complete file should now look like this: + + ```ts + import "server-only"; + import { initValServer } from "@valbuild/next/server"; + import { config } from "../val.config"; + import { draftMode } from "next/headers"; + import valModules from "../val.modules"; + import prettier from "prettier"; + import prettierOptions from "../.prettierrc.json"; + + const { valNextAppRouter } = initValServer( + valModules, + { ...config }, + { + draftMode, + formatter: (code: string, filePath: string) => { + return prettier.format(code, { + filepath: filePath, + ...prettierOptions, // <- use the same rules as in development + } as prettier.Options); + }, + }, + ); + + export { valNextAppRouter }; + ``` + +You should now be able to hit the save button locally and see prettier rules being applied. + +## Other formatters + +Val is formatter agnostic, so it is possible to use the same flow as the one described for `prettier` above to any formatter you might want to use. + +**NOTE**: this will be applied at runtime in production so you need make sure that the formatting dependencies are in the `dependencies` section of your `package.json` + +# Monorepos + +Val supports projects that are not under the root path in GitHub, and therefore monorepos. +To configure your project for monorepos, you can use the `root` parameter described in the [config](#valconfig-properties) section. + +# Schema types + +## String + +```ts +import { s } from "./val.config"; + +s.string(); // <- Schema +``` + +## Number + +```ts +import { s } from "./val.config"; + +s.number(); // <- Schema +``` + +## Boolean + +```ts +import { s } from "./val.config"; + +s.boolean(); // <- Schema +``` + +## Nullable + +All schema types can be nullable (optional). A nullable schema creates a union of the type and `null`. + +```ts +import { s } from "./val.config"; + +s.string().nullable(); // <- Schema +``` + +## Array + +```ts +s.array(t.string()); // <- Schema +``` + +## Record + +The type of `s.record` is `Record`. + +It is similar to an array, in that editors can add and remove items in it, however it has a unique key which can be used as, for example, the slug or as a part of an route. + +**NOTE**: records can also be used with `keyOf`. + +```ts +s.record(t.number()); // <- Schema> +``` + +## Router + +The `router` schema is a convenient shorthand for creating a record with router configuration. It combines `s.record()` and `.router()` into a single call. + +```ts +import { s, c, nextAppRouter } from "../val.config"; + +const pageSchema = s.object({ title: s.string() }); + +// Using s.router() - shorthand +const pagesSchema = s.router(nextAppRouter, pageSchema); + +// Equivalent to: +const pagesSchema = s.record(pageSchema).router(nextAppRouter); +``` + +### Example: + +```ts +import { s, c, nextAppRouter } from "../val.config"; + +const pageSchema = s.object({ title: s.string() }); + +// NOTE: to use router(nextAppRouter) - the module must be a sibling of the page.tsx +export default c.define( + "/app/[slug]/page.val.ts", + s.router(nextAppRouter, pageSchema), + { + "/test-page": { + // This is the full pathname of the page - it must match the pattern of the Next JS route + title: "Test page", + }, + }, +); +``` + +## Object + +```ts +s.object({ + myProperty: s.string(), +}); +``` + +#### Page router (using .router() method) + +You can configure Val to track your page structure and display content as navigable web pages in the editor interface. + +For editors, this creates an intuitive page-based editing experience where they can navigate through your site structure and edit content directly within the context of each page. + +You can use the `.router` method on `record` to achieve this. + +When using the `.router` with the `nextAppRouter`, it will automatically generate routes based on your record keys and integrate seamlessly with Next.js App Router conventions. + +#### Example: + +```ts +import { s, c, nextAppRouter } from "../val.config"; + +const pageSchema = s.object({ title: s.string() }); +const pagesSchema = s.record(pageSchema).router(nextAppRouter); + +// NOTE: to use router(nextAppRouter) - the module must be a sibling of the page.tsx. In other words it must with page.val.ts or page.val.js +export default c.define("/app/[slug]/page.val.ts", pageSchema, { + "/test-page": { + // This is the full pathname of the page - it must match the pattern of the Next JS route of the page.tsx you are consuming this from + title: "Test page", + }, +}); +``` + +To consume a page route from a NextJS "page component", it is recommended you use `fetchValRoute` or `useValRoute`. + +NOTE: to be refactor proof (i.e. not break when changing the route), you should always provide the params of the NextJS page component. + +#### Example fetchValRoute + +```ts +export default async function MyPage({ params }: { params: + Promise<{ slug: string }> // use params: Promise if this page route has no params +}) { + const page = await fetchValRoute(pageVal, params) + return +} +``` + +### Custom render + +You can customize how records are displayed in the Val editor interface by using the `.render` method on `record`. This allows you to control how individual record items appear in the editor's preview. + +The only currently supported layout is `list`, which provides a customizable list view for record previews. + +#### Example + +```ts +const pagesSchema = s + .record(s.object({ title: s.string(), image: s.image() })) + .router(nextAppRouter) + .render({ + layout: "list", // Use list layout for record preview + select({ key, val }) { + return { + // Capitalize the first letter of the key for display + title: key?.[0]?.toUpperCase() + key?.slice(1), + // Show the image from the record + image: val.image, + }; + }, + }); +``` + +## RichText + +
+RichText in Val represented both in code and to the editors as semantic html5. + +This means that content will be accessible and according to spec out of the box. The flip-side is that Val will not support RichText that includes elements that is not part of the html 5 standard. + +This opinionated approach was chosen since rendering anything, makes it hard for developers to maintain and hard for editors to understand. + +
+ +### RichText Schema + +```ts +s.richtext({ + // options +}); +``` + +### Initializing RichText content + +To initialize some text content using a RichText schema, you can use follow the example below: + +```ts +import { s, c } from "./val.config"; + +export const schema = s.richtext({ + // styling + style: { + bold: true, // enables bold + italic: true, // enables italic text + lineThrough: true, // enables line/strike-through + }, + // tags: + block: { + //ul: true, // enables unordered lists + //ol: true, // enables ordered lists + // headings: + h1: true, + h2: true, + // h3: true, + // h4: true, + // h5: true, + // h6: true + }, + inline: { + //a: true, // enables links + //img: true, // enables images + }, +}); + +export default c.define("/src/app/content", schema, [ + { + tag: "p", + children: ["This is richtext"], + }, + { + tag: "p", + children: [{ tag: "span", styles: ["bold"], children: ["Bold"] }, "text"], + }, +]); +``` + +### Rendering RichText + +You can use the `ValRichText` component to render content. + +```tsx +"use client"; +import { ValRichText } from "@valbuild/next"; +import contentVal from "./content.val"; +import { useVal } from "./val/val.client"; + +export default function Page() { + const content = useVal(contentVal); + return ( +
+ +
+ ); +} +``` + +#### ValRichText: theme property + +To add classes to `ValRichText` you can use the theme property: + +```tsx + +``` + +**NOTE**: if a theme is defined, you must define a mapping for every tag that the you get. What tags you have is decided based on the `options` defined on the `s.richtext()` schema. For example: `s.richtext({ style: { bold: true } })` requires that you add a `bold` theme. + +```tsx + +``` + +**NOTE**: the reason you must define themes for every tag that the RichText is that this will force you to revisit the themes that are used if the schema changes. The alternative would be to accept changes to the schema. + +### ValRichText: transform property + +Vals `RichText` type maps RichText 1-to-1 with semantic HTML5. + +If you want to customize / override the type of elements which are rendered, you can use the `transform` property. + +```tsx + { + if (typeof node !== "string" && node.tag === "img") { + return ( +
+ +
+ ); + } + // if transform returns undefined the default render will be used + }} + content={content} +/> +``` + +### The RichText type + +The `RichText` type is actually an AST (abstract syntax tree) representing semantic HTML5 elements. + +That means they look something like this: + +```ts +type RichTextNode = { + tag: + | "img" + | "a" + | "ul" + | "ol" + | "h1" + | "h2" + | "h3" + | "h4" + | "h5" + | "h6" + | "br" + | "p" + | "li" + | "span"; + classes: "bold" | "line-through" | "italic"; // all styling classes + children: RichTextNode[] | undefined; +}; +``` + +### RichText: full custom + +The `RichText` type maps 1-to-1 to HTML. +That means it is straightforward to build your own implementation of a React component that renders `RichText`. + +This example is a simplified version of the `ValRichText` component. +You can use this as a template to create your own. + +NOTE: before writing your own, make sure you check out the `theme` and `transform` properties on the `ValRichText` - most simpler cases should be covered by them. + +```tsx +export function ValRichText({ + content, +}: { + content: RichText; +}) { + function build( + node: RichTextNode, + key?: number, + ): JSX.Element | string { + if (typeof node === "string") { + return node; + } + // you can map the classes to something else here + const className = node.classes.join(" "); + const tag = node.tag; // one of: "img" | "a" | "ul" | "ol" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "br" | "p" | "li" | "span" + + // Example of rendering img with MyOwnImageComponent: + if (tag === "img") { + return ; + } + return React.createElement( + tag, + { + key, + className, + }, + "children" in node ? node.children.map(build) : null, + ); + } + return
{content.map(build)}
; +} +type MyRichTextOptions = AnyRichTextOptions; // you can reduce the surface of what you need to render, by restricting the `options` in `s.richtext(options)` +``` + +## Image + +### Image Schema + +```ts +s.image(); +``` + +### Initializing image content + +Local images must be stored under the `/public/val` folder. + +```ts +import { s, c } from "../val.config"; + +export const schema = s.image(); + +export default c.define("/image", schema, c.image("/public/myfile.jpg")); +``` + +**NOTE**: This will not validate, since images requires `width`, `height` and `mimeType`. You can fix validation errors like this by using the CLI or by using the VS Code plugin. + +### Rendering images + +The `ValImage` component is a wrapper around `next/image` that accepts a Val `Image` type. + +You can use it like this: + +```tsx +const content = useVal(contentVal); // schema of contentVal: s.object({ image: s.image() }) + +return ; +``` + +### Using images in components + +Images are transformed to object that have a `url` property which can be used to render them. + +Example: + +```tsx +// in a Functional Component +const image = useVal(imageVal); + +return ; +``` + +## Union + +The union schema can be used to create either "tagged unions" or a union of string literals. + +### Union Schema tagged unions + +A tagged union is a union of objects which all have the same field (of the same type). This field can be used to determine (or "discriminate") the exact type of one of the types of the union. + +It is useful when editors should be able to chose from a set of objects that are different. + +Example: let us say you have a page that can be one of the following: blog (page) or product (page). In this case your schema could look like this: + +```ts +s.union( + "type", // the key of the "discriminator" + s.object({ + type: s.literal("blogPage"), // <- each type must have a UNIQUE value + author: s.string(), + // ... + }), + s.object({ + type: s.literal("productPage"), + sku: s.number(), + // ... + }), +); // <- Schema<{ type: "blogPage", author: string } | { type: "productPage", sku: number }> +``` + +## Union Schema: union of string literals + +You can also use a union to create a union of string literals. This is useful if you want a type-safe way to describe a set of valid strings that can be chosen by an editor. + +```ts +s.union( + s.literal("one"), + s.literal("two"), + //... +); // <- Schema<"one" | "two"> +``` + +## KeyOf + +You can use `keyOf` to reference a key in a record of a Val module. + +**NOTE**: currently you must reference keys in Val modules, you cannot reference keys of values nested inside a Val module. This is a feature on the roadmap. + +```ts +const schema = s.record(s.object({ nested: s.record(s.string()) })); + +export default c.define("/keyof.val.ts", schema, { + "you-can-reference-me": { + // <- this can be referenced + nested: { + "but-not-me": ":(", // <- this cannot be referenced + }, + }, +}); +``` + +### KeyOf Schema + +```ts +import otherVal from "./other.val"; // NOTE: this must be a record + +s.keyOf(otherVal); +``` + +### Initializing keyOf + +### Using keyOf to reference content + +```tsx +const article = useVal(articleVal); // s.object({ author: s.keyOf(otherVal) }) +const authors = useVal(otherVal); // s.record(s.object({ name: s.string() })) + +const nameOfAuthor = authors[articleVal.author].name; +``` + +## Route + +The `route` schema represents a string that references a route path in your application. It can be used with `include` and `exclude` patterns to constrain which routes are valid. + +### Route Schema + +```ts +s.route(); // <- Schema +``` + +### Route with patterns + +You can use `include` and `exclude` to constrain valid routes using regular expressions: + +```ts +// Only allow API routes +s.route().include(/^\/api\//); + +// Exclude admin routes +s.route().exclude(/^\/admin\//); + +// Combine both: API routes except internal ones +s.route() + .include(/^\/api\//) + .exclude(/^\/api\/internal\//); +``` + +**Pattern semantics:** + +- If only `include` is set: route must match the include pattern +- If only `exclude` is set: route must NOT match the exclude pattern +- If both are set: route must match include AND must NOT match exclude + +### Using routes + +Routes are validated against router modules (records with `.router()` or `s.router()`). The route value must exist as a key in one of your router modules. + +```ts +import { s, c } from "../val.config"; + +const linkSchema = s.object({ + label: s.string(), + href: s.route().include(/^\//), // Only allow routes starting with / +}); + +export default c.define("/components/link.val.ts", linkSchema, { + label: "Home", + href: "/", // This must exist in a router module +}); +``` + +# Custom validation + +All schema can use the `validate` method to show custom validation errors to editors. + +## Example + +```ts +s.string().validate((val) => { + if (val.startsWith("something")) { + return "Cannot have something in this string"; + } + return false; // no validation error +}); +``` + +## Get in touch + +Join us on [discord](https://discord.gg/cZzqPvaX8k) to get help or give us feedback. diff --git a/packages/next/client/package.json b/packages/next/client/package.json new file mode 100644 index 000000000..9e647ad84 --- /dev/null +++ b/packages/next/client/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/valbuild-next-client.cjs.js", + "module": "dist/valbuild-next-client.esm.js" +} diff --git a/packages/next/jest.config.js b/packages/next/jest.config.js new file mode 100644 index 000000000..6458500a7 --- /dev/null +++ b/packages/next/jest.config.js @@ -0,0 +1,4 @@ +/** @type {import("jest").Config} */ +module.exports = { + preset: "../../jest.preset", +}; diff --git a/packages/next/package.json b/packages/next/package.json new file mode 100644 index 000000000..53344dc8f --- /dev/null +++ b/packages/next/package.json @@ -0,0 +1,78 @@ +{ + "name": "@valbuild/next", + "description": "Val NextJS: hard-coded content - super-charged", + "private": false, + "repository": { + "type": "git", + "url": "git+https://github.com/valbuild/val.git" + }, + "sideEffects": true, + "keywords": [ + "CMS", + "next", + "react" + ], + "version": "0.96.0", + "scripts": { + "typecheck": "tsc --noEmit", + "test": "jest" + }, + "main": "dist/valbuild-next.cjs.js", + "module": "dist/valbuild-next.esm.js", + "exports": { + ".": { + "module": "./dist/valbuild-next.esm.js", + "default": "./dist/valbuild-next.cjs.js" + }, + "./rsc": { + "module": "./rsc/dist/valbuild-next-rsc.esm.js", + "default": "./rsc/dist/valbuild-next-rsc.cjs.js" + }, + "./client": { + "module": "./client/dist/valbuild-next-client.esm.js", + "default": "./client/dist/valbuild-next-client.cjs.js" + }, + "./server": { + "module": "./server/dist/valbuild-next-server.esm.js", + "default": "./server/dist/valbuild-next-server.cjs.js" + }, + "./package.json": "./package.json" + }, + "types": "dist/valbuild-next.cjs.d.ts", + "preconstruct": { + "entrypoints": [ + "./index.ts", + "./server/index.ts", + "./client/index.ts", + "./rsc/index.ts" + ], + "exports": true + }, + "dependencies": { + "@valbuild/core": "workspace:*", + "@valbuild/react": "workspace:*", + "@valbuild/server": "workspace:*", + "@valbuild/shared": "workspace:*", + "@valbuild/ui": "workspace:*", + "client-only": "^0.0.1", + "server-only": "^0.0.1" + }, + "devDependencies": { + "@types/react": "^18.2.38", + "next": "^13.4.0", + "typescript": "^5.9.3" + }, + "peerDependencies": { + "next": ">=13.4.0", + "react": ">=18.2.0 || ^19.0 || ^19.0.0-rc" + }, + "externals": [ + "next" + ], + "files": [ + "dist", + "client", + "server", + "rsc" + ] +} diff --git a/packages/next/rsc/package.json b/packages/next/rsc/package.json new file mode 100644 index 000000000..3fdfe8bd9 --- /dev/null +++ b/packages/next/rsc/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/valbuild-next-rsc.cjs.js", + "module": "dist/valbuild-next-rsc.esm.js" +} diff --git a/packages/next/server/package.json b/packages/next/server/package.json new file mode 100644 index 000000000..089bbd806 --- /dev/null +++ b/packages/next/server/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/valbuild-next-server.cjs.js", + "module": "dist/valbuild-next-server.esm.js" +} diff --git a/packages/next/src/ValApp.tsx b/packages/next/src/ValApp.tsx new file mode 100644 index 000000000..47575358c --- /dev/null +++ b/packages/next/src/ValApp.tsx @@ -0,0 +1,140 @@ +"use client"; + +import { ValConfig } from "@valbuild/core"; +import { VAL_APP_PATH, VAL_APP_ID, VERSION as UIVersion } from "@valbuild/ui"; +import Script from "next/script"; +import { useEffect, useRef, useState } from "react"; +import { useConfigStorageSave } from "./useConfigStorageSave"; +import { cn, valPrefixedClass } from "./cssUtils"; + +export const ValApp = ({ config }: { config: ValConfig }) => { + const route = "/api/val"; // TODO: make configurable + const [inMessageMode, setInMessageMode] = useState(); + const isClientSIde = inMessageMode === undefined; + useConfigStorageSave(config); + const container = useRef(null); + useEffect(() => { + if (location.search === "?message_onready=true") { + setInMessageMode(true); + const interval = setInterval(() => { + window.parent.postMessage( + { + type: "val-ready", + }, + "*", + ); + }); + return () => { + clearInterval(interval); + }; + } else { + setInMessageMode(false); + } + }, []); + useEffect(() => { + if (container.current?.childElementCount === 0) { + window.dispatchEvent(new CustomEvent("val-append-studio")); + } + }); + + // this theme is used to avoid flickering + const [loadingTheme, setLoadingTheme] = useState( + config.defaultTheme || null, + ); + useEffect(() => { + const theme = localStorage.getItem( + "val-theme-" + (config?.project || "unknown"), + ); + if (theme === "dark") { + setLoadingTheme("dark"); + } else if (theme === "light") { + setLoadingTheme("light"); + } else if (config.defaultTheme) { + setLoadingTheme(config.defaultTheme); + } + }, [config]); + const darkBg = "#0c111d"; + const lightBg = "white"; + useEffect(() => { + if (inMessageMode || loadingTheme === null) { + return; + } + const body = document.body; + const prevBodyBg = body.style.backgroundColor; + const prevBodyMinHeight = body.style.minHeight; + const prevBodyMinWidth = body.style.minWidth; + body.style.backgroundColor = loadingTheme === "dark" ? darkBg : lightBg; + body.style.minHeight = "100vh"; + body.style.minWidth = "100%"; + window.addEventListener("val-css-loaded", () => { + // css was loaded, has been loaded, so let app decide what to do + setLoadingTheme(null); + }); + return () => { + body.style.backgroundColor = prevBodyBg; + body.style.minHeight = prevBodyMinHeight; + body.style.minWidth = prevBodyMinWidth; + window.removeEventListener("val-css-loaded", () => { + setLoadingTheme(null); + }); + }; + }, [inMessageMode, loadingTheme]); + + if (loadingTheme !== null && isClientSIde) { + return ( +
+ + + + +
+ ); + } + if (inMessageMode) { + return
Val Studio is disabled: in message mode
; + } + return ( + <> + + + + +
+ + diff --git a/packages/ui/jest.config.js b/packages/ui/jest.config.js new file mode 100644 index 000000000..6458500a7 --- /dev/null +++ b/packages/ui/jest.config.js @@ -0,0 +1,4 @@ +/** @type {import("jest").Config} */ +module.exports = { + preset: "../../jest.preset", +}; diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 000000000..784583f66 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,184 @@ +{ + "name": "@valbuild/ui", + "version": "0.96.0", + "sideEffects": false, + "repository": { + "type": "git", + "url": "git+https://github.com/valbuild/val.git" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "build": "vite build && vite --config server.vite.config.mts build && rollup --config rollup.config.js && vite --config spa.vite.config.mts build && node fix-server-hack.js && node fix-version-hack.js", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", + "dev": "vite" + }, + "devDependencies": { + "@codemirror/lang-angular": "^0.1.4", + "@codemirror/lang-cpp": "^6.0.3", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-go": "^6.0.1", + "@codemirror/lang-html": "^6.4.11", + "@codemirror/lang-java": "^6.0.2", + "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/lang-php": "^6.0.2", + "@codemirror/lang-python": "^6.2.1", + "@codemirror/lang-rust": "^6.0.2", + "@codemirror/lang-sass": "^6.0.2", + "@codemirror/lang-sql": "^6.10.0", + "@codemirror/lang-vue": "^0.1.3", + "@codemirror/lang-xml": "^6.1.0", + "@codemirror/lang-yaml": "^6.1.2", + "@codemirror/language": "^6.12.1", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@hookform/resolvers": "^5.2.2", + "@lezer/highlight": "^1.2.3", + "@radix-ui/primitive": "^1.1.3", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-compose-refs": "^1.1.2", + "@radix-ui/react-context": "^1.1.3", + "@radix-ui/react-dismissable-layer": "^1.1.11", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-focus-guards": "^1.1.3", + "@radix-ui/react-focus-scope": "^1.1.8", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-id": "^1.1.1", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-portal": "^1.1.10", + "@radix-ui/react-presence": "^1.1.5", + "@radix-ui/react-primitive": "^2.1.4", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-tooltip": "^1.2.8", + "@radix-ui/react-use-controllable-state": "^1.2.2", + "@remirror/core": "^3.0.2", + "@remirror/extension-bold": "^3.0.2", + "@remirror/extension-drop-cursor": "^3.0.2", + "@remirror/extension-hard-break": "^3.0.2", + "@remirror/extension-heading": "^3.0.2", + "@remirror/extension-image": "^3.0.2", + "@remirror/extension-italic": "^3.0.2", + "@remirror/extension-link": "^3.0.2", + "@remirror/extension-list": "^3.0.2", + "@remirror/extension-strike": "^3.0.2", + "@remirror/pm": "^3.0.1", + "@remirror/react": "^3.0.3", + "@remirror/styles": "^3.0.0", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-commonjs": "^29.0.0", + "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-typescript": "^12.3.0", + "@storybook/addon-essentials": "^7.6.7", + "@storybook/addon-interactions": "^7.6.7", + "@storybook/addon-links": "^7.6.7", + "@storybook/addon-styling": "^1.0.8", + "@storybook/addon-themes": "^7.6.7", + "@storybook/blocks": "^7.6.7", + "@storybook/builder-vite": "^7.6.7", + "@storybook/react": "^7.6.7", + "@storybook/react-vite": "^7.6.7", + "@storybook/testing-library": "^0.0.14-next.2", + "@testing-library/react": "^16.3.2", + "@types/express": "^5.0.6", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@types/react-resizable": "^3.0.8", + "@uiw/codemirror-themes": "^4.25.4", + "@uiw/react-codemirror": "^4.25.4", + "@valbuild/core": "workspace:*", + "@valbuild/shared": "workspace:*", + "aria-hidden": "^1.2.6", + "autoprefixer": "^10.4.23", + "class-variance-authority": "^0.7.1", + "classnames": "^2.5.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "eslint": "^9.39.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.26", + "flexsearch": "^0.8.212", + "lucide-react": "^0.563.0", + "postcss": "^8.5.6", + "prop-types": "^15.8.1", + "react": "^18.2.0", + "react-day-picker": "^9.13.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^6.1.0", + "react-feather": "^2.0.10", + "react-hook-form": "^7.71.1", + "react-markdown": "^10.1.0", + "react-remove-scroll": "^2.7.2", + "react-resizable": "^3.1.3", + "rfc6902": "^5.1.2", + "rollup": "^4.56.0", + "rollup-plugin-dts": "^6.3.0", + "rollup-plugin-peer-deps-external": "^2.2.4", + "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-typescript2": "^0.36.0", + "storybook": "^7.6.21", + "tailwind-merge": "^2.0.0", + "tailwindcss": "^3.2.7", + "tailwindcss-animate": "^1.0.7", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "zod": "^4.3.6" + }, + "peerDependencies": { + "react": ">=18.2.0 || ^19.0 || ^19.0.0-rc", + "react-dom": ">=18.2.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + }, + "@types/react": { + "optional": true + } + }, + "preconstruct": { + "entrypoints": [ + "./index.ts", + "./server.ts" + ], + "exports": true + }, + "main": "dist/valbuild-ui.cjs.js", + "module": "dist/valbuild-ui.esm.js", + "exports": { + ".": { + "module": "./dist/valbuild-ui.esm.js", + "default": "./dist/valbuild-ui.cjs.js" + }, + "./server": { + "module": "./server/dist/valbuild-ui-server.esm.js", + "default": "./server/dist/valbuild-ui-server.cjs.js" + }, + "./package.json": "./package.json" + }, + "types": "dist/valbuild-ui.cjs.d.ts", + "files": [ + "dist", + "server" + ], + "dependencies": { + "@tanstack/react-virtual": "^3.13.18" + } +} diff --git a/packages/ui/postcss.config.js b/packages/ui/postcss.config.js new file mode 100644 index 000000000..12a703d90 --- /dev/null +++ b/packages/ui/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/ui/public/sample-image-1.jpg b/packages/ui/public/sample-image-1.jpg new file mode 100644 index 000000000..f73cf9027 Binary files /dev/null and b/packages/ui/public/sample-image-1.jpg differ diff --git a/packages/ui/public/sample-image-2.jpg b/packages/ui/public/sample-image-2.jpg new file mode 100644 index 000000000..138c4c78d Binary files /dev/null and b/packages/ui/public/sample-image-2.jpg differ diff --git a/packages/ui/public/sample-image-3.jpg b/packages/ui/public/sample-image-3.jpg new file mode 100644 index 000000000..e36b50dbd Binary files /dev/null and b/packages/ui/public/sample-image-3.jpg differ diff --git a/packages/ui/public/sample-video.mp4 b/packages/ui/public/sample-video.mp4 new file mode 100644 index 000000000..0a4dd5b40 Binary files /dev/null and b/packages/ui/public/sample-video.mp4 differ diff --git a/packages/ui/rollup.config.js b/packages/ui/rollup.config.js new file mode 100644 index 000000000..f583cc3c8 --- /dev/null +++ b/packages/ui/rollup.config.js @@ -0,0 +1,23 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { default: dts } = require("rollup-plugin-dts"); + +/** + * This rollup config is used solely for bundling type definitions. Vite builds + * the rest! + * + * @type{import("rollup").RollupOptions[]} + */ +const config = [ + { + input: "./src/vite-index.ts", + output: [{ file: "dist/valbuild-ui.cjs.d.ts", format: "es" }], + plugins: [dts()], + }, + { + input: "./src/vite-server.ts", + output: [{ file: "server/dist/valbuild-ui-server.cjs.d.ts", format: "es" }], + plugins: [dts()], + }, +]; + +module.exports = config; diff --git a/packages/ui/server.vite.config.mts b/packages/ui/server.vite.config.mts new file mode 100644 index 000000000..e711814ea --- /dev/null +++ b/packages/ui/server.vite.config.mts @@ -0,0 +1,33 @@ +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + mode: "production", + define: { + "process.env.NODE_ENV": '"production"', + }, + build: { + outDir: "./server/dist", + manifest: true, + lib: { + entry: { + "valbuild-ui-server": "./src/vite-server.ts", + }, + formats: ["cjs", "es"], + /** + * Sets file names to match the output from Preconstruct + */ + fileName(format, entryName) { + switch (format) { + case "es": + return `${entryName}.esm.js`; + case "cjs": + return `${entryName}.cjs.js`; + default: + throw Error(`Unexpected format: ${format}`); + } + }, + }, + minify: false, // do not minify server - it can corrupt the injected code + }, +}); diff --git a/packages/ui/server/package.json b/packages/ui/server/package.json new file mode 100644 index 000000000..2b1b2d191 --- /dev/null +++ b/packages/ui/server/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/valbuild-ui-server.cjs.js", + "module": "dist/valbuild-ui-server.esm.js" +} diff --git a/packages/ui/spa.vite.config.mts b/packages/ui/spa.vite.config.mts new file mode 100644 index 000000000..e40501634 --- /dev/null +++ b/packages/ui/spa.vite.config.mts @@ -0,0 +1,17 @@ +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + mode: "production", + define: { + "process.env.NODE_ENV": '"production"', + }, + base: "/api/val/static", // TODO: needs to be configurable + esbuild: { + target: "ES2020", + }, + build: { + outDir: "./server/.tmp", + minify: true, + }, +}); diff --git a/packages/ui/spa/App.tsx b/packages/ui/spa/App.tsx new file mode 100644 index 000000000..368f947d7 --- /dev/null +++ b/packages/ui/spa/App.tsx @@ -0,0 +1,87 @@ +"use client"; +import { ValStudio } from "./components/ValStudio"; +import { ErrorBoundary } from "react-error-boundary"; +import { fallbackRender } from "./fallbackRender"; +import { useMemo, useState } from "react"; +import { + createValClient, + VAL_CONFIG_SESSION_STORAGE_KEY, + VAL_THEME_SESSION_STORAGE_KEY, +} from "@valbuild/shared/internal"; +import { ShadowRoot } from "./components/ShadowRoot"; +import { VAL_CSS_PATH, VERSION } from "../src"; +import { Fonts } from "./Fonts"; +import { DEFAULT_CONTENT_HOST } from "@valbuild/core"; +import { useConfig } from "./hooks/useConfig"; +import { Themes } from "./components/ValThemeProvider"; + +function App() { + const config = useConfig(); + const host = "/api/val"; // TODO: make configurable + const { client } = useMemo(() => { + const client = createValClient(host, { + ...config, + contentHostUrl: DEFAULT_CONTENT_HOST, + }); + return { client }; + }, [host, config]); + const [cssLoaded, setCssLoaded] = useState(false); + // Theme is initialized by ValNextProvider in session storage + // We just read it once on init and then rely on React state + const [theme, setTheme] = useState(() => { + try { + const stored = sessionStorage.getItem(VAL_THEME_SESSION_STORAGE_KEY); + if (stored === "light" || stored === "dark") return stored; + const configRaw = sessionStorage.getItem(VAL_CONFIG_SESSION_STORAGE_KEY); + const config = configRaw ? JSON.parse(configRaw) : null; + const local = localStorage.getItem( + "val-theme-" + (config?.project || "unknown"), + ); + if (local === "light" || local === "dark") return local; + if (config?.defaultTheme === "light" || config?.defaultTheme === "dark") { + return config.defaultTheme; + } + } catch { + // ignore storage errors + } + return "dark"; + }); + return ( + <> + + + { + // send an event that css is loaded: + window.dispatchEvent( + new CustomEvent("val-css-loaded", { + detail: { + type: "val-css-loaded", + }, + }), + ); + setCssLoaded(true); + }} + /> + +
+ +
+
+
+ + ); +} + +export default App; diff --git a/packages/ui/spa/Fonts.tsx b/packages/ui/spa/Fonts.tsx new file mode 100644 index 000000000..d976802ca --- /dev/null +++ b/packages/ui/spa/Fonts.tsx @@ -0,0 +1,16 @@ +export function Fonts() { + return ( + <> + + + + + ); +} diff --git a/packages/ui/spa/Overlay.tsx b/packages/ui/spa/Overlay.tsx new file mode 100644 index 000000000..95e11372a --- /dev/null +++ b/packages/ui/spa/Overlay.tsx @@ -0,0 +1,138 @@ +"use client"; +import { ErrorBoundary } from "react-error-boundary"; +import { + createValClient, + VAL_CONFIG_SESSION_STORAGE_KEY, + VAL_THEME_SESSION_STORAGE_KEY, +} from "@valbuild/shared/internal"; +import { ShadowRoot } from "./components/ShadowRoot"; +import { VAL_CSS_PATH } from "../src/constants"; +import { fallbackRender, FallbackComponent } from "./fallbackRender"; +import { ValOverlay } from "./components/ValOverlay"; +import { ValRouter } from "./components/ValRouter"; +import { useEffect, useState } from "react"; +import { ValProvider } from "./components/ValProvider"; +import { Themes } from "./components/ValThemeProvider"; +import { Fonts } from "./Fonts"; +import { DEFAULT_CONTENT_HOST } from "@valbuild/core"; +import { useConfig } from "./hooks/useConfig"; +import { VERSION } from "../src"; + +function Overlay() { + const config = useConfig(); + // Theme is initialized by ValNextProvider in session storage + // We just read it once on init and then rely on React state + const [theme, setTheme] = useState(() => { + try { + const stored = sessionStorage.getItem(VAL_THEME_SESSION_STORAGE_KEY); + if (stored === "light" || stored === "dark") return stored; + const configRaw = sessionStorage.getItem(VAL_CONFIG_SESSION_STORAGE_KEY); + const config = configRaw ? JSON.parse(configRaw) : null; + const local = localStorage.getItem( + "val-theme-" + (config?.project || "unknown"), + ); + if (local === "light" || local === "dark") return local; + if (config?.defaultTheme === "light" || config?.defaultTheme === "dark") { + return config.defaultTheme; + } + } catch { + // ignore storage errors + } + return "dark"; + }); + const host = "/api/val"; + const client = createValClient("/api/val", { + ...config, + contentHostUrl: DEFAULT_CONTENT_HOST, + }); + + const [draftMode, setDraftMode] = useState(false); + const [draftModeLoading, setDraftModeLoading] = useState(false); + useEffect(() => { + const listener = (event: Event) => { + if (event instanceof CustomEvent) { + if ( + event?.detail.type === "draftMode" && + typeof event?.detail.value === "boolean" + ) { + setDraftMode(event.detail.value); + } else if ( + event.detail.type === "draftModeLoading" && + typeof event.detail.value === "boolean" + ) { + setDraftModeLoading(event.detail.value); + } else { + console.error( + "Val: invalid event detail (val-overlay-spa)", + event.detail, + ); + } + } else { + console.error("Val: invalid event (val-overlay-spa)", event); + } + }; + window.addEventListener("val-overlay-spa", listener); + window.dispatchEvent( + new CustomEvent("val-overlay-provider", { + detail: { + type: "spa-ready", + }, + }), + ); + return () => { + window.removeEventListener("val-overlay-spa", listener); + }; + }, []); + return ( + <> + + + + + + + + + { + const event = new CustomEvent("val-overlay-provider", { + detail: { + type: "draftMode", + value, + }, + }); + window.dispatchEvent(event); + }} + disableOverlay={() => { + location.href = `${ + window.location.origin + }/api/val/disable?redirect_to=${encodeURIComponent( + window.location.href, + )}`; + }} + /> + + + + + + + ); +} + +export default Overlay; diff --git a/packages/ui/spa/ValSyncEngine.test.ts b/packages/ui/spa/ValSyncEngine.test.ts new file mode 100644 index 000000000..43e1814b2 --- /dev/null +++ b/packages/ui/spa/ValSyncEngine.test.ts @@ -0,0 +1,809 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Api, ClientFetchErrors } from "@valbuild/shared/internal"; +import { ValSyncEngine } from "./ValSyncEngine"; +import { + initVal, + Internal, + ModuleFilePath, + PatchId, + ReifiedRender, + SelectorSource, + SerializedSchema, + SourcePath, + ValConfig, + ValidationError, + ValModule, +} from "@valbuild/core"; +import { + applyPatch, + deepClone, + JSONOps, + JSONValue, + Patch, +} from "@valbuild/core/patch"; +import { z } from "zod"; + +describe("ValSyncEngine", () => { + test("basic init and sync", async () => { + const { s, c, config } = initVal(); + const tester = new SyncEngineTester( + "fs", + [c.define("/test.val.ts", s.string().minLength(2), "test")], + config, + ); + const updateValue = (syncEngine: ValSyncEngine, value: string) => { + return syncEngine.addPatch( + toSourcePath("/test.val.ts"), + "string", + [{ op: "replace", path: [], value }], + tester.getNextNow(), + ); + }; + + const syncEngine1 = await tester.createInitializedSyncEngine(); + + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("test"); + + expect(updateValue(syncEngine1, "")).toMatchObject({ + status: "patch-added", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual(""); + expect(updateValue(syncEngine1, "value 1 from store 1")).toMatchObject({ + status: "patch-merged", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 1 from store 1"); + tester.simulatePassingOfSeconds(5); + expect(await tester.simulateStatCallback(syncEngine1)).toMatchObject({ + status: "done", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 1 from store 1"); + }); + + test("basic reset", async () => { + const { s, c, config } = initVal(); + const tester = new SyncEngineTester( + "fs", + [c.define("/test.val.ts", s.string().minLength(2), "test")], + config, + ); + const updateValue = (syncEngine: ValSyncEngine, value: string) => { + return syncEngine.addPatch( + toSourcePath("/test.val.ts"), + "string", + [{ op: "replace", path: [], value }], + tester.getNextNow(), + ); + }; + const syncEngine1 = await tester.createInitializedSyncEngine(); + updateValue(syncEngine1, "value 0 from store 1"); + expect(await syncEngine1.sync(tester.getNextNow())).toMatchObject({ + status: "done", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 0 from store 1"); + syncEngine1.reset(); + syncEngine1.reset(); + syncEngine1.reset(); + syncEngine1.reset(); + expect(await syncEngine1.sync(tester.getNextNow())).toMatchObject({ + status: "done", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 0 from store 1"); + }); + + test("wait 1 second from last op before allowing sync", async () => { + const { s, c, config } = initVal(); + const tester = new SyncEngineTester( + "fs", + [c.define("/test.val.ts", s.string().minLength(2), "test")], + config, + ); + const updateValue = (syncEngine: ValSyncEngine, value: string) => { + return syncEngine.addPatch( + toSourcePath("/test.val.ts"), + "string", + [{ op: "replace", path: [], value }], + tester.getNextNow(), + ); + }; + const syncEngine1 = await tester.createInitializedSyncEngine(); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("test"); + expect(updateValue(syncEngine1, "value 0 from store 1")).toMatchObject({ + status: "patch-added", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 0 from store 1"); + expect(updateValue(syncEngine1, "value 1 from store 1")).toMatchObject({ + status: "patch-merged", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 1 from store 1"); + tester.simulatePassingOfSeconds(0.5); + expect(await syncEngine1.sync(tester.getNextNow())).toMatchObject({ + status: "retry", + reason: "too-fast", + }); + + expect(updateValue(syncEngine1, "value 2 from store 1")).toMatchObject({ + status: "patch-merged", + }); + tester.simulatePassingOfSeconds(1); + expect(await syncEngine1.sync(tester.getNextNow())).toMatchObject({ + status: "done", + }); + + expect(updateValue(syncEngine1, "value 3 from store 1")).toMatchObject({ + status: "patch-added", + }); + tester.simulatePassingOfSeconds(4.5); + expect(updateValue(syncEngine1, "value 4 from store 1")).toMatchObject({ + status: "patch-merged", + }); + tester.simulatePassingOfSeconds(0.5); + expect(await syncEngine1.sync(tester.getNextNow())).toMatchObject({ + status: "done", + }); + + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 4 from store 1"); + }); + + test("basic conflict", async () => { + const { s, c, config } = initVal(); + const tester = new SyncEngineTester( + "fs", + [c.define("/test.val.ts", s.string().minLength(2), "test")], + config, + ); + const updateValue = (syncEngine: ValSyncEngine, value: string) => { + return syncEngine.addPatch( + toSourcePath("/test.val.ts"), + "string", + [{ op: "replace", path: [], value }], + tester.getNextNow(), + ); + }; + + const syncEngine1 = await tester.createInitializedSyncEngine(); + + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("test"); + expect(updateValue(syncEngine1, "value 0 from store 1")).toMatchObject({ + status: "patch-added", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 0 from store 1"); + expect(updateValue(syncEngine1, "value 1 from store 1")).toMatchObject({ + status: "patch-merged", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 1 from store 1"); + // Start up sync store 2 before sync... + const syncEngine2 = await tester.createInitializedSyncEngine(); + expect(updateValue(syncEngine2, "value 2 from store 2")).toMatchObject({ + status: "patch-added", + }); + expect( + syncEngine2.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 2 from store 2"); + // ...then sync store 1 + tester.simulatePassingOfSeconds(5); + expect(await syncEngine1.sync(tester.getNextNow())).toMatchObject({ + status: "done", + }); + expect(await tester.simulateStatCallback(syncEngine1)).toMatchObject({ + status: "done", + }); + // We must get stat before we can sync again + tester.simulatePassingOfSeconds(5); + expect(await syncEngine2.sync(tester.getNextNow())).toMatchObject({ + status: "retry", + reason: "conflict", + }); + tester.simulatePassingOfSeconds(5); + expect(await tester.simulateStatCallback(syncEngine2)).toMatchObject({ + status: "done", + }); + tester.simulatePassingOfSeconds(5); + expect(await tester.simulateStatCallback(syncEngine1)).toMatchObject({ + status: "done", + }); + expect( + syncEngine1.getSourceSnapshot(toModuleFilePath("/test.val.ts")).data, + ).toStrictEqual("value 2 from store 2"); + }); + + test("setSchemas sets schemas and invalidates caches", async () => { + const { s, c, config } = initVal(); + const tester = new SyncEngineTester( + "fs", + [c.define("/test.val.ts", s.string().minLength(2), "test")], + config, + ); + const syncEngine = await tester.createInitializedSyncEngine(); + + const mockSchemas = { + [toModuleFilePath("/test.val.ts")]: Internal.getSchema( + c.define("/test.val.ts", s.string().minLength(2), "test"), + )?.["executeSerialize"](), + } as Record; + + let schemaListenerCalled = false; + const unsubscribe = syncEngine.subscribe("schema")(() => { + schemaListenerCalled = true; + }); + + syncEngine.setSchemas(mockSchemas); + + expect(schemaListenerCalled).toBe(true); + const schemaSnapshot = syncEngine.getSchemaSnapshot( + toModuleFilePath("/test.val.ts"), + ); + expect(schemaSnapshot.status).toBe("success"); + if (schemaSnapshot.status === "success") { + expect(schemaSnapshot.data).toEqual( + mockSchemas[toModuleFilePath("/test.val.ts")], + ); + } + + unsubscribe(); + }); + + test("setSources sets both serverSources and optimisticClientSources", async () => { + const { s, c, config } = initVal(); + const tester = new SyncEngineTester( + "fs", + [c.define("/test.val.ts", s.string().minLength(2), "test")], + config, + ); + const syncEngine = await tester.createInitializedSyncEngine(); + + const mockSources = { + [toModuleFilePath("/test.val.ts")]: "mock value", + } as Record; + + let sourceListenerCalled = false; + let allSourcesListenerCalled = false; + const unsubscribeSource = syncEngine.subscribe( + "source", + toModuleFilePath("/test.val.ts"), + )(() => { + sourceListenerCalled = true; + }); + const unsubscribeAllSources = syncEngine.subscribe("all-sources")(() => { + allSourcesListenerCalled = true; + }); + + syncEngine.setSources(mockSources); + + expect(sourceListenerCalled).toBe(true); + expect(allSourcesListenerCalled).toBe(true); + const sourceSnapshot = syncEngine.getSourceSnapshot( + toModuleFilePath("/test.val.ts"), + ); + expect(sourceSnapshot.data).toEqual("mock value"); + + unsubscribeSource(); + unsubscribeAllSources(); + }); + + test("setRenders sets renders and invalidates caches", async () => { + const { s, c, config } = initVal(); + const tester = new SyncEngineTester( + "fs", + [c.define("/test.val.ts", s.string().minLength(2), "test")], + config, + ); + const syncEngine = await tester.createInitializedSyncEngine(); + + const mockRenders = { + [toModuleFilePath("/test.val.ts")]: null, + } as Record; + + let renderListenerCalled = false; + const unsubscribe = syncEngine.subscribe( + "render", + toModuleFilePath("/test.val.ts"), + )(() => { + renderListenerCalled = true; + }); + + syncEngine.setRenders(mockRenders); + + expect(renderListenerCalled).toBe(true); + const renderSnapshot = syncEngine.getRenderSnapshot( + toModuleFilePath("/test.val.ts"), + ); + expect(renderSnapshot).toBe(null); + + unsubscribe(); + }); + + test("setInitializedAt sets initializedAt and invalidates cache", async () => { + const { s, c, config } = initVal(); + const tester = new SyncEngineTester( + "fs", + [c.define("/test.val.ts", s.string().minLength(2), "test")], + config, + ); + const syncEngine = await tester.createInitializedSyncEngine(); + + const mockTimestamp = 1234567890; + + let initializedAtListenerCalled = false; + const unsubscribe = syncEngine.subscribe("initialized-at")(() => { + initializedAtListenerCalled = true; + }); + + syncEngine.setInitializedAt(mockTimestamp); + + expect(initializedAtListenerCalled).toBe(true); + const initializedAtSnapshot = syncEngine.getInitializedAtSnapshot(); + expect(initializedAtSnapshot.data).toBe(mockTimestamp); + + unsubscribe(); + }); +}); + +function toModuleFilePath(moduleFilePath: `/${string}.val.ts`): ModuleFilePath { + return moduleFilePath as ModuleFilePath; +} + +function toSourcePath( + moduleFilePath: `/${string}.val.ts${`` | `?p=${string}`}`, +): SourcePath { + return moduleFilePath as SourcePath; +} + +type InferReq> = { + [K in keyof T]: T[K] extends z.ZodTypeAny + ? z.infer + : T[K] extends Record + ? InferReq + : never; +}; + +type FakeApi = { + "/sources/~": { + PUT: z.infer | ClientFetchErrors | null; + }; + "/patches": { + GET: z.infer | ClientFetchErrors | null; + PUT: z.infer | ClientFetchErrors | null; + DELETE: + | z.infer + | ClientFetchErrors + | null; + }; + "/schema": { + GET: z.infer | ClientFetchErrors | null; + }; +}; + +class SyncEngineTester { + fakeModules: any[]; + ops: JSONOps; + fakePatches: { + path: ModuleFilePath; + patchId: PatchId; + patch?: Patch; + createdAt: string; + authorId: null; + appliedAt: null; + }[]; + fakeSchemas: Record; + fakeSources: Record; + now: number; + fakeResponses: Partial; + + constructor( + private mode: "fs" | "http", + public valModules: ValModule[], + public config: ValConfig, + ) { + this.fakeModules = valModules; + this.ops = new JSONOps(); + this.fakePatches = []; + this.fakeSchemas = Object.fromEntries( + this.fakeModules.map((m) => { + const path = Internal.getValPath(m)!; + return [path, Internal.getSchema(m)!] as const; + }), + ); + this.fakeSources = Object.fromEntries( + this.fakeModules.map((m) => { + const path = Internal.getValPath(m)!; + return [path, Internal.getSource(m)] as const; + }), + ); + this.now = 0; + this.fakeResponses = {}; + } + + getMode() { + return this.mode; + } + + getSchemasSha() { + // We could have used the way we do this in ValOps which is better (more stable), but this is simple and should work for the tests + const textEncoder = new TextEncoder(); + return Internal.getSHA256Hash( + textEncoder.encode(JSON.stringify(this.fakeSchemas)), + ); + } + + getSourcesSha() { + // We could have used the way we do this in ValOps which is better (more stable), but this is simple and should work for the tests + const textEncoder = new TextEncoder(); + return Internal.getSHA256Hash( + textEncoder.encode(JSON.stringify(this.fakeSources)), + ); + } + + getBaseSha() { + // We could have used the way we do this in ValOps which is better (more stable), but this is simple and should work for the tests + const textEncoder = new TextEncoder(); + return Internal.getSHA256Hash( + textEncoder.encode( + JSON.stringify(this.fakeSchemas) + + JSON.stringify(this.fakeSources) + + JSON.stringify(this.config), + ), + ); + } + + getAuthorId() { + return "6e4d2995-ac82-4e29-8c23-25b859371a9a"; + } + + getCommitSha() { + return "e83c5163316f89bfbde7d9ab23ca2e25604af290"; + } + + getSchema( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _req: InferReq, + ): z.infer { + const serializedSchemas = Object.fromEntries( + Object.entries(this.fakeSchemas).map(([path, schema]) => { + return [path, schema?.["executeSerialize"]()] as const; + }), + ); + return { + status: 200, + json: { + schemas: serializedSchemas, + schemaSha: this.getSchemasSha(), + }, + }; + } + + putPatches( + req: InferReq, + ): z.infer { + const { patches, parentRef } = req.body; + const isParentRefFirstHead = + this.fakePatches.length === 0 && + parentRef.type === "head" && + parentRef.headBaseSha === this.getBaseSha(); + const isParentPatchHead = + this.fakePatches.length > 0 && + parentRef.type === "patch" && + this.fakePatches[this.fakePatches.length - 1].patchId === + parentRef.patchId; + const isConflict = !(isParentRefFirstHead || isParentPatchHead); + if (isConflict) { + return { + status: 409, + json: { + type: "patch-head-conflict", + message: "Conflict", + }, + }; + } + const newPatchIds: PatchId[] = []; + for (const patchData of patches) { + newPatchIds.push(patchData.patchId); + this.fakePatches.push({ + ...patchData, + createdAt: new Date().toISOString(), + authorId: null, + appliedAt: null, + }); + } + return { + status: 200, + json: { + newPatchIds, + parentRef: { + type: "patch", + patchId: this.fakePatches[this.fakePatches.length - 1].patchId, + }, + }, + }; + } + + deletePatches( + req: InferReq, + ): z.infer { + const patch_ids = req.query.id; + const deletedPatchIds: PatchId[] = []; + for (const patchId of patch_ids) { + const index = this.fakePatches.findIndex((p) => p.patchId === patchId); + if (index !== -1) { + deletedPatchIds.push(patchId); + this.fakePatches.splice(index, 1); + } + } + return { + status: 200, + json: deletedPatchIds, + }; + } + + getPatches( + req: InferReq, + ): z.infer { + const patches: { + path: ModuleFilePath; + patchId: PatchId; + createdAt: string; + authorId: string | null; + appliedAt: { + commitSha: string; + } | null; + patch?: Patch | undefined; + }[] = []; + const allPatchIds = new Set(this.fakePatches.map((p) => p.patchId)); + for (const patchData of this.fakePatches) { + allPatchIds.add(patchData.patchId); + if ( + req.query.patch_id === undefined || + (req.query.patch_id !== undefined && + req.query.patch_id.includes(patchData.patchId)) + ) { + patches.push(patchData); + } + } + let error: { message: string } | undefined = undefined; + let errors: Record | undefined = undefined; + for (const requestedPatchId of req.query.patch_id || []) { + if (!allPatchIds.has(requestedPatchId)) { + if (!errors) { + errors = {}; + } + errors[requestedPatchId] = { + message: `Patch ${requestedPatchId} not found.`, + }; + } + } + if (errors && Object.keys(errors).length > 0) { + error = { + message: "Some patches were not found.", + }; + } + return { + status: 200, + json: { + error, + errors, + patches, + baseSha: this.getBaseSha(), + }, + }; + } + + putSources( + req: InferReq, + ): z.infer { + const modules: Record< + ModuleFilePath, + { + patches?: + | { + applied: PatchId[]; + errors?: + | Record< + PatchId, + { + message: string; + } + > + | undefined; + skipped?: PatchId[] | undefined; + } + | undefined; + source?: any; + render?: any; + validationErrors?: Record | undefined; + } + > = {}; + for (const patchData of this.fakePatches) { + const { patch, patchId, path: moduleFilePath } = patchData; + if (!patch) { + continue; + } + const patchRes = applyPatch( + deepClone(this.fakeSources[moduleFilePath]), + this.ops, + patch, + ); + if (!modules[moduleFilePath]) { + modules[moduleFilePath] = {}; + } + if (!modules[moduleFilePath].patches) { + modules[moduleFilePath].patches = { + applied: [], + }; + } + if (patchRes.kind === "ok") { + modules[moduleFilePath].source = patchRes.value; + modules[moduleFilePath].patches?.applied.push(patchId); + } else { + if ( + modules[moduleFilePath].patches !== undefined && + !modules[moduleFilePath].patches?.skipped + ) { + modules[moduleFilePath].patches!.skipped = []; + } + modules[moduleFilePath].patches?.skipped?.push(patchId); + if (!modules[moduleFilePath].patches!.errors) { + modules[moduleFilePath].patches!.errors = {}; + } + if (!modules[moduleFilePath].patches?.errors?.[patchId]) { + modules[moduleFilePath].patches!.errors![patchId] = { + message: patchRes.error.message, + }; + } + } + } + for (const moduleFilePathS of Object.keys(this.fakeSources)) { + const moduleFilePath = moduleFilePathS as ModuleFilePath; + const source = + modules?.[moduleFilePath]?.source ?? this.fakeSources[moduleFilePath]; + const schema = this.fakeSchemas[moduleFilePath]; + const validationErrors = + req.query.validate_sources && + schema["executeValidate"]( + moduleFilePath as unknown as SourcePath, + source, + ); + modules[moduleFilePath] = { + source: deepClone(source), + }; + if (validationErrors) { + modules[moduleFilePath].validationErrors = validationErrors; + } + } + return { + status: 200, + json: { + modules, + sourcesSha: this.getSourcesSha(), + schemaSha: this.getSchemasSha(), + }, + }; + } + + removeFakeResponse( + route: R, + method: M, + ): this { + const maybeAnyRoute = this.fakeResponses[ + route as keyof typeof this.fakeResponses + ] as any; + delete maybeAnyRoute[method]; + return this; + } + + setFakeResponse< + R extends keyof FakeApi, + M extends keyof FakeApi[R], + ResType = FakeApi[R][M], + >(route: R, method: M, response: ResType | ClientFetchErrors): this { + if (!this.fakeResponses[route as keyof typeof this.fakeResponses]) { + this.fakeResponses[route as keyof typeof this.fakeResponses] = {} as any; + } + + (this.fakeResponses[route as keyof typeof this.fakeResponses] as any)[ + method + ] = response; + return this; + } + + createMockClient(): any { + return (route: string, method: string, req: any) => { + const anyFakeResponses = this.fakeResponses as any; + if ( + anyFakeResponses && + route in anyFakeResponses && + method in anyFakeResponses[route] && + anyFakeResponses[route][method] + ) { + return anyFakeResponses[route][method]; + } + if (route === "/sources/~" && method === "PUT") { + return this.putSources(req); + } + if (route === "/patches" && method === "GET") { + return this.getPatches(req); + } + if (route === "/patches" && method === "PUT") { + return this.putPatches(req); + } + if (route === "/patches" && method === "DELETE") { + return this.deletePatches(req); + } + if (route === "/schema" && method === "GET") { + return this.getSchema(req); + } + return { + status: 404, + json: { + message: `Invalid route ${route} with method ${ + method as string + }. This is most likely a Val bug.`, + path: route, + method: method as string, + }, + } satisfies ClientFetchErrors; + }; + } + + async simulateStatCallback(valStore: ValSyncEngine) { + const authorId = null; + return await valStore.syncWithUpdatedStat( + this.getMode(), + this.getBaseSha(), + this.getSchemasSha(), + this.getSourcesSha(), + this.fakePatches.map((p) => p.patchId), + authorId, + this.getCommitSha(), + this.now++, + ); + } + + async createInitializedSyncEngine() { + const syncEngine = new ValSyncEngine(this.createMockClient(), undefined); + const authorId = null; + await syncEngine.init( + this.getMode(), + this.getBaseSha(), + this.getSchemasSha(), + this.getSourcesSha(), + this.fakePatches.map((p) => p.patchId), + authorId, + this.getCommitSha(), + this.now++, + ); + return syncEngine; + } + + simulatePassingOfSeconds(seconds: number) { + this.now += seconds * 1000; + } + + getNextNow() { + return this.now++; + } +} diff --git a/packages/ui/spa/ValSyncEngine.ts b/packages/ui/spa/ValSyncEngine.ts new file mode 100644 index 000000000..d3efc1304 --- /dev/null +++ b/packages/ui/spa/ValSyncEngine.ts @@ -0,0 +1,2570 @@ +import { + deserializeSchema, + Internal, + Json, + ModuleFilePath, + PatchId, + Schema, + SelectorSource, + SerializedSchema, + SourcePath, + ValidationError, + ValidationErrors, +} from "@valbuild/core"; +import { result } from "@valbuild/core/fp"; +import { + applyPatch, + deepClone, + deepEqual, + JSONOps, + JSONValue, + ReadonlyJSONValue, +} from "@valbuild/core/patch"; +import { ParentRef, ValClient, Patch } from "@valbuild/shared/internal"; +import { canMerge } from "./utils/mergePatches"; +import { PatchSets, SerializedPatchSet } from "./utils/PatchSets"; +import { ReifiedRender } from "@valbuild/core"; + +/** + * ValSyncEngine is the engine that keeps track of the state of the Val client. + * It is intended to be used with useSyncExternalStore. + * + * It is a MASSIVE class that handles all the syncing, patching, and state management for the Val client. + * Lack of time / the complexity of the domain together with ambitious performance goals (and maybe incompetency?), + * but not lack of caring (!), is the reason it is so big. + * + * NOTE: simply splitting it in smaller modules was considered to be a bad fix to this problem since, + * although this file would be smaller, the actual complexity would not be reduced and it would + * most likely (at least we believe so) make it even harder to work with / reason about. + * + * We believe that we either must: + * 1) accept that this is complex and make sure it is well tested (this is where we try to go now) + * 2) find a better model of the problem / cut down on the performance ambitions + * + * NOTE: we haven't actually measured the performance well, so one might argue that until we do that + * we have no business in optimizing for performance. However, wrt performance the stance now is to, + * for obviously common operations (writing text a string / richtext field), + * we should to think a bit about what the minimum amount of work is required to safely get the job done (duh). + * What we're trying to say is... ...That although optimizing performance is stupid without measuring, + * it is even stupider to do lots of work that we simply know is unnecessary. + */ +export class ValSyncEngine { + private initializedAt: number | null; + private autoPublish: boolean = false; + /** + * Patch Ids reported by the /stat endpoint or webhook + * + * These are all the patch ids that are currently in the server; from this client AND FROM OTHER CLIENTS. + **/ + private globalServerSidePatchIds: PatchId[] | null; + /** + * Patch Ids created by this client, that are not yet stored + */ + private pendingClientPatchIds: PatchId[]; + /** + * Patch Ids that have been successfully been applied (or skipped) server side + */ + private syncedServerSidePatchIds: PatchId[]; + /** + * Patch Ids that have been saved server side, but that not part of the global server state + * i.e. they are currently only known by this client + */ + private savedButNotYetGlobalServerSidePatchIds: PatchId[]; + private publishDisabled: boolean; + private isPublishing: boolean; + private patchDataByPatchId: Record< + PatchId, + | { + moduleFilePath: ModuleFilePath; + patch: Patch; + isPending: boolean; + createdAt: string; + authorId: string | null; + isCommitted?: { + commitSha: string; + }; + } + | undefined + >; + private authorId: string | null; + private patchSets: PatchSets; + /** serverSources is the state on the server, it is the actual state */ + private serverSources: Record | null; + /** optimisticClientSources is the state of the client, optimistic means that patches have been applied in client-only */ + private optimisticClientSources: Record< + ModuleFilePath, + JSONValue | undefined + >; + private renders: Record | null; + private schemas: Record | null; + private serverSideSchemaSha: string | null; + private clientSideSchemaSha: string | null; + private mode: "fs" | "http" | null; + + private commitSha: string | null; + private baseSha: string | null; // TODO: Currently only used for headBaseSha in head patches - we think we should replace headBaseSha with headSourcesSha + private sourcesSha: string | null; + private syncStatus: Record; + private pendingOps: PendingOp[]; + private errors: Partial<{ + /** + * Transient global errors are errors that are + * 1) transient (reloading might fix) + * 2) affects entire Val Studio app: not just a patch or a module + * + * They will be showed in a toast notification. (should we rename to toastQueue?) + * Examples: network errors, transient sync errors, ... + */ + globalTransientErrorQueue: { + message: string; + timestamp: number; + details?: string; + id: string; + }[]; + // TODO: unused for now, so remove: + // /** + // * Persistent global errors are errors that are + // * 1) persistent / permanent (reloading won't fix) + // * 2) requires a developer to fix + // * 3) affects entire Val Studio app: not just a patch or a module + // * + // * These errors will be showed prominently in the UI and cannot be dismissed. + // * NOTE: Persistent errors also prohibits publishing. + // * Examples: invalid config, invalid schema, ... + // */ + // persistentGlobalError: string | null; + // /** Errors that prohibits publishing */ + // publishError: string | null; + // patchErrors: Record; + /** + * If hasNetworkErrorTimestamp is not null, we show a network error + */ + hasNetworkErrorTimestamp: number | null; + /** + * If hasSchemaErrorTimestamp is not null, we show a schema error + */ + hasSchemaErrorTimestamp: number | null; + validationErrors: Record; + patchErrors: Record< + ModuleFilePath, + Record | null + >; + }>; + /** + * If this is true, the next sync (and only the next) will sync all modules + * + * We use this if there's unknown patch ids or to initialize + */ + private forceSyncAllModules: boolean; + + constructor( + private readonly client: ValClient, + private readonly overlayEmitter: + | typeof defaultOverlayEmitter + | undefined = undefined, + ) { + this.initializedAt = null; + this.forceSyncAllModules = true; + this.errors = {}; + this.listeners = {}; + this.syncStatus = {}; + this.schemas = null; + this.serverSideSchemaSha = null; + this.clientSideSchemaSha = null; + this.baseSha = null; + this.sourcesSha = null; + this.mode = null; + this.optimisticClientSources = {}; + this.serverSources = null; + this.renders = null; + this.globalServerSidePatchIds = []; + this.syncedServerSidePatchIds = []; + this.savedButNotYetGlobalServerSidePatchIds = []; + this.pendingOps = []; + this.pendingClientPatchIds = []; + this.patchDataByPatchId = {}; + this.isSyncing = false; + this.patchSets = new PatchSets(); + this.authorId = null; + this.publishDisabled = true; + this.isPublishing = false; + this.commitSha = null; + // + this.cachedSourceSnapshots = null; + this.cachedSchemaSnapshots = null; + this.cachedRenderSnapshots = null; + this.cachedPatchData = null; + this.cachedSerializedPatchSetsSnapshot = null; + this.cachedValidationErrors = null; + this.cachedAllSchemasSnapshot = null; + this.cachedDeserializedSchemas = null; + this.cachedGlobalServerSidePatchIdsSnapshot = null; + this.cachedPendingClientSidePatchIdsSnapshot = null; + this.cachedSyncedServerSidePatchIdsSnapshot = null; + this.cachedSavedServerSidePatchIdsSnapshot = null; + this.cachedAllSourcesSnapshot = null; + this.cachedSourcesSnapshot = null; + this.cachedSyncStatus = null; + this.cachedPendingOpsCountSnapshot = null; + this.cachedInitializedAtSnapshot = null; + this.cachedAutoPublishSnapshot = null; + this.cachedPublishDisabledSnapshot = null; + this.cachedGlobalTransientErrorSnapshot = null; + this.cachedParentRef = undefined; + this.cachedPatchErrorsSnapshot = null; + } + + setAutoPublish(now: number, autoPublish: boolean) { + this.autoPublish = autoPublish; + try { + localStorage.setItem("val-auto-publish", autoPublish.toString()); + } catch { + // ignore + } + this.invalidateAutoPublish(); + return this.sync(now); + } + + private loadAutoPublish() { + try { + this.autoPublish = localStorage.getItem("val-auto-publish") === "true"; + this.invalidateAutoPublish(); + } catch { + // ignore + } + } + + async init( + mode: "fs" | "http", + baseSha: string, + schemaSha: string, + sourcesSha: string, + patchIds: PatchId[], + authorId: string | null, + commitSha: string | null, + now: number, + ) { + this.mode = mode; + this.baseSha = baseSha; + this.commitSha = commitSha; + this.sourcesSha = sourcesSha; + this.authorId = authorId; + const start = Date.now(); + if (mode === "fs") { + this.loadAutoPublish(); + } else { + this.autoPublish = false; + } + const res = await this.syncWithUpdatedStat( + mode, + baseSha, + schemaSha, + sourcesSha, + patchIds, + authorId, + commitSha, + now, + ); + if (res.status === "done") { + await this.syncPatches(true, now); + this.publishDisabled = false; + this.invalidatePublishDisabled(); + this.initializedAt = now + (Date.now() - start); + this.invalidateInitializedAt(); + } + return res; + } + + reset() { + console.debug("Resetting ValSyncEngine"); + this.initializedAt = null; + this.forceSyncAllModules = true; + this.errors = {}; + this.listeners = {}; + this.syncStatus = {}; + this.schemas = null; + this.serverSideSchemaSha = null; + this.clientSideSchemaSha = null; + this.sourcesSha = null; + this.optimisticClientSources = {}; + this.serverSources = null; + this.renders = null; + this.globalServerSidePatchIds = []; + this.syncedServerSidePatchIds = []; + this.savedButNotYetGlobalServerSidePatchIds = []; + this.pendingOps = []; + this.pendingClientPatchIds = []; + this.patchDataByPatchId = {}; + this.isSyncing = false; + this.patchSets = new PatchSets(); + this.authorId = null; + this.publishDisabled = true; + this.isPublishing = false; + this.commitSha = null; + // + this.cachedSourceSnapshots = null; + this.cachedSchemaSnapshots = null; + this.cachedRenderSnapshots = null; + this.cachedPatchData = null; + this.cachedSerializedPatchSetsSnapshot = null; + this.cachedValidationErrors = null; + this.cachedAllSchemasSnapshot = null; + this.cachedDeserializedSchemas = null; + this.cachedGlobalServerSidePatchIdsSnapshot = null; + this.cachedPendingClientSidePatchIdsSnapshot = null; + this.cachedSyncedServerSidePatchIdsSnapshot = null; + this.cachedSavedServerSidePatchIdsSnapshot = null; + this.cachedAllSourcesSnapshot = null; + this.cachedSyncStatus = null; + this.cachedPendingOpsCountSnapshot = null; + this.cachedInitializedAtSnapshot = null; + this.cachedAutoPublishSnapshot = null; + this.cachedPublishDisabledSnapshot = null; + this.cachedGlobalTransientErrorSnapshot = null; + this.cachedParentRef = undefined; + this.cachedPatchErrorsSnapshot = null; + + this.invalidateInitializedAt(); + } + + // #region Subscribe + private listeners: Partial< + Record void)[]>> + >; + subscribe( + type: "source", + path: ModuleFilePath, + ): (listener: () => void) => () => void; + subscribe( + type: "sources", + paths: ModuleFilePath[], + ): (listener: () => void) => () => void; + subscribe( + type: "render", + path: ModuleFilePath, + ): (listener: () => void) => () => void; + subscribe(type: "all-sources"): (listener: () => void) => () => void; + subscribe(type: "auto-publish"): (listener: () => void) => () => void; + subscribe(type: "parent-ref"): (listener: () => void) => () => void; + subscribe(type: "pending-ops-count"): (listener: () => void) => () => void; + subscribe( + type: "validation-error", + path: SourcePath, + ): (listener: () => void) => () => void; + subscribe( + type: "all-validation-errors", + ): (listener: () => void) => () => void; + subscribe(type: "initialized-at"): (listener: () => void) => () => void; + subscribe( + type: "sync-status", + path: SourcePath, + ): (listener: () => void) => () => void; + subscribe( + type: "global-transient-errors", + ): (listener: () => void) => () => void; + subscribe(type: "network-error"): (listener: () => void) => () => void; + subscribe(type: "schema-error"): (listener: () => void) => () => void; + subscribe( + type: "global-server-side-patch-ids", + ): (listener: () => void) => () => void; + subscribe( + type: "pending-client-side-patch-ids", + ): (listener: () => void) => () => void; + subscribe( + type: "synced-server-side-patch-ids", + ): (listener: () => void) => () => void; + subscribe( + type: "saved-server-side-patch-ids", + ): (listener: () => void) => () => void; + subscribe(type: "publish-disabled"): (listener: () => void) => () => void; + subscribe(type: "schema"): (listener: () => void) => () => void; + subscribe(type: "patch-sets"): (listener: () => void) => () => void; + subscribe(type: "all-patches"): (listener: () => void) => () => void; + subscribe( + type: "patch-errors", + path: ModuleFilePath[], + ): (listener: () => void) => () => void; + subscribe( + type: SyncEngineListenerType, + path?: string | string[], + ): (listener: () => void) => () => void { + const p = path || globalNamespace; + return (listener: () => void) => { + // Our TS version is too low to figure out what is possible undefined here, so we do any's... + // On TS 5.8+ we should be able to remove const listeners and replace listeners with this.listeners + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const listeners = this.listeners as any; + if (!listeners[type]) { + listeners[type] = {}; + } + if (Array.isArray(p)) { + const indices: number[] = []; + for (const path of p) { + if (!listeners[type][path]) { + listeners[type][path] = []; + } + const idx = listeners[type][path].push(listener) - 1; + indices.push(idx); + } + return () => { + for (const idx of indices) { + listeners[type]?.[p[idx]]?.splice(idx, 1); + } + }; + } else { + if (!listeners[type][p]) { + listeners[type][p] = []; + } + const idx = listeners[type][p].push(listener) - 1; + return () => { + listeners[type]?.[p].splice(idx, 1); + }; + } + }; + } + private emit(listeners?: (() => void)[]) { + if (listeners) { + for (const listener of listeners) { + listener(); + } + } + } + + // TODO: remove this (used for manual testing) + public setCommitSha(sha: string | null) { + this.commitSha = sha; + } + + // #region Invalidate + private invalidateInitializedAt() { + this.cachedInitializedAtSnapshot = null; + this.emit(this.listeners["initialized-at"]?.[globalNamespace]); + } + + private invalidateSource(moduleFilePath: ModuleFilePath) { + if (this.cachedSourceSnapshots !== null) { + this.cachedSourceSnapshots = { + ...this.cachedSourceSnapshots, + [moduleFilePath]: undefined, + }; + } + this.cachedAllSourcesSnapshot = null; + this.cachedSourcesSnapshot = null; + this.emit(this.listeners["sources"]?.[moduleFilePath]); + this.emit(this.listeners["source"]?.[moduleFilePath]); + this.emit(this.listeners["all-sources"]?.[globalNamespace]); + } + + private invalidatePatchErrors(moduleFilePath: ModuleFilePath) { + this.cachedPatchErrorsSnapshot = null; + this.emit(this.listeners["patch-errors"]?.[moduleFilePath]); + } + + private invalidateRenders(moduleFilePath: ModuleFilePath) { + if (this.cachedSourceSnapshots === null) { + this.cachedSourceSnapshots = {}; + } + this.cachedRenderSnapshots = { + ...this.cachedRenderSnapshots, + [moduleFilePath]: null, + }; + this.emit(this.listeners["render"]?.[moduleFilePath]); + } + + private invalidateSyncStatus(sourcePath: SourcePath | ModuleFilePath) { + this.cachedSyncStatus = { + ...this.cachedSyncStatus, + [sourcePath]: undefined, + }; + this.emit(this.listeners["sync-status"]?.[sourcePath]); + } + private invalidateValidationError(sourcePath: SourcePath) { + this.emit(this.listeners["validation-error"]?.[sourcePath]); + } + private invalidateAllValidationErrors() { + // TODO: ugly - we need to do this to make sure we get new references across the board + this.cachedValidationErrors = null; + this.emit(this.listeners["all-validation-errors"]?.[globalNamespace]); + } + private invalidateGlobalTransientErrors() { + this.cachedGlobalTransientErrorSnapshot = null; + this.emit(this.listeners["global-transient-errors"]?.[globalNamespace]); + } + private invalidateNetworkError() { + // NOTE: normally we invalidate by setting to null, but network error can be null as well + this.cachedNetworkErrorSnapshot = undefined; + this.emit(this.listeners["network-error"]?.[globalNamespace]); + } + private invalidateSchemaError() { + // NOTE: normally we invalidate by setting to null, but schema error can be null as well + this.cachedSchemaErrorSnapshot = undefined; + this.emit(this.listeners["schema-error"]?.[globalNamespace]); + } + private invalidatePatchSets() { + this.cachedSerializedPatchSetsSnapshot = null; + this.emit(this.listeners["patch-sets"]?.[globalNamespace]); + } + private invalidatePendingOps() { + this.cachedPendingOpsCountSnapshot = null; + this.emit(this.listeners["pending-ops-count"]?.[globalNamespace]); + } + + private invalidateAllPatches() { + this.cachedPatchData = null; + this.emit(this.listeners["all-patches"]?.[globalNamespace]); + } + + private invalidateSchema() { + this.cachedAllSchemasSnapshot = null; + this.cachedDeserializedSchemas = null; + this.cachedSchemaSnapshots = null; + this.cachedAllSourcesSnapshot = null; + this.emit(this.listeners["schema"]?.[globalNamespace]); + this.invalidateAllValidationErrors(); + for (const sourcePathS in this.listeners?.["validation-error"] || {}) { + const sourcePath = sourcePathS as SourcePath; + this.invalidateValidationError(sourcePath); + } + } + + private invalidateParentRef() { + this.cachedParentRef = undefined; + this.emit(this.listeners["parent-ref"]?.[globalNamespace]); + } + + private invalidateGlobalServerSidePatchIds() { + this.cachedGlobalServerSidePatchIdsSnapshot = null; + this.invalidateParentRef(); + this.emit( + this.listeners["global-server-side-patch-ids"]?.[globalNamespace], + ); + } + + private invalidatePendingClientSidePatchIds() { + this.cachedPendingClientSidePatchIdsSnapshot = null; + this.emit( + this.listeners["pending-client-side-patch-ids"]?.[globalNamespace], + ); + } + + private invalidateSyncedServerSidePatchIds() { + this.cachedSyncedServerSidePatchIdsSnapshot = null; + this.emit( + this.listeners["synced-server-side-patch-ids"]?.[globalNamespace], + ); + } + + private invalidateSavedServerSidePatchIds() { + this.cachedSavedServerSidePatchIdsSnapshot = null; + this.invalidateParentRef(); + this.emit(this.listeners["saved-server-side-patch-ids"]?.[globalNamespace]); + } + + private invalidatePublishDisabled() { + this.cachedPublishDisabledSnapshot = null; + this.emit(this.listeners["publish-disabled"]?.[globalNamespace]); + } + + private invalidateAutoPublish() { + this.cachedAutoPublishSnapshot = null; + this.emit(this.listeners["auto-publish"]?.[globalNamespace]); + } + + // #region Snapshot + + private cachedSchemaSnapshots: Record< + SourcePath | ModuleFilePath, + | { + status: "success"; + data: SerializedSchema; + } + | { + status: "no-schemas"; + message?: string; + } + | { + status: "module-schema-not-found"; + message?: string; + } + > | null; + getSchemaSnapshot(sourcePath: ModuleFilePath) { + if (this.cachedSchemaSnapshots === null) { + this.cachedSchemaSnapshots = {}; + } + if (this.cachedSchemaSnapshots[sourcePath] === undefined) { + if (!this.schemas) { + this.cachedSchemaSnapshots[sourcePath] = { + status: "no-schemas", + }; + } else { + const schemaAtPath = this.schemas[sourcePath]; + if (!schemaAtPath) { + this.cachedSchemaSnapshots[sourcePath] = { + status: "module-schema-not-found", + }; + } else { + this.cachedSchemaSnapshots[sourcePath] = { + status: "success", + data: deepClone(schemaAtPath), + }; + } + } + } + return this.cachedSchemaSnapshots[sourcePath]; + } + + private cachedRenderSnapshots: Record< + ModuleFilePath, + ReifiedRender | null + > | null; + getRenderSnapshot(moduleFilePath: ModuleFilePath) { + if (this.cachedRenderSnapshots === null) { + this.cachedRenderSnapshots = {}; + } + if (this.cachedRenderSnapshots[moduleFilePath] === null) { + this.cachedRenderSnapshots[moduleFilePath] = + this.renders?.[moduleFilePath] || null; + } + return this.cachedRenderSnapshots[moduleFilePath]; + } + + private cachedSourceSnapshots: Record< + ModuleFilePath, + | { + status: "success"; + data: Json; + optimistic: boolean; + } + | { + data?: undefined; + status: "no-schemas" | "source-not-found" | "schema-not-found"; + message?: string; + } + > | null; + getSourceSnapshot(sourcePath: ModuleFilePath) { + if (this.cachedSourceSnapshots === null) { + this.cachedSourceSnapshots = {}; + } + if (this.cachedSourceSnapshots[sourcePath] === undefined) { + const moduleData = + this.optimisticClientSources[sourcePath] !== undefined + ? this.optimisticClientSources[sourcePath] + : this.serverSources?.[sourcePath]; + + if (this.schemas === null) { + this.cachedSourceSnapshots[sourcePath] = { + status: "no-schemas", + }; + } else if (!this.schemas[sourcePath]) { + this.cachedSourceSnapshots[sourcePath] = { + status: "schema-not-found", + }; + } else if (moduleData === undefined) { + this.cachedSourceSnapshots[sourcePath] = { + status: "source-not-found", + }; + } else { + this.cachedSourceSnapshots[sourcePath] = { + status: "success", + data: deepClone(moduleData), + optimistic: this.optimisticClientSources[sourcePath] !== undefined, + }; + } + } + return this.cachedSourceSnapshots[sourcePath]; + } + + private cachedAllSourcesSnapshot: Record | null; + getAllSourcesSnapshot() { + if (this.cachedAllSourcesSnapshot === null) { + this.cachedAllSourcesSnapshot = {}; + for (const moduleFilePathS in this.schemas || {}) { + const moduleFilePath = moduleFilePathS as ModuleFilePath; + const data = + this.optimisticClientSources[moduleFilePath] || + this.serverSources?.[moduleFilePath]; + if (data !== undefined) { + this.cachedAllSourcesSnapshot[moduleFilePath] = deepClone(data); + } + } + } + return this.cachedAllSourcesSnapshot; + } + + private multipleSourcesSep = "|"; + private cachedSourcesSnapshot: Record | null; + getSourcesSnapshot(paths: ModuleFilePath[]) { + const pathsKey = paths + .sort() + .map((path) => path + this.multipleSourcesSep) + .join(this.multipleSourcesSep); + if (this.cachedSourcesSnapshot === null) { + this.cachedSourcesSnapshot = {}; + } + if (this.cachedSourcesSnapshot[pathsKey] === undefined) { + for (const moduleFilePath of paths) { + const data = + this.optimisticClientSources[moduleFilePath] || + this.serverSources?.[moduleFilePath]; + if (data !== undefined) { + this.cachedSourcesSnapshot[pathsKey] = [ + ...(this.cachedSourcesSnapshot[pathsKey] || []), + deepClone(data), + ]; + } + } + } + return this.cachedSourcesSnapshot[pathsKey]; + } + + private cachedAllSchemasSnapshot: Record< + ModuleFilePath, + SerializedSchema + > | null; + private cachedDeserializedSchemas: Record< + ModuleFilePath, + Schema + > | null; + getAllSchemasSnapshot() { + if (this.cachedAllSchemasSnapshot === null) { + this.cachedAllSchemasSnapshot = {}; + } + for (const moduleFilePathS in this.schemas || {}) { + const moduleFilePath = moduleFilePathS as ModuleFilePath; + const schema = this.schemas?.[moduleFilePath]; + if (schema) { + this.cachedAllSchemasSnapshot[moduleFilePath] = deepClone(schema); + } + } + return this.cachedAllSchemasSnapshot; + } + + private cachedValidationErrors: Record | null; + getValidationErrorSnapshot(sourcePath: SourcePath) { + const allValidationErrorsSnapshot = this.getAllValidationErrorsSnapshot(); + return allValidationErrorsSnapshot?.[sourcePath]; + } + + getAllValidationErrorsSnapshot() { + if (!this.cachedValidationErrors) { + this.cachedValidationErrors = {}; + for (const sourcePathS in this.errors.validationErrors) { + const sourcePath = sourcePathS as SourcePath; + const newErrors = []; + for (const error of this.errors.validationErrors[sourcePath] || []) { + if (error) { + newErrors.push(error); + } + } + if (newErrors.length > 0) { + this.cachedValidationErrors[sourcePath] = newErrors; + } + } + } + return this.cachedValidationErrors; + } + + private cachedSyncStatus: Record | null; + getSyncStatusSnapshot(sourcePath: SourcePath) { + if (this.cachedSyncStatus === null) { + this.cachedSyncStatus = {}; + } + if (this.cachedSyncStatus[sourcePath] === undefined) { + this.cachedSyncStatus[sourcePath] = this.syncStatus[sourcePath] || null; + } + return this.cachedSyncStatus[sourcePath]; + } + + private cachedPendingOpsCountSnapshot: number | null; + getPendingOpsSnapshot() { + if (this.cachedPendingOpsCountSnapshot === null) { + this.cachedPendingOpsCountSnapshot = this.pendingOps.length; + } + return this.cachedPendingOpsCountSnapshot; + } + + private cachedSerializedPatchSetsSnapshot: SerializedPatchSet | null; + getSerializedPatchSetsSnapshot() { + if (!this.cachedSerializedPatchSetsSnapshot) { + this.cachedSerializedPatchSetsSnapshot = this.patchSets.serialize(); + } + return this.cachedSerializedPatchSetsSnapshot; + } + + private cachedInitializedAtSnapshot: { data: number | null } | null; + getInitializedAtSnapshot() { + if (this.cachedInitializedAtSnapshot === null) { + this.cachedInitializedAtSnapshot = { + data: this.initializedAt, + }; + } + return this.cachedInitializedAtSnapshot; + } + + private cachedPatchErrorsSnapshot: Record< + string, + Record | null> + > | null; + getPatchErrorsSnapshot( + moduleFilePaths: ModuleFilePath[], + ): + | Record | null> + | undefined { + const pathsKey = moduleFilePaths.sort().join("|"); + // TODO: not quite sure this works well, however it is only used in one place and seems to work there - something to revise! + if (this.cachedPatchErrorsSnapshot === null) { + this.cachedPatchErrorsSnapshot = {}; + const result: Record< + ModuleFilePath, + Record | null + > = {}; + let hasErrors = false; + for (const moduleFilePath of moduleFilePaths) { + if (this.errors.patchErrors?.[moduleFilePath]) { + result[moduleFilePath] = { + ...(result[moduleFilePath] || {}), + ...deepClone(this.errors.patchErrors[moduleFilePath]!), + }; + hasErrors = true; + } + } + if (hasErrors) { + this.cachedPatchErrorsSnapshot[pathsKey] = result; + } + } + return this.cachedPatchErrorsSnapshot[pathsKey]; + } + + private cachedPatchData: Record< + PatchId, + { + moduleFilePath: ModuleFilePath; + patch: Patch; + isPending: boolean; + createdAt: string; + authorId: string | null; + isCommitted?: { + commitSha: string; + }; + } + > | null; + getAllPatchesSnapshot() { + if (!this.cachedPatchData) { + this.cachedPatchData = {}; + for (const patchIdS in this.patchDataByPatchId) { + const patchId = patchIdS as PatchId; + const patchData = this.patchDataByPatchId[patchId]; + if (patchData) { + this.cachedPatchData[patchId] = deepClone(patchData); + } + } + } + return this.cachedPatchData; + } + + private cachedGlobalServerSidePatchIdsSnapshot: PatchId[] | null; + getGlobalServerSidePatchIdsSnapshot() { + if (this.cachedGlobalServerSidePatchIdsSnapshot === null) { + this.cachedGlobalServerSidePatchIdsSnapshot = + this.globalServerSidePatchIds?.slice() || []; + } + return this.cachedGlobalServerSidePatchIdsSnapshot; + } + + private cachedPendingClientSidePatchIdsSnapshot: PatchId[] | null; + getPendingClientSidePatchIdsSnapshot() { + if (this.cachedPendingClientSidePatchIdsSnapshot === null) { + this.cachedPendingClientSidePatchIdsSnapshot = + this.pendingClientPatchIds?.slice() || []; + } + return this.cachedPendingClientSidePatchIdsSnapshot; + } + + private cachedSyncedServerSidePatchIdsSnapshot: PatchId[] | null; + getSyncedServerSidePatchIdsSnapshot() { + if (this.cachedSyncedServerSidePatchIdsSnapshot === null) { + this.cachedSyncedServerSidePatchIdsSnapshot = + this.syncedServerSidePatchIds?.slice() || []; + } + return this.cachedSyncedServerSidePatchIdsSnapshot; + } + + private cachedSavedServerSidePatchIdsSnapshot: PatchId[] | null; + getSavedServerSidePatchIdsSnapshot() { + if (this.cachedSavedServerSidePatchIdsSnapshot === null) { + this.cachedSavedServerSidePatchIdsSnapshot = + this.savedButNotYetGlobalServerSidePatchIds?.slice() || []; + } + return this.cachedSavedServerSidePatchIdsSnapshot; + } + + private cachedPublishDisabledSnapshot: boolean | null; + getPublishDisabledSnapshot() { + if (this.cachedPublishDisabledSnapshot === null) { + this.cachedPublishDisabledSnapshot = this.publishDisabled; + } + return this.cachedPublishDisabledSnapshot; + } + + private cachedAutoPublishSnapshot: boolean | null; + getAutoPublishSnapshot() { + if (this.cachedAutoPublishSnapshot === null) { + this.cachedAutoPublishSnapshot = this.autoPublish; + } + return this.cachedAutoPublishSnapshot; + } + + private cachedGlobalTransientErrorSnapshot: + | { + message: string; + timestamp: number; + details?: string; + id: string; + }[] + | null; + getGlobalTransientErrorsSnapshot() { + if (this.cachedGlobalTransientErrorSnapshot === null) { + this.cachedGlobalTransientErrorSnapshot = + this.errors.globalTransientErrorQueue?.slice() || []; + } + return this.cachedGlobalTransientErrorSnapshot; + } + + private cachedNetworkErrorSnapshot: number | null | undefined; + getNetworkErrorSnapshot() { + if (this.cachedNetworkErrorSnapshot === undefined) { + this.cachedNetworkErrorSnapshot = + this.errors.hasNetworkErrorTimestamp || null; + } + return this.cachedNetworkErrorSnapshot; + } + + private cachedSchemaErrorSnapshot: number | null | undefined; + getSchemaErrorSnapshot() { + if (this.cachedSchemaErrorSnapshot === undefined) { + this.cachedSchemaErrorSnapshot = + this.errors.hasSchemaErrorTimestamp || null; + } + return this.cachedSchemaErrorSnapshot; + } + + private cachedParentRef: ParentRef | null | undefined; + getParentRefSnapshot() { + if (this.cachedParentRef === undefined) { + this.cachedParentRef = this.getParentRef(); + } + return this.cachedParentRef; + } + + // #region Patching + private addPatchOnClientOnly( + sourcePath: SourcePath | ModuleFilePath, + patch: Patch, + now: number, + ): + | { + status: "optimistic-client-sources-updated"; + moduleFilePath: ModuleFilePath; + prevSource: JSONValue; + patch: Patch; + } + | { + status: "patch-error"; + message: string; + moduleFilePath: ModuleFilePath; + } { + const [moduleFilePath] = Internal.splitModuleFilePathAndModulePath( + sourcePath as SourcePath, + ); + if ( + this.serverSources === null || + this.serverSources?.[moduleFilePath] === undefined + ) { + // This happens if the client add patches, but the server sources have not yet been initialized + // so this should not happen + this.addGlobalTransientError( + `Content at '${moduleFilePath}' is not yet initialized`, + now, + ); + return { + status: "patch-error", + message: `Content at '${moduleFilePath}' is not yet initialized`, + moduleFilePath, + }; + } + if (this.optimisticClientSources[moduleFilePath] === undefined) { + this.optimisticClientSources[moduleFilePath] = + this.serverSources[moduleFilePath]; + } + const patchableOps = patch.filter((op) => op.op !== "file"); + const patchRes = applyPatch( + deepClone(this.optimisticClientSources[moduleFilePath] as JSONValue), + ops, + patchableOps, + ); + if (result.isErr(patchRes)) { + console.error("Could not apply patch:", patchRes.error); + this.addGlobalTransientError( + `Could apply patch: ${patchRes.error.message}`, + now, + ); + return { + status: "patch-error", + message: patchRes.error.message, + moduleFilePath, + }; + } else { + const newSource = patchRes.value; + const prevSource = deepClone( + this.optimisticClientSources[moduleFilePath] as JSONValue, + ); + this.optimisticClientSources[moduleFilePath] = newSource; + return { + status: "optimistic-client-sources-updated", + moduleFilePath, + prevSource, + patch, + } as const; + } + } + + validatePatchResult( + moduleFilePath: ModuleFilePath, + patch: Patch, + ): + | ValidationErrors + | { status: "no-source" | "no-schema" | "patch-error"; message: string } { + const currentSource = + this.optimisticClientSources[moduleFilePath] ?? + this.serverSources?.[moduleFilePath]; + if (currentSource === undefined) { + return { + status: "no-source", + message: `Content at '${moduleFilePath}' is not yet initialized`, + }; + } + const serializedSchema = this.schemas?.[moduleFilePath]; + if (!serializedSchema) { + return { + status: "no-schema", + message: `Schema not found for '${moduleFilePath}'`, + }; + } + const patchableOps = patch.filter((op) => op.op !== "file"); + const patchRes = applyPatch( + deepClone(currentSource as JSONValue), + ops, + patchableOps, + ); + if (result.isErr(patchRes)) { + return { + status: "patch-error", + message: patchRes.error.message, + }; + } + if (!this.cachedDeserializedSchemas) { + this.cachedDeserializedSchemas = {}; + } + if (!this.cachedDeserializedSchemas[moduleFilePath]) { + this.cachedDeserializedSchemas[moduleFilePath] = + deserializeSchema(serializedSchema); + } + const schema = this.cachedDeserializedSchemas[moduleFilePath]; + return schema["executeValidate"]( + moduleFilePath as string as SourcePath, + patchRes.value, + ); + } + + /** + * Use this to add a patch and IMMEDIATELY sync it to the server. + * The original intended use case is in conjunction with file operations. + * We first use this and add / create a new patch, then we can + * transfer the files to the server directly. + */ + async addPatchAwaitable( + sourcePath: SourcePath | ModuleFilePath, + type: SerializedSchema["type"], + patch: Patch, + patchId: PatchId, + sessionId: string | null, + now: number, + ): Promise< + | { + status: "patch-synced"; + patchId: PatchId; + parentRef: ParentRef; // this is the parent ref of the patch we just added (so before it was added) - we use it to upload files + moduleFilePath: ModuleFilePath; + } + | { + status: "patch-sync-error"; + message: string; + moduleFilePath: ModuleFilePath; + } + | { + status: "patch-error"; + message: string; + moduleFilePath: ModuleFilePath; + } + > { + const res = this.addPatchOnClientOnly(sourcePath, patch, now); + if (res.status !== "optimistic-client-sources-updated") { + return res; + } + + const { moduleFilePath, patch: addedPatch } = res; + const addOp: AddPatchOp = { + type: "add-patches", + data: { + [moduleFilePath]: [ + { + patch: addedPatch, + patchId, + type, + }, + ], + }, + createdAt: now, + }; + let tries = 0; + this.syncStatus[sourcePath] = "patches-pending"; + this.invalidateSyncStatus(sourcePath); + let opRes = await this.executeAddPatches(addOp, {}, now); + while (opRes.status === "retry" && tries < 3) { + tries++; + await new Promise((resolve) => setTimeout(resolve, 500 * (tries + 1))); // wait 500ms, 1000ms, 1500ms + opRes = await this.executeAddPatches(addOp, {}, now); + if (opRes.status !== "retry") { + break; + } + } + this.syncStatus[sourcePath] = "done"; + this.invalidateSyncStatus(sourcePath); + if (opRes.status === "done") { + return { + status: "patch-synced", + patchId, + parentRef: opRes.parentRef, + moduleFilePath, + } as const; + } + // Reset optimistic state on failure + this.optimisticClientSources[moduleFilePath] = res.prevSource; + return { + status: "patch-sync-error", + message: "Could not sync patch. Tried 3 times.", + moduleFilePath, + } as const; + } + + addPatch( + sourcePath: SourcePath | ModuleFilePath, + type: SerializedSchema["type"], + patch: Patch, + now: number, + ): + | { + status: "patch-merged"; + patchId: PatchId; + moduleFilePath: ModuleFilePath; + } + | { + status: "patch-added"; + patchId: PatchId; + moduleFilePath: ModuleFilePath; + } + | { + status: "patch-error"; + message: string; + moduleFilePath: ModuleFilePath; + } { + const res = this.addPatchOnClientOnly(sourcePath, patch, now); + if (res.status !== "optimistic-client-sources-updated") { + return res; + } + const moduleFilePath = res.moduleFilePath; + this.syncStatus[sourcePath] = "patches-pending"; + this.invalidateSyncStatus(sourcePath); + const lastOp = this.pendingOps[this.pendingOps.length - 1]; + // Try to batch add-patches ops together to avoid too many requests... + if (lastOp?.type === "add-patches") { + // ... either by merging them if possible (reduces amount of patch ops and data) + const lastPatchIdx = (lastOp.data?.[moduleFilePath]?.length || 0) - 1; + const lastPatch = lastOp.data?.[moduleFilePath]?.[lastPatchIdx]?.patch; + const lastPatchId = + lastOp.data?.[moduleFilePath]?.[lastPatchIdx]?.patchId; + if ( + canMerge(lastPatch, patch) && + // The type of the last should always be the same as long as the schema has not changed + lastOp.data?.[moduleFilePath]?.[lastPatchIdx]?.type === type && + // If we do not have patchId nor patchData something is wrong and in this case we simply do not merge the patch + lastPatchId && + this.patchDataByPatchId[lastPatchId] + ) { + lastOp.data[moduleFilePath][lastPatchIdx].patch = patch; + lastOp.updatedAt = now; + this.invalidatePendingOps(); + this.patchDataByPatchId[lastPatchId]!.patch = patch; + this.invalidateAllPatches(); + this.patchSetInsert(moduleFilePath, lastPatchId, patch, now); + + this.invalidateSyncStatus(sourcePath); + this.invalidateSource(moduleFilePath); + + return { + status: "patch-merged", + patchId: lastPatchId, + moduleFilePath, + } as const; + } else { + // ... or by just pushing it to the last op + if (!lastOp.data[moduleFilePath]) { + lastOp.data[moduleFilePath] = []; + } + const patchId = this.createPatchId(); + lastOp.data[moduleFilePath].push({ + patch, + type, + patchId, + }); + this.invalidatePendingOps(); + this.pendingClientPatchIds.push(patchId); + this.invalidatePendingClientSidePatchIds(); + this.patchDataByPatchId[patchId] = { + moduleFilePath: moduleFilePath, + patch: patch, + isPending: true, + createdAt: new Date(now).toISOString(), + authorId: this.authorId, + }; + this.invalidateAllPatches(); + this.patchSetInsert(moduleFilePath, patchId, patch, now); + + this.invalidateSyncStatus(sourcePath); + this.invalidateSource(moduleFilePath); + + return { + status: "patch-added", + patchId, + moduleFilePath, + } as const; + } + } else { + const patchId = this.createPatchId(); + this.pendingOps.push({ + type: "add-patches", + data: { + [moduleFilePath]: [{ patch, type, patchId }], + }, + createdAt: now, + }); + this.invalidatePendingOps(); + this.pendingClientPatchIds.push(patchId); + this.invalidatePendingClientSidePatchIds(); + this.patchDataByPatchId[patchId] = { + moduleFilePath: moduleFilePath, + patch: patch, + isPending: true, + createdAt: new Date(now).toISOString(), + authorId: this.authorId, + }; + this.invalidateAllPatches(); + this.patchSetInsert(moduleFilePath, patchId, patch, now); + + this.invalidateSyncStatus(sourcePath); + this.invalidateSource(moduleFilePath); + + return { + status: "patch-added", + patchId, + moduleFilePath, + } as const; + } + } + + createPatchId() { + const patchId = crypto.randomUUID() as PatchId; + return patchId; + } + + patchSetInsert( + moduleFilePath: ModuleFilePath, + patchId: PatchId, + patch: Patch, + now: number, + ) { + const createdAt = new Date(now).toISOString(); + for (const op of patch) { + this.patchSets.insert( + moduleFilePath, + this.schemas?.[moduleFilePath] ?? undefined, + op, + patchId, + createdAt, + this.authorId, + ); + } + this.invalidatePatchSets(); + } + + deletePatches(patchIds: PatchId[], now: number) { + const lastOp = this.pendingOps[this.pendingOps.length - 1]; + if (lastOp?.type === "delete-patches") { + lastOp.patchIds.push(...patchIds); + lastOp.updatedAt = now; + return; + } + this.pendingOps.push({ + type: "delete-patches", + patchIds: patchIds, + createdAt: now, + }); + this.invalidatePendingOps(); + } + + // #region Misc + + private markAllSyncStatusIn( + moduleFilePath: ModuleFilePath, + syncStatus: SyncStatus, + ) { + for (const path in this.syncStatus) { + if (path.startsWith(moduleFilePath)) { + this.syncStatus[path as SourcePath] = syncStatus; + } + } + } + + getParentRef(): ParentRef | null { + if (this.baseSha === null) { + return null; + } + if (this.globalServerSidePatchIds === null) { + return null; + } + // NOTE: if we change this function, remember to update to reset the cachedParentRef where appropriate + const patchId = + // Avoid conflicts when it is only this client that creates patches + this.savedButNotYetGlobalServerSidePatchIds[ + this.savedButNotYetGlobalServerSidePatchIds.length - 1 + ] || + this.globalServerSidePatchIds[this.globalServerSidePatchIds.length - 1]; + + if (!patchId) { + return { + type: "head", + headBaseSha: this.baseSha, + }; + } + return { + type: "patch", + patchId, + }; + } + + // #region Stat + + async syncWithUpdatedStat( + mode: "fs" | "http", + baseSha: string, + schemaSha: string, + sourcesSha: string, + patchIds: PatchId[], + authorId: string | null, + commitSha: string | null, + now: number, + ): Promise< + | { + status: "done"; + } + | { + status: "retry"; + reason: RetryReason; + } + > { + const sourcesShaDidChange = this.sourcesSha !== sourcesSha; + this.sourcesSha = sourcesSha; + this.baseSha = baseSha; + this.mode = mode; + if ( + this.serverSideSchemaSha !== schemaSha || + this.commitSha !== commitSha + ) { + this.reset(); + this.serverSideSchemaSha = schemaSha; + this.commitSha = commitSha; + return this.init( + mode, + baseSha, + schemaSha, + sourcesSha, + patchIds, + authorId, + commitSha, + now, + ); + } + const patchIdsDidChange = + this.globalServerSidePatchIds === null || + !deepEqual(this.globalServerSidePatchIds, patchIds); + if (patchIdsDidChange) { + // Do not update the globalServerSidePatchIds if they are the same + // since we using this directly in get snapshot method + this.globalServerSidePatchIds = patchIds; + const uniquePatchIds = new Set(patchIds); + this.deletePendingPatchId(uniquePatchIds); + this.deleteSavedButNotYetGlobalServerSidePatchIds(uniquePatchIds); + // if (mode === "http") { + await this.syncPatches(false, now); + // } + this.invalidateGlobalServerSidePatchIds(); + this.invalidateSyncedServerSidePatchIds(); + this.invalidateSavedServerSidePatchIds(); + this.invalidatePendingClientSidePatchIds(); + } + if ( + !this.forceSyncAllModules && + (sourcesShaDidChange || patchIdsDidChange) + ) { + this.forceSyncAllModules = true; + } + return this.sync(now); + } + + // #region Sync utils + async executeAddPatches( + op: AddPatchOp, + changes: Record>, + now: number, + ): Promise< + | { + status: "done"; + parentRef: ParentRef; + } + | { + status: "retry"; + reason: RetryReason; + } + > { + const postPatchesBody: { + path: ModuleFilePath; + patch: Patch; + patchId: PatchId; + }[] = []; + const newPatchIds: PatchId[] = []; + let didUpdatePatchData = false; + for (const [path, patchesData] of Object.entries(op.data)) { + const moduleFilePath = path as ModuleFilePath; + for (const patchData of patchesData) { + postPatchesBody.push({ + path: moduleFilePath, + patchId: patchData.patchId, + patch: patchData.patch, + }); + newPatchIds.push(patchData.patchId); + if (!changes[moduleFilePath]) { + changes[moduleFilePath] = new Set(); + } + changes[moduleFilePath].add(patchData.type); + } + } + const parentRef = this.getParentRef(); + if (parentRef === null) { + this.addGlobalTransientError( + `Tried to update content with changes, but could not since Val is not yet initialized`, + now, + ); + return { + status: "retry", + reason: "not-initialized", + }; + } + const addPatchesRes = await this.client("/patches", "PUT", { + body: { + patches: postPatchesBody, + parentRef, + sessionId: op.sessionId, + }, + }); + if (addPatchesRes.status !== null) { + this.resetNetworkError(); + } + if ( + addPatchesRes.status === null && + addPatchesRes.json.type === "network_error" + ) { + console.warn("Network error: trying again..."); + this.addNetworkError(now); + // Try again if it is a network error: + return { + status: "retry", + reason: "network-error", + }; + } else if (addPatchesRes.status === 409) { + // Reset saved patch ids since they are not valid anymore + this.savedButNotYetGlobalServerSidePatchIds = []; + // Try again if it is a conflict error (NOTE: this can absolutely happen if there are multiple concurrent users) + return { + status: "retry", + reason: "conflict", + }; + } else if (addPatchesRes.status !== 200) { + console.error("Failed to add patches", { + error: addPatchesRes.json.message, + }); + this.addGlobalTransientError( + `Failed to save changes`, + now, + addPatchesRes.json.message, + ); + // We failed to add these patches so we must clean up after ourselves + // NOTE: These patches will be removed, in the future we might want to retry or something + // Also note that there is (or at least should) be something permanently wrong with these + // patches so there shouldn't be any need to retry + for (const patchId of newPatchIds) { + this.patchDataByPatchId = { + ...this.patchDataByPatchId, + [patchId]: undefined, + }; + didUpdatePatchData = true; + } + const newPatchIdsSet = new Set(newPatchIds); + this.pendingClientPatchIds = this.pendingClientPatchIds.filter( + (id) => !newPatchIdsSet.has(id), + ); + } else { + // Success + const createdPatchIds = new Set(addPatchesRes.json.newPatchIds); + this.deletePendingPatchId(createdPatchIds); + for (const patchIdS of newPatchIds) { + const patchId = patchIdS as PatchId; + this.savedButNotYetGlobalServerSidePatchIds.push(patchId); + if (this.patchDataByPatchId[patchId]) { + this.patchDataByPatchId[patchId]!.isPending = false; + didUpdatePatchData = true; + } + } + } + if (didUpdatePatchData) { + this.invalidateAllPatches(); + } + return { + status: "done", + parentRef, + }; + } + + private deletePendingPatchId(patchIds: Set) { + let deleteCount = 0; + for (let i = 0; i < this.pendingClientPatchIds.length; i++) { + const patchId = this.pendingClientPatchIds[i] as PatchId; + if (patchIds.has(patchId)) { + this.pendingClientPatchIds.splice(i, 1); + i--; + deleteCount++; + if (patchIds.size === deleteCount) { + break; + } + } + } + } + + private deleteSavedButNotYetGlobalServerSidePatchIds(patchIds: Set) { + let deleteCount = 0; + for ( + let i = 0; + i < this.savedButNotYetGlobalServerSidePatchIds.length; + i++ + ) { + const patchId = this.savedButNotYetGlobalServerSidePatchIds[i] as PatchId; + if (patchIds.has(patchId)) { + this.savedButNotYetGlobalServerSidePatchIds.splice(i, 1); + i--; + deleteCount++; + if (patchIds.size === deleteCount) { + break; + } + } + } + } + + async executeDeletePatches( + op: DeletePatchesOp, + changes: Record>, + now: number, + ): Promise< + | { + status: "done"; + syncAllRequired: boolean; + } + | { + status: "retry"; + reason: RetryReason; + } + > { + let syncAllRequired = false; + const deletePatchIds = op.patchIds; + const deletePatchIdsSet = new Set(deletePatchIds); + const deleteRes = await this.client("/patches", "DELETE", { + query: { + id: op.patchIds.reverse(), + }, + }); + if (deleteRes.status !== null) { + this.resetNetworkError(); + } + if (deleteRes.status === null && deleteRes.json.type === "network_error") { + this.addNetworkError(now); + return { + status: "retry", + reason: "network-error", + }; + } else if (deleteRes.status !== 200) { + // Give up unless it is a network error + this.addGlobalTransientError("Failed to delete patches", now); + } else { + for (const patchId of op.patchIds) { + if (this.patchDataByPatchId[patchId]) { + const currentModuleFilePath = + this.patchDataByPatchId[patchId]!.moduleFilePath; + + if (!changes[currentModuleFilePath]) { + changes[currentModuleFilePath] = new Set(); + } + changes[currentModuleFilePath].add("unknown"); + this.patchDataByPatchId = { + ...this.patchDataByPatchId, + [patchId]: undefined, + }; + } else { + syncAllRequired = true; + } + } + this.pendingClientPatchIds = this.pendingClientPatchIds.filter( + (id) => !deletePatchIdsSet.has(id), + ); + this.globalServerSidePatchIds = + this.globalServerSidePatchIds?.filter( + (id) => !deletePatchIdsSet.has(id), + ) ?? null; + this.deleteSavedButNotYetGlobalServerSidePatchIds(deletePatchIdsSet); + } + return { + status: "done", + syncAllRequired, + }; + } + + async syncSchema(): Promise< + | { + status: "done"; + } + | { + status: "retry"; + reason: "error"; + } + > { + const schemaRes = await this.client("/schema", "GET", {}); + if (schemaRes.status === 200) { + this.schemas = {}; + for (const [moduleFilePathS, schema] of Object.entries( + schemaRes.json.schemas, + )) { + const moduleFilePath = moduleFilePathS as ModuleFilePath; + if (schema) { + this.schemas[moduleFilePath] = schema; + } + } + if (this.clientSideSchemaSha !== schemaRes.json.schemaSha) { + this.clientSideSchemaSha = schemaRes.json.schemaSha; + } + + console.debug("Invalidating schema"); + this.resetSchemaError(); + this.invalidateSchema(); + return { + status: "done", + }; + } else if (schemaRes.status === null) { + return { + status: "retry", + reason: "error", + }; + } + // Schema endpoint returned an error (e.g., 500) + this.addSchemaError(Date.now()); + return { + status: "retry", + reason: "error", + }; + } + + private async syncPatches( + reset: boolean, + now: number, + ): Promise< + | { + status: "done"; + } + | { + status: "retry"; + } + > { + const currentPatchIds = this.globalServerSidePatchIds || []; + let didUpdatePatchSet = false; + let didUpdatePatchData = false; + + // get missing data + if (this.initializedAt === null || reset) { + this.patchSets = new PatchSets(); + didUpdatePatchSet = true; + // When we are initializing, we don't want to sync all individual patch sets + // since we are going to get them all at once anyway + // Why is this a problem? It's because we can only do about 300 patch ids at a time before the URL gets too long + // Now, you might be saying that is an API issue, and you might be right (but this way we at least can cache the patch ids heavily) + const res = await this.client("/patches", "GET", { + query: { + exclude_patch_ops: false, + patch_id: undefined, // all patches + }, + }); + if (res.status !== 200) { + console.debug("Val: SyncEngine: Failed to get changes (full sync)", { + res, + }); + return { + status: "retry", + }; + } + for (const patchData of res.json.patches) { + if (patchData.patch) { + didUpdatePatchData = true; + this.patchDataByPatchId[patchData.patchId] = { + moduleFilePath: patchData.path, + patch: patchData.patch, + isPending: false, + createdAt: patchData.createdAt, + authorId: patchData.authorId, + isCommitted: patchData.appliedAt + ? { + commitSha: patchData.appliedAt.commitSha, + } + : undefined, + }; + } + } + if (res.json.error) { + this.addGlobalTransientError( + "Some changes has errors", + now, + res.json.error.message, + ); + } + for (const error of Object.values(res.json.errors || {})) { + if (error) { + this.addGlobalTransientError( + "A change has an error", + now, + error.message, + ); + } + } + } else { + // Get missing patch data for potentially new global server side patch ids + const missingPatchData: PatchId[] = []; + for (const serverSidePatchId of this.globalServerSidePatchIds || []) { + if (!this.patchDataByPatchId[serverSidePatchId]) { + missingPatchData.push(serverSidePatchId); + } + } + if (missingPatchData.length > 0) { + // Batch in batches of 100 to avoid URL length issues + const batchSize = 100; + const batches = []; + for (let i = 0; i < missingPatchData.length; i += batchSize) { + batches.push(missingPatchData.slice(i, i + batchSize)); + } + for (const batch of batches) { + const res = await this.client("/patches", "GET", { + query: { + exclude_patch_ops: false, + patch_id: batch, + }, + }); + if (res.status !== 200) { + console.debug( + "Val: SyncEngine: Failed to get changes (batch) - null status", + { + res, + }, + ); + return { + status: "retry", + }; + } + for (const patchData of res.json.patches) { + if (patchData.patch) { + didUpdatePatchData = true; + this.patchDataByPatchId[patchData.patchId] = { + moduleFilePath: patchData.path, + patch: patchData.patch, + isPending: false, + createdAt: patchData.createdAt, + authorId: patchData.authorId, + isCommitted: patchData.appliedAt + ? { + commitSha: patchData.appliedAt.commitSha, + } + : undefined, + }; + } + } + } + } + } + + const allCurrentPatchIds = new Set(currentPatchIds); + for (const patchId of Array.from(this.patchSets.getInsertedPatches()) || + []) { + if (!allCurrentPatchIds.has(patchId)) { + // The patch set is dirty, so we need to reset it + // Maybe we should add a remove method on PatchSets? + this.patchSets = new PatchSets(); + didUpdatePatchSet = true; + break; + } + } + + // All patch ids should be good, but we might have had new patches added while we were syncing data + // In that case, we will retry + const missingDataPatchIds = []; + for (const patchId of currentPatchIds) { + if (!this.patchSets.isInserted(patchId)) { + const patchData = this.patchDataByPatchId[patchId]; + const schema = + patchData?.moduleFilePath && + this.schemas?.[patchData?.moduleFilePath]; + if (patchData && schema) { + for (const op of patchData.patch) { + didUpdatePatchSet = true; + this.patchSets.insert( + patchData.moduleFilePath, + schema, + op, + patchId, + patchData.createdAt, + patchData.authorId, + ); + } + } else { + missingDataPatchIds.push(patchId); + } + } + } + if (didUpdatePatchData) { + this.invalidateAllPatches(); + } + if (didUpdatePatchSet) { + this.invalidatePatchSets(); + } + if (missingDataPatchIds.length > 0) { + if (this.initializedAt !== null) { + console.debug("Missing data for patch ids", missingDataPatchIds, { + currentPatchIds, + reset, + }); + // TODO: we disabled this error on fs since in auto save it comes every time it saves. + // We should figure out why that happens and re-enable the error + if (this.mode !== "fs") { + this.addGlobalTransientError( + "Failed to get changes", + now, + `Missing data for patch ids: ${missingDataPatchIds.join(", ")}`, + ); + } + } + return { + status: "retry", + }; + } + + return { + status: "done", + }; + } + + private getChangedModules( + changes: Record>, + ): "all" | ModuleFilePath[] { + // This is currently a pretty basic implementation to that figures out, based on a set of changes, + // which modules needs to be synced. + // It is meant to err on the side of caution, so it will return "all" if we cannot be a 100% certain + const changedModules = Object.entries(changes); + if (changedModules.length === 0) { + return []; + } + if (changedModules.length === 1) { + const [changedModuleFilePathS, types] = changedModules[0]; + if ( + Array.from(types).every((type) => nonInterDependentTypes.includes(type)) + ) { + return [changedModuleFilePathS as ModuleFilePath]; + } + } + return "all"; + } + + // #region Sync + public isSyncing = false; + private MIN_WAIT_SECONDS = 1; + private MAX_WAIT_SECONDS = 5; + + async sync(now: number): Promise< + | { + status: "done"; + } + | { + status: "retry"; + reason: RetryReason; + } + > { + if (this.isSyncing) { + // Already syncing, don't start a new sync + return { + status: "retry", + reason: "already-syncing", + }; + } + if (this.isPublishing) { + // Publishing, wait until complete before syncing + return { + status: "retry", + reason: "publishing", + }; + } + let changedModules: "all" | ModuleFilePath[] = []; + if (this.forceSyncAllModules) { + this.forceSyncAllModules = false; + changedModules = "all"; + } + if (this.initializedAt === null) { + // We are not initialized yet, so we need to sync everything + changedModules = "all"; + } + if (this.clientSideSchemaSha !== this.serverSideSchemaSha) { + // Schema has changed, so we need to sync everything + changedModules = "all"; + } + + this.isSyncing = true; + let pendingOps: PendingOp[] = []; + let serverPatchIdsDidChange = false; + const allSyncedPatchIds = new Set([ + ...this.syncedServerSidePatchIds, + ...this.savedButNotYetGlobalServerSidePatchIds, + ]); + + if (this.globalServerSidePatchIds && this.mode === "http") { + // This will happen if there's patches that are deleted server side + // that was created by the client + for (const clientCreatedPatchId of allSyncedPatchIds) { + if ( + // Client believes it has synced clientCreatedPatchId... + // ... but it is no longer in the global server side patch ids + // (this means that the patch id was removed from the server) + !this.globalServerSidePatchIds.includes(clientCreatedPatchId) + ) { + // resetting the patches stored by client + this.syncedServerSidePatchIds = []; + this.savedButNotYetGlobalServerSidePatchIds = []; + this.pendingClientPatchIds = []; + await this.syncPatches(true, now); + // in http mode we need to sync patches + serverPatchIdsDidChange = true; + break; + } + } + } + + try { + const changes: Record< + ModuleFilePath, + Set + > = {}; + + const lessThanNSecondsSince = (seconds: number, timestamp: number) => { + const timeElapsed = now - timestamp; + return timeElapsed <= seconds * 1000; + }; + + const moreThanNSecondsSince = (seconds: number, timestamp: number) => { + const timeElapsed = now - timestamp; + + return timeElapsed >= seconds * 1000; + }; + if ( + this.pendingOps[this.pendingOps.length - 1]?.updatedAt !== undefined && + // Less than N seconds ago since last op was updated - we should wait... + lessThanNSecondsSince( + this.MIN_WAIT_SECONDS, + this.pendingOps[this.pendingOps.length - 1].updatedAt!, + ) && + // ... unless if we have already waited more than N seconds - we still sync + !moreThanNSecondsSince( + this.MAX_WAIT_SECONDS, + this.pendingOps[this.pendingOps.length - 1].createdAt, + ) + ) { + return { + status: "retry", + reason: "too-fast", + }; + } + // #region Write operations + pendingOps = this.pendingOps.slice(); + this.pendingOps = []; + let didWrite = false; + while (pendingOps[0]) { + const op = pendingOps[0]; + if (op.type === "add-patches") { + try { + const res = await this.executeAddPatches(op, changes, now); + if (res.status !== "done") { + return res; + } + } catch { + return { + status: "retry", + reason: "error", + }; + } + } else if (op.type === "delete-patches") { + try { + const res = await this.executeDeletePatches(op, changes, now); + if (res.status !== "done") { + return res; + } else { + if (res.syncAllRequired && changedModules !== "all") { + changedModules = "all"; + } + } + } catch { + return { + status: "retry", + reason: "error", + }; + } + } + didWrite = true; + pendingOps.shift(); + } + this.invalidatePendingOps(); + if (changedModules !== "all") { + const currentChangedModules = this.getChangedModules(changes); + if (currentChangedModules === "all") { + changedModules = "all"; + } else { + for (const moduleFilePath of currentChangedModules) { + changedModules.push(moduleFilePath); + } + } + } + + // #region Read operations + if ( + this.clientSideSchemaSha === null || + this.schemas === null || + this.initializedAt === null + ) { + const res = await this.syncSchema(); + if (res.status !== "done") { + return res; + } + } + if (changedModules === "all" || changedModules.length > 0) { + const path = + // We could be smarter wrt to the modules we fetch. + // However, note that we are not sure how long it takes to evaluate 1 vs many + // - there' might not be that much to gain by being much more specific... + // NOTE currently we're trying to optimize for the case where + // there's a lot of changes in a single text / richtext field that needs to be synced + // (e.g. an editor is typing inside a richtext / text field) + changedModules !== "all" && changedModules.length === 1 + ? (changedModules[0] as ModuleFilePath) + : undefined; + + // TODO: change sources endpoint so that you can have multiple moduleFilePaths + const sourcesRes = await this.client("/sources/~", "PUT", { + path: path, + query: { + validate_sources: true, + validate_binary_files: false, + exclude_patches: false, + }, + }); + if (sourcesRes.status !== null) { + this.resetNetworkError(); + } + if ( + sourcesRes.status === null && + sourcesRes.json.type === "network_error" + ) { + this.addNetworkError(now); + return { + status: "retry", + reason: "network-error", + }; + } else if (sourcesRes.status !== 200) { + this.addGlobalTransientError( + "Could not sync content with server. Please wait or reload the application.", + now, + sourcesRes.json.message, + ); + } else { + // Clean up validation errors + const changedValidationErrors = new Set(); + for (const sourcePathS in this.errors.validationErrors) { + const sourcePath = sourcePathS as SourcePath; + if (path === undefined || sourcePath.startsWith(path)) { + changedValidationErrors.add(sourcePath); + if (this.errors.validationErrors[sourcePath]) { + this.errors.validationErrors = { + ...this.errors.validationErrors, + [sourcePath]: undefined, + }; + } + } + } + for (const [moduleFilePathS, valModule] of Object.entries( + sourcesRes.json.modules, + )) { + const moduleFilePath = moduleFilePathS as ModuleFilePath; + if (valModule) { + if (this.serverSources === null) { + this.serverSources = {}; + } + this.serverSources[moduleFilePath] = valModule.source; + if (this.renders === null) { + this.renders = {}; + } + this.renders[moduleFilePath] = valModule.render || null; + this.invalidateRenders(moduleFilePath); + + if ( + // Feel free to revisit / rewrite this if statement: + // We cannot remove optimisticClientSources, even if we just synced because the optimistic client side sources might have been changed while we were syncing + // If we remove the optimistic client side sources without verifying that the server side sources are the same, the user will see a flash and it revert back to the previously saved state. + // It feels like there might be errors that pops up because of this: what if the patch never is written / is wrong?! The change will then be lost. + deepEqual( + this.serverSources[moduleFilePath] as ReadonlyJSONValue, + this.optimisticClientSources[ + moduleFilePath + ] as ReadonlyJSONValue, + ) || + // We check for pendingOps, because the check above will fail for files since they inject a patchId... + this.pendingOps.length === 0 + ) { + this.optimisticClientSources = { + ...this.optimisticClientSources, + [moduleFilePath]: undefined, + }; + } + console.debug("Invalidating source", moduleFilePath); + // this.optimisticClientSources = {}; + // this.cachedDataSnapshots = {}; + this.invalidateSource(moduleFilePath); + this.overlayEmitter?.(moduleFilePath, valModule.source); + this.invalidatePatchErrors(moduleFilePath); + if (valModule.patches?.errors) { + if (this.errors.patchErrors === undefined) { + this.errors.patchErrors = {}; + } + this.errors.patchErrors[moduleFilePath] = valModule.patches + .errors as Record; + } + // NOTE: we clean up relevant validation errors above + for (const sourcePathS in valModule.validationErrors) { + const sourcePath = sourcePathS as SourcePath; + if (!this.errors.validationErrors) { + this.errors.validationErrors = {}; + } + this.errors.validationErrors[sourcePath] = + valModule.validationErrors[sourcePath]; + changedValidationErrors.add(sourcePath); + } + for (const syncedPatchId of valModule.patches?.applied || []) { + this.syncedServerSidePatchIds.push(syncedPatchId); + } + for (const syncedPatchId of valModule.patches?.skipped || []) { + this.syncedServerSidePatchIds.push(syncedPatchId); + } + } else { + this.addGlobalTransientError( + `Could not find '${moduleFilePath}' in server reply`, + now, + "This is most likely a bug", + ); + } + this.markAllSyncStatusIn(moduleFilePath, "done"); + } + // Invalidate validation errors: + // if (changedValidationErrors.size > 0) { + this.invalidateAllValidationErrors(); + // } + for (const sourcePath of Array.from(changedValidationErrors)) { + this.invalidateValidationError(sourcePath); + } + + // Sync Schema if it changed: + if (sourcesRes.json.schemaSha !== this.clientSideSchemaSha) { + await this.syncSchema(); + } + } + } + + if ( + this.autoPublish && + this.mode === "fs" && + this.globalServerSidePatchIds && + this.globalServerSidePatchIds.length > 0 + ) { + let hasValidationError = false; + for (const sourcePathS in this.errors.validationErrors || {}) { + const sourcePath = sourcePathS as SourcePath; + if ( + this.errors?.validationErrors?.[sourcePath] && + this.errors?.validationErrors?.[sourcePath]!.length > 0 + ) { + hasValidationError = true; + break; + } + } + if (!hasValidationError) { + await this.publish( + this.globalServerSidePatchIds.concat( + ...Array.from(this.syncedServerSidePatchIds), + ), + undefined, + now, + ); + didWrite = true; + } else { + console.debug( + "Skip auto-publish since there's validation errors", + this.errors.validationErrors, + ); + } + } + if (serverPatchIdsDidChange || didWrite) { + this.invalidatePendingClientSidePatchIds(); + this.invalidateGlobalServerSidePatchIds(); + this.invalidateSyncedServerSidePatchIds(); + this.invalidateSavedServerSidePatchIds(); + } + return { + status: "done", + }; + } finally { + this.isSyncing = false; + this.pendingOps = [...pendingOps, ...this.pendingOps]; + } + } + + // #region Publish + async publish(patchIds: PatchId[], message: string | undefined, now: number) { + try { + if (this.isPublishing) { + console.debug("Already publishing changes", now); + return { + status: "retry", + reason: "already-publishing", + } as const; + } + this.isPublishing = true; + if (this.publishDisabled) { + console.debug( + "Could not publish changes, since the publish is disabled", + now, + ); + return { + status: "retry", + reason: "publish-disabled", + } as const; + } + this.publishDisabled = true; + this.invalidatePublishDisabled(); + + const hasValidationError = + Object.values(this.errors.validationErrors || {}).flatMap( + (errors) => errors || [], + ).length > 0; + if (hasValidationError) { + console.debug( + "Skipping publish since there's validation errors", + this.errors.validationErrors, + ); + this.addGlobalTransientError( + "Could not publish changes, since there are validation errors", + now, + ); + return { + status: "retry", + reason: "validation-error", + } as const; + } + if (patchIds.length === 0) { + this.addGlobalTransientError( + "Could not publish changes, since there are no changes to publish", + Date.now(), + ); + return { + status: "done", + } as const; + } + const res = await this.client("/save", "POST", { + body: { + message: message, + patchIds: patchIds, + }, + }); + if (res.status === null) { + this.addGlobalTransientError( + "Network error: could not publish", + Date.now(), + ); + return { + status: "retry", + } as const; + } else if (res.status !== 200) { + this.addGlobalTransientError( + "Failed to publish changes", + Date.now(), + res.json.message, + ); + return { + status: "retry", + } as const; + } else { + if (this.mode === "fs") { + // In fs mode we delete all patch ids, so we start fresh + this.globalServerSidePatchIds = []; + console.debug("Deleting all patch ids"); + } + this.pendingClientPatchIds = []; + this.syncedServerSidePatchIds = []; + this.savedButNotYetGlobalServerSidePatchIds = []; + this.patchDataByPatchId = {}; + this.patchSets = new PatchSets(); + const fullReset = true; + await this.syncPatches(fullReset, now); + this.invalidatePatchSets(); + this.invalidateAllPatches(); + this.invalidatePendingClientSidePatchIds(); + this.invalidateSyncedServerSidePatchIds(); + this.invalidateSavedServerSidePatchIds(); + return { + status: "done", + } as const; + } + } catch (err) { + console.error("Error while publishing", err); + this.addGlobalTransientError( + "Failed to publish changes", + Date.now(), + (err as Error).message, + ); + return { + status: "retry", + reason: "error", + } as const; + } finally { + this.isPublishing = false; + this.publishDisabled = false; + this.invalidatePublishDisabled(); + } + } + + resetNetworkError() { + this.errors.hasNetworkErrorTimestamp = null; + this.invalidateNetworkError(); + } + + addNetworkError(now: number) { + this.errors.hasNetworkErrorTimestamp = now; + this.invalidateNetworkError(); + } + + resetSchemaError() { + this.errors.hasSchemaErrorTimestamp = null; + this.invalidateSchemaError(); + } + + addSchemaError(now: number) { + this.errors.hasSchemaErrorTimestamp = now; + this.invalidateSchemaError(); + } + + addGlobalTransientError(message: string, now: number, details?: string) { + if (!this.errors.globalTransientErrorQueue) { + this.errors.globalTransientErrorQueue = []; + } + console.error("Global transient error", message, details || ""); + this.errors.globalTransientErrorQueue.push({ + message, + details, + timestamp: now, + id: crypto.randomUUID(), + }); + this.invalidateGlobalTransientErrors(); + } + + removeGlobalTransientErrors(ids: string[]) { + if (this.errors.globalTransientErrorQueue) { + const idsSet = new Set(ids); + this.errors.globalTransientErrorQueue = + this.errors.globalTransientErrorQueue.filter( + (error) => !idsSet.has(error.id), + ); + this.invalidateGlobalTransientErrors(); + } + } + + /** + * Mock method for testing and Storybook. + * Sets schemas directly and invalidates related caches. + */ + setSchemas( + schemas: Record, + ): void { + this.schemas = schemas; + this.cachedSchemaSnapshots = null; + this.cachedAllSchemasSnapshot = null; + this.cachedDeserializedSchemas = null; + this.emit(this.listeners["schema"]?.[globalNamespace]); + } + + /** + * Mock method for testing and Storybook. + * Sets both serverSources and optimisticClientSources to the same value and invalidates related caches. + */ + setSources(sources: Record): void { + this.serverSources = sources; + this.cachedSourceSnapshots = null; + this.cachedAllSourcesSnapshot = null; + this.cachedSourcesSnapshot = null; + for (const moduleFilePath in sources) { + this.emit(this.listeners["sources"]?.[moduleFilePath as ModuleFilePath]); + this.emit(this.listeners["source"]?.[moduleFilePath as ModuleFilePath]); + } + this.emit(this.listeners["all-sources"]?.[globalNamespace]); + } + + /** + * Mock method for testing and Storybook. + * Sets renders directly and invalidates related caches. + */ + setRenders(renders: Record): void { + this.renders = renders; + this.cachedRenderSnapshots = renders; + for (const moduleFilePath in renders) { + const path = moduleFilePath as ModuleFilePath; + this.emit(this.listeners["render"]?.[path]); + } + } + + /** + * Mock method for testing and Storybook. + * Sets initializedAt directly and invalidates related caches. + */ + setInitializedAt(timestamp: number): void { + this.initializedAt = timestamp; + this.cachedInitializedAtSnapshot = null; + this.emit(this.listeners["initialized-at"]?.[globalNamespace]); + } +} + +// #region Supporting code +const ops = new JSONOps(); +const globalNamespace = "global"; +/** + * These are types where we can be 100% certain that a change in this type, will not result in validations failing in some other module. + * We use this to determine if syncing 1 module is enough or if we need to sync all modules. + */ +const nonInterDependentTypes = [ + "string", + "boolean", + "number", + "date", + "richtext", + "file", + "image", +]; +export const defaultOverlayEmitter = ( + moduleFilePath: ModuleFilePath, + newSource: JSONValue, +) => { + window.dispatchEvent( + new CustomEvent("val-event", { + detail: { + type: "source-update", + moduleFilePath, + source: newSource, + }, + }), + ); +}; + +// #region Types +type RetryReason = + | "conflict" + | "not-initialized" + | "network-error" + | "too-fast" + | "publishing" + | "error" + | "patch-ids-changed" + | "already-syncing"; +type SyncEngineListenerType = + | "schema" + | "initialized-at" + | "auto-publish" + | "parent-ref" + | "sync-status" + | "patch-sets" + | "all-patches" + | "validation-error" + | "all-validation-errors" + | "global-transient-errors" + | "failed-patches" + | "skipped-patches" + | "network-error" + | "schema-error" + | "global-server-side-patch-ids" + | "pending-client-side-patch-ids" + | "synced-server-side-patch-ids" + | "saved-server-side-patch-ids" + | "publish-disabled" + | "pending-ops-count" + | "all-sources" + | "render" + | "source" + | "sources" + | "patch-errors"; +type SyncStatus = "not-asked" | "fetching" | "patches-pending" | "done"; +type CommonOpProps = T & { + createdAt: number; + updatedAt?: number; +}; +type AddPatchOp = CommonOpProps<{ + type: "add-patches"; + data: Record< + ModuleFilePath, + { patch: Patch; type: SerializedSchema["type"]; patchId: PatchId }[] + >; + sessionId?: string | null; +}>; +type DeletePatchesOp = CommonOpProps<{ + type: "delete-patches"; + patchIds: PatchId[]; +}>; +type PendingOp = AddPatchOp | DeletePatchesOp; diff --git a/packages/ui/spa/assets/icons/Bold.tsx b/packages/ui/spa/assets/icons/Bold.tsx new file mode 100644 index 000000000..543907eb4 --- /dev/null +++ b/packages/ui/spa/assets/icons/Bold.tsx @@ -0,0 +1,23 @@ +import { FC } from "react"; + +const Bold: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + ); +}; + +export default Bold; diff --git a/packages/ui/spa/assets/icons/Chevron.tsx b/packages/ui/spa/assets/icons/Chevron.tsx new file mode 100644 index 000000000..c77a6d0fc --- /dev/null +++ b/packages/ui/spa/assets/icons/Chevron.tsx @@ -0,0 +1,28 @@ +import { FC } from "react"; + +const Chevron: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + ); +}; + +export default Chevron; diff --git a/packages/ui/spa/assets/icons/FontColor.tsx b/packages/ui/spa/assets/icons/FontColor.tsx new file mode 100644 index 000000000..eaee3c6f3 --- /dev/null +++ b/packages/ui/spa/assets/icons/FontColor.tsx @@ -0,0 +1,30 @@ +import { FC } from "react"; + +const FontColor: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + + + + ); +}; + +export default FontColor; diff --git a/packages/ui/spa/assets/icons/ImageIcon.tsx b/packages/ui/spa/assets/icons/ImageIcon.tsx new file mode 100644 index 000000000..df6ca5a67 --- /dev/null +++ b/packages/ui/spa/assets/icons/ImageIcon.tsx @@ -0,0 +1,29 @@ +import { FC } from "react"; + +const ImageIcon: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + + + + + + + + + + + ); +}; + +export default ImageIcon; diff --git a/packages/ui/spa/assets/icons/Italic.tsx b/packages/ui/spa/assets/icons/Italic.tsx new file mode 100644 index 000000000..34337f80f --- /dev/null +++ b/packages/ui/spa/assets/icons/Italic.tsx @@ -0,0 +1,24 @@ +import { FC } from "react"; + +const Italic: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + + ); +}; + +export default Italic; diff --git a/packages/ui/spa/assets/icons/Logo.tsx b/packages/ui/spa/assets/icons/Logo.tsx new file mode 100644 index 000000000..9c270930f --- /dev/null +++ b/packages/ui/spa/assets/icons/Logo.tsx @@ -0,0 +1,103 @@ +import { FC } from "react"; + +const Logo: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; +export default Logo; diff --git a/packages/ui/spa/assets/icons/Section.tsx b/packages/ui/spa/assets/icons/Section.tsx new file mode 100644 index 000000000..8a8474395 --- /dev/null +++ b/packages/ui/spa/assets/icons/Section.tsx @@ -0,0 +1,41 @@ +import { FC } from "react"; + +const Section: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + + + + + + + + ); +}; + +export default Section; diff --git a/packages/ui/spa/assets/icons/Strikethrough.tsx b/packages/ui/spa/assets/icons/Strikethrough.tsx new file mode 100644 index 000000000..49e2dc03c --- /dev/null +++ b/packages/ui/spa/assets/icons/Strikethrough.tsx @@ -0,0 +1,22 @@ +import { FC } from "react"; + +const Strikethrough: FC<{ className?: string }> = ({ className }) => { + return ( + + + + ); +}; + +export default Strikethrough; diff --git a/packages/ui/spa/assets/icons/TextIcon.tsx b/packages/ui/spa/assets/icons/TextIcon.tsx new file mode 100644 index 000000000..43d6f92d0 --- /dev/null +++ b/packages/ui/spa/assets/icons/TextIcon.tsx @@ -0,0 +1,20 @@ +import { FC } from "react"; + +const TextIcon: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + + ); +}; + +export default TextIcon; diff --git a/packages/ui/spa/assets/icons/Underline.tsx b/packages/ui/spa/assets/icons/Underline.tsx new file mode 100644 index 000000000..364253f4b --- /dev/null +++ b/packages/ui/spa/assets/icons/Underline.tsx @@ -0,0 +1,22 @@ +import { FC } from "react"; + +const Underline: FC<{ className?: string }> = ({ className }) => { + return ( + + + + ); +}; + +export default Underline; diff --git a/packages/ui/spa/assets/icons/Undo.tsx b/packages/ui/spa/assets/icons/Undo.tsx new file mode 100644 index 000000000..2f6033745 --- /dev/null +++ b/packages/ui/spa/assets/icons/Undo.tsx @@ -0,0 +1,20 @@ +import { FC } from "react"; + +const Undo: FC<{ className?: string }> = ({ className }) => { + return ( + + + + ); +}; + +export default Undo; diff --git a/packages/ui/spa/components/AIChat.stories.tsx b/packages/ui/spa/components/AIChat.stories.tsx new file mode 100644 index 000000000..b12c2be7a --- /dev/null +++ b/packages/ui/spa/components/AIChat.stories.tsx @@ -0,0 +1,347 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { useEffect, useRef } from "react"; +import { AIChat, AIChatHandle, ChatMessage } from "./AIChat"; + +const meta: Meta = { + title: "Components/AIChat", + component: AIChat, + parameters: { + layout: "fullscreen", + }, + tags: ["autodocs"], + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +// --------------------------------------------------------------------------- +// 1. Empty — shows greeting + suggestion chips +// --------------------------------------------------------------------------- + +export const Empty: Story = { + args: { + onSendMessage: (text: string) => { + console.log("Send:", text); + return true; + }, + }, +}; + +export const CustomSuggestions: Story = { + args: { + suggestions: [ + "Update the blog title", + "Translate to Norwegian", + "Fix validation errors", + "Generate a summary", + ], + onSendMessage: (text: string) => { + console.log("Send:", text); + return true; + }, + }, +}; + +// --------------------------------------------------------------------------- +// 2. With conversation — pre-populated via initialMessages +// --------------------------------------------------------------------------- + +const conversationMessages: ChatMessage[] = [ + { + id: "msg-1", + role: "user", + content: "Can you summarize the recent changes?", + status: "complete", + }, + { + id: "msg-2", + role: "assistant", + content: + "Here's a summary of the recent changes:\n\n" + + "1. **Schema updates** — The `blog` schema was extended with a new `author` field\n" + + "2. **Content fixes** — Three validation errors in `/content/posts` were resolved\n" + + "3. **New module** — Added `events.val.ts` with a rich-text description field\n\n" + + "Would you like me to go into more detail on any of these?", + status: "complete", + }, + { + id: "msg-3", + role: "user", + content: "Tell me more about the schema updates", + status: "complete", + }, + { + id: "msg-4", + role: "assistant", + content: + "The `blog` schema in `content/posts.val.ts` was updated with:\n\n" + + "```typescript\ns.object({\n title: s.string(),\n author: s.object({\n name: s.string(),\n avatar: s.image(),\n }),\n body: s.richtext(),\n})\n```\n\n" + + "The new `author` field is an object containing a `name` (string) and an `avatar` (image). " + + "All existing content has been migrated — no manual changes needed.", + status: "complete", + }, +]; + +export const WithConversation: Story = { + args: { + initialMessages: conversationMessages, + onSendMessage: (text: string) => { + console.log("Send:", text); + return true; + }, + }, +}; + +// --------------------------------------------------------------------------- +// 3. Streaming — simulates token-append arriving over time +// --------------------------------------------------------------------------- + +const STREAMING_TEXT = + "Let me analyze that for you.\n\n" + + "The content module at `/content/authors.val.ts` defines the following schema:\n\n" + + "```typescript\nexport const authors = val.content(\n" + + ' "/content/authors",\n' + + " s.array(\n" + + " s.object({\n" + + " name: s.string(),\n" + + " role: s.string(),\n" + + " bio: s.richtext(),\n" + + " })\n" + + " )\n" + + ");\n```\n\n" + + "This schema supports:\n" + + "- **name** — plain text string for the author's display name\n" + + "- **role** — their role or title\n" + + "- **bio** — rich text content with full formatting support\n\n" + + "The array wrapper means you can have multiple authors. Each author entry will be validated against this shape."; + +export const Streaming: Story = { + render: () => , +}; + +function AutoStartStreamingDemo() { + const chatRef = useRef(null); + + useEffect(() => { + if (!chatRef.current) return; + + const assistantId = "auto-stream-1"; + chatRef.current.startAssistantMessage(assistantId); + + let idx = 0; + const interval = setInterval(() => { + if (!chatRef.current) return; + const chunkSize = 2 + Math.floor(Math.random() * 3); + const chunk = STREAMING_TEXT.slice(idx, idx + chunkSize); + if (chunk) { + chatRef.current.appendAssistantChunk(assistantId, chunk); + idx += chunkSize; + } else { + chatRef.current.completeAssistantMessage(assistantId); + clearInterval(interval); + } + }, 30); + + return () => clearInterval(interval); + }, []); + + return ( + + ); +} + +// --------------------------------------------------------------------------- +// 4. Error — assistant message failed, with retry button +// --------------------------------------------------------------------------- + +export const Error: Story = { + args: { + initialMessages: [ + { + id: "err-user-1", + role: "user", + content: "Generate a commit message for my changes", + status: "complete", + }, + { + id: "err-assistant-1", + role: "assistant", + content: "", + status: "error", + error: "Connection lost — the server closed the WebSocket unexpectedly", + }, + ], + onSendMessage: (text: string) => { + console.log("Retry send:", text); + return true; + }, + }, +}; + +export const ErrorAfterPartialResponse: Story = { + args: { + initialMessages: [ + { + id: "errp-user-1", + role: "user", + content: "Summarize the content changes", + status: "complete", + }, + { + id: "errp-assistant-1", + role: "assistant", + content: + "Here are the recent content changes:\n\n1. **Blog post updated** — The title was changed from", + status: "error", + error: "Stream interrupted — request timed out after 30s", + }, + ], + onSendMessage: (text: string) => { + console.log("Retry send:", text); + return true; + }, + }, +}; + +// --------------------------------------------------------------------------- +// 5. Long markdown — exercises prose rendering +// --------------------------------------------------------------------------- + +const LONG_MARKDOWN = + "# Content Migration Guide\n\n" + + "## Overview\n\n" + + "This guide walks through the steps to migrate your content from the legacy format to the new Val schema system.\n\n" + + "## Prerequisites\n\n" + + "- Node.js 18 or later\n" + + "- Access to the Val dashboard\n" + + "- Your project's `val.config.ts` file\n\n" + + "## Steps\n\n" + + "### 1. Install the CLI\n\n" + + "```bash\nnpx @valbuild/cli init\n```\n\n" + + "### 2. Define your schema\n\n" + + "Create a `.val.ts` file for each content module:\n\n" + + "```typescript\nimport { s, val } from '@valbuild/core';\n\n" + + "export const blogPosts = val.content(\n" + + ' "/content/blog",\n' + + " s.array(\n" + + " s.object({\n" + + " title: s.string().min(1).max(200),\n" + + " slug: s.string(),\n" + + " publishedAt: s.string(),\n" + + " excerpt: s.string().optional(),\n" + + " body: s.richtext(),\n" + + " tags: s.array(s.string()),\n" + + " coverImage: s.image().optional(),\n" + + " })\n" + + " )\n" + + ");\n```\n\n" + + "### 3. Run the migration\n\n" + + "```bash\nnpx @valbuild/cli migrate --dry-run\n```\n\n" + + "> **Note:** Always use `--dry-run` first to preview changes before applying them.\n\n" + + "### 4. Verify\n\n" + + "Check that all content validates against the new schema:\n\n" + + "| Status | Count | Description |\n" + + "|--------|-------|-------------|\n" + + "| ✅ Valid | 42 | Content matches schema |\n" + + "| ⚠️ Warning | 3 | Optional fields missing |\n" + + "| ❌ Error | 0 | No errors found |\n\n" + + "## Troubleshooting\n\n" + + "If you encounter `SchemaError: unexpected field`, make sure your content files don't contain extra properties " + + "not defined in the schema. You can use `s.record(s.unknown())` as a temporary escape hatch while migrating.\n\n" + + "---\n\n" + + "*Last updated: March 2026*"; + +export const LongMarkdown: Story = { + args: { + initialMessages: [ + { + id: "md-user-1", + role: "user", + content: "Write a content migration guide", + status: "complete", + }, + { + id: "md-assistant-1", + role: "assistant", + content: LONG_MARKDOWN, + status: "complete", + }, + ], + onSendMessage: (text: string) => { + console.log("Send:", text); + return true; + }, + }, +}; + +// --------------------------------------------------------------------------- +// 6. Interactive — full send → stream → complete cycle +// --------------------------------------------------------------------------- + +function InteractiveDemo() { + const chatRef = useRef(null); + + const handleSend = (): boolean => { + if (!chatRef.current) return false; + + const assistantId = `interactive-${Date.now()}`; + chatRef.current.startAssistantMessage(assistantId); + + const response = + "Thanks for your message! I've processed your request.\n\n" + + "Here's what I found:\n" + + "- Your content is **up to date**\n" + + "- No validation errors detected\n" + + "- All patches have been applied successfully\n\n" + + "Is there anything else I can help with?"; + + let idx = 0; + const interval = setInterval(() => { + if (!chatRef.current) return; + const chunkSize = 2 + Math.floor(Math.random() * 3); + const chunk = response.slice(idx, idx + chunkSize); + if (chunk) { + chatRef.current.appendAssistantChunk(assistantId, chunk); + idx += chunkSize; + } else { + chatRef.current.completeAssistantMessage(assistantId); + clearInterval(interval); + } + }, 30); + return true; + }; + + return ( + + ); +} + +export const Interactive: Story = { + render: () => , +}; diff --git a/packages/ui/spa/components/AIChat.tsx b/packages/ui/spa/components/AIChat.tsx new file mode 100644 index 000000000..2cd1a4f59 --- /dev/null +++ b/packages/ui/spa/components/AIChat.tsx @@ -0,0 +1,1201 @@ +import React, { + useState, + useRef, + useEffect, + useCallback, + useImperativeHandle, + forwardRef, +} from "react"; +import ReactMarkdown from "react-markdown"; +import { ScrollArea } from "./designSystem/scroll-area"; +import { Button } from "./designSystem/button"; +import { cn } from "./designSystem/cn"; +import { + Send, + RotateCcw, + Sparkles, + Check, + Loader2, + LogIn, + Search, + FileText, + Database, + ShieldCheck, + Pencil, + XCircle, + Plus, + Navigation, + User, + Clock, + History, + ChevronLeft, + Tag, + Paperclip, + X, +} from "lucide-react"; +import type { AISession } from "../hooks/useAIWebSocket"; +import type { AIContentBlock, AIMessageContent } from "./ValProvider"; +import { ToolName } from "../utils/toolNames"; +import { useValConfig } from "./ValFieldProvider"; +import { DEFAULT_APP_HOST } from "@valbuild/core"; +import { urlOf } from "@valbuild/shared/internal"; +import { CopyableCodeBlock } from "./designSystem/CopyableCodeBlock"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export type ChatMessageStatus = "complete" | "streaming" | "error"; + +export type ToolActivityStatus = "pending" | "complete" | "error"; + +export type ToolActivity = { + toolCallId: string; + name: string; + status: ToolActivityStatus; +}; + +export type ChatMessageAttachment = { + key: string; + name: string; + mimeType?: string; + previewUrl?: string; +}; + +export type ChatMessage = { + id: string; + role: "user" | "assistant"; + content: AIMessageContent; + status: ChatMessageStatus; + error?: string; + errorCode?: string; + toolActivities?: ToolActivity[]; + attachments?: ChatMessageAttachment[]; +}; + +type AttachedFile = { + id: string; + file: File; + status: "uploading" | "done" | "error"; + key?: string; + previewUrl?: string; +}; + +type CurrentMessage = { + message: ChatMessage; + startedAt: number; +}; + +export type AIChatHandle = { + /** Create a new empty assistant message in streaming state */ + startAssistantMessage: (id: string) => void; + /** Append a token/chunk to the assistant message with the given id */ + appendAssistantChunk: (id: string, chunk: string) => void; + /** Mark the assistant message as complete */ + completeAssistantMessage: (id: string) => void; + /** Mark the assistant message as errored */ + errorAssistantMessage: (id: string, error: string, code?: string) => void; + /** Add a tool call indicator to the current assistant message */ + addToolCall: ( + messageId: string, + toolCallId: string, + toolName: string, + ) => void; + /** Mark a tool call as complete */ + completeToolCall: (messageId: string, toolCallId: string) => void; + /** Mark a tool call as errored */ + errorToolCall: (messageId: string, toolCallId: string) => void; + /** Clear all messages (used when starting a new session) */ + clearMessages: () => void; + /** Bulk-load historical messages (e.g. when restoring a session) */ + loadMessages: (messages: ChatMessage[]) => void; +}; + +export type AIChatProps = { + /** Called when the user submits a message (via input or suggestion chip). Returns true if sent successfully. */ + onSendMessage?: ( + text: string, + attachments?: ChatMessageAttachment[], + ) => boolean; + /** Called to upload a file to the current AI session. Returns the server key. */ + onUploadFile?: (file: File) => Promise<{ key: string }>; + /** Called when the user clicks "New Chat" to start a fresh session */ + onNewSession?: () => void; + /** Prompt suggestion chips shown on the empty state */ + suggestions?: string[]; + /** Extra class names on the root container */ + className?: string; + /** Whether the underlying WebSocket connection is ready */ + isConnected: boolean; + /** Set when /ai/initialize returned 401 — the user needs to authenticate */ + authError: boolean; + /** Val server mode — controls which auth instructions to show on authError */ + mode: "http" | "fs" | "unknown"; + /** List of past sessions (fetched on demand) */ + sessions?: AISession[]; + /** The currently active session ID */ + currentSessionId?: string; + /** Called to load a previous session */ + onLoadSession?: (sessionId: string) => void; + /** Called to trigger a sessions fetch */ + onFetchSessions?: () => void; + /** Called to rename a session */ + onSetSessionName?: (sessionId: string, name: string) => void; + /** + * @internal – seed messages for Storybook / testing only. + * Not part of the public API. + */ + initialMessages?: ChatMessage[]; +}; + +// --------------------------------------------------------------------------- +// Defaults +// --------------------------------------------------------------------------- + +const DEFAULT_SUGGESTIONS = [ + "Summarize recent changes", + "What am I looking at?", + "Fix typos", +]; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +let _msgId = 0; +function nextId(): string { + return `chat-${++_msgId}-${Date.now()}`; +} + +function getTextContent(content: AIMessageContent): string { + if (typeof content === "string") { + return content; + } + return content + .filter( + (block): block is Extract => + block.type === "text", + ) + .map((block) => block.text) + .join("\n\n"); +} + +function getImageUrls(content: AIMessageContent): string[] { + if (typeof content === "string") { + return []; + } + return content + .filter( + (block): block is Extract => + block.type === "image_url", + ) + .map((block) => block.url); +} + +// --------------------------------------------------------------------------- +// Component +// --------------------------------------------------------------------------- + +export const AIChat = forwardRef(function AIChat( + { + onSendMessage, + onUploadFile, + onNewSession, + suggestions = DEFAULT_SUGGESTIONS, + className, + isConnected, + authError, + mode, + sessions, + currentSessionId, + onLoadSession, + onFetchSessions, + onSetSessionName, + initialMessages, + }, + ref, +) { + const [completedMessages, setCompletedMessages] = useState( + initialMessages ?? [], + ); + const [currentMessage, setCurrentMessage] = useState( + null, + ); + const [inputValue, setInputValue] = useState(""); + const [attachedFiles, setAttachedFiles] = useState([]); + const fileInputRef = useRef(null); + const textareaRef = useRef(null); + const bottomRef = useRef(null); + const [showSessions, setShowSessions] = useState(false); + const [renamingSessionId, setRenamingSessionId] = useState( + null, + ); + const [renameValue, setRenameValue] = useState(""); + const config = useValConfig(); + const effectiveSuggestions = config?.ai?.chat?.suggestions ?? suggestions; + const emptyTitle = config?.ai?.chat?.title; + const emptyDescription = config?.ai?.chat?.description; + + // Derive combined list for rendering + const messages: ChatMessage[] = currentMessage + ? [...completedMessages, currentMessage.message] + : completedMessages; + + // Auto-scroll to bottom whenever messages change + useEffect(() => { + requestAnimationFrame(() => { + bottomRef.current?.scrollIntoView({ block: "end" }); + }); + }, [messages]); + + // 2-minute timeout for in-progress assistant messages + useEffect(() => { + if (!currentMessage) return; + const remaining = 2 * 60 * 1000 - (Date.now() - currentMessage.startedAt); + if (remaining <= 0) { + setCompletedMessages((prev) => [ + ...prev, + { + ...currentMessage.message, + status: "error", + error: "Response timed out", + }, + ]); + setCurrentMessage(null); + return; + } + const timer = setTimeout(() => { + setCurrentMessage((prev) => { + if (!prev) return null; + setCompletedMessages((msgs) => [ + ...msgs, + { ...prev.message, status: "error", error: "Response timed out" }, + ]); + return null; + }); + }, remaining); + return () => clearTimeout(timer); + }, [currentMessage]); + + // ---- Imperative handle for WebSocket layer ---- + + useImperativeHandle(ref, () => ({ + startAssistantMessage(id: string) { + setCurrentMessage({ + message: { id, role: "assistant", content: "", status: "streaming" }, + startedAt: Date.now(), + }); + }, + appendAssistantChunk(id: string, chunk: string) { + setCurrentMessage((prev) => + prev?.message.id === id + ? { + ...prev, + message: { + ...prev.message, + content: getTextContent(prev.message.content) + chunk, + }, + } + : prev, + ); + }, + completeAssistantMessage(id: string) { + setCurrentMessage((prev) => { + if (!prev || prev.message.id !== id) return prev; + setCompletedMessages((msgs) => [ + ...msgs, + { ...prev.message, status: "complete" }, + ]); + return null; + }); + }, + errorAssistantMessage(id: string, error: string, code?: string) { + setCurrentMessage((prev) => { + if (!prev || prev.message.id !== id) return prev; + setCompletedMessages((msgs) => [ + ...msgs, + { ...prev.message, status: "error", error, errorCode: code }, + ]); + return null; + }); + }, + addToolCall(messageId: string, toolCallId: string, toolName: string) { + const activity: ToolActivity = { + toolCallId, + name: toolName, + status: "pending", + }; + setCurrentMessage((prev) => { + if (prev && prev.message.id === messageId) { + return { + ...prev, + message: { + ...prev.message, + toolActivities: [ + ...(prev.message.toolActivities ?? []), + activity, + ], + }, + }; + } + return prev; + }); + }, + completeToolCall(messageId: string, toolCallId: string) { + setCurrentMessage((prev) => { + if (!prev || prev.message.id !== messageId) return prev; + return { + ...prev, + message: { + ...prev.message, + toolActivities: (prev.message.toolActivities ?? []).map((t) => + t.toolCallId === toolCallId + ? { ...t, status: "complete" as const } + : t, + ), + }, + }; + }); + }, + errorToolCall(messageId: string, toolCallId: string) { + setCurrentMessage((prev) => { + if (!prev || prev.message.id !== messageId) return prev; + return { + ...prev, + message: { + ...prev.message, + toolActivities: (prev.message.toolActivities ?? []).map((t) => + t.toolCallId === toolCallId + ? { ...t, status: "error" as const } + : t, + ), + }, + }; + }); + }, + clearMessages() { + setCompletedMessages([]); + setCurrentMessage(null); + }, + loadMessages(messages: ChatMessage[]) { + setCurrentMessage(null); + setCompletedMessages(messages); + }, + })); + + // ---- Derived state ---- + + const isStreaming = currentMessage !== null; + const isUploading = attachedFiles.some((f) => f.status === "uploading"); + const isEmpty = messages.length === 0; + + // ---- Handlers ---- + + const handleFileChange = useCallback( + (e: React.ChangeEvent) => { + const files = Array.from(e.target.files ?? []); + if (files.length === 0) return; + // Reset input so the same file can be re-selected + e.target.value = ""; + + const newEntries: AttachedFile[] = files.map((file) => ({ + id: crypto.randomUUID(), + file, + status: "uploading" as const, + previewUrl: file.type.startsWith("image/") + ? URL.createObjectURL(file) + : undefined, + })); + + setAttachedFiles((prev) => [...prev, ...newEntries]); + + if (onUploadFile) { + newEntries.forEach((entry) => { + onUploadFile(entry.file) + .then(({ key }) => { + setAttachedFiles((prev) => + prev.map((f) => + f.id === entry.id ? { ...f, status: "done", key } : f, + ), + ); + }) + .catch((err) => { + console.error("Failed to upload file", { + fileName: entry.file.name, + error: err, + }); + setAttachedFiles((prev) => + prev.map((f) => + f.id === entry.id ? { ...f, status: "error" } : f, + ), + ); + }); + }); + } + }, + [onUploadFile], + ); + + const removeAttachedFile = useCallback((id: string) => { + setAttachedFiles((prev) => { + const file = prev.find((f) => f.id === id); + if (file?.previewUrl) URL.revokeObjectURL(file.previewUrl); + return prev.filter((f) => f.id !== id); + }); + }, []); + + const handleSend = useCallback( + (text?: string) => { + const content = (text ?? inputValue).trim(); + if (!content || isStreaming) return; + + const doneAttachments: ChatMessageAttachment[] = attachedFiles + .filter( + (f): f is AttachedFile & { key: string } => + f.status === "done" && f.key !== undefined, + ) + .map((f) => ({ + key: f.key, + name: f.file.name, + mimeType: f.file.type || undefined, + previewUrl: f.previewUrl, + })); + + // Revoke object URLs for files we're sending (they'll be in the message) + attachedFiles.forEach((f) => { + if (f.previewUrl && f.status !== "done") + URL.revokeObjectURL(f.previewUrl); + }); + setAttachedFiles([]); + + const msgId = nextId(); + const userMsg: ChatMessage = { + id: msgId, + role: "user", + content, + status: "complete", + attachments: doneAttachments.length > 0 ? doneAttachments : undefined, + }; + setCompletedMessages((prev) => [...prev, userMsg]); + setInputValue(""); + + const sent = onSendMessage + ? onSendMessage( + content, + doneAttachments.length > 0 ? doneAttachments : undefined, + ) + : true; + if (!sent) { + setCompletedMessages((prev) => + prev.map((m) => + m.id === msgId + ? { ...m, status: "error", error: "Failed to send" } + : m, + ), + ); + } + + // Refocus textarea after send + requestAnimationFrame(() => textareaRef.current?.focus()); + }, + [inputValue, isStreaming, attachedFiles, onSendMessage], + ); + + const handleRetry = useCallback( + (errorMsgId: string) => { + const errorMsg = messages.find((m) => m.id === errorMsgId); + + // Retry a failed user message (WebSocket send error) + if (errorMsg?.role === "user") { + setCompletedMessages((prev) => + prev.map((m) => + m.id === errorMsgId + ? { ...m, status: "complete", error: undefined } + : m, + ), + ); + const retryText = getTextContent(errorMsg.content); + const sent = onSendMessage + ? onSendMessage(retryText, errorMsg.attachments) + : true; + if (!sent) { + setCompletedMessages((prev) => + prev.map((m) => + m.id === errorMsgId + ? { ...m, status: "error", error: "Failed to send" } + : m, + ), + ); + } + return; + } + + // Find the user message right before the errored assistant message + const idx = messages.findIndex((m) => m.id === errorMsgId); + if (idx <= 0) return; + + const prevUserMsg = messages + .slice(0, idx) + .reverse() + .find((m) => m.role === "user"); + if (!prevUserMsg) return; + + // Remove the errored assistant message + setCompletedMessages((prev) => prev.filter((m) => m.id !== errorMsgId)); + onSendMessage?.( + getTextContent(prevUserMsg.content), + prevUserMsg.attachments, + ); + }, + [messages, onSendMessage], + ); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }, + [handleSend], + ); + + // ---- Render ---- + + return ( +
+ {/* Header with New Chat + History buttons */} + {(!isEmpty || onFetchSessions) && ( +
+
+ {onFetchSessions && ( + + )} +
+
+ {!isEmpty && onNewSession && ( + + )} +
+
+ )} + + {/* Sessions panel overlay */} + {showSessions && ( +
+
+ + Chat history + {onNewSession && ( + + )} +
+ + {!sessions || sessions.length === 0 ? ( +
+ No previous sessions +
+ ) : ( +
+ {sessions.map((session) => { + const isActive = session.id === currentSessionId; + const isRenaming = renamingSessionId === session.id; + const displayName = + session.name ?? + `Chat, ${new Date(session.updatedAt).toLocaleDateString(undefined, { month: "short", day: "numeric" })}`; + return ( +
+ {isRenaming ? ( + setRenameValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + const trimmed = renameValue.trim(); + if (trimmed && onSetSessionName) { + onSetSessionName(session.id, trimmed); + } + setRenamingSessionId(null); + } else if (e.key === "Escape") { + setRenamingSessionId(null); + } + }} + onBlur={() => { + const trimmed = renameValue.trim(); + if (trimmed && onSetSessionName) { + onSetSessionName(session.id, trimmed); + } + setRenamingSessionId(null); + }} + /> + ) : ( + + )} + {!isRenaming && ( + + )} +
+ ); + })} +
+ )} +
+
+ )} + + {/* Message list */} + +
+ {authError ? ( + + ) : isEmpty ? ( + handleSend(s)} + /> + ) : ( + messages.map((msg) => ( + + )) + )} +
+
+ + + {/* Input area */} +
+ {!isConnected && !authError && ( +
+ + Connecting… +
+ )} + {/* Attached file previews */} + {attachedFiles.length > 0 && ( +
+ {attachedFiles.map((f) => ( +
+ {f.previewUrl ? ( + {f.file.name} + ) : ( + + )} + {f.file.name} + {f.status === "uploading" && ( + + )} + {f.status === "error" && ( + + )} + +
+ ))} +
+ )} + {onUploadFile && ( + + )} +
+ {onUploadFile && ( + + )} +
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+