From 4ed5ec432a44476f62e7c0b7758f121380b46c53 Mon Sep 17 00:00:00 2001 From: Code Date: Wed, 22 Jan 2025 20:49:04 +0100 Subject: [PATCH] init --- .github/workflows/release.yml | 39 + .gitignore | 11 +- .npmrc | 2 + .vscode/settings.json | 7 + LICENSE | 9 - README.md | 193 +- eslint.config.js | 93 + package.json | 55 +- packages/commons/.gitignore | 39 + packages/commons/.npmignore | 5 + packages/commons/LICENSE | 29 + packages/commons/README.md | 2 + packages/commons/eslint.config.js | 95 + packages/commons/package.json | 57 + .../commons/src/index.ts | 0 packages/commons/src/shemas/index.ts | 218 + packages/commons/src/shemas/openapi.ts | 20 + packages/commons/src/shemas/path.ts | 256 + packages/commons/src/shemas/types.ts | 187 + packages/commons/src/shemas/vfs.ts | 1 + packages/commons/src/shemas/zod_map.ts | 112 + packages/commons/tsconfig.json | 13 + packages/commons/tsup.config.ts | 12 + packages/core/.gitignore | 39 + packages/core/.kbot/completion.json | 44 + packages/core/.kbot/content.json | 8 + packages/core/.kbot/openai-message.json | 41 + packages/core/.kbot/params.json | 471 + packages/core/.kbot/tool-call-result.json | 15 + packages/core/.kbot/tool-call.json | 22 + packages/core/.npmignore | 5 + packages/core/LICENSE | 29 + packages/core/README.md | 2 + packages/core/eslint.config.js | 95 + packages/core/package.json | 63 + packages/core/src/arrays.ts | 891 ++ packages/core/src/arraysFind.ts | 202 + packages/core/src/aspects.ts | 348 + packages/core/src/aspects_simple.ts | 232 + packages/core/src/assert.ts | 71 + packages/core/src/cache.ts | 120 + packages/core/src/cancellation.ts | 148 + packages/core/src/charCode.ts | 422 + packages/core/src/collections.ts | 140 + packages/core/src/constants.ts | 6 + packages/core/src/equals.ts | 146 + packages/core/src/errors.ts | 326 + packages/core/src/event.ts | 1787 ++++ packages/core/src/extpath.ts | 423 + packages/core/src/functional.ts | 32 + packages/core/src/index.ts | 33 + packages/core/src/iterator.ts | 262 + packages/core/src/labels.ts | 463 + packages/core/src/lazy.ts | 47 + packages/core/src/lifecycle.ts | 829 ++ packages/core/src/linkedList.ts | 142 + packages/core/src/map.ts | 952 ++ packages/core/src/marshallingIds.ts | 29 + packages/core/src/mime.ts | 126 + packages/core/src/network.ts | 408 + packages/core/src/nls.messages.ts | 19 + packages/core/src/nls.ts | 240 + packages/core/src/objects.ts | 275 + packages/core/src/observable.ts | 8 + packages/core/src/observableInternal/api.ts | 30 + .../core/src/observableInternal/autorun.ts | 327 + packages/core/src/observableInternal/base.ts | 522 + .../commonFacade/cancellation.ts | 7 + .../observableInternal/commonFacade/deps.ts | 10 + .../core/src/observableInternal/debugName.ts | 145 + .../core/src/observableInternal/derived.ts | 495 + packages/core/src/observableInternal/index.ts | 29 + .../observableInternal/lazyObservableValue.ts | 148 + .../core/src/observableInternal/logging.ts | 407 + .../core/src/observableInternal/promise.ts | 117 + packages/core/src/observableInternal/utils.ts | 664 ++ .../observableInternal/utilsCancellation.ts | 98 + packages/core/src/path.ts | 1529 +++ packages/core/src/platform.ts | 280 + packages/core/src/primitives.ts | 200 + packages/core/src/process.ts | 76 + packages/core/src/resources.ts | 444 + packages/core/src/sequence.ts | 34 + packages/core/src/set.ts | 38 + packages/core/src/stopwatch.ts | 43 + packages/core/src/strings.ts | 1334 +++ packages/core/src/symbols.ts | 9 + packages/core/src/types.ts | 299 + packages/core/src/uint.ts | 59 + packages/core/src/uri.ts | 750 ++ packages/core/src/utils.ts | 443 + packages/core/src/uuid.ts | 120 + packages/core/tsconfig.json | 13 + packages/core/tsup.config.ts | 12 + packages/eslint-config/README.md | 3 + packages/eslint-config/library.js | 35 + packages/eslint-config/package.json | 17 + packages/eslint-config/react.js | 39 + packages/eslint-config/storybook.js | 45 + packages/fs/.editorconfig | 12 + packages/fs/.gitignore | 4 + .npmignore => packages/fs/.npmignore | 3 +- packages/fs/.travis.yml | 10 + packages/fs/.vscode/launch.json | 24 + packages/fs/.vscode/settings.json | 5 + packages/fs/CHANGELOG.md | 103 + packages/fs/EXAMPLES.md | 82 + packages/fs/LICENSE | 28 + packages/fs/README.md | 20 + packages/fs/eslint.config.js | 138 + packages/fs/package.json | 78 + packages/fs/scripts/docs.sh | 4 + packages/fs/scripts/test.sh | 4 + packages/fs/scripts/update.sh | 6 + packages/fs/src/append.ts | 54 + packages/fs/src/copy.ts | 673 ++ packages/fs/src/dir.ts | 198 + packages/fs/src/errors.ts | 45 + packages/fs/src/exists.ts | 52 + packages/fs/src/file.ts | 167 + packages/fs/src/find.ts | 132 + packages/fs/src/imports.ts | 8 + packages/fs/src/index.ts | 328 + packages/fs/src/inspect.ts | 137 + packages/fs/src/inspect_tree.ts | 143 + packages/fs/src/interfaces.ts | 527 + packages/fs/src/iterator.ts | 77 + packages/fs/src/list.ts | 66 + packages/fs/src/main.ts | 11 + packages/fs/src/move.ts | 104 + packages/fs/src/promisify.ts | 44 + packages/fs/src/read.ts | 91 + packages/fs/src/remove.ts | 339 + packages/fs/src/rename.ts | 23 + packages/fs/src/stats.ts | 18 + packages/fs/src/streams.ts | 2 + packages/fs/src/symlink.ts | 50 + packages/fs/src/util.ts | 2 + packages/fs/src/utils/dot.ts | 10 + packages/fs/src/utils/fs.ts | 43 + packages/fs/src/utils/matcher.ts | 79 + packages/fs/src/utils/mime_match.ts | 19 + packages/fs/src/utils/mode.ts | 10 + packages/fs/src/utils/paths.ts | 47 + packages/fs/src/utils/platform.ts | 40 + packages/fs/src/utils/stats.ts | 98 + packages/fs/src/utils/strings.ts | 15 + packages/fs/src/utils/tree_walker.ts | 120 + packages/fs/src/utils/validate.ts | 123 + packages/fs/src/utils/wildcard.ts | 62 + packages/fs/src/write.ts | 91 + packages/fs/tsconfig.json | 13 + packages/fs/tslint.json | 21 + packages/typescript-config/base.json | 13 + packages/typescript-config/package.json | 9 + packages/typescript-config/react-app.json | 16 + packages/typescript-config/react-library.json | 10 + pnpm-lock.yaml | 8660 +++++++++++++++++ pnpm-workspace.yaml | 2 + tsconfig.base.json | 15 + tsconfig.eslint.json | 6 + turbo.json | 20 + 162 files changed, 33536 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .npmrc create mode 100644 .vscode/settings.json delete mode 100644 LICENSE create mode 100644 eslint.config.js create mode 100644 packages/commons/.gitignore create mode 100644 packages/commons/.npmignore create mode 100644 packages/commons/LICENSE create mode 100644 packages/commons/README.md create mode 100644 packages/commons/eslint.config.js create mode 100644 packages/commons/package.json rename src/.gitignore => packages/commons/src/index.ts (100%) create mode 100644 packages/commons/src/shemas/index.ts create mode 100644 packages/commons/src/shemas/openapi.ts create mode 100644 packages/commons/src/shemas/path.ts create mode 100644 packages/commons/src/shemas/types.ts create mode 100644 packages/commons/src/shemas/vfs.ts create mode 100644 packages/commons/src/shemas/zod_map.ts create mode 100644 packages/commons/tsconfig.json create mode 100644 packages/commons/tsup.config.ts create mode 100644 packages/core/.gitignore create mode 100644 packages/core/.kbot/completion.json create mode 100644 packages/core/.kbot/content.json create mode 100644 packages/core/.kbot/openai-message.json create mode 100644 packages/core/.kbot/params.json create mode 100644 packages/core/.kbot/tool-call-result.json create mode 100644 packages/core/.kbot/tool-call.json create mode 100644 packages/core/.npmignore create mode 100644 packages/core/LICENSE create mode 100644 packages/core/README.md create mode 100644 packages/core/eslint.config.js create mode 100644 packages/core/package.json create mode 100644 packages/core/src/arrays.ts create mode 100644 packages/core/src/arraysFind.ts create mode 100644 packages/core/src/aspects.ts create mode 100644 packages/core/src/aspects_simple.ts create mode 100644 packages/core/src/assert.ts create mode 100644 packages/core/src/cache.ts create mode 100644 packages/core/src/cancellation.ts create mode 100644 packages/core/src/charCode.ts create mode 100644 packages/core/src/collections.ts create mode 100644 packages/core/src/constants.ts create mode 100644 packages/core/src/equals.ts create mode 100644 packages/core/src/errors.ts create mode 100644 packages/core/src/event.ts create mode 100644 packages/core/src/extpath.ts create mode 100644 packages/core/src/functional.ts create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/src/iterator.ts create mode 100644 packages/core/src/labels.ts create mode 100644 packages/core/src/lazy.ts create mode 100644 packages/core/src/lifecycle.ts create mode 100644 packages/core/src/linkedList.ts create mode 100644 packages/core/src/map.ts create mode 100644 packages/core/src/marshallingIds.ts create mode 100644 packages/core/src/mime.ts create mode 100644 packages/core/src/network.ts create mode 100644 packages/core/src/nls.messages.ts create mode 100644 packages/core/src/nls.ts create mode 100644 packages/core/src/objects.ts create mode 100644 packages/core/src/observable.ts create mode 100644 packages/core/src/observableInternal/api.ts create mode 100644 packages/core/src/observableInternal/autorun.ts create mode 100644 packages/core/src/observableInternal/base.ts create mode 100644 packages/core/src/observableInternal/commonFacade/cancellation.ts create mode 100644 packages/core/src/observableInternal/commonFacade/deps.ts create mode 100644 packages/core/src/observableInternal/debugName.ts create mode 100644 packages/core/src/observableInternal/derived.ts create mode 100644 packages/core/src/observableInternal/index.ts create mode 100644 packages/core/src/observableInternal/lazyObservableValue.ts create mode 100644 packages/core/src/observableInternal/logging.ts create mode 100644 packages/core/src/observableInternal/promise.ts create mode 100644 packages/core/src/observableInternal/utils.ts create mode 100644 packages/core/src/observableInternal/utilsCancellation.ts create mode 100644 packages/core/src/path.ts create mode 100644 packages/core/src/platform.ts create mode 100644 packages/core/src/primitives.ts create mode 100644 packages/core/src/process.ts create mode 100644 packages/core/src/resources.ts create mode 100644 packages/core/src/sequence.ts create mode 100644 packages/core/src/set.ts create mode 100644 packages/core/src/stopwatch.ts create mode 100644 packages/core/src/strings.ts create mode 100644 packages/core/src/symbols.ts create mode 100644 packages/core/src/types.ts create mode 100644 packages/core/src/uint.ts create mode 100644 packages/core/src/uri.ts create mode 100644 packages/core/src/utils.ts create mode 100644 packages/core/src/uuid.ts create mode 100644 packages/core/tsconfig.json create mode 100644 packages/core/tsup.config.ts create mode 100644 packages/eslint-config/README.md create mode 100644 packages/eslint-config/library.js create mode 100644 packages/eslint-config/package.json create mode 100644 packages/eslint-config/react.js create mode 100644 packages/eslint-config/storybook.js create mode 100644 packages/fs/.editorconfig create mode 100644 packages/fs/.gitignore rename .npmignore => packages/fs/.npmignore (58%) create mode 100644 packages/fs/.travis.yml create mode 100644 packages/fs/.vscode/launch.json create mode 100644 packages/fs/.vscode/settings.json create mode 100644 packages/fs/CHANGELOG.md create mode 100644 packages/fs/EXAMPLES.md create mode 100644 packages/fs/LICENSE create mode 100644 packages/fs/README.md create mode 100644 packages/fs/eslint.config.js create mode 100644 packages/fs/package.json create mode 100644 packages/fs/scripts/docs.sh create mode 100644 packages/fs/scripts/test.sh create mode 100644 packages/fs/scripts/update.sh create mode 100644 packages/fs/src/append.ts create mode 100644 packages/fs/src/copy.ts create mode 100644 packages/fs/src/dir.ts create mode 100644 packages/fs/src/errors.ts create mode 100644 packages/fs/src/exists.ts create mode 100644 packages/fs/src/file.ts create mode 100644 packages/fs/src/find.ts create mode 100644 packages/fs/src/imports.ts create mode 100644 packages/fs/src/index.ts create mode 100644 packages/fs/src/inspect.ts create mode 100644 packages/fs/src/inspect_tree.ts create mode 100644 packages/fs/src/interfaces.ts create mode 100644 packages/fs/src/iterator.ts create mode 100644 packages/fs/src/list.ts create mode 100644 packages/fs/src/main.ts create mode 100644 packages/fs/src/move.ts create mode 100644 packages/fs/src/promisify.ts create mode 100644 packages/fs/src/read.ts create mode 100644 packages/fs/src/remove.ts create mode 100644 packages/fs/src/rename.ts create mode 100644 packages/fs/src/stats.ts create mode 100644 packages/fs/src/streams.ts create mode 100644 packages/fs/src/symlink.ts create mode 100644 packages/fs/src/util.ts create mode 100644 packages/fs/src/utils/dot.ts create mode 100644 packages/fs/src/utils/fs.ts create mode 100644 packages/fs/src/utils/matcher.ts create mode 100644 packages/fs/src/utils/mime_match.ts create mode 100644 packages/fs/src/utils/mode.ts create mode 100644 packages/fs/src/utils/paths.ts create mode 100644 packages/fs/src/utils/platform.ts create mode 100644 packages/fs/src/utils/stats.ts create mode 100644 packages/fs/src/utils/strings.ts create mode 100644 packages/fs/src/utils/tree_walker.ts create mode 100644 packages/fs/src/utils/validate.ts create mode 100644 packages/fs/src/utils/wildcard.ts create mode 100644 packages/fs/src/write.ts create mode 100644 packages/fs/tsconfig.json create mode 100644 packages/fs/tslint.json create mode 100644 packages/typescript-config/base.json create mode 100644 packages/typescript-config/package.json create mode 100644 packages/typescript-config/react-app.json create mode 100644 packages/typescript-config/react-library.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 tsconfig.base.json create mode 100644 tsconfig.eslint.json create mode 100644 turbo.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..47c54b31 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Release + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Setup Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Dependencies + run: yarn + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@v1 + with: + # This expects you to have a script called release which does a build for your packages and calls changeset publish + publish: yarn release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Send a Slack notification if a publish happens + if: steps.changesets.outputs.published == 'true' + # You can do something when a publish happens. + run: my-slack-bot send-notification --message "A new version of ${GITHUB_REPOSITORY} was published!" diff --git a/.gitignore b/.gitignore index cab85ca2..31065a94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ -/node_modules -/coverage -*.log .DS_Store +node_modules +.turbo +*.log +.next +*.local +.env +.cache +storybook-static/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..4e8955b1 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +auto-install-peers = true +public-hoist-pattern[]=*storybook* diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..44a73ec3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ] +} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b0e20f53..00000000 --- a/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -Copyright (c) All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index dc27f0f2..e998f547 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,192 @@ -# osr-package-template +# Turborepo Design System Starter -Package basics \ No newline at end of file +This guide explains how to use a React design system starter powered by: + +- 🏎 [Turborepo](https://turbo.build/repo) — High-performance build system for Monorepos +- 🚀 [React](https://reactjs.org/) — JavaScript library for user interfaces +- 🛠 [Tsup](https://github.com/egoist/tsup) — TypeScript bundler powered by esbuild +- 📖 [Storybook](https://storybook.js.org/) — UI component environment powered by Vite + +As well as a few others tools preconfigured: + +- [TypeScript](https://www.typescriptlang.org/) for static type checking +- [ESLint](https://eslint.org/) for code linting +- [Prettier](https://prettier.io) for code formatting +- [Changesets](https://github.com/changesets/changesets) for managing versioning and changelogs +- [GitHub Actions](https://github.com/changesets/action) for fully automated package publishing + +## Using this example + +Run the following command: + +```sh +npx create-turbo@latest -e design-system +``` + +### Useful Commands + +- `pnpm build` - Build all packages, including the Storybook site +- `pnpm dev` - Run all packages locally and preview with Storybook +- `pnpm lint` - Lint all packages +- `pnpm changeset` - Generate a changeset +- `pnpm clean` - Clean up all `node_modules` and `dist` folders (runs each package's clean script) + +## Turborepo + +[Turborepo](https://turbo.build/repo) is a high-performance build system for JavaScript and TypeScript codebases. It was designed after the workflows used by massive software engineering organizations to ship code at scale. Turborepo abstracts the complex configuration needed for monorepos and provides fast, incremental builds with zero-configuration remote caching. + +Using Turborepo simplifies managing your design system monorepo, as you can have a single lint, build, test, and release process for all packages. [Learn more](https://vercel.com/blog/monorepos-are-changing-how-teams-build-software) about how monorepos improve your development workflow. + +## Apps & Packages + +This Turborepo includes the following packages and applications: + +- `apps/docs`: Component documentation site with Storybook +- `packages/ui`: Core React components +- `packages/utils`: Shared React utilities +- `packages/typescript-config`: Shared `tsconfig.json`s used throughout the Turborepo +- `packages/eslint-config`: ESLint preset + +Each package and app is 100% [TypeScript](https://www.typescriptlang.org/). Workspaces enables us to "hoist" dependencies that are shared between packages to the root `package.json`. This means smaller `node_modules` folders and a better local dev experience. To install a dependency for the entire monorepo, use the `-w` workspaces flag with `pnpm add`. + +This example sets up your `.gitignore` to exclude all generated files, other folders like `node_modules` used to store your dependencies. + +### Compilation + +To make the core library code work across all browsers, we need to compile the raw TypeScript and React code to plain JavaScript. We can accomplish this with `tsup`, which uses `esbuild` to greatly improve performance. + +Running `pnpm build` from the root of the Turborepo will run the `build` command defined in each package's `package.json` file. Turborepo runs each `build` in parallel and caches & hashes the output to speed up future builds. + +For `acme-core`, the `build` command is the following: + +```bash +tsup src/index.tsx --format esm,cjs --dts --external react +``` + +`tsup` compiles `src/index.tsx`, which exports all of the components in the design system, into both ES Modules and CommonJS formats as well as their TypeScript types. The `package.json` for `acme-core` then instructs the consumer to select the correct format: + +```json:acme-core/package.json +{ + "name": "@acme/core", + "version": "0.0.0", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "sideEffects": false, +} +``` + +Run `pnpm build` to confirm compilation is working correctly. You should see a folder `acme-core/dist` which contains the compiled output. + +```bash +acme-core +└── dist + ├── index.d.ts <-- Types + ├── index.js <-- CommonJS version + └── index.mjs <-- ES Modules version +``` + +## Components + +Each file inside of `acme-core/src` is a component inside our design system. For example: + +```tsx:acme-core/src/Button.tsx +import * as React from 'react'; + +export interface ButtonProps { + children: React.ReactNode; +} + +export function Button(props: ButtonProps) { + return ; +} + +Button.displayName = 'Button'; +``` + +When adding a new file, ensure the component is also exported from the entry `index.tsx` file: + +```tsx:acme-core/src/index.tsx +import * as React from "react"; +export { Button, type ButtonProps } from "./Button"; +// Add new component exports here +``` + +## Storybook + +Storybook provides us with an interactive UI playground for our components. This allows us to preview our components in the browser and instantly see changes when developing locally. This example preconfigures Storybook to: + +- Use Vite to bundle stories instantly (in milliseconds) +- Automatically find any stories inside the `stories/` folder +- Support using module path aliases like `@acme-core` for imports +- Write MDX for component documentation pages + +For example, here's the included Story for our `Button` component: + +```js:apps/docs/stories/button.stories.mdx +import { Button } from '@acme-core/src'; +import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'; + + + +# Button + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec euismod, nisl eget consectetur tempor, nisl nunc egestas nisi, euismod aliquam nisl nunc euismod. + +## Props + + + +## Examples + + + + + + +``` + +This example includes a few helpful Storybook scripts: + +- `pnpm dev`: Starts Storybook in dev mode with hot reloading at `localhost:6006` +- `pnpm build`: Builds the Storybook UI and generates the static HTML files +- `pnpm preview-storybook`: Starts a local server to view the generated Storybook UI + +## Versioning & Publishing Packages + +This example uses [Changesets](https://github.com/changesets/changesets) to manage versions, create changelogs, and publish to npm. It's preconfigured so you can start publishing packages immediately. + +You'll need to create an `NPM_TOKEN` and `GITHUB_TOKEN` and add it to your GitHub repository settings to enable access to npm. It's also worth installing the [Changesets bot](https://github.com/apps/changeset-bot) on your repository. + +### Generating the Changelog + +To generate your changelog, run `pnpm changeset` locally: + +1. **Which packages would you like to include?** – This shows which packages and changed and which have remained the same. By default, no packages are included. Press `space` to select the packages you want to include in the `changeset`. +1. **Which packages should have a major bump?** – Press `space` to select the packages you want to bump versions for. +1. If doing the first major version, confirm you want to release. +1. Write a summary for the changes. +1. Confirm the changeset looks as expected. +1. A new Markdown file will be created in the `changeset` folder with the summary and a list of the packages included. + +### Releasing + +When you push your code to GitHub, the [GitHub Action](https://github.com/changesets/action) will run the `release` script defined in the root `package.json`: + +```bash +turbo run build --filter=docs^... && changeset publish +``` + +Turborepo runs the `build` script for all publishable packages (excluding docs) and publishes the packages to npm. By default, this example includes `acme` as the npm organization. To change this, do the following: + +- Rename folders in `packages/*` to replace `acme` with your desired scope +- Search and replace `acme` with your desired scope +- Re-run `pnpm install` + +To publish packages to a private npm organization scope, **remove** the following from each of the `package.json`'s + +```diff +- "publishConfig": { +- "access": "public" +- }, +``` diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..3d2e55bd --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,93 @@ +import tseslint from 'typescript-eslint'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +// plugins +import regexpEslint from 'eslint-plugin-regexp'; +const typescriptEslint = tseslint.plugin; + +// parsers +const typescriptParser = tseslint.parser; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { files: ["src/*.{js,mjs,cjs,ts}"] }, + // { languageOptions: { globals: globals.browser } }, + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + regexpEslint.configs['flat/recommended'], + { + languageOptions: { + parser: typescriptParser, + parserOptions: { + project: ['./packages/*/tsconfig.json', './tsconfig.eslint.json'], + tsconfigRootDir: __dirname, + }, + }, + plugins: { + '@typescript-eslint': typescriptEslint, + regexp: regexpEslint, + }, + rules: { + // These off/configured-differently-by-default rules fit well for us + '@typescript-eslint/switch-exhaustiveness-check': 'error', + '@typescript-eslint/no-shadow': 'off', + 'no-console': 'off', + '@typescript-eslint/no-unsafe-enum-comparison' : 'off', + '@typescript-eslint/no-empty-object-type': 'off', + // Todo: do we want these? + 'no-var': 'off', + + 'regexp/prefer-regexp-exec': 'off', + '@typescript-eslint/no-duplicate-enum-values': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + '@typescript-eslint/prefer-for-of': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/class-literal-property-style': 'off', + '@typescript-eslint/consistent-indexed-object-style': 'off', + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/dot-notation': 'off', + '@typescript-eslint/no-base-to-string': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-redundant-type-constituents': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/only-throw-error': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'off', + '@typescript-eslint/prefer-optional-chain': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-string-starts-ends-with': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/sort-type-constituents': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/no-explicit-any': 'off', + + // Used by Biome + '@typescript-eslint/consistent-type-imports': 'off', + // These rules enabled by the preset configs don't work well for us + '@typescript-eslint/await-thenable': 'off', + 'prefer-const': 'off', + + // In some cases, using explicit letter-casing is more performant than the `i` flag + 'regexp/use-ignore-case': 'off', + 'regexp/prefer-regexp-exec': 'warn', + 'regexp/prefer-regexp-test': 'warn', + 'no-control-regex': 'off' + } + } +] \ No newline at end of file diff --git a/package.json b/package.json index e67de546..fbe11afe 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,20 @@ { - "name": "@plastichub/template", - "description": "", - "version": "0.3.1", - "main": "main.js", - "typings": "index.d.ts", - "publishConfig": { - "access": "public" - }, - "bin": { - "osr-bin": "main.js" - }, - "dependencies": { - "@types/node": "^14.17.5", - "@types/yargs": "^17.0.2", - "chalk": "^2.4.1", - "convert-units": "^2.3.4", - "env-var": "^7.0.1", - "typescript": "^4.3.5", - "yargs": "^14.2.3", - "yargs-parser": "^15.0.3" - }, + "private": true, "scripts": { - "test": "tsc; mocha --full-trace mocha \"spec/**/*.spec.js\"", - "test-with-coverage": "istanbul cover node_modules/.bin/_mocha -- 'spec/**/*.spec.js'", - "lint": "tslint --project=./tsconfig.json", - "build": "tsc -p .", - "dev": "tsc -p . --declaration -w", - "typings": "tsc --declaration", - "docs": "npx typedoc src/index.ts", - "dev-test-watch": "mocha-typescript-watch" + "build": "turbo run build", + "dev": "turbo run dev", + "lint": "turbo run lint", + "clean": "turbo run clean && rm -rf node_modules", + "format": "prettier --write \"**/*.{ts,tsx,md}\"", + "changeset": "changeset", + "version-packages": "changeset version", + "release": "turbo run build --filter=docs^... && changeset publish" }, - "homepage": "https://git.osr-plastic.org/plastichub/lib-content", - "repository": { - "type": "git", - "url": "https://git.osr-plastic.org/plastichub/lib-content.git" + "devDependencies": { + "@changesets/cli": "^2.27.1", + "prettier": "^3.2.5", + "turbo": "^2.3.3" }, - "engines": { - "node": ">= 14.0.0" - }, - "license": "BSD-3-Clause", - "keywords": [ - "typescript" - ] + "packageManager": "pnpm@8.15.6", + "name": "design-system" } diff --git a/packages/commons/.gitignore b/packages/commons/.gitignore new file mode 100644 index 00000000..466ca394 --- /dev/null +++ b/packages/commons/.gitignore @@ -0,0 +1,39 @@ +# Logs +logs +*.log +npm-debug.log* +yarn.lock +package-lock.json +node_modules +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history diff --git a/packages/commons/.npmignore b/packages/commons/.npmignore new file mode 100644 index 00000000..7786c9a0 --- /dev/null +++ b/packages/commons/.npmignore @@ -0,0 +1,5 @@ +node_modules +src +package-lock.json +docs +scripts diff --git a/packages/commons/LICENSE b/packages/commons/LICENSE new file mode 100644 index 00000000..09d493bf --- /dev/null +++ b/packages/commons/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/commons/README.md b/packages/commons/README.md new file mode 100644 index 00000000..ed223087 --- /dev/null +++ b/packages/commons/README.md @@ -0,0 +1,2 @@ +# core +core stuff diff --git a/packages/commons/eslint.config.js b/packages/commons/eslint.config.js new file mode 100644 index 00000000..9dd72a92 --- /dev/null +++ b/packages/commons/eslint.config.js @@ -0,0 +1,95 @@ +import tseslint from 'typescript-eslint'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +// plugins +import regexpEslint from 'eslint-plugin-regexp'; +const typescriptEslint = tseslint.plugin; + +// parsers +const typescriptParser = tseslint.parser; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { + files: ["src/*.{ts}"] +}, + + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + regexpEslint.configs['flat/recommended'], + { + languageOptions: { + parser: typescriptParser, + parserOptions: { + project: ['./packages/*/tsconfig.json', './tsconfig.eslint.json'], + tsconfigRootDir: __dirname, + }, + }, + plugins: { + '@typescript-eslint': typescriptEslint, + regexp: regexpEslint, + }, + rules: { + // These off/configured-differently-by-default rules fit well for us + '@typescript-eslint/switch-exhaustiveness-check': 'error', + '@typescript-eslint/no-shadow': 'off', + 'no-console': 'off', + '@typescript-eslint/no-unsafe-enum-comparison' : 'off', + '@typescript-eslint/no-empty-object-type': 'off', + // Todo: do we want these? + 'no-var': 'off', + + 'regexp/prefer-regexp-exec': 'off', + '@typescript-eslint/no-duplicate-enum-values': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + '@typescript-eslint/prefer-for-of': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/class-literal-property-style': 'off', + '@typescript-eslint/consistent-indexed-object-style': 'off', + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/dot-notation': 'off', + '@typescript-eslint/no-base-to-string': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-redundant-type-constituents': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/only-throw-error': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'off', + '@typescript-eslint/prefer-optional-chain': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-string-starts-ends-with': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/sort-type-constituents': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/no-explicit-any': 'off', + + // Used by Biome + '@typescript-eslint/consistent-type-imports': 'off', + // These rules enabled by the preset configs don't work well for us + '@typescript-eslint/await-thenable': 'off', + 'prefer-const': 'off', + + // In some cases, using explicit letter-casing is more performant than the `i` flag + 'regexp/use-ignore-case': 'off', + 'regexp/prefer-regexp-exec': 'warn', + 'regexp/prefer-regexp-test': 'warn', + 'no-control-regex': 'off' + } + } +] \ No newline at end of file diff --git a/packages/commons/package.json b/packages/commons/package.json new file mode 100644 index 00000000..36ba98c3 --- /dev/null +++ b/packages/commons/package.json @@ -0,0 +1,57 @@ +{ + "name": "@polymech/commons", + "version": "0.2.6", + "license": "BSD", + "type": "module", + "publishConfig": { + "access": "public" + }, + "licenses": [ + { + "type": "BSD", + "url": "https://git.osr-plastic.org/osr-plastic/osr-core/blob/master/LICENSE" + } + ], + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "main": "dist/index.js", + "repository": { + "type": "git", + "url": "https://git.osr-plastic.org/osr-plastic/osr-core.git" + }, + "types": "index.d.ts", + "dependencies": { + "tslog": "^3.3.3", + "tsup": "^8.3.5", + "zod": "^3.24.1", + "@polymech/fs": "workspace:*", + "@polymech/core": "workspace:*" + }, + "devDependencies": { + "@eslint/js": "^9.18.0", + "@repo/eslint-config": "workspace:*", + "@repo/typescript-config": "workspace:*", + "@types/node": "^8.10.66", + "eslint": "^8.57.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-regexp": "^2.7.0", + "globals": "^15.14.0", + "ts-node": "^10.9.1", + "typescript": "^4.9.5", + "typescript-eslint": "^8.20.0" + }, + "scripts": { + "test": "tsc && mocha build/test", + "buildtsc": "tsc -p . --declaration", + "build": "tsup", + "start": "node build/index.js", + "typings": "tsc -p . --declaration", + "dev": "tsc -p . --declaration -w" + }, + "modules": [], + "readmeFilename": "Readme.md" +} diff --git a/src/.gitignore b/packages/commons/src/index.ts similarity index 100% rename from src/.gitignore rename to packages/commons/src/index.ts diff --git a/packages/commons/src/shemas/index.ts b/packages/commons/src/shemas/index.ts new file mode 100644 index 00000000..f90d65a9 --- /dev/null +++ b/packages/commons/src/shemas/index.ts @@ -0,0 +1,218 @@ +import * as path from 'node:path' +import * as CLI from 'yargs' +import { z, ZodTypeAny, ZodObject, ZodEffects, ZodOptional, ZodDefault } from 'zod' +import { sync as writeFS } from '@polymech/fs/write' +import { zodToTs, printNode } from 'zod-to-ts' +import { zodToJsonSchema } from "zod-to-json-schema" +import { logger } from '../logger' + +type InnerType = T extends ZodEffects ? InnerType : T + +type GetInnerType = T extends ZodObject + ? T + : T extends ZodEffects> + ? InnerType + : never; + +export * from './path' + +export const generate_interfaces = (schemas: ZodObject[], dst: string) => { + const types = schemas.map(schema => `export interface ${schema.description || 'IOptions'} ${printNode(zodToTs(schema).node)}`) + writeFS(dst, types.join('\n')) +} +export const enumerateHelpStrings = (schema: ZodTypeAny, path: string[] = [], logger: any): void => { + if (schema instanceof ZodObject) { + for (const key in schema.shape) { + const nestedSchema = schema.shape[key]; + enumerateHelpStrings(nestedSchema, [...path, key], logger) + } + } else { + const description = schema._def.description; + if (description) { + logger.debug(`\t ${path.join('.')}: ${description}`) + } + } +} +export const yargsDefaults = (yargs: CLI.Argv) => yargs.parserConfiguration({ "camel-case-expansion": false }) + +export const getInnerSchema = (schema: ZodTypeAny): ZodTypeAny => { + while (schema instanceof ZodEffects) { + schema = schema._def.schema + } + return schema +} +export const getInnerType = (type: ZodTypeAny) => { + while (type instanceof ZodOptional) { + type = type._def.innerType + } + while (type._def.typeName === 'ZodDefault' || type._def.typeName === 'ZodOptional') { + type = type._def.innerType; + } + return type._def.typeName +} +export const getDefaultValue = (schema: ZodTypeAny) => { + if (schema instanceof ZodDefault) { + return schema._def.defaultValue(); + } + return undefined; +} +export const getFieldDefaultValue = (schema: ZodTypeAny): any | undefined => { + if(!schema){ + return undefined + } + if (schema._def.typeName === 'ZodDefault') { + return schema._def.defaultValue(); + } + if (schema instanceof ZodOptional) { + return getFieldDefaultValue(schema.unwrap()); + } + if (schema instanceof ZodEffects) { + return getFieldDefaultValue(schema._def.schema); + } + if(typeof schema._def){ + return getFieldDefaultValue(schema._def.schema) + } + return undefined; + } +export const getDescription = (schema: ZodTypeAny): string | undefined =>{ + if(!schema){ + return undefined + } + if (schema._def.description) { + return schema._def.description; + } + if (schema instanceof ZodOptional) { + return getDescription(schema.unwrap()); + } + + if (schema instanceof ZodEffects) { + return getDescription(schema._def.schema); + } + + if(typeof schema._def){ + return getDescription(schema._def.schema) + } + return undefined; + } +export const toYargs = (yargs: CLI.Argv, zodSchema: ZodObject, options?: { + onKey?: (yargs: CLI.Argv, key: string, options:any) => any +}) => { + yargsDefaults(yargs) + try { + const shape = zodSchema.shape + for (const key in shape) { + const zodField = shape[key] as ZodTypeAny + const innerDef = getInnerSchema(zodField) + if (!innerDef) { + continue + } + let type: 'string' | 'boolean' | 'number' | undefined; + const inner_type = getInnerType(innerDef) + let descriptionExtra = '' + switch (inner_type) { + case 'ZodString': + type = 'string' + break + case 'ZodBoolean': + type = 'boolean' + break + case 'ZodNumber': + type = 'number' + break + case 'ZodOptional': + case 'ZodEnum': + type = getInnerType(innerDef) + if (innerDef._def.typeName === 'ZodEnum') { + descriptionExtra = `\n\t ${innerDef._def.values.join(' \n\t ')}` + } + break + } + const defaultValue = getFieldDefaultValue(zodField) + let handled = false + const args = { + type, + default: defaultValue, + describe: `${zodField._def.description || ''} ${descriptionExtra}`.trim() + } + if(options?.onKey){ + handled = options.onKey(yargs, key, args) + } + if(!handled){ + yargs.option(key,args) + } + } + return yargs + } catch (error) { + logger.error('Error processing schema:', error) + return yargs + } +} +///////////////////////////////////////////////////////// +// +// Schema Writers +// +const extension = (file: string) => path.parse(file).ext +const json = (data: any, file: string, name: string, options: {}) => writeFS(file, data.map((s) => zodToJsonSchema(s, name))) + +export const WRITERS = +{ + '.json': json +} + +export const writer = (file: string) => WRITERS[extension(file)] + +export const write = (schemas: ZodObject[], file: string, name: string, options: {}) => { + if (!WRITERS[extension(file)]) { + logger.error(`No writer found for file extension: ${extension(file)} : file: ${file}`) + return + } + logger.debug(`Writing schema to ${file} : ${name}`) + try { + writer(file)(schemas, file, name, options) + } catch (e) { + logger.trace(`Error writing schema to ${file} : ${name}`, e, e.stack, e.message) + } +} +//////////////////////////////////////////////////////////////////// +// +// Schema Combinators +export const combineValidatorsOr = (validators: z.ZodTypeAny[]) => { + return z.string().refine((value) => { + const errors = []; + const isValid = validators.some((validator) => { + try { + validator.parse(value) + return true; + } catch (err) { + errors.push(err.errors) + return false; + } + }); + if (!isValid) { + throw new z.ZodError(errors.flat()) + } + return true; + }, 'Invalid value for all provided validators') +} + +export const combineValidatorsOrUsingZod = (validators: z.ZodTypeAny[]) => { + return validators.reduce((acc, validator) => acc.or(validator)); +}; +export const combineValidatorsOrUsingZod2 = (validators: z.ZodTypeAny[]) => { + return validators.reduce((acc, validator) => { + return acc.or(validator).refine((value) => { + try { + acc.parse(value); + return true; + } catch (errAcc) { + try { + validator.parse(value); + return true; + } catch (errValidator) { + throw new z.ZodError([...errAcc.errors, ...errValidator.errors]); + } + } + }) + }) +} +export * from './zod_map' diff --git a/packages/commons/src/shemas/openapi.ts b/packages/commons/src/shemas/openapi.ts new file mode 100644 index 00000000..64bb4de0 --- /dev/null +++ b/packages/commons/src/shemas/openapi.ts @@ -0,0 +1,20 @@ +/* +export const openapi = (data: ZodObject[], file: string, name: string, options: {}) => { + const registry = new OpenAPIRegistry() + data.forEach((s) => registry.register(s.description, s)) + const generator = new OpenApiGeneratorV3(registry.definitions) + const component = generator.generateComponents() + // const content = stringifyYAML(component) + return component +} +*/ +/* +const yaml = (data: ZodObject[], file: string, name: string, options: {}) => { + const registry = new OpenAPIRegistry() + data.forEach((s) => registry.register(s.description, s)) + const generator = new OpenApiGeneratorV3(registry.definitions) + const component = generator.generateComponents() + logger.debug(`Writing schema to ${file} : ${name}`,component) + writeFS(file,stringifyYAML(component)) +} +*/ \ No newline at end of file diff --git a/packages/commons/src/shemas/path.ts b/packages/commons/src/shemas/path.ts new file mode 100644 index 00000000..2daf8fbe --- /dev/null +++ b/packages/commons/src/shemas/path.ts @@ -0,0 +1,256 @@ +import { z, ZodTypeAny } from 'zod' +import * as path from 'path' +import { accessSync, constants, lstatSync, existsSync } from 'fs' + +import { isString } from '@polymech/' + +import { logger } from '../logger' + +import { sync as exists } from '@polymech/fs/exists' +import { sync as read } from '@polymech/fs/read' + +import { DEFAULT_VARS, resolve, resolveVariables } from '../variables' + +import { getDescription } from '../' +import { isFile } from '../lib/fs' + + +type TResult = { resolved: string, source: string, value: unknown } +type TRefine = (src: string, ctx: any, variables: Record) => string | z.ZodNever +type TTransform = (src: string, variables?: Record) => string | TResult +type TExtend = { refine: Array, transform: Array } + +const DefaultPathSchemaBase = z.string().describe('Path to a file or directory') + +const PathErrorMessages = { + INVALID_INPUT: 'INVALID_INPUT: ${inputPath}', + PATH_DOES_NOT_EXIST: 'Path does not exist ${inputPath} = ${resolvedPath}', + DIRECTORY_NOT_WRITABLE: 'Directory is not writable ${inputPath} = ${resolvedPath}', + NOT_A_DIRECTORY: 'Path is not a directory or does not exist ${inputPath} = ${resolvedPath}', + NOT_A_JSON_FILE: 'File is not a JSON file or does not exist ${inputPath} = ${resolvedPath}', + PATH_NOT_ABSOLUTE: 'Path is not absolute ${inputPath} = ${resolvedPath}', + PATH_NOT_RELATIVE: 'Path is not relative ${inputPath} = ${resolvedPath}', +} as const + +export enum E_PATH { + ENSURE_PATH_EXISTS = 1, + INVALID_INPUT, + ENSURE_DIRECTORY_WRITABLE, + ENSURE_FILE_IS_JSON, + ENSURE_PATH_IS_ABSOLUTE, + ENSURE_PATH_IS_RELATIVE, + GET_PATH_INFO +} +export const Transformers = { + resolve: (val: string, variables: Record = {}) => { + if (!val) { + return null + } + return { + resolved: path.resolve(resolve(val, false, variables)), + source: val + } + }, + json: (val: string | { resolved: string, source: string }, variables: Record = {}) => { + if (!val) { + return null + } + const resolved = path.resolve(resolve(isString(val) ? val : val.source, false, variables)) + return { + resolved, + source: val, + value: read(resolved, 'json') + } + }, + string: (val: string | { resolved: string, source: string }, variables: Record = {}) => { + if (!val) { + return null + } + let src = isString(val) ? val : val.source + src = resolve(src, false, variables) + const resolved = path.resolve(src) + if (!exists(resolved) || !isFile(resolved)) { + return { + resolved, + source: val, + value: null + } + } + else { + let value = null + try { + value = read(resolved, 'string') + } catch (e) { + logger.error('Failed to read file', { resolved, source: val, error: e.message }) + } + return { + resolved, + source: val, + value + } + } + } +} + +export const TransformersDescription = [ + { + description: 'RESOLVE_PATH', + fn: Transformers.resolve + }, + { + description: 'READ_JSON', + fn: Transformers.json + }, + { + description: 'READ_STRING', + fn: Transformers.string + } +] +const extendType = (type: ZodTypeAny, extend: TExtend, variables: Record = {}) => { + if (Array.isArray(extend.refine)) { + for (const refine of extend.refine) { + type = type.refine(refine as any) + } + } else { + type = type.refine(extend.refine) + } + if (Array.isArray(extend.transform)) { + for (const transform of extend.transform) { + type = type.transform((val) => transform(val, variables)) + } + } else { + type = type.transform(extend.transform) + } + return type +} + +const extendTypeDescription = (type: ZodTypeAny, extension: TExtend, variables: Record = {}) => { + const description = getDescription(type) || '' + let transformerDescriptions = 'Transformers:\n' + if (Array.isArray(extension.transform)) { + for (const transform of extension.transform) { + transformerDescriptions += transformerDescription(transform) + '\n' + } + } else { + transformerDescriptions += transformerDescription(extension.transform) + '\n' + } + type = type.describe(description + '\n' + transformerDescriptions) + return type +} + +const transformerDescription = (fn: TTransform) => { + const description = TransformersDescription.find((t) => t.fn === fn) + return description ? description.description : 'Unknown' +} + +export const extendSchema = (baseSchema: z.ZodObject, extend: Record) => { + const baseShape = baseSchema.shape + const extendedShape: Record = { ...baseShape } + for (const [key, refines] of Object.entries(extend)) { + if (!baseShape[key]) + continue + + let fieldSchema = baseShape[key] + if (Array.isArray(refines.refine)) { + for (const refine of refines.refine) { + fieldSchema = fieldSchema.superRefine(refine) + } + } else { + fieldSchema = fieldSchema.superRefine(refines) + } + if (Array.isArray(refines.transform)) { + for (const transform of refines.transform) { + fieldSchema = fieldSchema.transform((val) => transform(val)) + } + } else { + fieldSchema = fieldSchema.transform(refines.transform) + } + extendedShape[key] = fieldSchema + + } + return z.object(extendedShape) +} + +export const ENSURE_DIRECTORY_WRITABLE = (inputPath: string, ctx: any, variables: Record) => { + const resolvedPath = path.resolve(resolve(inputPath, false, variables)) + const parts = path.parse(resolvedPath) + if (resolvedPath && existsSync(parts.dir) && lstatSync(parts.dir).isDirectory()) { + try { + accessSync(resolvedPath, constants.W_OK) + return resolvedPath + } catch (e) { + ctx.addIssue({ + code: E_PATH.ENSURE_DIRECTORY_WRITABLE, + message: resolveVariables(PathErrorMessages.DIRECTORY_NOT_WRITABLE, false, { inputPath, resolvedPath }) + }) + return z.NEVER + } + } else { + ctx.addIssue({ + code: E_PATH.ENSURE_DIRECTORY_WRITABLE, + message: resolveVariables(PathErrorMessages.NOT_A_DIRECTORY, false, { inputPath, resolvedPath }) + }) + return z.NEVER + } + +} +export const IS_VALID_STRING = (inputPath: string) => { + return isString(inputPath) +} +export const ENSURE_PATH_EXISTS = (inputPath: string, ctx: any, variables: Record) => { + if (!inputPath || !ctx) { + return z.NEVER + } + if (!isString(inputPath)) { + ctx.addIssue({ + code: E_PATH.INVALID_INPUT, + message: resolveVariables(PathErrorMessages.INVALID_INPUT, false, {}) + }) + return z.NEVER + } + const resolvedPath = path.resolve(resolve(inputPath, false, variables)) + if (!exists(resolvedPath)) { + ctx.addIssue({ + code: E_PATH.ENSURE_PATH_EXISTS, + message: resolveVariables(PathErrorMessages.PATH_DOES_NOT_EXIST, false, { inputPath, resolvedPath }) + }) + + return z.NEVER + } + return resolvedPath +} + +export const test = () => { + const BaseCompilerOptions = () => z.object({ + root: DefaultPathSchemaBase.default(`${process.cwd()}`) + }) + const ret = extendSchema(BaseCompilerOptions(), { + root: { + refine: [ + (val, ctx) => ENSURE_DIRECTORY_WRITABLE(val, ctx, DEFAULT_VARS({ exampleVar: 'exampleValue' })), + (val, ctx) => ENSURE_PATH_EXISTS(val, ctx, DEFAULT_VARS({ exampleVar: 'exampleValue' })) + ], + transform: [ + (val) => path.resolve(resolve(val, false, DEFAULT_VARS({ exampleVar: 'exampleValue' }))) + ] + } + }) + return ret +} + +export const Templates = +{ + json: { + refine: [IS_VALID_STRING, ENSURE_PATH_EXISTS], + transform: [Transformers.resolve, Transformers.json] + }, + string: { + refine: [ENSURE_PATH_EXISTS], + transform: [Transformers.resolve, Transformers.string] + } +} + +export const extend = (baseSchema: ZodTypeAny, template: any, variables: Record = {}) => { + const type = extendType(baseSchema, template, variables) + return extendTypeDescription(type, template, variables) +} diff --git a/packages/commons/src/shemas/types.ts b/packages/commons/src/shemas/types.ts new file mode 100644 index 00000000..25d974b3 --- /dev/null +++ b/packages/commons/src/shemas/types.ts @@ -0,0 +1,187 @@ +export enum FLAG { + /** + * Instruct for no additional extra processing + * @constant + * @type int + */ + NONE = 0x00000000, + /** + * Will instruct the pre/post processor to base-64 decode or encode + * @constant + * @type int + */ + BASE_64 = 0x00000001, + /** + * Post/Pre process the value with a user function + * @constant + * @type int + */ + USE_FUNCTION = 0x00000002, + /** + * Replace variables with local scope's variables during the post/pre process + * @constant + * @type int + */ + REPLACE_VARIABLES = 0x00000004, + /** + * Replace variables with local scope's variables during the post/pre process but evaluate the whole string + * as Javascript + * @constant + * @type int + */ + REPLACE_VARIABLES_EVALUATED = 0x00000008, + /** + * Will instruct the pre/post processor to escpape evaluated or replaced variables or expressions + * @constant + * @type int + */ + ESCAPE = 0x00000010, + /** + * Will instruct the pre/post processor to replace block calls with oridinary vanilla script + * @constant + * @type int + */ + REPLACE_BLOCK_CALLS = 0x00000020, + /** + * Will instruct the pre/post processor to remove variable delimitters/placeholders from the final string + * @constant + * @type int + */ + REMOVE_DELIMTTERS = 0x00000040, + /** + * Will instruct the pre/post processor to remove "[" ,"]" , "(" , ")" , "{", "}" , "*" , "+" , "." + * @constant + * @type int + */ + ESCAPE_SPECIAL_CHARS = 0x00000080, + /** + * Will instruct the pre/post processor to use regular expressions over string substitution + * @constant + * @type int + */ + USE_REGEX = 0x00000100, + /** + * Will instruct the pre/post processor to use Filtrex (custom bison parser, needs xexpression) over string substitution + * @constant + * @type int + */ + USE_FILTREX = 0x00000200, + /** + * Cascade entry. There are cases where #USE_FUNCTION is not enough or we'd like to avoid further type checking. + * @constant + * @type int + */ + CASCADE = 0x00000400, + /** + * Cascade entry. There are cases where #USE_FUNCTION is not enough or we'd like to avoid further type checking. + * @constant + * @type int + */ + EXPRESSION = 0x00000800, + /** + * Dont parse anything + * @constant + * @type int + */ + DONT_PARSE = 0x000001000, + /** + * Convert to hex + * @constant + * @type int + */ + TO_HEX = 0x000002000, + /** + * Convert to hex + * @constant + * @type int + */ + REPLACE_HEX = 0x000004000, + /** + * Wait for finish + * @constant + * @type int + */ + WAIT = 0x000008000, + /** + * Wait for finish + * @constant + * @type int + */ + DONT_ESCAPE = 0x000010000, + /** + * Flag to mark the maximum core bit mask, after here its user land + * @constant + * @type int + */ + END = 0x000020000 +} + +export enum EType +{ + Number = 'Number', + String = 'String', + Boolean = 'Boolean', + Date = 'Date', + TimeStamp = 'TimeStamp', + Duration = 'Duration', + Url = 'Url', + UrlScheme = 'Url-Scheme', + Asset = 'Asset', + Symbol = 'Symbol', + Value = 'Value', + Values = 'Values', + Attribute = 'Attribute', + Parameter = 'Parameter', + Operation = 'Operation', + ParameterOperation = 'ParameterOperation', + Template = 'Template', + Arguments = 'Arguments' +} +export type TVector2D = [number, number]; +export type TVector3D = [number, number, number]; +export type TBBox = [TVector3D, TVector3D]; +export type TQuaternion = [number, number, number, number]; +export type TFlags = Record; +export type TExpression = string | [string | RegExp, { [key: string]: any }]; +export type TOptions = { flags?: TFlags | { [key: string]: any } }; + +export interface IUrlScheme { + url: string; + options?: { [key: string]: any }; +} + +export interface IAsset { + urlScheme: IUrlScheme; + options?: { [key: string]: any }; +} + +export type TSelector = TExpression | [TExpression, { [key: string]: any }]; + +export interface ITypeInfo { + type: string; + symbol: bigint; +} + +export interface IRef { + key: string | string; + struct: { [key: string]: any }; +} + +export interface IAttribute { + type: ITypeInfo; + value: bigint; +} + +export interface IParameter { + type: ITypeInfo; + value: bigint; +} + +export interface IParameterOperation { + param1: bigint; + param2: bigint; + operation: bigint; +} + +export type TTemplate = string | [ITypeInfo | TSelector, { [key: string]: any }]; +export type TArguments = { [key: string]: any } | any[]; diff --git a/packages/commons/src/shemas/vfs.ts b/packages/commons/src/shemas/vfs.ts new file mode 100644 index 00000000..b644c02f --- /dev/null +++ b/packages/commons/src/shemas/vfs.ts @@ -0,0 +1 @@ +//import { zodToJsonSchema } from "zod-to-json-schema" diff --git a/packages/commons/src/shemas/zod_map.ts b/packages/commons/src/shemas/zod_map.ts new file mode 100644 index 00000000..f2014967 --- /dev/null +++ b/packages/commons/src/shemas/zod_map.ts @@ -0,0 +1,112 @@ +import { z, ZodObject, ZodTypeAny } from 'zod'; + +/** + * Manages a collection of Zod schema properties + * and combines them into a single Zod object schema. + * + * @template MetaType The type of metadata you want to store for each field. + * Defaults to Record if not provided. + */ +export class ZodMetaMap> { + private fieldMap = new Map< + string, + { schema: ZodTypeAny; metadata?: MetaType } + >(); + + /** + * Adds a Zod schema under a specific key (property name), + * optionally attaching typed metadata. + * + * @param key - The name of the property in the root object. + * @param schema - The Zod schema for that property. + * @param metadata - Optional metadata object (type MetaType). + */ + add(key: string, schema: T, metadata?: MetaType): this { + this.fieldMap.set(key, { schema, metadata }); + return this; + } + + /** + * Builds and returns a root Zod object + * that combines all properties which were added. + */ + root(): ZodObject> { + const shape: Record = {}; + for (const [key, { schema }] of this.fieldMap.entries()) { + shape[key] = schema; + } + return z.object(shape); + } + + /** + * Retrieves the metadata for a specific key, if any. + */ + getMetadata(key: string): MetaType | undefined { + return this.fieldMap.get(key)?.metadata; + } + + /** + * Static factory method: creates a SchemaMetaManager + * while letting you optionally specify the MetaType. + * + * Usage: + * const manager = SchemaMetaManager.create(); + */ + static create>(): ZodMetaMap { + return new ZodMetaMap(); + } + + /** + * Returns a basic UiSchema object that RJSF can use to render form controls. + * + * - Adds a top-level "ui:submitButtonOptions" (example). + * - For each field, we set `ui:title` (uppercase key), + * `ui:description` (from Zod's .describe() if available), + * and a naive placeholder from the default value (if parse(undefined) succeeds). + */ + getUISchema(): Record { + // Start with some top-level UI schema config (optional) + const uiSchema: Record = { + 'ui:submitButtonOptions': { + props: { + disabled: false, + className: 'btn btn-info', + }, + norender: false, + submitText: 'Submit', + }, + }; + + for (const [key, { schema }] of this.fieldMap.entries()) { + let fieldUi: Record = { }; + // Use the Zod description if available + // (Accessing `._def.description` is private/hacky, but commonly done.) + const sAny = schema as any; + if (sAny?._def?.description) { + fieldUi['ui:description'] = sAny._def.description; + } + + // RJSF usually reads 'title' from JSON schema. But if you want + // to override it in UI schema, you can do so: + fieldUi['ui:title'] = key[0].toUpperCase() + key.substr(1).toLowerCase() + + // If the Zod schema allows a default, we can parse(undefined) to get it. + try { + const defaultVal = schema.parse(undefined); + // There's no official 'ui:default' in RJSF, but you could do a placeholder: + fieldUi['ui:placeholder'] = defaultVal; + } catch { + // no default + } + if(key=='path'){ + debugger + } + fieldUi = { + ...fieldUi, + ...this.getMetadata(key), + } + uiSchema[key] = fieldUi; + } + return uiSchema; + } +} \ No newline at end of file diff --git a/packages/commons/tsconfig.json b/packages/commons/tsconfig.json new file mode 100644 index 00000000..fdf8e607 --- /dev/null +++ b/packages/commons/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../typescript-config/base.json", + "include": ["src/**/*.ts"], + "files": ["src/index.ts"], + "compilerOptions": { + "allowJs": true, + "declarationDir": "./dist", + "outDir": "./dist", + "sourceMap": true, + + "preserveConstEnums": true + }, +} diff --git a/packages/commons/tsup.config.ts b/packages/commons/tsup.config.ts new file mode 100644 index 00000000..03b4a81f --- /dev/null +++ b/packages/commons/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsup"; + +export default defineConfig((options) => ({ + entryPoints: [ + "src/*.ts" + ], + format: ["cjs", "esm"], + dts: true, + sourcemap: true, + ...options, + bundle: false +})); diff --git a/packages/core/.gitignore b/packages/core/.gitignore new file mode 100644 index 00000000..466ca394 --- /dev/null +++ b/packages/core/.gitignore @@ -0,0 +1,39 @@ +# Logs +logs +*.log +npm-debug.log* +yarn.lock +package-lock.json +node_modules +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history diff --git a/packages/core/.kbot/completion.json b/packages/core/.kbot/completion.json new file mode 100644 index 00000000..6603664b --- /dev/null +++ b/packages/core/.kbot/completion.json @@ -0,0 +1,44 @@ +[ + { + "level": "debug", + "message": { + "id": "gen-1736963368-mc5na3vrtYewOp9vu7hf", + "provider": "Amazon Bedrock", + "model": "anthropic/claude-3.5-sonnet", + "object": "chat.completion", + "created": 1736963368, + "choices": [ + { + "logprobs": null, + "finish_reason": "tool_calls", + "index": 0, + "message": { + "role": "assistant", + "content": "I'll help you set up a TypeScript project with tsup. I notice you have an existing TypeScript/Node.js project configuration, and you want to switch to using tsup. I'll create a new configuration that maintains your existing functionality while migrating to tsup.\n\nLet me help you set this up by:\n1. Adding tsup as a dependency\n2. Creating a tsup configuration file\n3. Updating the package.json build scripts\n\nFirst, let's install tsup:", + "refusal": null, + "tool_calls": [ + { + "id": "tooluse_iLzgz83QRXm3viq24ahmpg", + "index": 0, + "type": "function", + "function": { + "name": "execute_command", + "arguments": "{\"command\": \"npm\", \"args\": [\"install\", \"--save-dev\", \"tsup\"]}", + "parsed_arguments": null + } + } + ], + "parsed": null + } + } + ], + "usage": { + "prompt_tokens": 2592, + "completion_tokens": 156, + "total_tokens": 2748 + } + }, + "timestamp": "2025-01-15T17:49:34.356Z", + "service": "collector:onChatCompletion" + } +] \ No newline at end of file diff --git a/packages/core/.kbot/content.json b/packages/core/.kbot/content.json new file mode 100644 index 00000000..fdef1b60 --- /dev/null +++ b/packages/core/.kbot/content.json @@ -0,0 +1,8 @@ +[ + { + "level": "debug", + "message": "I'll help you set up a TypeScript project with tsup. I notice you have an existing TypeScript/Node.js project configuration, and you want to switch to using tsup. I'll create a new configuration that maintains your existing functionality while migrating to tsup.\n\nLet me help you set this up by:\n1. Adding tsup as a dependency\n2. Creating a tsup configuration file\n3. Updating the package.json build scripts\n\nFirst, let's install tsup:", + "timestamp": "2025-01-15T17:49:34.361Z", + "service": "collector:onContent" + } +] \ No newline at end of file diff --git a/packages/core/.kbot/openai-message.json b/packages/core/.kbot/openai-message.json new file mode 100644 index 00000000..30d04537 --- /dev/null +++ b/packages/core/.kbot/openai-message.json @@ -0,0 +1,41 @@ +[ + { + "level": "info", + "message": { + "role": "assistant", + "content": "I'll help you set up a TypeScript project with tsup. I notice you have an existing TypeScript/Node.js project configuration, and you want to switch to using tsup. I'll create a new configuration that maintains your existing functionality while migrating to tsup.\n\nLet me help you set this up by:\n1. Adding tsup as a dependency\n2. Creating a tsup configuration file\n3. Updating the package.json build scripts\n\nFirst, let's install tsup:", + "refusal": null, + "tool_calls": [ + { + "id": "tooluse_iLzgz83QRXm3viq24ahmpg", + "index": 0, + "type": "function", + "function": { + "name": "execute_command", + "arguments": "{\"command\": \"npm\", \"args\": [\"install\", \"--save-dev\", \"tsup\"]}", + "parsed_arguments": null + } + } + ], + "parsed": null, + "timestamp": "2025-01-15T17:49:34.357Z", + "sessionId": "1736963367295", + "prompt": "init tsup" + }, + "timestamp": "2025-01-15T17:49:34.358Z", + "service": "collector:onMessage" + }, + { + "level": "info", + "message": { + "role": "tool", + "tool_call_id": "tooluse_iLzgz83QRXm3viq24ahmpg", + "content": "{\"command\":\"npm\",\"args\":[\"install\",\"--save-dev\",\"tsup\"]}", + "timestamp": "2025-01-15T17:49:37.113Z", + "sessionId": "1736963367295", + "prompt": "init tsup" + }, + "timestamp": "2025-01-15T17:49:37.115Z", + "service": "collector:onMessage" + } +] \ No newline at end of file diff --git a/packages/core/.kbot/params.json b/packages/core/.kbot/params.json new file mode 100644 index 00000000..04a1aa2b --- /dev/null +++ b/packages/core/.kbot/params.json @@ -0,0 +1,471 @@ +{ + "model": "anthropic/claude-3.5-sonnet", + "messages": [ + { + "role": "user", + "path": "tsconfig.json", + "content": "{\n \"extends\": \"../typescript-config/base.json\",\n \"include\": [\"src/**/*.ts\"],\n \"files\": [\"src/index.ts\"],\n \"compilerOptions\": {\n \"allowJs\": true,\n \"declarationDir\": \"./dist\",\n \"outDir\": \"./dist\",\n \"sourceMap\": true,\n \n \"preserveConstEnums\": true\n },\n}\n" + }, + { + "role": "user", + "path": "package.json", + "content": "{\n \"name\": \"@polymech/core\",\n \"version\": \"0.2.6\",\n \"license\": \"BSD\",\n \"type\": \"module\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"licenses\": [\n {\n \"type\": \"BSD\",\n \"url\": \"https://git.osr-plastic.org/osr-plastic/osr-core/blob/master/LICENSE\"\n }\n ],\n \"exports\": {\n \"./iterator.js\": {\n \"import\": \"./dist/iterator.js\",\n \"require\": \"./dist/iterator.js\"\n },\n \"./strings.js\": {\n \"import\": \"./dist/strings.js\",\n \"require\": \"./dist/strings.js\"\n }\n },\n \"main\": \"dist/index.js\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://git.osr-plastic.org/osr-plastic/osr-core.git\"\n },\n \"types\": \"index.d.ts\",\n \"dependencies\": {\n \"tslog\": \"^3.3.3\"\n },\n \"devDependencies\": {\n \"eslint-plugin-regexp\": \"^2.7.0\",\n \"@eslint/js\": \"^9.18.0\",\n \"@repo/eslint-config\": \"workspace:*\",\n \"@repo/typescript-config\": \"workspace:*\",\n \"@types/node\": \"^8.10.66\",\n \"eslint\": \"^8.57.1\",\n \"eslint-plugin-import\": \"^2.31.0\",\n \"globals\": \"^15.14.0\",\n \"ts-node\": \"^10.9.1\",\n \"typescript\": \"^4.9.5\",\n \"typescript-eslint\": \"^8.20.0\"\n },\n \"scripts\": {\n \"test\": \"tsc && mocha build/test\",\n \"build\": \"tsc -p . --declaration\",\n \"start\": \"node build/index.js\",\n \"typings\": \"tsc -p . --declaration\",\n \"dev\": \"tsc -p . --declaration -w\"\n },\n \"modules\": [],\n \"readmeFilename\": \"Readme.md\"\n}\n" + }, + { + "role": "user", + "content": "init tsup" + }, + { + "role": "user", + "content": "" + } + ], + "tools": [ + { + "type": "function", + "function": { + "name": "list_files", + "description": "List all files in a directory", + "parameters": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "pattern": { + "type": "string", + "optional": true + } + }, + "required": [ + "directory" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "read_files", + "description": "Reads files in a directory with a given pattern", + "parameters": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "pattern": { + "type": "string", + "optional": true + } + }, + "required": [ + "directory" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "remove_file", + "description": "Remove a file at given path", + "parameters": { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "rename_file", + "description": "Rename or move a file or directory", + "parameters": { + "type": "object", + "properties": { + "src": { + "type": "string" + }, + "dst": { + "type": "string" + } + }, + "required": [ + "path" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "modify_project_files", + "description": "Create or modify existing project files in one shot, preferably used for creating project structure)", + "parameters": { + "type": "object", + "properties": { + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "content": { + "type": "string", + "description": "base64 encoded string" + } + }, + "required": [ + "path", + "content" + ] + } + } + }, + "required": [ + "files" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "write_file", + "description": "Writes to a file, given a path and content (base64). No directory or file exists check needed!", + "parameters": { + "type": "object", + "properties": { + "file": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "content": { + "type": "string", + "description": "base64 encoded string" + } + } + } + }, + "required": [ + "file" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "file_exists", + "description": "check if a file or folder exists", + "parameters": { + "type": "object", + "properties": { + "file": { + "type": "object", + "properties": { + "path": { + "type": "string" + } + } + } + }, + "required": [ + "file" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "read_file", + "description": "read a file, at given a path", + "parameters": { + "type": "object", + "properties": { + "file": { + "type": "object", + "properties": { + "path": { + "type": "string" + } + } + } + }, + "required": [ + "file" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "init_repository", + "description": "Initialize a new git repository", + "parameters": { + "type": "object", + "properties": {}, + "required": [] + } + } + }, + { + "type": "function", + "function": { + "name": "commit_files_git", + "description": "Commit files using git", + "parameters": { + "type": "object", + "properties": { + "files": { + "type": "array", + "items": { + "type": "string" + } + }, + "message": { + "type": "string" + } + }, + "required": [ + "files" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "ask_question", + "description": "Ask user a simple question and get response", + "parameters": { + "type": "object", + "properties": { + "question": { + "type": "string", + "description": "Question to ask the user" + }, + "default": { + "type": "string", + "description": "Default answer", + "optional": true + } + }, + "required": [ + "question" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "choose_option", + "description": "Ask user to choose from multiple options", + "parameters": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Message to show the user" + }, + "choices": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of choices" + }, + "multiple": { + "type": "boolean", + "description": "Allow multiple selections", + "optional": true + } + }, + "required": [ + "message", + "choices" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "execute_command", + "description": "Execute a terminal command and capture output", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "Command to execute" + }, + "args": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Command arguments", + "optional": true + }, + "cwd": { + "type": "string", + "description": "Working directory for command execution", + "optional": true + }, + "background": { + "type": "boolean", + "description": "Run command in background (non-blocking)", + "optional": true, + "default": false + }, + "window": { + "type": "boolean", + "description": "Open command in new terminal window", + "optional": true, + "default": false + }, + "detached": { + "type": "boolean", + "description": "Run process detached from parent", + "optional": true, + "default": false + } + }, + "required": [ + "command" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "google", + "description": "Searches Google for the given query", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string" + } + }, + "required": [ + "query" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "serpapi", + "description": "Searches Serpapi (finds locations (engine:google_local), places on the map (engine:google_maps) ) for the given query", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string" + }, + "engine": { + "type": "string", + "default": "google" + } + }, + "required": [ + "query" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "browse_page", + "description": "Browse a webpage and return its content as markdown, all links, images and pages main image", + "parameters": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "URL of the webpage to browse" + } + }, + "required": [ + "url" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "send_email", + "description": "Sends an email", + "parameters": { + "type": "object", + "properties": { + "recipient": { + "type": [ + "string", + "array" + ], + "items": { + "type": "string" + }, + "description": "The email address of the recipient(s). Can be a single email or an array of emails. For \"me\", use the default email address" + }, + "subject": { + "type": "string", + "description": "the subject", + "optional": true + }, + "body": { + "type": "string", + "description": "Markdown formatted body of the email", + "optional": true + } + }, + "required": [ + "url" + ] + } + } + }, + { + "type": "function", + "function": { + "name": "capture_screen", + "description": "Capture a screenshot and store it as file (jpg). Returns the path to the file", + "parameters": { + "type": "object", + "properties": { + "file": { + "type": "string" + } + }, + "required": [ + "file" + ] + } + } + } + ], + "tool_choice": "auto", + "parallel_tool_calls": false +} \ No newline at end of file diff --git a/packages/core/.kbot/tool-call-result.json b/packages/core/.kbot/tool-call-result.json new file mode 100644 index 00000000..9fe98b84 --- /dev/null +++ b/packages/core/.kbot/tool-call-result.json @@ -0,0 +1,15 @@ +[ + { + "level": "debug", + "message": { + "command": "npm", + "args": [ + "install", + "--save-dev", + "tsup" + ] + }, + "timestamp": "2025-01-15T17:49:37.116Z", + "service": "collector:onFunctionCallResult" + } +] \ No newline at end of file diff --git a/packages/core/.kbot/tool-call.json b/packages/core/.kbot/tool-call.json new file mode 100644 index 00000000..0fa4ea5e --- /dev/null +++ b/packages/core/.kbot/tool-call.json @@ -0,0 +1,22 @@ +[ + { + "level": "debug", + "message": { + "name": "execute_command", + "arguments": { + "command": "npm", + "args": [ + "install", + "--save-dev", + "tsup" + ] + }, + "parsed_arguments": null, + "timestamp": "2025-01-15T17:49:34.359Z", + "sessionId": "1736963367295", + "prompt": "init tsup" + }, + "timestamp": "2025-01-15T17:49:34.360Z", + "service": "collector:onToolCall" + } +] \ No newline at end of file diff --git a/packages/core/.npmignore b/packages/core/.npmignore new file mode 100644 index 00000000..7786c9a0 --- /dev/null +++ b/packages/core/.npmignore @@ -0,0 +1,5 @@ +node_modules +src +package-lock.json +docs +scripts diff --git a/packages/core/LICENSE b/packages/core/LICENSE new file mode 100644 index 00000000..09d493bf --- /dev/null +++ b/packages/core/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 00000000..ed223087 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,2 @@ +# core +core stuff diff --git a/packages/core/eslint.config.js b/packages/core/eslint.config.js new file mode 100644 index 00000000..9dd72a92 --- /dev/null +++ b/packages/core/eslint.config.js @@ -0,0 +1,95 @@ +import tseslint from 'typescript-eslint'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +// plugins +import regexpEslint from 'eslint-plugin-regexp'; +const typescriptEslint = tseslint.plugin; + +// parsers +const typescriptParser = tseslint.parser; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { + files: ["src/*.{ts}"] +}, + + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + regexpEslint.configs['flat/recommended'], + { + languageOptions: { + parser: typescriptParser, + parserOptions: { + project: ['./packages/*/tsconfig.json', './tsconfig.eslint.json'], + tsconfigRootDir: __dirname, + }, + }, + plugins: { + '@typescript-eslint': typescriptEslint, + regexp: regexpEslint, + }, + rules: { + // These off/configured-differently-by-default rules fit well for us + '@typescript-eslint/switch-exhaustiveness-check': 'error', + '@typescript-eslint/no-shadow': 'off', + 'no-console': 'off', + '@typescript-eslint/no-unsafe-enum-comparison' : 'off', + '@typescript-eslint/no-empty-object-type': 'off', + // Todo: do we want these? + 'no-var': 'off', + + 'regexp/prefer-regexp-exec': 'off', + '@typescript-eslint/no-duplicate-enum-values': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + '@typescript-eslint/prefer-for-of': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/class-literal-property-style': 'off', + '@typescript-eslint/consistent-indexed-object-style': 'off', + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/dot-notation': 'off', + '@typescript-eslint/no-base-to-string': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-redundant-type-constituents': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/only-throw-error': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'off', + '@typescript-eslint/prefer-optional-chain': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-string-starts-ends-with': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/sort-type-constituents': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/no-explicit-any': 'off', + + // Used by Biome + '@typescript-eslint/consistent-type-imports': 'off', + // These rules enabled by the preset configs don't work well for us + '@typescript-eslint/await-thenable': 'off', + 'prefer-const': 'off', + + // In some cases, using explicit letter-casing is more performant than the `i` flag + 'regexp/use-ignore-case': 'off', + 'regexp/prefer-regexp-exec': 'warn', + 'regexp/prefer-regexp-test': 'warn', + 'no-control-regex': 'off' + } + } +] \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 00000000..3d0c0d34 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,63 @@ +{ + "name": "@polymech/core", + "version": "0.2.6", + "license": "BSD", + "type": "module", + "publishConfig": { + "access": "public" + }, + "licenses": [ + { + "type": "BSD", + "url": "https://git.osr-plastic.org/osr-plastic/osr-core/blob/master/LICENSE" + } + ], + "exports": { + "./iterator": { + "import": "./dist/iterator.js", + "require": "./dist/iterator.cjs" + }, + "./strings.js": { + "import": "./dist/strings.js", + "require": "./dist/strings.js" + }, + "./primitives.js": { + "import": "./dist/primitives.js", + "require": "./dist/primitives.js" + } + }, + "main": "dist/index.js", + "repository": { + "type": "git", + "url": "https://git.osr-plastic.org/osr-plastic/osr-core.git" + }, + "types": "index.d.ts", + "dependencies": { + "tslog": "^3.3.3", + "tsup": "^8.3.5", + "zod": "^3.24.1" + }, + "devDependencies": { + "@eslint/js": "^9.18.0", + "@repo/eslint-config": "workspace:*", + "@repo/typescript-config": "workspace:*", + "@types/node": "^8.10.66", + "eslint": "^8.57.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-regexp": "^2.7.0", + "globals": "^15.14.0", + "ts-node": "^10.9.1", + "typescript": "^4.9.5", + "typescript-eslint": "^8.20.0" + }, + "scripts": { + "test": "tsc && mocha build/test", + "buildtsc": "tsc -p . --declaration", + "build": "tsup", + "start": "node build/index.js", + "typings": "tsc -p . --declaration", + "dev": "tsc -p . --declaration -w" + }, + "modules": [], + "readmeFilename": "Readme.md" +} diff --git a/packages/core/src/arrays.ts b/packages/core/src/arrays.ts new file mode 100644 index 00000000..7f2fb130 --- /dev/null +++ b/packages/core/src/arrays.ts @@ -0,0 +1,891 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { findFirstIdxMonotonousOrArrLen } from './arraysFind.js'; +import { CancellationToken } from './cancellation.js'; +import { CancellationError } from './errors.js'; +import { ISplice } from './sequence.js'; + +/** + * Returns the last element of an array. + * @param array The array. + * @param n Which element from the end (default is zero). + */ +export function tail(array: ArrayLike, n: number = 0): T | undefined { + return array[array.length - (1 + n)]; +} + +export function tail2(arr: T[]): [T[], T] { + if (arr.length === 0) { + throw new Error('Invalid tail call'); + } + + return [arr.slice(0, arr.length - 1), arr[arr.length - 1]]; +} + +export function equals(one: ReadonlyArray | undefined, other: ReadonlyArray | undefined, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean { + if (one === other) { + return true; + } + + if (!one || !other) { + return false; + } + + if (one.length !== other.length) { + return false; + } + + for (let i = 0, len = one.length; i < len; i++) { + if (!itemEquals(one[i], other[i])) { + return false; + } + } + + return true; +} + +/** + * Remove the element at `index` by replacing it with the last element. This is faster than `splice` + * but changes the order of the array + */ +export function removeFastWithoutKeepingOrder(array: T[], index: number) { + const last = array.length - 1; + if (index < last) { + array[index] = array[last]; + } + array.pop(); +} + +/** + * Performs a binary search algorithm over a sorted array. + * + * @param array The array being searched. + * @param key The value we search for. + * @param comparator A function that takes two array elements and returns zero + * if they are equal, a negative number if the first element precedes the + * second one in the sorting order, or a positive number if the second element + * precedes the first one. + * @return See {@link binarySearch2} + */ +export function binarySearch(array: ReadonlyArray, key: T, comparator: (op1: T, op2: T) => number): number { + return binarySearch2(array.length, i => comparator(array[i], key)); +} + +/** + * Performs a binary search algorithm over a sorted collection. Useful for cases + * when we need to perform a binary search over something that isn't actually an + * array, and converting data to an array would defeat the use of binary search + * in the first place. + * + * @param length The collection length. + * @param compareToKey A function that takes an index of an element in the + * collection and returns zero if the value at this index is equal to the + * search key, a negative number if the value precedes the search key in the + * sorting order, or a positive number if the search key precedes the value. + * @return A non-negative index of an element, if found. If not found, the + * result is -(n+1) (or ~n, using bitwise notation), where n is the index + * where the key should be inserted to maintain the sorting order. + */ +export function binarySearch2(length: number, compareToKey: (index: number) => number): number { + let low = 0, + high = length - 1; + + while (low <= high) { + const mid = ((low + high) / 2) | 0; + const comp = compareToKey(mid); + if (comp < 0) { + low = mid + 1; + } else if (comp > 0) { + high = mid - 1; + } else { + return mid; + } + } + return -(low + 1); +} + +type Compare = (a: T, b: T) => number; + + +export function quickSelect(nth: number, data: T[], compare: Compare): T { + + nth = nth | 0; + + if (nth >= data.length) { + throw new TypeError('invalid index'); + } + + const pivotValue = data[Math.floor(data.length * Math.random())]; + const lower: T[] = []; + const higher: T[] = []; + const pivots: T[] = []; + + for (const value of data) { + const val = compare(value, pivotValue); + if (val < 0) { + lower.push(value); + } else if (val > 0) { + higher.push(value); + } else { + pivots.push(value); + } + } + + if (nth < lower.length) { + return quickSelect(nth, lower, compare); + } else if (nth < lower.length + pivots.length) { + return pivots[0]; + } else { + return quickSelect(nth - (lower.length + pivots.length), higher, compare); + } +} + +export function groupBy(data: ReadonlyArray, compare: (a: T, b: T) => number): T[][] { + const result: T[][] = []; + let currentGroup: T[] | undefined = undefined; + for (const element of data.slice(0).sort(compare)) { + if (!currentGroup || compare(currentGroup[0], element) !== 0) { + currentGroup = [element]; + result.push(currentGroup); + } else { + currentGroup.push(element); + } + } + return result; +} + +/** + * Splits the given items into a list of (non-empty) groups. + * `shouldBeGrouped` is used to decide if two consecutive items should be in the same group. + * The order of the items is preserved. + */ +export function* groupAdjacentBy(items: Iterable, shouldBeGrouped: (item1: T, item2: T) => boolean): Iterable { + let currentGroup: T[] | undefined; + let last: T | undefined; + for (const item of items) { + if (last !== undefined && shouldBeGrouped(last, item)) { + currentGroup!.push(item); + } else { + if (currentGroup) { + yield currentGroup; + } + currentGroup = [item]; + } + last = item; + } + if (currentGroup) { + yield currentGroup; + } +} + +export function forEachAdjacent(arr: T[], f: (item1: T | undefined, item2: T | undefined) => void): void { + for (let i = 0; i <= arr.length; i++) { + f(i === 0 ? undefined : arr[i - 1], i === arr.length ? undefined : arr[i]); + } +} + +export function forEachWithNeighbors(arr: T[], f: (before: T | undefined, element: T, after: T | undefined) => void): void { + for (let i = 0; i < arr.length; i++) { + f(i === 0 ? undefined : arr[i - 1], arr[i], i + 1 === arr.length ? undefined : arr[i + 1]); + } +} + +interface IMutableSplice extends ISplice { + readonly toInsert: T[]; + deleteCount: number; +} + +/** + * Diffs two *sorted* arrays and computes the splices which apply the diff. + */ +export function sortedDiff(before: ReadonlyArray, after: ReadonlyArray, compare: (a: T, b: T) => number): ISplice[] { + const result: IMutableSplice[] = []; + + function pushSplice(start: number, deleteCount: number, toInsert: T[]): void { + if (deleteCount === 0 && toInsert.length === 0) { + return; + } + + const latest = result[result.length - 1]; + + if (latest && latest.start + latest.deleteCount === start) { + latest.deleteCount += deleteCount; + latest.toInsert.push(...toInsert); + } else { + result.push({ start, deleteCount, toInsert }); + } + } + + let beforeIdx = 0; + let afterIdx = 0; + + while (true) { + if (beforeIdx === before.length) { + pushSplice(beforeIdx, 0, after.slice(afterIdx)); + break; + } + if (afterIdx === after.length) { + pushSplice(beforeIdx, before.length - beforeIdx, []); + break; + } + + const beforeElement = before[beforeIdx]; + const afterElement = after[afterIdx]; + const n = compare(beforeElement, afterElement); + if (n === 0) { + // equal + beforeIdx += 1; + afterIdx += 1; + } else if (n < 0) { + // beforeElement is smaller -> before element removed + pushSplice(beforeIdx, 1, []); + beforeIdx += 1; + } else if (n > 0) { + // beforeElement is greater -> after element added + pushSplice(beforeIdx, 0, [afterElement]); + afterIdx += 1; + } + } + + return result; +} + +/** + * Takes two *sorted* arrays and computes their delta (removed, added elements). + * Finishes in `Math.min(before.length, after.length)` steps. + */ +export function delta(before: ReadonlyArray, after: ReadonlyArray, compare: (a: T, b: T) => number): { removed: T[]; added: T[] } { + const splices = sortedDiff(before, after, compare); + const removed: T[] = []; + const added: T[] = []; + + for (const splice of splices) { + removed.push(...before.slice(splice.start, splice.start + splice.deleteCount)); + added.push(...splice.toInsert); + } + + return { removed, added }; +} + +/** + * Returns the top N elements from the array. + * + * Faster than sorting the entire array when the array is a lot larger than N. + * + * @param array The unsorted array. + * @param compare A sort function for the elements. + * @param n The number of elements to return. + * @return The first n elements from array when sorted with compare. + */ +export function top(array: ReadonlyArray, compare: (a: T, b: T) => number, n: number): T[] { + if (n === 0) { + return []; + } + const result = array.slice(0, n).sort(compare); + topStep(array, compare, result, n, array.length); + return result; +} + +/** + * Asynchronous variant of `top()` allowing for splitting up work in batches between which the event loop can run. + * + * Returns the top N elements from the array. + * + * Faster than sorting the entire array when the array is a lot larger than N. + * + * @param array The unsorted array. + * @param compare A sort function for the elements. + * @param n The number of elements to return. + * @param batch The number of elements to examine before yielding to the event loop. + * @return The first n elements from array when sorted with compare. + */ +export function topAsync(array: T[], compare: (a: T, b: T) => number, n: number, batch: number, token?: CancellationToken): Promise { + if (n === 0) { + return Promise.resolve([]); + } + + return new Promise((resolve, reject) => { + (async () => { + const o = array.length; + const result = array.slice(0, n).sort(compare); + for (let i = n, m = Math.min(n + batch, o); i < o; i = m, m = Math.min(m + batch, o)) { + if (i > n) { + await new Promise(resolve => setTimeout(resolve)); // any other delay function would starve I/O + } + if (token && token.isCancellationRequested) { + throw new CancellationError(); + } + topStep(array, compare, result, i, m); + } + return result; + })() + .then(resolve, reject); + }); +} + +function topStep(array: ReadonlyArray, compare: (a: T, b: T) => number, result: T[], i: number, m: number): void { + for (const n = result.length; i < m; i++) { + const element = array[i]; + if (compare(element, result[n - 1]) < 0) { + result.pop(); + const j = findFirstIdxMonotonousOrArrLen(result, e => compare(element, e) < 0); + result.splice(j, 0, element); + } + } +} + +/** + * @returns New array with all falsy values removed. The original array IS NOT modified. + */ +export function coalesce(array: ReadonlyArray): T[] { + return array.filter((e): e is T => !!e); +} + +/** + * Remove all falsy values from `array`. The original array IS modified. + */ +export function coalesceInPlace(array: Array): asserts array is Array { + let to = 0; + for (let i = 0; i < array.length; i++) { + if (!!array[i]) { + array[to] = array[i]; + to += 1; + } + } + array.length = to; +} + +/** + * @deprecated Use `Array.copyWithin` instead + */ +export function move(array: unknown[], from: number, to: number): void { + array.splice(to, 0, array.splice(from, 1)[0]); +} + +/** + * @returns false if the provided object is an array and not empty. + */ +export function isFalsyOrEmpty(obj: any): boolean { + return !Array.isArray(obj) || obj.length === 0; +} + +/** + * @returns True if the provided object is an array and has at least one element. + */ +export function isNonEmptyArray(obj: T[] | undefined | null): obj is T[]; +export function isNonEmptyArray(obj: readonly T[] | undefined | null): obj is readonly T[]; +export function isNonEmptyArray(obj: T[] | readonly T[] | undefined | null): obj is T[] | readonly T[] { + return Array.isArray(obj) && obj.length > 0; +} + +/** + * Removes duplicates from the given array. The optional keyFn allows to specify + * how elements are checked for equality by returning an alternate value for each. + */ +export function distinct(array: ReadonlyArray, keyFn: (value: T) => unknown = value => value): T[] { + const seen = new Set(); + + return array.filter(element => { + const key = keyFn!(element); + if (seen.has(key)) { + return false; + } + seen.add(key); + return true; + }); +} + +export function uniqueFilter(keyFn: (t: T) => R): (t: T) => boolean { + const seen = new Set(); + + return element => { + const key = keyFn(element); + + if (seen.has(key)) { + return false; + } + + seen.add(key); + return true; + }; +} + +export function commonPrefixLength(one: ReadonlyArray, other: ReadonlyArray, equals: (a: T, b: T) => boolean = (a, b) => a === b): number { + let result = 0; + + for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) { + result++; + } + + return result; +} + +export function range(to: number): number[]; +export function range(from: number, to: number): number[]; +export function range(arg: number, to?: number): number[] { + let from = typeof to === 'number' ? arg : 0; + + if (typeof to === 'number') { + from = arg; + } else { + from = 0; + to = arg; + } + + const result: number[] = []; + + if (from <= to) { + for (let i = from; i < to; i++) { + result.push(i); + } + } else { + for (let i = from; i > to; i--) { + result.push(i); + } + } + + return result; +} + +export function index(array: ReadonlyArray, indexer: (t: T) => string): { [key: string]: T }; +export function index(array: ReadonlyArray, indexer: (t: T) => string, mapper: (t: T) => R): { [key: string]: R }; +export function index(array: ReadonlyArray, indexer: (t: T) => string, mapper?: (t: T) => R): { [key: string]: R } { + return array.reduce((r, t) => { + r[indexer(t)] = mapper ? mapper(t) : t; + return r; + }, Object.create(null)); +} + +/** + * Inserts an element into an array. Returns a function which, when + * called, will remove that element from the array. + * + * @deprecated In almost all cases, use a `Set` instead. + */ +export function insert(array: T[], element: T): () => void { + array.push(element); + + return () => remove(array, element); +} + +/** + * Removes an element from an array if it can be found. + * + * @deprecated In almost all cases, use a `Set` instead. + */ +export function remove(array: T[], element: T): T | undefined { + const index = array.indexOf(element); + if (index > -1) { + array.splice(index, 1); + + return element; + } + + return undefined; +} + +/** + * Insert `insertArr` inside `target` at `insertIndex`. + * Please don't touch unless you understand https://jsperf.com/inserting-an-array-within-an-array + */ +export function arrayInsert(target: T[], insertIndex: number, insertArr: T[]): T[] { + const before = target.slice(0, insertIndex); + const after = target.slice(insertIndex); + return before.concat(insertArr, after); +} + +/** + * Uses Fisher-Yates shuffle to shuffle the given array + */ +export function shuffle(array: T[], _seed?: number): void { + let rand: () => number; + + if (typeof _seed === 'number') { + let seed = _seed; + // Seeded random number generator in JS. Modified from: + // https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript + rand = () => { + const x = Math.sin(seed++) * 179426549; // throw away most significant digits and reduce any potential bias + return x - Math.floor(x); + }; + } else { + rand = Math.random; + } + + for (let i = array.length - 1; i > 0; i -= 1) { + const j = Math.floor(rand() * (i + 1)); + const temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} + +/** + * Pushes an element to the start of the array, if found. + */ +export function pushToStart(arr: T[], value: T): void { + const index = arr.indexOf(value); + + if (index > -1) { + arr.splice(index, 1); + arr.unshift(value); + } +} + +/** + * Pushes an element to the end of the array, if found. + */ +export function pushToEnd(arr: T[], value: T): void { + const index = arr.indexOf(value); + + if (index > -1) { + arr.splice(index, 1); + arr.push(value); + } +} + +export function pushMany(arr: T[], items: ReadonlyArray): void { + for (const item of items) { + arr.push(item); + } +} + +export function mapArrayOrNot(items: T | T[], fn: (_: T) => U): U | U[] { + return Array.isArray(items) ? + items.map(fn) : + fn(items); +} + +export function asArray(x: T | T[]): T[]; +export function asArray(x: T | readonly T[]): readonly T[]; +export function asArray(x: T | T[]): T[] { + return Array.isArray(x) ? x : [x]; +} + +export function getRandomElement(arr: T[]): T | undefined { + return arr[Math.floor(Math.random() * arr.length)]; +} + +/** + * Insert the new items in the array. + * @param array The original array. + * @param start The zero-based location in the array from which to start inserting elements. + * @param newItems The items to be inserted + */ +export function insertInto(array: T[], start: number, newItems: T[]): void { + const startIdx = getActualStartIndex(array, start); + const originalLength = array.length; + const newItemsLength = newItems.length; + array.length = originalLength + newItemsLength; + // Move the items after the start index, start from the end so that we don't overwrite any value. + for (let i = originalLength - 1; i >= startIdx; i--) { + array[i + newItemsLength] = array[i]; + } + + for (let i = 0; i < newItemsLength; i++) { + array[i + startIdx] = newItems[i]; + } +} + +/** + * Removes elements from an array and inserts new elements in their place, returning the deleted elements. Alternative to the native Array.splice method, it + * can only support limited number of items due to the maximum call stack size limit. + * @param array The original array. + * @param start The zero-based location in the array from which to start removing elements. + * @param deleteCount The number of elements to remove. + * @returns An array containing the elements that were deleted. + */ +export function splice(array: T[], start: number, deleteCount: number, newItems: T[]): T[] { + const index = getActualStartIndex(array, start); + let result = array.splice(index, deleteCount); + if (result === undefined) { + // see https://bugs.webkit.org/show_bug.cgi?id=261140 + result = []; + } + insertInto(array, index, newItems); + return result; +} + +/** + * Determine the actual start index (same logic as the native splice() or slice()) + * If greater than the length of the array, start will be set to the length of the array. In this case, no element will be deleted but the method will behave as an adding function, adding as many element as item[n*] provided. + * If negative, it will begin that many elements from the end of the array. (In this case, the origin -1, meaning -n is the index of the nth last element, and is therefore equivalent to the index of array.length - n.) If array.length + start is less than 0, it will begin from index 0. + * @param array The target array. + * @param start The operation index. + */ +function getActualStartIndex(array: T[], start: number): number { + return start < 0 ? Math.max(start + array.length, 0) : Math.min(start, array.length); +} + +/** + * When comparing two values, + * a negative number indicates that the first value is less than the second, + * a positive number indicates that the first value is greater than the second, + * and zero indicates that neither is the case. +*/ +export type CompareResult = number; + +export namespace CompareResult { + export function isLessThan(result: CompareResult): boolean { + return result < 0; + } + + export function isLessThanOrEqual(result: CompareResult): boolean { + return result <= 0; + } + + export function isGreaterThan(result: CompareResult): boolean { + return result > 0; + } + + export function isNeitherLessOrGreaterThan(result: CompareResult): boolean { + return result === 0; + } + + export const greaterThan = 1; + export const lessThan = -1; + export const neitherLessOrGreaterThan = 0; +} + +/** + * A comparator `c` defines a total order `<=` on `T` as following: + * `c(a, b) <= 0` iff `a` <= `b`. + * We also have `c(a, b) == 0` iff `c(b, a) == 0`. +*/ +export type Comparator = (a: T, b: T) => CompareResult; + +export function compareBy(selector: (item: TItem) => TCompareBy, comparator: Comparator): Comparator { + return (a, b) => comparator(selector(a), selector(b)); +} + +export function tieBreakComparators(...comparators: Comparator[]): Comparator { + return (item1, item2) => { + for (const comparator of comparators) { + const result = comparator(item1, item2); + if (!CompareResult.isNeitherLessOrGreaterThan(result)) { + return result; + } + } + return CompareResult.neitherLessOrGreaterThan; + }; +} + +/** + * The natural order on numbers. +*/ +export const numberComparator: Comparator = (a, b) => a - b; + +export const booleanComparator: Comparator = (a, b) => numberComparator(a ? 1 : 0, b ? 1 : 0); + +export function reverseOrder(comparator: Comparator): Comparator { + return (a, b) => -comparator(a, b); +} + +export class ArrayQueue { + private firstIdx = 0; + private items: T[] = []; + private lastIdx = this.items.length - 1; + + /** + * Constructs a queue that is backed by the given array. Runtime is O(1). + */ + constructor() { } + + get length(): number { + return this.lastIdx - this.firstIdx + 1; + } + + /** + * Consumes elements from the beginning of the queue as long as the predicate returns true. + * If no elements were consumed, `null` is returned. Has a runtime of O(result.length). + */ + takeWhile(predicate: (value: T) => boolean): T[] | null { + // P(k) := k <= this.lastIdx && predicate(this.items[k]) + // Find s := min { k | k >= this.firstIdx && !P(k) } and return this.data[this.firstIdx...s) + + let startIdx = this.firstIdx; + while (startIdx < this.items.length && predicate(this.items[startIdx])) { + startIdx++; + } + const result = startIdx === this.firstIdx ? null : this.items.slice(this.firstIdx, startIdx); + this.firstIdx = startIdx; + return result; + } + + /** + * Consumes elements from the end of the queue as long as the predicate returns true. + * If no elements were consumed, `null` is returned. + * The result has the same order as the underlying array! + */ + takeFromEndWhile(predicate: (value: T) => boolean): T[] | null { + // P(k) := this.firstIdx >= k && predicate(this.items[k]) + // Find s := max { k | k <= this.lastIdx && !P(k) } and return this.data(s...this.lastIdx] + + let endIdx = this.lastIdx; + while (endIdx >= 0 && predicate(this.items[endIdx])) { + endIdx--; + } + const result = endIdx === this.lastIdx ? null : this.items.slice(endIdx + 1, this.lastIdx + 1); + this.lastIdx = endIdx; + return result; + } + + peek(): T | undefined { + if (this.length === 0) { + return undefined; + } + return this.items[this.firstIdx]; + } + + peekLast(): T | undefined { + if (this.length === 0) { + return undefined; + } + return this.items[this.lastIdx]; + } + + dequeue(): T | undefined { + const result = this.items[this.firstIdx]; + this.firstIdx++; + return result; + } + + removeLast(): T | undefined { + const result = this.items[this.lastIdx]; + this.lastIdx--; + return result; + } + + takeCount(count: number): T[] { + const result = this.items.slice(this.firstIdx, this.firstIdx + count); + this.firstIdx += count; + return result; + } +} + +/** + * This class is faster than an iterator and array for lazy computed data. +*/ +export class CallbackIterable { + public static readonly empty = new CallbackIterable(_callback => { }); + + constructor( + /** + * Calls the callback for every item. + * Stops when the callback returns false. + */ + public readonly iterate: (callback: (item: T) => boolean) => void + ) { + } + + forEach(handler: (item: T) => void) { + this.iterate(item => { handler(item); return true; }); + } + + toArray(): T[] { + const result: T[] = []; + this.iterate(item => { result.push(item); return true; }); + return result; + } + + filter(predicate: (item: T) => boolean): CallbackIterable { + return new CallbackIterable(cb => this.iterate(item => predicate(item) ? cb(item) : true)); + } + + map(mapFn: (item: T) => TResult): CallbackIterable { + return new CallbackIterable(cb => this.iterate(item => cb(mapFn(item)))); + } + + some(predicate: (item: T) => boolean): boolean { + let result = false; + this.iterate(item => { result = predicate(item); return !result; }); + return result; + } + + findFirst(predicate: (item: T) => boolean): T | undefined { + let result: T | undefined; + this.iterate(item => { + if (predicate(item)) { + result = item; + return false; + } + return true; + }); + return result; + } + + findLast(predicate: (item: T) => boolean): T | undefined { + let result: T | undefined; + this.iterate(item => { + if (predicate(item)) { + result = item; + } + return true; + }); + return result; + } + + findLastMaxBy(comparator: Comparator): T | undefined { + let result: T | undefined; + let first = true; + this.iterate(item => { + if (first || CompareResult.isGreaterThan(comparator(item, result!))) { + first = false; + result = item; + } + return true; + }); + return result; + } +} + +/** + * Represents a re-arrangement of items in an array. + */ +export class Permutation { + constructor(private readonly _indexMap: readonly number[]) { } + + /** + * Returns a permutation that sorts the given array according to the given compare function. + */ + public static createSortPermutation(arr: readonly T[], compareFn: (a: T, b: T) => number): Permutation { + const sortIndices = Array.from(arr.keys()).sort((index1, index2) => compareFn(arr[index1], arr[index2])); + return new Permutation(sortIndices); + } + + /** + * Returns a new array with the elements of the given array re-arranged according to this permutation. + */ + apply(arr: readonly T[]): T[] { + return arr.map((_, index) => arr[this._indexMap[index]]); + } + + /** + * Returns a new permutation that undoes the re-arrangement of this permutation. + */ + inverse(): Permutation { + const inverseIndexMap = this._indexMap.slice(); + for (let i = 0; i < this._indexMap.length; i++) { + inverseIndexMap[this._indexMap[i]] = i; + } + return new Permutation(inverseIndexMap); + } +} + +/** + * Asynchronous variant of `Array.find()`, returning the first element in + * the array for which the predicate returns true. + * + * This implementation does not bail early and waits for all promises to + * resolve before returning. + */ +export async function findAsync(array: readonly T[], predicate: (element: T, index: number) => Promise): Promise { + const results = await Promise.all(array.map( + async (element, index) => ({ element, ok: await predicate(element, index) }) + )); + + return results.find(r => r.ok)?.element; +} diff --git a/packages/core/src/arraysFind.ts b/packages/core/src/arraysFind.ts new file mode 100644 index 00000000..1774035a --- /dev/null +++ b/packages/core/src/arraysFind.ts @@ -0,0 +1,202 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Comparator } from './arrays.js'; + +export function findLast(array: readonly T[], predicate: (item: T) => boolean): T | undefined { + const idx = findLastIdx(array, predicate); + if (idx === -1) { + return undefined; + } + return array[idx]; +} + +export function findLastIdx(array: readonly T[], predicate: (item: T) => boolean, fromIndex = array.length - 1): number { + for (let i = fromIndex; i >= 0; i--) { + const element = array[i]; + + if (predicate(element)) { + return i; + } + } + + return -1; +} + +/** + * Finds the last item where predicate is true using binary search. + * `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`! + * + * @returns `undefined` if no item matches, otherwise the last item that matches the predicate. + */ +export function findLastMonotonous(array: readonly T[], predicate: (item: T) => boolean): T | undefined { + const idx = findLastIdxMonotonous(array, predicate); + return idx === -1 ? undefined : array[idx]; +} + +/** + * Finds the last item where predicate is true using binary search. + * `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`! + * + * @returns `startIdx - 1` if predicate is false for all items, otherwise the index of the last item that matches the predicate. + */ +export function findLastIdxMonotonous(array: readonly T[], predicate: (item: T) => boolean, startIdx = 0, endIdxEx = array.length): number { + let i = startIdx; + let j = endIdxEx; + while (i < j) { + const k = Math.floor((i + j) / 2); + if (predicate(array[k])) { + i = k + 1; + } else { + j = k; + } + } + return i - 1; +} + +/** + * Finds the first item where predicate is true using binary search. + * `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[false, ..., false, true, ..., true]`! + * + * @returns `undefined` if no item matches, otherwise the first item that matches the predicate. + */ +export function findFirstMonotonous(array: readonly T[], predicate: (item: T) => boolean): T | undefined { + const idx = findFirstIdxMonotonousOrArrLen(array, predicate); + return idx === array.length ? undefined : array[idx]; +} + +/** + * Finds the first item where predicate is true using binary search. + * `predicate` must be monotonous, i.e. `arr.map(predicate)` must be like `[false, ..., false, true, ..., true]`! + * + * @returns `endIdxEx` if predicate is false for all items, otherwise the index of the first item that matches the predicate. + */ +export function findFirstIdxMonotonousOrArrLen(array: readonly T[], predicate: (item: T) => boolean, startIdx = 0, endIdxEx = array.length): number { + let i = startIdx; + let j = endIdxEx; + while (i < j) { + const k = Math.floor((i + j) / 2); + if (predicate(array[k])) { + j = k; + } else { + i = k + 1; + } + } + return i; +} + +export function findFirstIdxMonotonous(array: readonly T[], predicate: (item: T) => boolean, startIdx = 0, endIdxEx = array.length): number { + const idx = findFirstIdxMonotonousOrArrLen(array, predicate, startIdx, endIdxEx); + return idx === array.length ? -1 : idx; +} + +/** + * Use this when + * * You have a sorted array + * * You query this array with a monotonous predicate to find the last item that has a certain property. + * * You query this array multiple times with monotonous predicates that get weaker and weaker. + */ +export class MonotonousArray { + public static assertInvariants = false; + + private _findLastMonotonousLastIdx = 0; + private _prevFindLastPredicate: ((item: T) => boolean) | undefined; + + constructor(private readonly _array: readonly T[]) { + } + + /** + * The predicate must be monotonous, i.e. `arr.map(predicate)` must be like `[true, ..., true, false, ..., false]`! + * For subsequent calls, current predicate must be weaker than (or equal to) the previous predicate, i.e. more entries must be `true`. + */ + findLastMonotonous(predicate: (item: T) => boolean): T | undefined { + if (MonotonousArray.assertInvariants) { + if (this._prevFindLastPredicate) { + for (const item of this._array) { + if (this._prevFindLastPredicate(item) && !predicate(item)) { + throw new Error('MonotonousArray: current predicate must be weaker than (or equal to) the previous predicate.'); + } + } + } + this._prevFindLastPredicate = predicate; + } + + const idx = findLastIdxMonotonous(this._array, predicate, this._findLastMonotonousLastIdx); + this._findLastMonotonousLastIdx = idx + 1; + return idx === -1 ? undefined : this._array[idx]; + } +} + +/** + * Returns the first item that is equal to or greater than every other item. +*/ +export function findFirstMax(array: readonly T[], comparator: Comparator): T | undefined { + if (array.length === 0) { + return undefined; + } + + let max = array[0]; + for (let i = 1; i < array.length; i++) { + const item = array[i]; + if (comparator(item, max) > 0) { + max = item; + } + } + return max; +} + +/** + * Returns the last item that is equal to or greater than every other item. +*/ +export function findLastMax(array: readonly T[], comparator: Comparator): T | undefined { + if (array.length === 0) { + return undefined; + } + + let max = array[0]; + for (let i = 1; i < array.length; i++) { + const item = array[i]; + if (comparator(item, max) >= 0) { + max = item; + } + } + return max; +} + +/** + * Returns the first item that is equal to or less than every other item. +*/ +export function findFirstMin(array: readonly T[], comparator: Comparator): T | undefined { + return findFirstMax(array, (a, b) => -comparator(a, b)); +} + +export function findMaxIdx(array: readonly T[], comparator: Comparator): number { + if (array.length === 0) { + return -1; + } + + let maxIdx = 0; + for (let i = 1; i < array.length; i++) { + const item = array[i]; + if (comparator(item, array[maxIdx]) > 0) { + maxIdx = i; + } + } + return maxIdx; +} + +/** + * Returns the first mapped value of the array which is not undefined. + */ +export function mapFindFirst(items: Iterable, mapFn: (value: T) => R | undefined): R | undefined { + for (const value of items) { + const mapped = mapFn(value); + if (mapped !== undefined) { + return mapped; + } + } + + return undefined; +} diff --git a/packages/core/src/aspects.ts b/packages/core/src/aspects.ts new file mode 100644 index 00000000..c3a23884 --- /dev/null +++ b/packages/core/src/aspects.ts @@ -0,0 +1,348 @@ +/* ------------------------------------------------------------------------- + * aspects.ts + * + * A robust “aspect” system supporting: + * - before: optionally modifies arguments (sync or async) + * - after: optionally modifies return value (sync or async) + * - around: complete control over function invocation + * - error: intercept errors (sync or async) + * + * Works as both: + * 1) Decorators for class methods (e.g. @before(...)) + * 2) Direct function wrappers (e.g. fn = before(fn, ...)). + * ------------------------------------------------------------------------ */ + +/* ------------------------------------------------------------------------- + * 1) SIGNALS Enum (string-based to avoid symbol issues) + * ------------------------------------------------------------------------ */ +export enum SIGNALS { + BEFORE = 'BEFORE', + AFTER = 'AFTER', + AROUND = 'AROUND', + ERROR = 'ERROR', +} + +/* ------------------------------------------------------------------------- + * 2) Basic Types + * ------------------------------------------------------------------------ */ +type AnyFunction = (...args: any[]) => any; + +/** + * If a function returns a Promise, then its awaited type is T. + * Otherwise, it's just ReturnType. + */ +type AwaitedReturn = + T extends (...args: any[]) => Promise ? U : ReturnType; + +/* ------------------------------------------------------------------------- + * 3) Advice Signatures + * ------------------------------------------------------------------------ */ + +/** + * BEFORE advice: + * - Receives `context` (the `this` of the function) + * - Receives the original `args` + * - Can return either nothing (`void`) or new arguments (`Parameters`) + * - Can be async, returning a Promise that resolves to new args or `void` + */ +export type BeforeAdvice = ( + context: ThisParameterType, + args: Parameters +) => void | Parameters | Promise>; + +/** + * AFTER advice: + * - Receives `context`, the original function’s final (awaited) result, + * and the original arguments + * - Can return a new result (sync or async) + */ +export type AfterAdvice = ( + context: ThisParameterType, + result: AwaitedReturn, + args: Parameters +) => AwaitedReturn | Promise>; + +/** + * AROUND advice: + * - Provides a `proceed(...args)` function that calls the original method + * - You can call `proceed` any number of times, or skip it + * - Supports both sync and async usage + */ +export type AroundAdvice = ( + proceed: (...args: Parameters) => ReturnType, + context: ThisParameterType, + args: Parameters +) => ReturnType; + +/** + * ERROR advice: + * - Intercepts errors thrown by the original method (sync or async) + * - Can return a fallback result or rethrow + */ +export type ErrorAdvice = ( + error: unknown, + context: ThisParameterType, + args: Parameters +) => ReturnType | void; + +/* ------------------------------------------------------------------------- + * 4) ISignalMap: Each signal has a distinct function wrapper signature + * ------------------------------------------------------------------------ */ +interface ISignalMap { + [SIGNALS.BEFORE]: (original: T, advice: BeforeAdvice) => T; + [SIGNALS.AFTER]: (original: T, advice: AfterAdvice) => T; + [SIGNALS.AROUND]: (original: T, advice: AroundAdvice) => T; + [SIGNALS.ERROR]: (original: T, advice: ErrorAdvice) => T; +} + +/* ------------------------------------------------------------------------- + * 5) The SignalMap Implementation + * - This is where the actual "wrapping" logic lives. + * ------------------------------------------------------------------------ */ +const SignalMap: ISignalMap = { + /** + * BEFORE: + * - Possibly modifies arguments + * - If returns a Promise, we await it before calling original + * - If returns an array, we use that as new arguments + */ + [SIGNALS.BEFORE](original: T, advice: BeforeAdvice): T { + return function (this: ThisParameterType, ...args: Parameters): ReturnType { + const maybeNewArgs = advice(this, args); + + if (maybeNewArgs instanceof Promise) { + return maybeNewArgs.then((resolvedArgs) => { + const finalArgs = resolvedArgs || args; + const result = original.apply(this, finalArgs); + return (result instanceof Promise) ? result : Promise.resolve(result); + }) as ReturnType; + } else { + const finalArgs = Array.isArray(maybeNewArgs) ? maybeNewArgs : args; + return original.apply(this, finalArgs); + } + } as T; + }, + + /** + * AFTER: + * - Possibly modifies the return value + * - If original is async, we chain on its promise + * - Advice can be sync or async + */ + [SIGNALS.AFTER](original: T, advice: AfterAdvice): T { + return function (this: ThisParameterType, ...args: Parameters): ReturnType { + const result = original.apply(this, args); + + if (result instanceof Promise) { + return result.then((unwrapped) => { + const maybeNewResult = advice(this, unwrapped, args); + return (maybeNewResult instanceof Promise) ? maybeNewResult : maybeNewResult; + }) as ReturnType; + } else { + const maybeNewResult = advice(this, result as AwaitedReturn, args); + if (maybeNewResult instanceof Promise) { + return maybeNewResult.then(r => r) as ReturnType; + } + return maybeNewResult as ReturnType; + } + } as T; + }, + + /** + * AROUND: + * - Full control over invocation + * - Typically you do: proceed(...args) + * - If you want to skip or call multiple times, you can + */ + [SIGNALS.AROUND](original: T, advice: AroundAdvice): T { + return function (this: ThisParameterType, ...args: Parameters): ReturnType { + const proceed = (...innerArgs: Parameters) => original.apply(this, innerArgs); + return advice(proceed, this, args); + } as T; + }, + + /** + * ERROR: + * - Intercepts errors thrown by the original function or a rejected Promise + * - Optionally returns a fallback or rethrows + */ + [SIGNALS.ERROR](original: T, advice: ErrorAdvice): T { + return function (this: ThisParameterType, ...args: Parameters): ReturnType { + try { + const result = original.apply(this, args); + if (result instanceof Promise) { + // Handle async rejections + return result.catch((err: unknown) => { + return advice(err, this, args); + }) as ReturnType; + } + return result; + } catch (err) { + // Synchronous error + return advice(err, this, args) as ReturnType; + } + } as T; + }, +}; + +/* ------------------------------------------------------------------------- + * 6) Decorator Helper + * ------------------------------------------------------------------------ */ + +/** Checks if we’re decorating a class method. */ +function isMethod( + _target: any, + descriptor?: PropertyDescriptor +): descriptor is PropertyDescriptor & { value: AnyFunction } { + return !!descriptor && typeof descriptor.value === 'function'; +} + +/* ------------------------------------------------------------------------- + * 7) Wrapped Helpers (cutMethod, cut, aspect) + * ------------------------------------------------------------------------ */ + +/** Strictly typed wrapping for class methods. */ +function cutMethod( + descriptor: PropertyDescriptor & { value: T }, + advice: A, + type: SIGNALS +): PropertyDescriptor { + const original = descriptor.value; + descriptor.value = SignalMap[type](original, advice as any); // Cast `any` or refine further + return descriptor; +} + +/** Strictly typed wrapping for direct function usage. */ +function cut(target: T, advice: A, type: SIGNALS): T { + return SignalMap[type](target, advice as any); +} + +interface AspectOptions { + type: T; + advice: A; +} + +/** + * The core aspect(...) function + * - Returns a decorator if used in that style + * - Otherwise, can wrap a function directly + */ +function aspect({ type, advice }: AspectOptions) { + // If type is invalid, produce a no-op decorator + if (!(type in SignalMap)) { + return function crosscut( + target: any, + _name?: string, + descriptor?: PropertyDescriptor + ) { + return descriptor || target; + }; + } + + // Return a decorator function + return function crosscut( + target: any, + _name?: string, + descriptor?: PropertyDescriptor + ): any { + // If used on a method + if (isMethod(target, descriptor)) { + return cutMethod(descriptor!, advice, type); + } + // If used directly on a function or something else + return cut(target, advice, type); + }; +} + +/* ------------------------------------------------------------------------- + * 8) Overloaded Decorator/Function Wrappers + * - Each can be used as a decorator or direct wrapper + * ------------------------------------------------------------------------ */ + +/** + * `before`: + * Decorator usage => @before((ctx, args) => ...) + * Direct usage => myFn = before(myFn, (ctx, args) => ...) + */ +export function before( + advice: BeforeAdvice +): (target: any, name?: string, descriptor?: PropertyDescriptor) => any; +export function before(fn: T, advice: BeforeAdvice): T; +export function before( + arg1: T | BeforeAdvice, + arg2?: BeforeAdvice +): any { + if (typeof arg1 === 'function' && typeof arg2 === 'function') { + return SignalMap[SIGNALS.BEFORE](arg1, arg2); + } + return aspect({ type: SIGNALS.BEFORE, advice: arg1 as BeforeAdvice }); +} + +/** + * `after`: + * Decorator usage => @after((ctx, result, args) => ...) + * Direct usage => myFn = after(myFn, (ctx, result, args) => ...) + */ +export function after( + advice: AfterAdvice +): (target: any, name?: string, descriptor?: PropertyDescriptor) => any; +export function after(fn: T, advice: AfterAdvice): T; +export function after( + arg1: T | AfterAdvice, + arg2?: AfterAdvice +): any { + if (typeof arg1 === 'function' && typeof arg2 === 'function') { + return SignalMap[SIGNALS.AFTER](arg1, arg2); + } + return aspect({ type: SIGNALS.AFTER, advice: arg1 as AfterAdvice }); +} + +/** + * `around`: + * Decorator usage => @around((proceed, ctx, args) => ...) + * Direct usage => myFn = around(myFn, (proceed, ctx, args) => ...) + */ +export function around( + advice: AroundAdvice +): (target: any, name?: string, descriptor?: PropertyDescriptor) => any; +export function around(fn: T, advice: AroundAdvice): T; +export function around( + arg1: T | AroundAdvice, + arg2?: AroundAdvice +): any { + if (typeof arg1 === 'function' && typeof arg2 === 'function') { + return SignalMap[SIGNALS.AROUND](arg1, arg2); + } + return aspect({ type: SIGNALS.AROUND, advice: arg1 as AroundAdvice }); +} + +/** + * `error`: + * Decorator usage => @error((err, ctx, args) => ...) + * Direct usage => myFn = error(myFn, (err, ctx, args) => ...) + */ +export function error( + advice: ErrorAdvice +): (target: any, name?: string, descriptor?: PropertyDescriptor) => any; +export function error(fn: T, advice: ErrorAdvice): T; +export function error( + arg1: T | ErrorAdvice, + arg2?: ErrorAdvice +): any { + if (typeof arg1 === 'function' && typeof arg2 === 'function') { + return SignalMap[SIGNALS.ERROR](arg1, arg2); + } + return aspect({ type: SIGNALS.ERROR, advice: arg1 as ErrorAdvice }); +} + +/* ------------------------------------------------------------------------- + * 9) Default Export + * ------------------------------------------------------------------------ */ +export default { + SIGNALS, + before, + after, + around, + error, + aspect, +}; diff --git a/packages/core/src/aspects_simple.ts b/packages/core/src/aspects_simple.ts new file mode 100644 index 00000000..ea55223b --- /dev/null +++ b/packages/core/src/aspects_simple.ts @@ -0,0 +1,232 @@ +/* ------------------------------------------------------------------------- + * aspects.ts + * + * A robust “aspect” system supporting: + * - before: optionally modifies arguments (sync or async) + * - after: optionally modifies return value (sync or async) + * - around: complete control over function invocation + * - error: intercept errors (sync or async) + * + * Only supports direct function wrappers (e.g. fn = before(fn, ...)). + * ------------------------------------------------------------------------ */ + +/* ------------------------------------------------------------------------- + * 1) SIGNALS Enum (string-based to avoid symbol issues) + * ------------------------------------------------------------------------ */ +export enum SIGNALS { + BEFORE = 'BEFORE', + AFTER = 'AFTER', + AROUND = 'AROUND', + ERROR = 'ERROR', + } + + /* ------------------------------------------------------------------------- + * 2) Basic Types + * ------------------------------------------------------------------------ */ + type AnyFunction = (...args: any[]) => any; + + /** + * If a function returns a Promise, then its awaited type is T. + * Otherwise, it's just ReturnType. + */ + type AwaitedReturn = + T extends (...args: any[]) => Promise ? U : ReturnType; + + /* ------------------------------------------------------------------------- + * 3) Advice Signatures + * ------------------------------------------------------------------------ */ + + /** + * BEFORE advice: + * - Receives `context` (the `this` of the function) + * - Receives the original `args` + * - Can return either nothing (`void`) or new arguments (`Parameters`) + * - Can be async, returning a Promise that resolves to new args or `void` + */ + export type BeforeAdvice = ( + context: ThisParameterType, + args: Parameters + ) => void | Parameters | Promise>; + + /** + * AFTER advice: + * - Receives `context`, the original function’s final (awaited) result, + * and the original arguments + * - Can return a new result (sync or async) + */ + export type AfterAdvice = ( + context: ThisParameterType, + result: AwaitedReturn, + args: Parameters + ) => AwaitedReturn | Promise>; + + /** + * AROUND advice: + * - Provides a `proceed(...args)` function that calls the original method + * - You can call `proceed` any number of times, or skip it + * - Supports both sync and async usage + */ + export type AroundAdvice = ( + proceed: (...args: Parameters) => ReturnType, + context: ThisParameterType, + args: Parameters + ) => ReturnType; + + /** + * ERROR advice: + * - Intercepts errors thrown by the original function (sync or async) + * - Can return a fallback result or rethrow + */ + export type ErrorAdvice = ( + error: unknown, + context: ThisParameterType, + args: Parameters + ) => ReturnType | void; + + /* ------------------------------------------------------------------------- + * 4) ISignalMap: Each signal has a distinct function wrapper signature + * ------------------------------------------------------------------------ */ + interface ISignalMap { + [SIGNALS.BEFORE]: (original: T, advice: BeforeAdvice) => T; + [SIGNALS.AFTER]: (original: T, advice: AfterAdvice) => T; + [SIGNALS.AROUND]: (original: T, advice: AroundAdvice) => T; + [SIGNALS.ERROR]: (original: T, advice: ErrorAdvice) => T; + } + + /* ------------------------------------------------------------------------- + * 5) The SignalMap Implementation + * - This is where the actual "wrapping" logic lives. + * ------------------------------------------------------------------------ */ + const SignalMap: ISignalMap = { + /** + * BEFORE: + * - Possibly modifies arguments + * - If returns a Promise, we await it before calling original + * - If returns an array, we use that as new arguments + */ + [SIGNALS.BEFORE](original: T, advice: BeforeAdvice): T { + return function (this: ThisParameterType, ...args: Parameters): ReturnType { + const maybeNewArgs = advice(this, args); + + if (maybeNewArgs instanceof Promise) { + return maybeNewArgs.then((resolvedArgs) => { + const finalArgs = resolvedArgs || args; + const result = original.apply(this, finalArgs); + return (result instanceof Promise) ? result : Promise.resolve(result); + }) as ReturnType; + } else { + const finalArgs = Array.isArray(maybeNewArgs) ? maybeNewArgs : args; + return original.apply(this, finalArgs); + } + } as T; + }, + + /** + * AFTER: + * - Possibly modifies the return value + * - If original is async, we chain on its promise + * - Advice can be sync or async + */ + [SIGNALS.AFTER](original: T, advice: AfterAdvice): T { + return function (this: ThisParameterType, ...args: Parameters): ReturnType { + const result = original.apply(this, args); + + if (result instanceof Promise) { + return result.then((unwrapped) => { + const maybeNewResult = advice(this, unwrapped, args); + return (maybeNewResult instanceof Promise) ? maybeNewResult : maybeNewResult; + }) as ReturnType; + } else { + const maybeNewResult = advice(this, result as AwaitedReturn, args); + if (maybeNewResult instanceof Promise) { + return maybeNewResult.then(r => r) as ReturnType; + } + return maybeNewResult as ReturnType; + } + } as T; + }, + + /** + * AROUND: + * - Full control over invocation + * - Typically you do: proceed(...args) + * - If you want to skip or call multiple times, you can + */ + [SIGNALS.AROUND](original: T, advice: AroundAdvice): T { + return function (this: ThisParameterType, ...args: Parameters): ReturnType { + const proceed = (...innerArgs: Parameters) => original.apply(this, innerArgs); + return advice(proceed, this, args); + } as T; + }, + + /** + * ERROR: + * - Intercepts errors thrown by the original function or a rejected Promise + * - Optionally returns a fallback or rethrows + */ + [SIGNALS.ERROR](original: T, advice: ErrorAdvice): T { + return function (this: ThisParameterType, ...args: Parameters): ReturnType { + try { + const result = original.apply(this, args); + if (result instanceof Promise) { + // Handle async rejections + return result.catch((err: unknown) => { + return advice(err, this, args); + }) as ReturnType; + } + return result; + } catch (err) { + // Synchronous error + return advice(err, this, args) as ReturnType; + } + } as T; + }, + }; + + /* ------------------------------------------------------------------------- + * 6) Direct Usage Functions (no decorator support) + * ------------------------------------------------------------------------ */ + + /** + * `before`: + * Direct usage => myFn = before(myFn, (ctx, args) => ...) + */ + export function before(fn: T, advice: BeforeAdvice): T { + return SignalMap[SIGNALS.BEFORE](fn, advice); + } + + /** + * `after`: + * Direct usage => myFn = after(myFn, (ctx, result, args) => ...) + */ + export function after(fn: T, advice: AfterAdvice): T { + return SignalMap[SIGNALS.AFTER](fn, advice); + } + + /** + * `around`: + * Direct usage => myFn = around(myFn, (proceed, ctx, args) => ...) + */ + export function around(fn: T, advice: AroundAdvice): T { + return SignalMap[SIGNALS.AROUND](fn, advice); + } + + /** + * `error`: + * Direct usage => myFn = error(myFn, (err, ctx, args) => ...) + */ + export function error(fn: T, advice: ErrorAdvice): T { + return SignalMap[SIGNALS.ERROR](fn, advice); + } + + /* ------------------------------------------------------------------------- + * 7) Default Export + * ------------------------------------------------------------------------ */ + export default { + SIGNALS, + before, + after, + around, + error, + }; + \ No newline at end of file diff --git a/packages/core/src/assert.ts b/packages/core/src/assert.ts new file mode 100644 index 00000000..b185897f --- /dev/null +++ b/packages/core/src/assert.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BugIndicatingError, onUnexpectedError } from './errors.js'; + +/** + * Throws an error with the provided message if the provided value does not evaluate to a true Javascript value. + * + * @deprecated Use `assert(...)` instead. + * This method is usually used like this: + * ```ts + * import * as assert from 'vs/base/common/assert'; + * assert.ok(...); + * ``` + * + * However, `assert` in that example is a user chosen name. + * There is no tooling for generating such an import statement. + * Thus, the `assert(...)` function should be used instead. + */ +export function ok(value?: unknown, message?: string) { + if (!value) { + throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed'); + } +} + +export function assertNever(value: never, message = 'Unreachable'): never { + throw new Error(message); +} + +export function assert(condition: boolean, message = 'unexpected state'): asserts condition { + if (!condition) { + throw new BugIndicatingError(`Assertion Failed: ${message}`); + } +} + +/** + * Like assert, but doesn't throw. + */ +export function softAssert(condition: boolean): void { + if (!condition) { + onUnexpectedError(new BugIndicatingError('Soft Assertion Failed')); + } +} + +/** + * condition must be side-effect free! + */ +export function assertFn(condition: () => boolean): void { + if (!condition()) { + // eslint-disable-next-line no-debugger + debugger; + // Reevaluate `condition` again to make debugging easier + condition(); + onUnexpectedError(new BugIndicatingError('Assertion Failed')); + } +} + +export function checkAdjacentItems(items: readonly T[], predicate: (item1: T, item2: T) => boolean): boolean { + let i = 0; + while (i < items.length - 1) { + const a = items[i]; + const b = items[i + 1]; + if (!predicate(a, b)) { + return false; + } + i++; + } + return true; +} diff --git a/packages/core/src/cache.ts b/packages/core/src/cache.ts new file mode 100644 index 00000000..321f5b0e --- /dev/null +++ b/packages/core/src/cache.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, CancellationTokenSource } from './cancellation.js'; +import { IDisposable } from './lifecycle.js'; + +export interface CacheResult extends IDisposable { + promise: Promise; +} + +export class Cache { + + private result: CacheResult | null = null; + constructor(private task: (ct: CancellationToken) => Promise) { } + + get(): CacheResult { + if (this.result) { + return this.result; + } + + const cts = new CancellationTokenSource(); + const promise = this.task(cts.token); + + this.result = { + promise, + dispose: () => { + this.result = null; + cts.cancel(); + cts.dispose(); + } + }; + + return this.result; + } +} + +export function identity(t: T): T { + return t; +} + +interface ICacheOptions { + /** + * The cache key is used to identify the cache entry. + * Strict equality is used to compare cache keys. + */ + getCacheKey: (arg: TArg) => unknown; +} + +/** + * Uses a LRU cache to make a given parametrized function cached. + * Caches just the last key/value. +*/ +export class LRUCachedFunction { + private lastCache: TComputed | undefined = undefined; + private lastArgKey: unknown | undefined = undefined; + + private readonly _fn: (arg: TArg) => TComputed; + private readonly _computeKey: (arg: TArg) => unknown; + + constructor(fn: (arg: TArg) => TComputed); + constructor(options: ICacheOptions, fn: (arg: TArg) => TComputed); + constructor(arg1: ICacheOptions | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) { + if (typeof arg1 === 'function') { + this._fn = arg1; + this._computeKey = identity; + } else { + this._fn = arg2!; + this._computeKey = arg1.getCacheKey; + } + } + + public get(arg: TArg): TComputed { + const key = this._computeKey(arg); + if (this.lastArgKey !== key) { + this.lastArgKey = key; + this.lastCache = this._fn(arg); + } + return this.lastCache!; + } +} + +/** + * Uses an unbounded cache to memoize the results of the given function. +*/ +export class CachedFunction { + private readonly _map = new Map(); + private readonly _map2 = new Map(); + public get cachedValues(): ReadonlyMap { + return this._map; + } + + private readonly _fn: (arg: TArg) => TComputed; + private readonly _computeKey: (arg: TArg) => unknown; + + constructor(fn: (arg: TArg) => TComputed); + constructor(options: ICacheOptions, fn: (arg: TArg) => TComputed); + constructor(arg1: ICacheOptions | ((arg: TArg) => TComputed), arg2?: (arg: TArg) => TComputed) { + if (typeof arg1 === 'function') { + this._fn = arg1; + this._computeKey = identity; + } else { + this._fn = arg2!; + this._computeKey = arg1.getCacheKey; + } + } + + public get(arg: TArg): TComputed { + const key = this._computeKey(arg); + if (this._map2.has(key)) { + return this._map2.get(key)!; + } + + const value = this._fn(arg); + this._map.set(arg, value); + this._map2.set(key, value); + return value; + } +} diff --git a/packages/core/src/cancellation.ts b/packages/core/src/cancellation.ts new file mode 100644 index 00000000..802913b4 --- /dev/null +++ b/packages/core/src/cancellation.ts @@ -0,0 +1,148 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from './event.js'; +import { DisposableStore, IDisposable } from './lifecycle.js'; + +export interface CancellationToken { + + /** + * A flag signalling is cancellation has been requested. + */ + readonly isCancellationRequested: boolean; + + /** + * An event which fires when cancellation is requested. This event + * only ever fires `once` as cancellation can only happen once. Listeners + * that are registered after cancellation will be called (next event loop run), + * but also only once. + * + * @event + */ + readonly onCancellationRequested: (listener: (e: any) => any, thisArgs?: any, disposables?: IDisposable[]) => IDisposable; +} + +const shortcutEvent: Event = Object.freeze(function (callback, context?): IDisposable { + const handle = setTimeout(callback.bind(context), 0); + return { dispose() { clearTimeout(handle); } }; +}); + +export namespace CancellationToken { + + export function isCancellationToken(thing: unknown): thing is CancellationToken { + if (thing === CancellationToken.None || thing === CancellationToken.Cancelled) { + return true; + } + if (thing instanceof MutableToken) { + return true; + } + if (!thing || typeof thing !== 'object') { + return false; + } + return typeof (thing as CancellationToken).isCancellationRequested === 'boolean' + && typeof (thing as CancellationToken).onCancellationRequested === 'function'; + } + + + export const None = Object.freeze({ + isCancellationRequested: false, + onCancellationRequested: Event.None + }); + + export const Cancelled = Object.freeze({ + isCancellationRequested: true, + onCancellationRequested: shortcutEvent + }); +} + +class MutableToken implements CancellationToken { + + private _isCancelled: boolean = false; + private _emitter: Emitter | null = null; + + public cancel() { + if (!this._isCancelled) { + this._isCancelled = true; + if (this._emitter) { + this._emitter.fire(undefined); + this.dispose(); + } + } + } + + get isCancellationRequested(): boolean { + return this._isCancelled; + } + + get onCancellationRequested(): Event { + if (this._isCancelled) { + return shortcutEvent; + } + if (!this._emitter) { + this._emitter = new Emitter(); + } + return this._emitter.event; + } + + public dispose(): void { + if (this._emitter) { + this._emitter.dispose(); + this._emitter = null; + } + } +} + +export class CancellationTokenSource { + + private _token?: CancellationToken = undefined; + private _parentListener?: IDisposable = undefined; + + constructor(parent?: CancellationToken) { + this._parentListener = parent && parent.onCancellationRequested(this.cancel, this); + } + + get token(): CancellationToken { + if (!this._token) { + // be lazy and create the token only when + // actually needed + this._token = new MutableToken(); + } + return this._token; + } + + cancel(): void { + if (!this._token) { + // save an object by returning the default + // cancelled token when cancellation happens + // before someone asks for the token + this._token = CancellationToken.Cancelled; + + } else if (this._token instanceof MutableToken) { + // actually cancel + this._token.cancel(); + } + } + + dispose(cancel: boolean = false): void { + if (cancel) { + this.cancel(); + } + this._parentListener?.dispose(); + if (!this._token) { + // ensure to initialize with an empty token if we had none + this._token = CancellationToken.None; + + } else if (this._token instanceof MutableToken) { + // actually dispose + this._token.dispose(); + } + } +} + +export function cancelOnDispose(store: DisposableStore): CancellationToken { + const source = new CancellationTokenSource(); + store.add({ dispose() { source.cancel(); } }); + return source.token; +} diff --git a/packages/core/src/charCode.ts b/packages/core/src/charCode.ts new file mode 100644 index 00000000..51307984 --- /dev/null +++ b/packages/core/src/charCode.ts @@ -0,0 +1,422 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/ + +/** + * An inlined enum containing useful character codes (to be used with String.charCodeAt). + * Please leave the const keyword such that it gets inlined when compiled to JavaScript! + */ +export const enum CharCode { + Null = 0, + /** + * The `\t` character. + */ + Tab = 9, + /** + * The `\n` character. + */ + LineFeed = 10, + /** + * The `\r` character. + */ + CarriageReturn = 13, + Space = 32, + /** + * The `!` character. + */ + ExclamationMark = 33, + /** + * The `"` character. + */ + DoubleQuote = 34, + /** + * The `#` character. + */ + Hash = 35, + /** + * The `$` character. + */ + DollarSign = 36, + /** + * The `%` character. + */ + PercentSign = 37, + /** + * The `&` character. + */ + Ampersand = 38, + /** + * The `'` character. + */ + SingleQuote = 39, + /** + * The `(` character. + */ + OpenParen = 40, + /** + * The `)` character. + */ + CloseParen = 41, + /** + * The `*` character. + */ + Asterisk = 42, + /** + * The `+` character. + */ + Plus = 43, + /** + * The `,` character. + */ + Comma = 44, + /** + * The `-` character. + */ + Dash = 45, + /** + * The `.` character. + */ + Period = 46, + /** + * The `/` character. + */ + Slash = 47, + + Digit0 = 48, + Digit1 = 49, + Digit2 = 50, + Digit3 = 51, + Digit4 = 52, + Digit5 = 53, + Digit6 = 54, + Digit7 = 55, + Digit8 = 56, + Digit9 = 57, + + /** + * The `:` character. + */ + Colon = 58, + /** + * The `;` character. + */ + Semicolon = 59, + /** + * The `<` character. + */ + LessThan = 60, + /** + * The `=` character. + */ + Equals = 61, + /** + * The `>` character. + */ + GreaterThan = 62, + /** + * The `?` character. + */ + QuestionMark = 63, + /** + * The `@` character. + */ + AtSign = 64, + + A = 65, + B = 66, + C = 67, + D = 68, + E = 69, + F = 70, + G = 71, + H = 72, + I = 73, + J = 74, + K = 75, + L = 76, + M = 77, + N = 78, + O = 79, + P = 80, + Q = 81, + R = 82, + S = 83, + T = 84, + U = 85, + V = 86, + W = 87, + X = 88, + Y = 89, + Z = 90, + + /** + * The `[` character. + */ + OpenSquareBracket = 91, + /** + * The `\` character. + */ + Backslash = 92, + /** + * The `]` character. + */ + CloseSquareBracket = 93, + /** + * The `^` character. + */ + Caret = 94, + /** + * The `_` character. + */ + Underline = 95, + /** + * The ``(`)`` character. + */ + BackTick = 96, + + a = 97, + b = 98, + c = 99, + d = 100, + e = 101, + f = 102, + g = 103, + h = 104, + i = 105, + j = 106, + k = 107, + l = 108, + m = 109, + n = 110, + o = 111, + p = 112, + q = 113, + r = 114, + s = 115, + t = 116, + u = 117, + v = 118, + w = 119, + x = 120, + y = 121, + z = 122, + + /** + * The `{` character. + */ + OpenCurlyBrace = 123, + /** + * The `|` character. + */ + Pipe = 124, + /** + * The `}` character. + */ + CloseCurlyBrace = 125, + /** + * The `~` character. + */ + Tilde = 126, + + U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent + U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent + U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent + U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde + U_Combining_Macron = 0x0304, // U+0304 Combining Macron + U_Combining_Overline = 0x0305, // U+0305 Combining Overline + U_Combining_Breve = 0x0306, // U+0306 Combining Breve + U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above + U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis + U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above + U_Combining_Ring_Above = 0x030A, // U+030A Combining Ring Above + U_Combining_Double_Acute_Accent = 0x030B, // U+030B Combining Double Acute Accent + U_Combining_Caron = 0x030C, // U+030C Combining Caron + U_Combining_Vertical_Line_Above = 0x030D, // U+030D Combining Vertical Line Above + U_Combining_Double_Vertical_Line_Above = 0x030E, // U+030E Combining Double Vertical Line Above + U_Combining_Double_Grave_Accent = 0x030F, // U+030F Combining Double Grave Accent + U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu + U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve + U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above + U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above + U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above + U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right + U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below + U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below + U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below + U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below + U_Combining_Left_Angle_Above = 0x031A, // U+031A Combining Left Angle Above + U_Combining_Horn = 0x031B, // U+031B Combining Horn + U_Combining_Left_Half_Ring_Below = 0x031C, // U+031C Combining Left Half Ring Below + U_Combining_Up_Tack_Below = 0x031D, // U+031D Combining Up Tack Below + U_Combining_Down_Tack_Below = 0x031E, // U+031E Combining Down Tack Below + U_Combining_Plus_Sign_Below = 0x031F, // U+031F Combining Plus Sign Below + U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below + U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below + U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below + U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below + U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below + U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below + U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below + U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla + U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek + U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below + U_Combining_Bridge_Below = 0x032A, // U+032A Combining Bridge Below + U_Combining_Inverted_Double_Arch_Below = 0x032B, // U+032B Combining Inverted Double Arch Below + U_Combining_Caron_Below = 0x032C, // U+032C Combining Caron Below + U_Combining_Circumflex_Accent_Below = 0x032D, // U+032D Combining Circumflex Accent Below + U_Combining_Breve_Below = 0x032E, // U+032E Combining Breve Below + U_Combining_Inverted_Breve_Below = 0x032F, // U+032F Combining Inverted Breve Below + U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below + U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below + U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line + U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line + U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay + U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay + U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay + U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay + U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay + U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below + U_Combining_Inverted_Bridge_Below = 0x033A, // U+033A Combining Inverted Bridge Below + U_Combining_Square_Below = 0x033B, // U+033B Combining Square Below + U_Combining_Seagull_Below = 0x033C, // U+033C Combining Seagull Below + U_Combining_X_Above = 0x033D, // U+033D Combining X Above + U_Combining_Vertical_Tilde = 0x033E, // U+033E Combining Vertical Tilde + U_Combining_Double_Overline = 0x033F, // U+033F Combining Double Overline + U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark + U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark + U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni + U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis + U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos + U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni + U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above + U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below + U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below + U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below + U_Combining_Not_Tilde_Above = 0x034A, // U+034A Combining Not Tilde Above + U_Combining_Homothetic_Above = 0x034B, // U+034B Combining Homothetic Above + U_Combining_Almost_Equal_To_Above = 0x034C, // U+034C Combining Almost Equal To Above + U_Combining_Left_Right_Arrow_Below = 0x034D, // U+034D Combining Left Right Arrow Below + U_Combining_Upwards_Arrow_Below = 0x034E, // U+034E Combining Upwards Arrow Below + U_Combining_Grapheme_Joiner = 0x034F, // U+034F Combining Grapheme Joiner + U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above + U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above + U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata + U_Combining_X_Below = 0x0353, // U+0353 Combining X Below + U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below + U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below + U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below + U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above + U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right + U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below + U_Combining_Double_Ring_Below = 0x035A, // U+035A Combining Double Ring Below + U_Combining_Zigzag_Above = 0x035B, // U+035B Combining Zigzag Above + U_Combining_Double_Breve_Below = 0x035C, // U+035C Combining Double Breve Below + U_Combining_Double_Breve = 0x035D, // U+035D Combining Double Breve + U_Combining_Double_Macron = 0x035E, // U+035E Combining Double Macron + U_Combining_Double_Macron_Below = 0x035F, // U+035F Combining Double Macron Below + U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde + U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve + U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below + U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A + U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E + U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I + U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O + U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U + U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C + U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D + U_Combining_Latin_Small_Letter_H = 0x036A, // U+036A Combining Latin Small Letter H + U_Combining_Latin_Small_Letter_M = 0x036B, // U+036B Combining Latin Small Letter M + U_Combining_Latin_Small_Letter_R = 0x036C, // U+036C Combining Latin Small Letter R + U_Combining_Latin_Small_Letter_T = 0x036D, // U+036D Combining Latin Small Letter T + U_Combining_Latin_Small_Letter_V = 0x036E, // U+036E Combining Latin Small Letter V + U_Combining_Latin_Small_Letter_X = 0x036F, // U+036F Combining Latin Small Letter X + + /** + * Unicode Character 'LINE SEPARATOR' (U+2028) + * http://www.fileformat.info/info/unicode/char/2028/index.htm + */ + LINE_SEPARATOR_2028 = 8232, + + // http://www.fileformat.info/info/unicode/category/Sk/list.htm + U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX + U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT + U_DIAERESIS = 0x00A8, // U+00A8 DIAERESIS + U_MACRON = 0x00AF, // U+00AF MACRON + U_ACUTE_ACCENT = 0x00B4, // U+00B4 ACUTE ACCENT + U_CEDILLA = 0x00B8, // U+00B8 CEDILLA + U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD + U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD + U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4, // U+02C4 MODIFIER LETTER UP ARROWHEAD + U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD + U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING + U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING + U_MODIFIER_LETTER_UP_TACK = 0x02D4, // U+02D4 MODIFIER LETTER UP TACK + U_MODIFIER_LETTER_DOWN_TACK = 0x02D5, // U+02D5 MODIFIER LETTER DOWN TACK + U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6, // U+02D6 MODIFIER LETTER PLUS SIGN + U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7, // U+02D7 MODIFIER LETTER MINUS SIGN + U_BREVE = 0x02D8, // U+02D8 BREVE + U_DOT_ABOVE = 0x02D9, // U+02D9 DOT ABOVE + U_RING_ABOVE = 0x02DA, // U+02DA RING ABOVE + U_OGONEK = 0x02DB, // U+02DB OGONEK + U_SMALL_TILDE = 0x02DC, // U+02DC SMALL TILDE + U_DOUBLE_ACUTE_ACCENT = 0x02DD, // U+02DD DOUBLE ACUTE ACCENT + U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE, // U+02DE MODIFIER LETTER RHOTIC HOOK + U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF, // U+02DF MODIFIER LETTER CROSS ACCENT + U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR + U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6, // U+02E6 MODIFIER LETTER HIGH TONE BAR + U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7, // U+02E7 MODIFIER LETTER MID TONE BAR + U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8, // U+02E8 MODIFIER LETTER LOW TONE BAR + U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR + U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK + U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK + U_MODIFIER_LETTER_UNASPIRATED = 0x02ED, // U+02ED MODIFIER LETTER UNASPIRATED + U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD + U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD + U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD + U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD + U_MODIFIER_LETTER_LOW_RING = 0x02F3, // U+02F3 MODIFIER LETTER LOW RING + U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT + U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT + U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT + U_MODIFIER_LETTER_LOW_TILDE = 0x02F7, // U+02F7 MODIFIER LETTER LOW TILDE + U_MODIFIER_LETTER_RAISED_COLON = 0x02F8, // U+02F8 MODIFIER LETTER RAISED COLON + U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE + U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA, // U+02FA MODIFIER LETTER END HIGH TONE + U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB, // U+02FB MODIFIER LETTER BEGIN LOW TONE + U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC, // U+02FC MODIFIER LETTER END LOW TONE + U_MODIFIER_LETTER_SHELF = 0x02FD, // U+02FD MODIFIER LETTER SHELF + U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE, // U+02FE MODIFIER LETTER OPEN SHELF + U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF, // U+02FF MODIFIER LETTER LOW LEFT ARROW + U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN + U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS + U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS + U_GREEK_KORONIS = 0x1FBD, // U+1FBD GREEK KORONIS + U_GREEK_PSILI = 0x1FBF, // U+1FBF GREEK PSILI + U_GREEK_PERISPOMENI = 0x1FC0, // U+1FC0 GREEK PERISPOMENI + U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI + U_GREEK_PSILI_AND_VARIA = 0x1FCD, // U+1FCD GREEK PSILI AND VARIA + U_GREEK_PSILI_AND_OXIA = 0x1FCE, // U+1FCE GREEK PSILI AND OXIA + U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF, // U+1FCF GREEK PSILI AND PERISPOMENI + U_GREEK_DASIA_AND_VARIA = 0x1FDD, // U+1FDD GREEK DASIA AND VARIA + U_GREEK_DASIA_AND_OXIA = 0x1FDE, // U+1FDE GREEK DASIA AND OXIA + U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF, // U+1FDF GREEK DASIA AND PERISPOMENI + U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED, // U+1FED GREEK DIALYTIKA AND VARIA + U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE, // U+1FEE GREEK DIALYTIKA AND OXIA + U_GREEK_VARIA = 0x1FEF, // U+1FEF GREEK VARIA + U_GREEK_OXIA = 0x1FFD, // U+1FFD GREEK OXIA + U_GREEK_DASIA = 0x1FFE, // U+1FFE GREEK DASIA + + + U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE' + + /** + * UTF-8 BOM + * Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF) + * http://www.fileformat.info/info/unicode/char/feff/index.htm + */ + UTF8_BOM = 65279 +} \ No newline at end of file diff --git a/packages/core/src/collections.ts b/packages/core/src/collections.ts new file mode 100644 index 00000000..f9bdf9a3 --- /dev/null +++ b/packages/core/src/collections.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * An interface for a JavaScript object that + * acts a dictionary. The keys are strings. + */ +export type IStringDictionary = Record; + +/** + * An interface for a JavaScript object that + * acts a dictionary. The keys are numbers. + */ +export type INumberDictionary = Record; + +/** + * Groups the collection into a dictionary based on the provided + * group function. + */ +export function groupBy(data: V[], groupFn: (element: V) => K): Record { + const result: Record = Object.create(null); + for (const element of data) { + const key = groupFn(element); + let target = result[key]; + if (!target) { + target = result[key] = []; + } + target.push(element); + } + return result; +} + +export function diffSets(before: ReadonlySet, after: ReadonlySet): { removed: T[]; added: T[] } { + const removed: T[] = []; + const added: T[] = []; + for (const element of before) { + if (!after.has(element)) { + removed.push(element); + } + } + for (const element of after) { + if (!before.has(element)) { + added.push(element); + } + } + return { removed, added }; +} + +export function diffMaps(before: Map, after: Map): { removed: V[]; added: V[] } { + const removed: V[] = []; + const added: V[] = []; + for (const [index, value] of before) { + if (!after.has(index)) { + removed.push(value); + } + } + for (const [index, value] of after) { + if (!before.has(index)) { + added.push(value); + } + } + return { removed, added }; +} + +/** + * Computes the intersection of two sets. + * + * @param setA - The first set. + * @param setB - The second iterable. + * @returns A new set containing the elements that are in both `setA` and `setB`. + */ +export function intersection(setA: Set, setB: Iterable): Set { + const result = new Set(); + for (const elem of setB) { + if (setA.has(elem)) { + result.add(elem); + } + } + return result; +} + +export class SetWithKey implements Set { + private _map = new Map(); + + constructor(values: T[], private toKey: (t: T) => unknown) { + for (const value of values) { + this.add(value); + } + } + + get size(): number { + return this._map.size; + } + + add(value: T): this { + const key = this.toKey(value); + this._map.set(key, value); + return this; + } + + delete(value: T): boolean { + return this._map.delete(this.toKey(value)); + } + + has(value: T): boolean { + return this._map.has(this.toKey(value)); + } + + *entries(): IterableIterator<[T, T]> { + for (const entry of this._map.values()) { + yield [entry, entry]; + } + } + + keys(): IterableIterator { + return this.values(); + } + + *values(): IterableIterator { + for (const entry of this._map.values()) { + yield entry; + } + } + + clear(): void { + this._map.clear(); + } + + forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void { + this._map.forEach(entry => callbackfn.call(thisArg, entry, entry, this)); + } + + [Symbol.iterator](): IterableIterator { + return this.values(); + } + + [Symbol.toStringTag]: string = 'SetWithKey'; +} diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts new file mode 100644 index 00000000..7c4e49f2 --- /dev/null +++ b/packages/core/src/constants.ts @@ -0,0 +1,6 @@ +// standard expression for variables, eg : ${foo} +export const REGEX_VAR = /\$\{([^\s:}]+)(?::([^\s:}]+))?\}/g + +// alternate expression for variables, eg : %{foo}. this is required +// to deal with parent expression parsers where '$' is reserved, eg: %{my_var} +export const REGEX_VAR_ALT = /&\{([^\s:}]+)(?::([^\s:}]+))?\}/g diff --git a/packages/core/src/equals.ts b/packages/core/src/equals.ts new file mode 100644 index 00000000..93a92520 --- /dev/null +++ b/packages/core/src/equals.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as arrays from './arrays.js'; + +export type EqualityComparer = (a: T, b: T) => boolean; + +/** + * Compares two items for equality using strict equality. +*/ +export const strictEquals: EqualityComparer = (a, b) => a === b; + +/** + * Checks if the items of two arrays are equal. + * By default, strict equality is used to compare elements, but a custom equality comparer can be provided. + */ +export function itemsEquals(itemEquals: EqualityComparer = strictEquals): EqualityComparer { + return (a, b) => arrays.equals(a, b, itemEquals); +} + +/** + * Two items are considered equal, if their stringified representations are equal. +*/ +export function jsonStringifyEquals(): EqualityComparer { + return (a, b) => JSON.stringify(a) === JSON.stringify(b); +} + +/** + * Uses `item.equals(other)` to determine equality. + */ +export function itemEquals(): EqualityComparer { + return (a, b) => a.equals(b); +} + +/** + * Checks if two items are both null or undefined, or are equal according to the provided equality comparer. +*/ +export function equalsIfDefined(v1: T | undefined | null, v2: T | undefined | null, equals: EqualityComparer): boolean; +/** + * Returns an equality comparer that checks if two items are both null or undefined, or are equal according to the provided equality comparer. +*/ +export function equalsIfDefined(equals: EqualityComparer): EqualityComparer; +export function equalsIfDefined(equalsOrV1: EqualityComparer | T, v2?: T | undefined | null, equals?: EqualityComparer): EqualityComparer | boolean { + if (equals !== undefined) { + const v1 = equalsOrV1 as T | undefined; + if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) { + return v2 === v1; + } + return equals(v1, v2); + } else { + const equals = equalsOrV1 as EqualityComparer; + return (v1, v2) => { + if (v1 === undefined || v1 === null || v2 === undefined || v2 === null) { + return v2 === v1; + } + return equals(v1, v2); + }; + } +} + +/** + * Drills into arrays (items ordered) and objects (keys unordered) and uses strict equality on everything else. +*/ +export function structuralEquals(a: T, b: T): boolean { + if (a === b) { + return true; + } + + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (!structuralEquals(a[i], b[i])) { + return false; + } + } + return true; + } + + if (a && typeof a === 'object' && b && typeof b === 'object') { + if (Object.getPrototypeOf(a) === Object.prototype && Object.getPrototypeOf(b) === Object.prototype) { + const aObj = a as Record; + const bObj = b as Record; + const keysA = Object.keys(aObj); + const keysB = Object.keys(bObj); + const keysBSet = new Set(keysB); + + if (keysA.length !== keysB.length) { + return false; + } + + for (const key of keysA) { + if (!keysBSet.has(key)) { + return false; + } + if (!structuralEquals(aObj[key], bObj[key])) { + return false; + } + } + + return true; + } + } + + return false; +} + +/** + * `getStructuralKey(a) === getStructuralKey(b) <=> structuralEquals(a, b)` + * (assuming that a and b are not cyclic structures and nothing extends globalThis Array). +*/ +export function getStructuralKey(t: unknown): string { + return JSON.stringify(toNormalizedJsonStructure(t)); +} + +let objectId = 0; +const objIds = new WeakMap(); + +function toNormalizedJsonStructure(t: unknown): unknown { + if (Array.isArray(t)) { + return t.map(toNormalizedJsonStructure); + } + + if (t && typeof t === 'object') { + if (Object.getPrototypeOf(t) === Object.prototype) { + const tObj = t as Record; + const res: Record = Object.create(null); + for (const key of Object.keys(tObj).sort()) { + res[key] = toNormalizedJsonStructure(tObj[key]); + } + return res; + } else { + let objId = objIds.get(t); + if (objId === undefined) { + objId = objectId++; + objIds.set(t, objId); + } + // Random string to prevent collisions + return objId + '----2b76a038c20c4bcc'; + } + } + return t; +} diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts new file mode 100644 index 00000000..20698cfa --- /dev/null +++ b/packages/core/src/errors.ts @@ -0,0 +1,326 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface ErrorListenerCallback { + (error: any): void; +} + +export interface ErrorListenerUnbind { + (): void; +} + +// Avoid circular dependency on EventEmitter by implementing a subset of the interface. +export class ErrorHandler { + private unexpectedErrorHandler: (e: any) => void; + private listeners: ErrorListenerCallback[]; + + constructor() { + + this.listeners = []; + + this.unexpectedErrorHandler = function (e: any) { + setTimeout(() => { + if (e.stack) { + if (ErrorNoTelemetry.isErrorNoTelemetry(e)) { + throw new ErrorNoTelemetry(e.message + '\n\n' + e.stack); + } + + throw new Error(e.message + '\n\n' + e.stack); + } + + throw e; + }, 0); + }; + } + + addListener(listener: ErrorListenerCallback): ErrorListenerUnbind { + this.listeners.push(listener); + + return () => { + this._removeListener(listener); + }; + } + + private emit(e: any): void { + this.listeners.forEach((listener) => { + listener(e); + }); + } + + private _removeListener(listener: ErrorListenerCallback): void { + this.listeners.splice(this.listeners.indexOf(listener), 1); + } + + setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void { + this.unexpectedErrorHandler = newUnexpectedErrorHandler; + } + + getUnexpectedErrorHandler(): (e: any) => void { + return this.unexpectedErrorHandler; + } + + onUnexpectedError(e: any): void { + this.unexpectedErrorHandler(e); + this.emit(e); + } + + // For external errors, we don't want the listeners to be called + onUnexpectedExternalError(e: any): void { + this.unexpectedErrorHandler(e); + } +} + +export const errorHandler = new ErrorHandler(); + +/** @skipMangle */ +export function setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void { + errorHandler.setUnexpectedErrorHandler(newUnexpectedErrorHandler); +} + +/** + * Returns if the error is a SIGPIPE error. SIGPIPE errors should generally be + * logged at most once, to avoid a loop. + * + * @see https://github.com/microsoft/vscode-remote-release/issues/6481 + */ +export function isSigPipeError(e: unknown): e is Error { + if (!e || typeof e !== 'object') { + return false; + } + + const cast = e as Record; + return cast.code === 'EPIPE' && cast.syscall?.toUpperCase() === 'WRITE'; +} + +/** + * This function should only be called with errors that indicate a bug in the product. + * E.g. buggy extensions/invalid user-input/network issues should not be able to trigger this code path. + * If they are, this indicates there is also a bug in the product. +*/ +export function onBugIndicatingError(e: any): undefined { + errorHandler.onUnexpectedError(e); + return undefined; +} + +export function onUnexpectedError(e: any): undefined { + // ignore errors from cancelled promises + if (!isCancellationError(e)) { + errorHandler.onUnexpectedError(e); + } + return undefined; +} + +export function onUnexpectedExternalError(e: any): undefined { + // ignore errors from cancelled promises + if (!isCancellationError(e)) { + errorHandler.onUnexpectedExternalError(e); + } + return undefined; +} + +export interface SerializedError { + readonly $isError: true; + readonly name: string; + readonly message: string; + readonly stack: string; + readonly noTelemetry: boolean; + readonly code?: string; + readonly cause?: SerializedError; +} + +type ErrorWithCode = Error & { + code: string | undefined; +}; + +export function transformErrorForSerialization(error: Error): SerializedError; +export function transformErrorForSerialization(error: any): any; +export function transformErrorForSerialization(error: any): any { + if (error instanceof Error) { + const { name, message, cause } = error; + const stack: string = (error).stacktrace || (error).stack; + return { + $isError: true, + name, + message, + stack, + noTelemetry: ErrorNoTelemetry.isErrorNoTelemetry(error), + cause: cause ? transformErrorForSerialization(cause) : undefined, + code: (error).code + }; + } + + // return as is + return error; +} + +export function transformErrorFromSerialization(data: SerializedError): Error { + let error: Error; + if (data.noTelemetry) { + error = new ErrorNoTelemetry(); + } else { + error = new Error(); + error.name = data.name; + } + error.message = data.message; + error.stack = data.stack; + if (data.code) { + (error).code = data.code; + } + if (data.cause) { + error.cause = transformErrorFromSerialization(data.cause); + } + return error; +} + +// see https://github.com/v8/v8/wiki/Stack%20Trace%20API#basic-stack-traces +export interface V8CallSite { + getThis(): unknown; + getTypeName(): string | null; + getFunction(): Function | undefined; + getFunctionName(): string | null; + getMethodName(): string | null; + getFileName(): string | null; + getLineNumber(): number | null; + getColumnNumber(): number | null; + getEvalOrigin(): string | undefined; + isToplevel(): boolean; + isEval(): boolean; + isNative(): boolean; + isConstructor(): boolean; + toString(): string; +} + +const canceledName = 'Canceled'; + +/** + * Checks if the given error is a promise in canceled state + */ +export function isCancellationError(error: any): boolean { + if (error instanceof CancellationError) { + return true; + } + return error instanceof Error && error.name === canceledName && error.message === canceledName; +} + +// !!!IMPORTANT!!! +// Do NOT change this class because it is also used as an API-type. +export class CancellationError extends Error { + constructor() { + super(canceledName); + this.name = this.message; + } +} + +/** + * @deprecated use {@link CancellationError `new CancellationError()`} instead + */ +export function canceled(): Error { + const error = new Error(canceledName); + error.name = error.message; + return error; +} + +export function illegalArgument(name?: string): Error { + if (name) { + return new Error(`Illegal argument: ${name}`); + } else { + return new Error('Illegal argument'); + } +} + +export function illegalState(name?: string): Error { + if (name) { + return new Error(`Illegal state: ${name}`); + } else { + return new Error('Illegal state'); + } +} + +export class ReadonlyError extends TypeError { + constructor(name?: string) { + super(name ? `${name} is read-only and cannot be changed` : 'Cannot change read-only property'); + } +} + +export function getErrorMessage(err: any): string { + if (!err) { + return 'Error'; + } + + if (err.message) { + return err.message; + } + + if (err.stack) { + return err.stack.split('\n')[0]; + } + + return String(err); +} + +export class NotImplementedError extends Error { + constructor(message?: string) { + super('NotImplemented'); + if (message) { + this.message = message; + } + } +} + +export class NotSupportedError extends Error { + constructor(message?: string) { + super('NotSupported'); + if (message) { + this.message = message; + } + } +} + +export class ExpectedError extends Error { + readonly isExpected = true; +} + +/** + * Error that when thrown won't be logged in telemetry as an unhandled error. + */ +export class ErrorNoTelemetry extends Error { + override readonly name: string; + + constructor(msg?: string) { + super(msg); + this.name = 'CodeExpectedError'; + } + + public static fromError(err: Error): ErrorNoTelemetry { + if (err instanceof ErrorNoTelemetry) { + return err; + } + + const result = new ErrorNoTelemetry(); + result.message = err.message; + result.stack = err.stack; + return result; + } + + public static isErrorNoTelemetry(err: Error): err is ErrorNoTelemetry { + return err.name === 'CodeExpectedError'; + } +} + +/** + * This error indicates a bug. + * Do not throw this for invalid user input. + * Only catch this error to recover gracefully from bugs. + */ +export class BugIndicatingError extends Error { + constructor(message?: string) { + super(message || 'An unexpected bug occurred.'); + Object.setPrototypeOf(this, BugIndicatingError.prototype); + + // Because we know for sure only buggy code throws this, + // we definitely want to break here and fix the bug. + // debugger; + } +} diff --git a/packages/core/src/event.ts b/packages/core/src/event.ts new file mode 100644 index 00000000..266f9eb5 --- /dev/null +++ b/packages/core/src/event.ts @@ -0,0 +1,1787 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from './cancellation.js'; +import { diffSets } from './collections.js'; +import { onUnexpectedError } from './errors.js'; +import { createSingleCallFunction } from './functional.js'; +import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from './lifecycle.js'; +import { LinkedList } from './linkedList.js'; +import { IObservable, IObservableWithChange, IObserver } from './observable.js'; +import { StopWatch } from './stopwatch.js'; +import { MicrotaskDelay } from './symbols.js'; + + +// ----------------------------------------------------------------------------------------------------------------------- +// Uncomment the next line to print warnings whenever an emitter with listeners is disposed. That is a sign of code smell. +// ----------------------------------------------------------------------------------------------------------------------- +const _enableDisposeWithListenerWarning = false + // || Boolean("TRUE") // causes a linter warning so that it cannot be pushed + ; + + +// ----------------------------------------------------------------------------------------------------------------------- +// Uncomment the next line to print warnings whenever a snapshotted event is used repeatedly without cleanup. +// See https://github.com/microsoft/vscode/issues/142851 +// ----------------------------------------------------------------------------------------------------------------------- +const _enableSnapshotPotentialLeakWarning = false + // || Boolean("TRUE") // causes a linter warning so that it cannot be pushed + ; + +/** + * An event with zero or one parameters that can be subscribed to. The event is a function itself. + */ +export interface Event { + (listener: (e: T) => unknown, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable; +} + +export namespace Event { + export const None: Event = () => Disposable.None; + + function _addLeakageTraceLogic(options: EmitterOptions) { + if (_enableSnapshotPotentialLeakWarning) { + const { onDidAddListener: origListenerDidAdd } = options; + const stack = Stacktrace.create(); + let count = 0; + options.onDidAddListener = () => { + if (++count === 2) { + console.warn('snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here'); + stack.print(); + } + origListenerDidAdd?.(); + }; + } + } + + /** + * Given an event, returns another event which debounces calls and defers the listeners to a later task via a shared + * `setTimeout`. The event is converted into a signal (`Event`) to avoid additional object creation as a + * result of merging events and to try prevent race conditions that could arise when using related deferred and + * non-deferred events. + * + * This is useful for deferring non-critical work (eg. general UI updates) to ensure it does not block critical work + * (eg. latency of keypress to text rendered). + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param disposable A disposable store to add the new EventEmitter to. + */ + export function defer(event: Event, disposable?: DisposableStore): Event { + return debounce(event, () => void 0, 0, undefined, true, undefined, disposable); + } + + /** + * Given an event, returns another event which only fires once. + * + * @param event The event source for the new event. + */ + export function once(event: Event): Event { + return (listener, thisArgs = null, disposables?) => { + // we need this, in case the event fires during the listener call + let didFire = false; + let result: IDisposable | undefined = undefined; + result = event(e => { + if (didFire) { + return; + } else if (result) { + result.dispose(); + } else { + didFire = true; + } + + return listener.call(thisArgs, e); + }, null, disposables); + + if (didFire) { + result.dispose(); + } + + return result; + }; + } + + /** + * Given an event, returns another event which only fires once, and only when the condition is met. + * + * @param event The event source for the new event. + */ + export function onceIf(event: Event, condition: (e: T) => boolean): Event { + return Event.once(Event.filter(event, condition)); + } + + /** + * Maps an event of one type into an event of another type using a mapping function, similar to how + * `Array.prototype.map` works. + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param map The mapping function. + * @param disposable A disposable store to add the new EventEmitter to. + */ + export function map(event: Event, map: (i: I) => O, disposable?: DisposableStore): Event { + return snapshot((listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables), disposable); + } + + /** + * Wraps an event in another event that performs some function on the event object before firing. + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param each The function to perform on the event object. + * @param disposable A disposable store to add the new EventEmitter to. + */ + export function forEach(event: Event, each: (i: I) => void, disposable?: DisposableStore): Event { + return snapshot((listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables), disposable); + } + + /** + * Wraps an event in another event that fires only when some condition is met. + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param filter The filter function that defines the condition. The event will fire for the object if this function + * returns true. + * @param disposable A disposable store to add the new EventEmitter to. + */ + export function filter(event: Event, filter: (e: T | U) => e is T, disposable?: DisposableStore): Event; + export function filter(event: Event, filter: (e: T) => boolean, disposable?: DisposableStore): Event; + export function filter(event: Event, filter: (e: T | R) => e is R, disposable?: DisposableStore): Event; + export function filter(event: Event, filter: (e: T) => boolean, disposable?: DisposableStore): Event { + return snapshot((listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables), disposable); + } + + /** + * Given an event, returns the same event but typed as `Event`. + */ + export function signal(event: Event): Event { + return event as Event as Event; + } + + /** + * Given a collection of events, returns a single event which emits whenever any of the provided events emit. + */ + export function any(...events: Event[]): Event; + export function any(...events: Event[]): Event; + export function any(...events: Event[]): Event { + return (listener, thisArgs = null, disposables?) => { + const disposable = combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e)))); + return addAndReturnDisposable(disposable, disposables); + }; + } + + /** + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + */ + export function reduce(event: Event, merge: (last: O | undefined, event: I) => O, initial?: O, disposable?: DisposableStore): Event { + let output: O | undefined = initial; + + return map(event, e => { + output = merge(output, e); + return output; + }, disposable); + } + + function snapshot(event: Event, disposable: DisposableStore | undefined): Event { + let listener: IDisposable | undefined; + + const options: EmitterOptions | undefined = { + onWillAddFirstListener() { + listener = event(emitter.fire, emitter); + }, + onDidRemoveLastListener() { + listener?.dispose(); + } + }; + + if (!disposable) { + _addLeakageTraceLogic(options); + } + + const emitter = new Emitter(options); + + disposable?.add(emitter); + + return emitter.event; + } + + /** + * Adds the IDisposable to the store if it's set, and returns it. Useful to + * Event function implementation. + */ + function addAndReturnDisposable(d: T, store: DisposableStore | IDisposable[] | undefined): T { + if (store instanceof Array) { + store.push(d); + } else if (store) { + store.add(d); + } + return d; + } + + /** + * Given an event, creates a new emitter that event that will debounce events based on {@link delay} and give an + * array event object of all events that fired. + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @param event The original event to debounce. + * @param merge A function that reduces all events into a single event. + * @param delay The number of milliseconds to debounce. + * @param leading Whether to fire a leading event without debouncing. + * @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not + * specified, some events could go missing. Use this if it's important that all events are processed, even if the + * listener gets disposed before the debounced event fires. + * @param leakWarningThreshold See {@link EmitterOptions.leakWarningThreshold}. + * @param disposable A disposable store to register the debounce emitter to. + */ + export function debounce(event: Event, merge: (last: T | undefined, event: T) => T, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event; + export function debounce(event: Event, merge: (last: O | undefined, event: I) => O, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event; + export function debounce(event: Event, merge: (last: O | undefined, event: I) => O, delay: number | typeof MicrotaskDelay = 100, leading = false, flushOnListenerRemove = false, leakWarningThreshold?: number, disposable?: DisposableStore): Event { + let subscription: IDisposable; + let output: O | undefined = undefined; + let handle: any = undefined; + let numDebouncedCalls = 0; + let doFire: (() => void) | undefined; + + const options: EmitterOptions | undefined = { + leakWarningThreshold, + onWillAddFirstListener() { + subscription = event(cur => { + numDebouncedCalls++; + output = merge(output, cur); + + if (leading && !handle) { + emitter.fire(output); + output = undefined; + } + + doFire = () => { + const _output = output; + output = undefined; + handle = undefined; + if (!leading || numDebouncedCalls > 1) { + emitter.fire(_output!); + } + numDebouncedCalls = 0; + }; + + if (typeof delay === 'number') { + clearTimeout(handle); + handle = setTimeout(doFire, delay); + } else { + if (handle === undefined) { + handle = 0; + queueMicrotask(doFire); + } + } + }); + }, + onWillRemoveListener() { + if (flushOnListenerRemove && numDebouncedCalls > 0) { + doFire?.(); + } + }, + onDidRemoveLastListener() { + doFire = undefined; + subscription.dispose(); + } + }; + + if (!disposable) { + _addLeakageTraceLogic(options); + } + + const emitter = new Emitter(options); + + disposable?.add(emitter); + + return emitter.event; + } + + /** + * Debounces an event, firing after some delay (default=0) with an array of all event original objects. + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + */ + export function accumulate(event: Event, delay: number = 0, disposable?: DisposableStore): Event { + return Event.debounce(event, (last, e) => { + if (!last) { + return [e]; + } + last.push(e); + return last; + }, delay, undefined, true, undefined, disposable); + } + + /** + * Filters an event such that some condition is _not_ met more than once in a row, effectively ensuring duplicate + * event objects from different sources do not fire the same event object. + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param equals The equality condition. + * @param disposable A disposable store to add the new EventEmitter to. + * + * @example + * ``` + * // Fire only one time when a single window is opened or focused + * Event.latch(Event.any(onDidOpenWindow, onDidFocusWindow)) + * ``` + */ + export function latch(event: Event, equals: (a: T, b: T) => boolean = (a, b) => a === b, disposable?: DisposableStore): Event { + let firstCall = true; + let cache: T; + + return filter(event, value => { + const shouldEmit = firstCall || !equals(value, cache); + firstCall = false; + cache = value; + return shouldEmit; + }, disposable); + } + + /** + * Splits an event whose parameter is a union type into 2 separate events for each type in the union. + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @example + * ``` + * const event = new EventEmitter().event; + * const [numberEvent, undefinedEvent] = Event.split(event, isUndefined); + * ``` + * + * @param event The event source for the new event. + * @param isT A function that determines what event is of the first type. + * @param disposable A disposable store to add the new EventEmitter to. + */ + export function split(event: Event, isT: (e: T | U) => e is T, disposable?: DisposableStore): [Event, Event] { + return [ + Event.filter(event, isT, disposable), + Event.filter(event, e => !isT(e), disposable) as Event, + ]; + } + + /** + * Buffers an event until it has a listener attached. + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param flushAfterTimeout Determines whether to flush the buffer after a timeout immediately or after a + * `setTimeout` when the first event listener is added. + * @param _buffer Internal: A source event array used for tests. + * + * @example + * ``` + * // Start accumulating events, when the first listener is attached, flush + * // the event after a timeout such that multiple listeners attached before + * // the timeout would receive the event + * this.onInstallExtension = Event.buffer(service.onInstallExtension, true); + * ``` + */ + export function buffer(event: Event, flushAfterTimeout = false, _buffer: T[] = [], disposable?: DisposableStore): Event { + let buffer: T[] | null = _buffer.slice(); + + let listener: IDisposable | null = event(e => { + if (buffer) { + buffer.push(e); + } else { + emitter.fire(e); + } + }); + + if (disposable) { + disposable.add(listener); + } + + const flush = () => { + buffer?.forEach(e => emitter.fire(e)); + buffer = null; + }; + + const emitter = new Emitter({ + onWillAddFirstListener() { + if (!listener) { + listener = event(e => emitter.fire(e)); + if (disposable) { + disposable.add(listener); + } + } + }, + + onDidAddFirstListener() { + if (buffer) { + if (flushAfterTimeout) { + setTimeout(flush); + } else { + flush(); + } + } + }, + + onDidRemoveLastListener() { + if (listener) { + listener.dispose(); + } + listener = null; + } + }); + + if (disposable) { + disposable.add(emitter); + } + + return emitter.event; + } + /** + * Wraps the event in an {@link IChainableEvent}, allowing a more functional programming style. + * + * @example + * ``` + * // Normal + * const onEnterPressNormal = Event.filter( + * Event.map(onKeyPress.event, e => new StandardKeyboardEvent(e)), + * e.keyCode === KeyCode.Enter + * ).event; + * + * // Using chain + * const onEnterPressChain = Event.chain(onKeyPress.event, $ => $ + * .map(e => new StandardKeyboardEvent(e)) + * .filter(e => e.keyCode === KeyCode.Enter) + * ); + * ``` + */ + export function chain(event: Event, sythensize: ($: IChainableSythensis) => IChainableSythensis): Event { + const fn: Event = (listener, thisArgs, disposables) => { + const cs = sythensize(new ChainableSynthesis()) as ChainableSynthesis; + return event(function (value) { + const result = cs.evaluate(value); + if (result !== HaltChainable) { + listener.call(thisArgs, result); + } + }, undefined, disposables); + }; + + return fn; + } + + const HaltChainable = Symbol('HaltChainable'); + + class ChainableSynthesis implements IChainableSythensis { + private readonly steps: ((input: any) => unknown)[] = []; + + map(fn: (i: any) => O): this { + this.steps.push(fn); + return this; + } + + forEach(fn: (i: any) => void): this { + this.steps.push(v => { + fn(v); + return v; + }); + return this; + } + + filter(fn: (e: any) => boolean): this { + this.steps.push(v => fn(v) ? v : HaltChainable); + return this; + } + + reduce(merge: (last: R | undefined, event: any) => R, initial?: R | undefined): this { + let last = initial; + this.steps.push(v => { + last = merge(last, v); + return last; + }); + return this; + } + + latch(equals: (a: any, b: any) => boolean = (a, b) => a === b): ChainableSynthesis { + let firstCall = true; + let cache: any; + this.steps.push(value => { + const shouldEmit = firstCall || !equals(value, cache); + firstCall = false; + cache = value; + return shouldEmit ? value : HaltChainable; + }); + + return this; + } + + public evaluate(value: any) { + for (const step of this.steps) { + value = step(value); + if (value === HaltChainable) { + break; + } + } + + return value; + } + } + + export interface IChainableSythensis { + map(fn: (i: T) => O): IChainableSythensis; + forEach(fn: (i: T) => void): IChainableSythensis; + filter(fn: (e: T) => e is R): IChainableSythensis; + filter(fn: (e: T) => boolean): IChainableSythensis; + reduce(merge: (last: R, event: T) => R, initial: R): IChainableSythensis; + reduce(merge: (last: R | undefined, event: T) => R): IChainableSythensis; + latch(equals?: (a: T, b: T) => boolean): IChainableSythensis; + } + + export interface NodeEventEmitter { + on(event: string | symbol, listener: Function): unknown; + removeListener(event: string | symbol, listener: Function): unknown; + } + + /** + * Creates an {@link Event} from a node event emitter. + */ + export function fromNodeEventEmitter(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event { + const fn = (...args: any[]) => result.fire(map(...args)); + const onFirstListenerAdd = () => emitter.on(eventName, fn); + const onLastListenerRemove = () => emitter.removeListener(eventName, fn); + const result = new Emitter({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove }); + + return result.event; + } + + export interface DOMEventEmitter { + addEventListener(event: string | symbol, listener: Function): void; + removeEventListener(event: string | symbol, listener: Function): void; + } + + /** + * Creates an {@link Event} from a DOM event emitter. + */ + export function fromDOMEventEmitter(emitter: DOMEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event { + const fn = (...args: any[]) => result.fire(map(...args)); + const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn); + const onLastListenerRemove = () => emitter.removeEventListener(eventName, fn); + const result = new Emitter({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove }); + + return result.event; + } + + /** + * Creates a promise out of an event, using the {@link Event.once} helper. + */ + export function toPromise(event: Event): Promise { + return new Promise(resolve => once(event)(resolve)); + } + + /** + * Creates an event out of a promise that fires once when the promise is + * resolved with the result of the promise or `undefined`. + */ + export function fromPromise(promise: Promise): Event { + const result = new Emitter(); + + promise.then(res => { + result.fire(res); + }, () => { + result.fire(undefined); + }).finally(() => { + result.dispose(); + }); + + return result.event; + } + + /** + * A convenience function for forwarding an event to another emitter which + * improves readability. + * + * This is similar to {@link Relay} but allows instantiating and forwarding + * on a single line and also allows for multiple source events. + * @param from The event to forward. + * @param to The emitter to forward the event to. + * @example + * Event.forward(event, emitter); + * // equivalent to + * event(e => emitter.fire(e)); + * // equivalent to + * event(emitter.fire, emitter); + */ + export function forward(from: Event, to: Emitter): IDisposable { + return from(e => to.fire(e)); + } + + /** + * Adds a listener to an event and calls the listener immediately with undefined as the event object. + * + * @example + * ``` + * // Initialize the UI and update it when dataChangeEvent fires + * runAndSubscribe(dataChangeEvent, () => this._updateUI()); + * ``` + */ + export function runAndSubscribe(event: Event, handler: (e: T) => unknown, initial: T): IDisposable; + export function runAndSubscribe(event: Event, handler: (e: T | undefined) => unknown): IDisposable; + export function runAndSubscribe(event: Event, handler: (e: T | undefined) => unknown, initial?: T): IDisposable { + handler(initial); + return event(e => handler(e)); + } + + class EmitterObserver implements IObserver { + + readonly emitter: Emitter; + + private _counter = 0; + private _hasChanged = false; + + constructor(readonly _observable: IObservable, store: DisposableStore | undefined) { + const options: EmitterOptions = { + onWillAddFirstListener: () => { + _observable.addObserver(this); + + // Communicate to the observable that we received its current value and would like to be notified about future changes. + this._observable.reportChanges(); + }, + onDidRemoveLastListener: () => { + _observable.removeObserver(this); + } + }; + if (!store) { + _addLeakageTraceLogic(options); + } + this.emitter = new Emitter(options); + if (store) { + store.add(this.emitter); + } + } + + beginUpdate(_observable: IObservable): void { + // assert(_observable === this.obs); + this._counter++; + } + + handlePossibleChange(_observable: IObservable): void { + // assert(_observable === this.obs); + } + + handleChange(_observable: IObservableWithChange, _change: TChange): void { + // assert(_observable === this.obs); + this._hasChanged = true; + } + + endUpdate(_observable: IObservable): void { + // assert(_observable === this.obs); + this._counter--; + if (this._counter === 0) { + this._observable.reportChanges(); + if (this._hasChanged) { + this._hasChanged = false; + this.emitter.fire(this._observable.get()); + } + } + } + } + + /** + * Creates an event emitter that is fired when the observable changes. + * Each listeners subscribes to the emitter. + */ + export function fromObservable(obs: IObservable, store?: DisposableStore): Event { + const observer = new EmitterObserver(obs, store); + return observer.emitter.event; + } + + /** + * Each listener is attached to the observable directly. + */ + export function fromObservableLight(observable: IObservable): Event { + return (listener, thisArgs, disposables) => { + let count = 0; + let didChange = false; + const observer: IObserver = { + beginUpdate() { + count++; + }, + endUpdate() { + count--; + if (count === 0) { + observable.reportChanges(); + if (didChange) { + didChange = false; + listener.call(thisArgs); + } + } + }, + handlePossibleChange() { + // noop + }, + handleChange() { + didChange = true; + } + }; + observable.addObserver(observer); + observable.reportChanges(); + const disposable = { + dispose() { + observable.removeObserver(observer); + } + }; + + if (disposables instanceof DisposableStore) { + disposables.add(disposable); + } else if (Array.isArray(disposables)) { + disposables.push(disposable); + } + + return disposable; + }; + } +} + +export interface EmitterOptions { + /** + * Optional function that's called *before* the very first listener is added + */ + onWillAddFirstListener?: Function; + /** + * Optional function that's called *after* the very first listener is added + */ + onDidAddFirstListener?: Function; + /** + * Optional function that's called after a listener is added + */ + onDidAddListener?: Function; + /** + * Optional function that's called *after* remove the very last listener + */ + onDidRemoveLastListener?: Function; + /** + * Optional function that's called *before* a listener is removed + */ + onWillRemoveListener?: Function; + /** + * Optional function that's called when a listener throws an error. Defaults to + * {@link onUnexpectedError} + */ + onListenerError?: (e: any) => void; + /** + * Number of listeners that are allowed before assuming a leak. Default to + * a globally configured value + * + * @see setGlobalLeakWarningThreshold + */ + leakWarningThreshold?: number; + /** + * Pass in a delivery queue, which is useful for ensuring + * in order event delivery across multiple emitters. + */ + deliveryQueue?: EventDeliveryQueue; + + /** ONLY enable this during development */ + _profName?: string; +} + + +export class EventProfiling { + + static readonly all = new Set(); + + private static _idPool = 0; + + readonly name: string; + public listenerCount: number = 0; + public invocationCount = 0; + public elapsedOverall = 0; + public durations: number[] = []; + + private _stopWatch?: StopWatch; + + constructor(name: string) { + this.name = `${name}_${EventProfiling._idPool++}`; + EventProfiling.all.add(this); + } + + start(listenerCount: number): void { + this._stopWatch = new StopWatch(); + this.listenerCount = listenerCount; + } + + stop(): void { + if (this._stopWatch) { + const elapsed = this._stopWatch.elapsed(); + this.durations.push(elapsed); + this.elapsedOverall += elapsed; + this.invocationCount += 1; + this._stopWatch = undefined; + } + } +} + +let _globalLeakWarningThreshold = -1; +export function setGlobalLeakWarningThreshold(n: number): IDisposable { + const oldValue = _globalLeakWarningThreshold; + _globalLeakWarningThreshold = n; + return { + dispose() { + _globalLeakWarningThreshold = oldValue; + } + }; +} + +class LeakageMonitor { + + private static _idPool = 1; + + private _stacks: Map | undefined; + private _warnCountdown: number = 0; + + constructor( + private readonly _errorHandler: (err: Error) => void, + readonly threshold: number, + readonly name: string = (LeakageMonitor._idPool++).toString(16).padStart(3, '0') + ) { } + + dispose(): void { + this._stacks?.clear(); + } + + check(stack: Stacktrace, listenerCount: number): undefined | (() => void) { + + const threshold = this.threshold; + if (threshold <= 0 || listenerCount < threshold) { + return undefined; + } + + if (!this._stacks) { + this._stacks = new Map(); + } + const count = (this._stacks.get(stack.value) || 0); + this._stacks.set(stack.value, count + 1); + this._warnCountdown -= 1; + + if (this._warnCountdown <= 0) { + // only warn on first exceed and then every time the limit + // is exceeded by 50% again + this._warnCountdown = threshold * 0.5; + + const [topStack, topCount] = this.getMostFrequentStack()!; + const message = `[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`; + console.warn(message); + console.warn(topStack!); + + const error = new ListenerLeakError(message, topStack); + this._errorHandler(error); + } + + return () => { + const count = (this._stacks!.get(stack.value) || 0); + this._stacks!.set(stack.value, count - 1); + }; + } + + getMostFrequentStack(): [string, number] | undefined { + if (!this._stacks) { + return undefined; + } + let topStack: [string, number] | undefined; + let topCount: number = 0; + for (const [stack, count] of this._stacks) { + if (!topStack || topCount < count) { + topStack = [stack, count]; + topCount = count; + } + } + return topStack; + } +} + +class Stacktrace { + + static create() { + const err = new Error(); + return new Stacktrace(err.stack ?? ''); + } + + private constructor(readonly value: string) { } + + print() { + console.warn(this.value.split('\n').slice(2).join('\n')); + } +} + +// error that is logged when going over the configured listener threshold +export class ListenerLeakError extends Error { + constructor(message: string, stack: string) { + super(message); + this.name = 'ListenerLeakError'; + this.stack = stack; + } +} + +// SEVERE error that is logged when having gone way over the configured listener +// threshold so that the emitter refuses to accept more listeners +export class ListenerRefusalError extends Error { + constructor(message: string, stack: string) { + super(message); + this.name = 'ListenerRefusalError'; + this.stack = stack; + } +} + +let id = 0; +class UniqueContainer { + stack?: Stacktrace; + public id = id++; + constructor(public readonly value: T) { } +} +const compactionThreshold = 2; + +type ListenerContainer = UniqueContainer<(data: T) => void>; +type ListenerOrListeners = (ListenerContainer | undefined)[] | ListenerContainer; + +const forEachListener = (listeners: ListenerOrListeners, fn: (c: ListenerContainer) => void) => { + if (listeners instanceof UniqueContainer) { + fn(listeners); + } else { + for (let i = 0; i < listeners.length; i++) { + const l = listeners[i]; + if (l) { + fn(l); + } + } + } +}; + +/** + * The Emitter can be used to expose an Event to the public + * to fire it from the insides. + * Sample: + class Document { + + private readonly _onDidChange = new Emitter<(value:string)=>any>(); + + public onDidChange = this._onDidChange.event; + + // getter-style + // get onDidChange(): Event<(value:string)=>any> { + // return this._onDidChange.event; + // } + + private _doIt() { + //... + this._onDidChange.fire(value); + } + } + */ +export class Emitter { + + private readonly _options?: EmitterOptions; + private readonly _leakageMon?: LeakageMonitor; + private readonly _perfMon?: EventProfiling; + private _disposed?: true; + private _event?: Event; + + /** + * A listener, or list of listeners. A single listener is the most common + * for event emitters (#185789), so we optimize that special case to avoid + * wrapping it in an array (just like Node.js itself.) + * + * A list of listeners never 'downgrades' back to a plain function if + * listeners are removed, for two reasons: + * + * 1. That's complicated (especially with the deliveryQueue) + * 2. A listener with >1 listener is likely to have >1 listener again at + * some point, and swapping between arrays and functions may[citation needed] + * introduce unnecessary work and garbage. + * + * The array listeners can be 'sparse', to avoid reallocating the array + * whenever any listener is added or removed. If more than `1 / compactionThreshold` + * of the array is empty, only then is it resized. + */ + protected _listeners?: ListenerOrListeners; + + /** + * Always to be defined if _listeners is an array. It's no longer a true + * queue, but holds the dispatching 'state'. If `fire()` is called on an + * emitter, any work left in the _deliveryQueue is finished first. + */ + private _deliveryQueue?: EventDeliveryQueuePrivate; + protected _size = 0; + + constructor(options?: EmitterOptions) { + this._options = options; + this._leakageMon = (_globalLeakWarningThreshold > 0 || this._options?.leakWarningThreshold) + ? new LeakageMonitor(options?.onListenerError ?? onUnexpectedError, this._options?.leakWarningThreshold ?? _globalLeakWarningThreshold) : + undefined; + this._perfMon = this._options?._profName ? new EventProfiling(this._options._profName) : undefined; + this._deliveryQueue = this._options?.deliveryQueue as EventDeliveryQueuePrivate | undefined; + } + + dispose() { + if (!this._disposed) { + this._disposed = true; + + // It is bad to have listeners at the time of disposing an emitter, it is worst to have listeners keep the emitter + // alive via the reference that's embedded in their disposables. Therefore we loop over all remaining listeners and + // unset their subscriptions/disposables. Looping and blaming remaining listeners is done on next tick because the + // the following programming pattern is very popular: + // + // const someModel = this._disposables.add(new ModelObject()); // (1) create and register model + // this._disposables.add(someModel.onDidChange(() => { ... }); // (2) subscribe and register model-event listener + // ...later... + // this._disposables.dispose(); disposes (1) then (2): don't warn after (1) but after the "overall dispose" is done + + if (this._deliveryQueue?.current === this) { + this._deliveryQueue.reset(); + } + if (this._listeners) { + if (_enableDisposeWithListenerWarning) { + const listeners = this._listeners; + queueMicrotask(() => { + forEachListener(listeners, l => l.stack?.print()); + }); + } + + this._listeners = undefined; + this._size = 0; + } + this._options?.onDidRemoveLastListener?.(); + this._leakageMon?.dispose(); + } + } + + /** + * For the public to allow to subscribe + * to events from this Emitter + */ + get event(): Event { + this._event ??= (callback: (e: T) => unknown, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => { + if (this._leakageMon && this._size > this._leakageMon.threshold ** 2) { + const message = `[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`; + console.warn(message); + + const tuple = this._leakageMon.getMostFrequentStack() ?? ['UNKNOWN stack', -1]; + const error = new ListenerRefusalError(`${message}. HINT: Stack shows most frequent listener (${tuple[1]}-times)`, tuple[0]); + const errorHandler = this._options?.onListenerError || onUnexpectedError; + errorHandler(error); + + return Disposable.None; + } + + if (this._disposed) { + // todo: should we warn if a listener is added to a disposed emitter? This happens often + return Disposable.None; + } + + if (thisArgs) { + callback = callback.bind(thisArgs); + } + + const contained = new UniqueContainer(callback); + + let removeMonitor: Function | undefined; + let stack: Stacktrace | undefined; + if (this._leakageMon && this._size >= Math.ceil(this._leakageMon.threshold * 0.2)) { + // check and record this emitter for potential leakage + contained.stack = Stacktrace.create(); + removeMonitor = this._leakageMon.check(contained.stack, this._size + 1); + } + + if (_enableDisposeWithListenerWarning) { + contained.stack = stack ?? Stacktrace.create(); + } + + if (!this._listeners) { + this._options?.onWillAddFirstListener?.(this); + this._listeners = contained; + this._options?.onDidAddFirstListener?.(this); + } else if (this._listeners instanceof UniqueContainer) { + this._deliveryQueue ??= new EventDeliveryQueuePrivate(); + this._listeners = [this._listeners, contained]; + } else { + this._listeners.push(contained); + } + this._options?.onDidAddListener?.(this); + + this._size++; + + + const result = toDisposable(() => { + removeMonitor?.(); + this._removeListener(contained); + }); + if (disposables instanceof DisposableStore) { + disposables.add(result); + } else if (Array.isArray(disposables)) { + disposables.push(result); + } + + return result; + }; + + return this._event; + } + + private _removeListener(listener: ListenerContainer) { + this._options?.onWillRemoveListener?.(this); + + if (!this._listeners) { + return; // expected if a listener gets disposed + } + + if (this._size === 1) { + this._listeners = undefined; + this._options?.onDidRemoveLastListener?.(this); + this._size = 0; + return; + } + + // size > 1 which requires that listeners be a list: + const listeners = this._listeners as (ListenerContainer | undefined)[]; + + const index = listeners.indexOf(listener); + if (index === -1) { + console.log('disposed?', this._disposed); + console.log('size?', this._size); + console.log('arr?', JSON.stringify(this._listeners)); + throw new Error('Attempted to dispose unknown listener'); + } + + this._size--; + listeners[index] = undefined; + + const adjustDeliveryQueue = this._deliveryQueue!.current === this; + if (this._size * compactionThreshold <= listeners.length) { + let n = 0; + for (let i = 0; i < listeners.length; i++) { + if (listeners[i]) { + listeners[n++] = listeners[i]; + } else if (adjustDeliveryQueue && n < this._deliveryQueue!.end) { + this._deliveryQueue!.end--; + if (n < this._deliveryQueue!.i) { + this._deliveryQueue!.i--; + } + } + } + listeners.length = n; + } + } + + private _deliver(listener: undefined | UniqueContainer<(value: T) => void>, value: T) { + if (!listener) { + return; + } + + const errorHandler = this._options?.onListenerError || onUnexpectedError; + if (!errorHandler) { + listener.value(value); + return; + } + + try { + listener.value(value); + } catch (e) { + errorHandler(e); + } + } + + /** Delivers items in the queue. Assumes the queue is ready to go. */ + private _deliverQueue(dq: EventDeliveryQueuePrivate) { + const listeners = dq.current!._listeners! as (ListenerContainer | undefined)[]; + while (dq.i < dq.end) { + // important: dq.i is incremented before calling deliver() because it might reenter deliverQueue() + this._deliver(listeners[dq.i++], dq.value as T); + } + dq.reset(); + } + + /** + * To be kept private to fire an event to + * subscribers + */ + fire(event: T): void { + if (this._deliveryQueue?.current) { + this._deliverQueue(this._deliveryQueue); + this._perfMon?.stop(); // last fire() will have starting perfmon, stop it before starting the next dispatch + } + + this._perfMon?.start(this._size); + + if (!this._listeners) { + // no-op + } else if (this._listeners instanceof UniqueContainer) { + this._deliver(this._listeners, event); + } else { + const dq = this._deliveryQueue!; + dq.enqueue(this, event, this._listeners.length); + this._deliverQueue(dq); + } + + this._perfMon?.stop(); + } + + hasListeners(): boolean { + return this._size > 0; + } +} + +export interface EventDeliveryQueue { + _isEventDeliveryQueue: true; +} + +export const createEventDeliveryQueue = (): EventDeliveryQueue => new EventDeliveryQueuePrivate(); + +class EventDeliveryQueuePrivate implements EventDeliveryQueue { + declare _isEventDeliveryQueue: true; + + /** + * Index in current's listener list. + */ + public i = -1; + + /** + * The last index in the listener's list to deliver. + */ + public end = 0; + + /** + * Emitter currently being dispatched on. Emitter._listeners is always an array. + */ + public current?: Emitter; + /** + * Currently emitting value. Defined whenever `current` is. + */ + public value?: unknown; + + public enqueue(emitter: Emitter, value: T, end: number) { + this.i = 0; + this.end = end; + this.current = emitter; + this.value = value; + } + + public reset() { + this.i = this.end; // force any current emission loop to stop, mainly for during dispose + this.current = undefined; + this.value = undefined; + } +} + +export interface IWaitUntil { + token: CancellationToken; + waitUntil(thenable: Promise): void; +} + +export type IWaitUntilData = Omit, 'token'>; + +export class AsyncEmitter extends Emitter { + + private _asyncDeliveryQueue?: LinkedList<[(ev: T) => void, IWaitUntilData]>; + + async fireAsync(data: IWaitUntilData, token: CancellationToken, promiseJoin?: (p: Promise, listener: Function) => Promise): Promise { + if (!this._listeners) { + return; + } + + if (!this._asyncDeliveryQueue) { + this._asyncDeliveryQueue = new LinkedList(); + } + + forEachListener(this._listeners, listener => this._asyncDeliveryQueue!.push([listener.value, data])); + + while (this._asyncDeliveryQueue.size > 0 && !token.isCancellationRequested) { + + const [listener, data] = this._asyncDeliveryQueue.shift()!; + const thenables: Promise[] = []; + + // eslint-disable-next-line local/code-no-dangerous-type-assertions + const event = { + ...data, + token, + waitUntil: (p: Promise): void => { + if (Object.isFrozen(thenables)) { + throw new Error('waitUntil can NOT be called asynchronous'); + } + if (promiseJoin) { + p = promiseJoin(p, listener); + } + thenables.push(p); + } + }; + + try { + listener(event); + } catch (e) { + onUnexpectedError(e); + continue; + } + + // freeze thenables-collection to enforce sync-calls to + // wait until and then wait for all thenables to resolve + Object.freeze(thenables); + + await Promise.allSettled(thenables).then(values => { + for (const value of values) { + if (value.status === 'rejected') { + onUnexpectedError(value.reason); + } + } + }); + } + } +} + + +export class PauseableEmitter extends Emitter { + + private _isPaused = 0; + protected _eventQueue = new LinkedList(); + private _mergeFn?: (input: T[]) => T; + + public get isPaused(): boolean { + return this._isPaused !== 0; + } + + constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) { + super(options); + this._mergeFn = options?.merge; + } + + pause(): void { + this._isPaused++; + } + + resume(): void { + if (this._isPaused !== 0 && --this._isPaused === 0) { + if (this._mergeFn) { + // use the merge function to create a single composite + // event. make a copy in case firing pauses this emitter + if (this._eventQueue.size > 0) { + const events = Array.from(this._eventQueue); + this._eventQueue.clear(); + super.fire(this._mergeFn(events)); + } + + } else { + // no merging, fire each event individually and test + // that this emitter isn't paused halfway through + while (!this._isPaused && this._eventQueue.size !== 0) { + super.fire(this._eventQueue.shift()!); + } + } + } + } + + override fire(event: T): void { + if (this._size) { + if (this._isPaused !== 0) { + this._eventQueue.push(event); + } else { + super.fire(event); + } + } + } +} + +export class DebounceEmitter extends PauseableEmitter { + + private readonly _delay: number; + private _handle: any | undefined; + + constructor(options: EmitterOptions & { merge: (input: T[]) => T; delay?: number }) { + super(options); + this._delay = options.delay ?? 100; + } + + override fire(event: T): void { + if (!this._handle) { + this.pause(); + this._handle = setTimeout(() => { + this._handle = undefined; + this.resume(); + }, this._delay); + } + super.fire(event); + } +} + +/** + * An emitter which queue all events and then process them at the + * end of the event loop. + */ +export class MicrotaskEmitter extends Emitter { + private _queuedEvents: T[] = []; + private _mergeFn?: (input: T[]) => T; + + constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) { + super(options); + this._mergeFn = options?.merge; + } + override fire(event: T): void { + + if (!this.hasListeners()) { + return; + } + + this._queuedEvents.push(event); + if (this._queuedEvents.length === 1) { + queueMicrotask(() => { + if (this._mergeFn) { + super.fire(this._mergeFn(this._queuedEvents)); + } else { + this._queuedEvents.forEach(e => super.fire(e)); + } + this._queuedEvents = []; + }); + } + } +} + +/** + * An event emitter that multiplexes many events into a single event. + * + * @example Listen to the `onData` event of all `Thing`s, dynamically adding and removing `Thing`s + * to the multiplexer as needed. + * + * ```typescript + * const anythingDataMultiplexer = new EventMultiplexer<{ data: string }>(); + * + * const thingListeners = DisposableMap(); + * + * thingService.onDidAddThing(thing => { + * thingListeners.set(thing, anythingDataMultiplexer.add(thing.onData); + * }); + * thingService.onDidRemoveThing(thing => { + * thingListeners.deleteAndDispose(thing); + * }); + * + * anythingDataMultiplexer.event(e => { + * console.log('Something fired data ' + e.data) + * }); + * ``` + */ +export class EventMultiplexer implements IDisposable { + + private readonly emitter: Emitter; + private hasListeners = false; + private events: { event: Event; listener: IDisposable | null }[] = []; + + constructor() { + this.emitter = new Emitter({ + onWillAddFirstListener: () => this.onFirstListenerAdd(), + onDidRemoveLastListener: () => this.onLastListenerRemove() + }); + } + + get event(): Event { + return this.emitter.event; + } + + add(event: Event): IDisposable { + const e = { event: event, listener: null }; + this.events.push(e); + + if (this.hasListeners) { + this.hook(e); + } + + const dispose = () => { + if (this.hasListeners) { + this.unhook(e); + } + + const idx = this.events.indexOf(e); + this.events.splice(idx, 1); + }; + + return toDisposable(createSingleCallFunction(dispose)); + } + + private onFirstListenerAdd(): void { + this.hasListeners = true; + this.events.forEach(e => this.hook(e)); + } + + private onLastListenerRemove(): void { + this.hasListeners = false; + this.events.forEach(e => this.unhook(e)); + } + + private hook(e: { event: Event; listener: IDisposable | null }): void { + e.listener = e.event(r => this.emitter.fire(r)); + } + + private unhook(e: { event: Event; listener: IDisposable | null }): void { + e.listener?.dispose(); + e.listener = null; + } + + dispose(): void { + this.emitter.dispose(); + + for (const e of this.events) { + e.listener?.dispose(); + } + this.events = []; + } +} + +export interface IDynamicListEventMultiplexer extends IDisposable { + readonly event: Event; +} +export class DynamicListEventMultiplexer implements IDynamicListEventMultiplexer { + private readonly _store = new DisposableStore(); + + readonly event: Event; + + constructor( + items: TItem[], + onAddItem: Event, + onRemoveItem: Event, + getEvent: (item: TItem) => Event + ) { + const multiplexer = this._store.add(new EventMultiplexer()); + const itemListeners = this._store.add(new DisposableMap()); + + function addItem(instance: TItem) { + itemListeners.set(instance, multiplexer.add(getEvent(instance))); + } + + // Existing items + for (const instance of items) { + addItem(instance); + } + + // Added items + this._store.add(onAddItem(instance => { + addItem(instance); + })); + + // Removed items + this._store.add(onRemoveItem(instance => { + itemListeners.deleteAndDispose(instance); + })); + + this.event = multiplexer.event; + } + + dispose() { + this._store.dispose(); + } +} + +/** + * The EventBufferer is useful in situations in which you want + * to delay firing your events during some code. + * You can wrap that code and be sure that the event will not + * be fired during that wrap. + * + * ``` + * const emitter: Emitter; + * const delayer = new EventDelayer(); + * const delayedEvent = delayer.wrapEvent(emitter.event); + * + * delayedEvent(console.log); + * + * delayer.bufferEvents(() => { + * emitter.fire(); // event will not be fired yet + * }); + * + * // event will only be fired at this point + * ``` + */ +export class EventBufferer { + + private data: { buffers: Function[] }[] = []; + + wrapEvent(event: Event): Event; + wrapEvent(event: Event, reduce: (last: T | undefined, event: T) => T): Event; + wrapEvent(event: Event, reduce: (last: O | undefined, event: T) => O, initial: O): Event; + wrapEvent(event: Event, reduce?: (last: T | O | undefined, event: T) => T | O, initial?: O): Event { + return (listener, thisArgs?, disposables?) => { + return event(i => { + const data = this.data[this.data.length - 1]; + + // Non-reduce scenario + if (!reduce) { + // Buffering case + if (data) { + data.buffers.push(() => listener.call(thisArgs, i)); + } else { + // Not buffering case + listener.call(thisArgs, i); + } + return; + } + + // Reduce scenario + const reduceData = data as typeof data & { + /** + * The accumulated items that will be reduced. + */ + items?: T[]; + /** + * The reduced result cached to be shared with other listeners. + */ + reducedResult?: T | O; + }; + + // Not buffering case + if (!reduceData) { + // TODO: Is there a way to cache this reduce call for all listeners? + listener.call(thisArgs, reduce(initial, i)); + return; + } + + // Buffering case + reduceData.items ??= []; + reduceData.items.push(i); + if (reduceData.buffers.length === 0) { + // Include a single buffered function that will reduce all events when we're done buffering events + data.buffers.push(() => { + // cache the reduced result so that the value can be shared across all listeners + reduceData.reducedResult ??= initial + ? reduceData.items!.reduce(reduce as (last: O | undefined, event: T) => O, initial) + : reduceData.items!.reduce(reduce as (last: T | undefined, event: T) => T); + listener.call(thisArgs, reduceData.reducedResult); + }); + } + }, undefined, disposables); + }; + } + + bufferEvents(fn: () => R): R { + const data = { buffers: new Array() }; + this.data.push(data); + const r = fn(); + this.data.pop(); + data.buffers.forEach(flush => flush()); + return r; + } +} + +/** + * A Relay is an event forwarder which functions as a replugabble event pipe. + * Once created, you can connect an input event to it and it will simply forward + * events from that input event through its own `event` property. The `input` + * can be changed at any point in time. + */ +export class Relay implements IDisposable { + + private listening = false; + private inputEvent: Event = Event.None; + private inputEventListener: IDisposable = Disposable.None; + + private readonly emitter = new Emitter({ + onDidAddFirstListener: () => { + this.listening = true; + this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter); + }, + onDidRemoveLastListener: () => { + this.listening = false; + this.inputEventListener.dispose(); + } + }); + + readonly event: Event = this.emitter.event; + + set input(event: Event) { + this.inputEvent = event; + + if (this.listening) { + this.inputEventListener.dispose(); + this.inputEventListener = event(this.emitter.fire, this.emitter); + } + } + + dispose() { + this.inputEventListener.dispose(); + this.emitter.dispose(); + } +} + +export interface IValueWithChangeEvent { + readonly onDidChange: Event; + get value(): T; +} + +export class ValueWithChangeEvent implements IValueWithChangeEvent { + public static const(value: T): IValueWithChangeEvent { + return new ConstValueWithChangeEvent(value); + } + + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + constructor(private _value: T) { } + + get value(): T { + return this._value; + } + + set value(value: T) { + if (value !== this._value) { + this._value = value; + this._onDidChange.fire(undefined); + } + } +} + +class ConstValueWithChangeEvent implements IValueWithChangeEvent { + public readonly onDidChange: Event = Event.None; + + constructor(readonly value: T) { } +} + +/** + * @param handleItem Is called for each item in the set (but only the first time the item is seen in the set). + * The returned disposable is disposed if the item is no longer in the set. + */ +export function trackSetChanges(getData: () => ReadonlySet, onDidChangeData: Event, handleItem: (d: T) => IDisposable): IDisposable { + const map = new DisposableMap(); + let oldData = new Set(getData()); + for (const d of oldData) { + map.set(d, handleItem(d)); + } + + const store = new DisposableStore(); + store.add(onDidChangeData(() => { + const newData = getData(); + const diff = diffSets(oldData, newData); + for (const r of diff.removed) { + map.deleteAndDispose(r); + } + for (const a of diff.added) { + map.set(a, handleItem(a)); + } + oldData = new Set(newData); + })); + store.add(map); + return store; +} diff --git a/packages/core/src/extpath.ts b/packages/core/src/extpath.ts new file mode 100644 index 00000000..f30ba048 --- /dev/null +++ b/packages/core/src/extpath.ts @@ -0,0 +1,423 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CharCode } from './charCode.js'; +import { isAbsolute, join, normalize, posix, sep } from './path.js'; +import { isWindows } from './platform.js'; +import { equalsIgnoreCase, rtrim, startsWithIgnoreCase } from './strings.js'; +import { isNumber } from './types.js'; + +export function isPathSeparator(code: number) { + return code === CharCode.Slash || code === CharCode.Backslash; +} + +/** + * Takes a Windows OS path and changes backward slashes to forward slashes. + * This should only be done for OS paths from Windows (or user provided paths potentially from Windows). + * Using it on a Linux or MaxOS path might change it. + */ +export function toSlashes(osPath: string) { + return osPath.replace(/[\\/]/g, posix.sep); +} + +/** + * Takes a Windows OS path (using backward or forward slashes) and turns it into a posix path: + * - turns backward slashes into forward slashes + * - makes it absolute if it starts with a drive letter + * This should only be done for OS paths from Windows (or user provided paths potentially from Windows). + * Using it on a Linux or MaxOS path might change it. + */ +export function toPosixPath(osPath: string) { + if (osPath.indexOf('/') === -1) { + osPath = toSlashes(osPath); + } + if (/^[a-zA-Z]:(\/|$)/.test(osPath)) { // starts with a drive letter + osPath = '/' + osPath; + } + return osPath; +} + +/** + * Computes the _root_ this path, like `getRoot('c:\files') === c:\`, + * `getRoot('files:///files/path') === files:///`, + * or `getRoot('\\server\shares\path') === \\server\shares\` + */ +export function getRoot(path: string, sep: string = posix.sep): string { + if (!path) { + return ''; + } + + const len = path.length; + const firstLetter = path.charCodeAt(0); + if (isPathSeparator(firstLetter)) { + if (isPathSeparator(path.charCodeAt(1))) { + // UNC candidate \\localhost\shares\ddd + // ^^^^^^^^^^^^^^^^^^^ + if (!isPathSeparator(path.charCodeAt(2))) { + let pos = 3; + const start = pos; + for (; pos < len; pos++) { + if (isPathSeparator(path.charCodeAt(pos))) { + break; + } + } + if (start !== pos && !isPathSeparator(path.charCodeAt(pos + 1))) { + pos += 1; + for (; pos < len; pos++) { + if (isPathSeparator(path.charCodeAt(pos))) { + return path.slice(0, pos + 1) // consume this separator + .replace(/[\\/]/g, sep); + } + } + } + } + } + + // /user/far + // ^ + return sep; + + } else if (isWindowsDriveLetter(firstLetter)) { + // check for windows drive letter c:\ or c: + + if (path.charCodeAt(1) === CharCode.Colon) { + if (isPathSeparator(path.charCodeAt(2))) { + // C:\fff + // ^^^ + return path.slice(0, 2) + sep; + } else { + // C: + // ^^ + return path.slice(0, 2); + } + } + } + + // check for URI + // scheme://authority/path + // ^^^^^^^^^^^^^^^^^^^ + let pos = path.indexOf('://'); + if (pos !== -1) { + pos += 3; // 3 -> "://".length + for (; pos < len; pos++) { + if (isPathSeparator(path.charCodeAt(pos))) { + return path.slice(0, pos + 1); // consume this separator + } + } + } + + return ''; +} + +/** + * Check if the path follows this pattern: `\\hostname\sharename`. + * + * @see https://msdn.microsoft.com/en-us/library/gg465305.aspx + * @return A boolean indication if the path is a UNC path, on none-windows + * always false. + */ +export function isUNC(path: string): boolean { + if (!isWindows) { + // UNC is a windows concept + return false; + } + + if (!path || path.length < 5) { + // at least \\a\b + return false; + } + + let code = path.charCodeAt(0); + if (code !== CharCode.Backslash) { + return false; + } + + code = path.charCodeAt(1); + + if (code !== CharCode.Backslash) { + return false; + } + + let pos = 2; + const start = pos; + for (; pos < path.length; pos++) { + code = path.charCodeAt(pos); + if (code === CharCode.Backslash) { + break; + } + } + + if (start === pos) { + return false; + } + + code = path.charCodeAt(pos + 1); + + if (isNaN(code) || code === CharCode.Backslash) { + return false; + } + + return true; +} + +// Reference: https://en.wikipedia.org/wiki/Filename +const WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g; +const UNIX_INVALID_FILE_CHARS = /[/]/g; +const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])(\.(.*?))?$/i; +export function isValidBasename(name: string | null | undefined, isWindowsOS: boolean = isWindows): boolean { + const invalidFileChars = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS; + + if (!name || name.length === 0 || /^\s+$/.test(name)) { + return false; // require a name that is not just whitespace + } + + invalidFileChars.lastIndex = 0; // the holy grail of software development + if (invalidFileChars.test(name)) { + return false; // check for certain invalid file characters + } + + if (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) { + return false; // check for certain invalid file names + } + + if (name === '.' || name === '..') { + return false; // check for reserved values + } + + if (isWindowsOS && name[name.length - 1] === '.') { + return false; // Windows: file cannot end with a "." + } + + if (isWindowsOS && name.length !== name.trim().length) { + return false; // Windows: file cannot end with a whitespace + } + + if (name.length > 255) { + return false; // most file systems do not allow files > 255 length + } + + return true; +} + +/** + * @deprecated please use `IUriIdentityService.extUri.isEqual` instead. If you are + * in a context without services, consider to pass down the `extUri` from the outside + * or use `extUriBiasedIgnorePathCase` if you know what you are doing. + */ +export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boolean { + const identityEquals = (pathA === pathB); + if (!ignoreCase || identityEquals) { + return identityEquals; + } + + if (!pathA || !pathB) { + return false; + } + + return equalsIgnoreCase(pathA, pathB); +} + +/** + * @deprecated please use `IUriIdentityService.extUri.isEqualOrParent` instead. If + * you are in a context without services, consider to pass down the `extUri` from the + * outside, or use `extUriBiasedIgnorePathCase` if you know what you are doing. + */ +export function isEqualOrParent(base: string, parentCandidate: string, ignoreCase?: boolean, separator = sep): boolean { + if (base === parentCandidate) { + return true; + } + + if (!base || !parentCandidate) { + return false; + } + + if (parentCandidate.length > base.length) { + return false; + } + + if (ignoreCase) { + const beginsWith = startsWithIgnoreCase(base, parentCandidate); + if (!beginsWith) { + return false; + } + + if (parentCandidate.length === base.length) { + return true; // same path, different casing + } + + let sepOffset = parentCandidate.length; + if (parentCandidate.charAt(parentCandidate.length - 1) === separator) { + sepOffset--; // adjust the expected sep offset in case our candidate already ends in separator character + } + + return base.charAt(sepOffset) === separator; + } + + if (parentCandidate.charAt(parentCandidate.length - 1) !== separator) { + parentCandidate += separator; + } + + return base.indexOf(parentCandidate) === 0; +} + +export function isWindowsDriveLetter(char0: number): boolean { + return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z; +} + +export function sanitizeFilePath(candidate: string, cwd: string): string { + + // Special case: allow to open a drive letter without trailing backslash + if (isWindows && candidate.endsWith(':')) { + candidate += sep; + } + + // Ensure absolute + if (!isAbsolute(candidate)) { + candidate = join(cwd, candidate); + } + + // Ensure normalized + candidate = normalize(candidate); + + // Ensure no trailing slash/backslash + return removeTrailingPathSeparator(candidate); +} + +export function removeTrailingPathSeparator(candidate: string): string { + if (isWindows) { + candidate = rtrim(candidate, sep); + + // Special case: allow to open drive root ('C:\') + if (candidate.endsWith(':')) { + candidate += sep; + } + + } else { + candidate = rtrim(candidate, sep); + + // Special case: allow to open root ('/') + if (!candidate) { + candidate = sep; + } + } + + return candidate; +} + +export function isRootOrDriveLetter(path: string): boolean { + const pathNormalized = normalize(path); + + if (isWindows) { + if (path.length > 3) { + return false; + } + + return hasDriveLetter(pathNormalized) && + (path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash); + } + + return pathNormalized === posix.sep; +} + +export function hasDriveLetter(path: string, isWindowsOS: boolean = isWindows): boolean { + if (isWindowsOS) { + return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === CharCode.Colon; + } + + return false; +} + +export function getDriveLetter(path: string, isWindowsOS: boolean = isWindows): string | undefined { + return hasDriveLetter(path, isWindowsOS) ? path[0] : undefined; +} + +export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number { + if (candidate.length > path.length) { + return -1; + } + + if (path === candidate) { + return 0; + } + + if (ignoreCase) { + path = path.toLowerCase(); + candidate = candidate.toLowerCase(); + } + + return path.indexOf(candidate); +} + +export interface IPathWithLineAndColumn { + path: string; + line?: number; + column?: number; +} + +export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn { + const segments = rawPath.split(':'); // C:\file.txt:: + + let path: string | undefined = undefined; + let line: number | undefined = undefined; + let column: number | undefined = undefined; + + for (const segment of segments) { + const segmentAsNumber = Number(segment); + if (!isNumber(segmentAsNumber)) { + path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...) + } else if (line === undefined) { + line = segmentAsNumber; + } else if (column === undefined) { + column = segmentAsNumber; + } + } + + if (!path) { + throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`'); + } + + return { + path, + line: line !== undefined ? line : undefined, + column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set + }; +} + +const pathChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +const windowsSafePathFirstChars = 'BDEFGHIJKMOQRSTUVWXYZbdefghijkmoqrstuvwxyz0123456789'; + +export function randomPath(parent?: string, prefix?: string, randomLength = 8): string { + let suffix = ''; + for (let i = 0; i < randomLength; i++) { + let pathCharsTouse: string; + if (i === 0 && isWindows && !prefix && (randomLength === 3 || randomLength === 4)) { + + // Windows has certain reserved file names that cannot be used, such + // as AUX, CON, PRN, etc. We want to avoid generating a random name + // that matches that pattern, so we use a different set of characters + // for the first character of the name that does not include any of + // the reserved names first characters. + + pathCharsTouse = windowsSafePathFirstChars; + } else { + pathCharsTouse = pathChars; + } + + suffix += pathCharsTouse.charAt(Math.floor(Math.random() * pathCharsTouse.length)); + } + + let randomFileName: string; + if (prefix) { + randomFileName = `${prefix}-${suffix}`; + } else { + randomFileName = suffix; + } + + if (parent) { + return join(parent, randomFileName); + } + + return randomFileName; +} diff --git a/packages/core/src/functional.ts b/packages/core/src/functional.ts new file mode 100644 index 00000000..62f02135 --- /dev/null +++ b/packages/core/src/functional.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Given a function, returns a function that is only calling that function once. + */ +export function createSingleCallFunction(this: unknown, fn: T, fnDidRunCallback?: () => void): T { + const _this = this; + let didCall = false; + let result: unknown; + + return function () { + if (didCall) { + return result; + } + + didCall = true; + if (fnDidRunCallback) { + try { + result = fn.apply(_this, arguments); + } finally { + fnDidRunCallback(); + } + } else { + result = fn.apply(_this, arguments); + } + + return result; + } as unknown as T; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 00000000..5f3abc56 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,33 @@ +import { substitute } from './strings.js' + +export type Hash = Record; +export interface List { + [index: number]: T + length: number +} +/** + * Interface of the simple literal object with any string keys. + */ +export type IObjectLiteral = Record; + +export type JSONPathExpression = string; + + + +const _resolve = (config) => { + for (const key in config) { + if (config[key] && typeof config[key] == 'string') { + const resolved = substitute(config[key], config); + config[key] = resolved; + } + } + return config; +} +export const resolveConfig = (config) => { + config = _resolve(config); + config = _resolve(config); + return config; +} + +export { substitute } from './strings.js' + diff --git a/packages/core/src/iterator.ts b/packages/core/src/iterator.ts new file mode 100644 index 00000000..50e76b47 --- /dev/null +++ b/packages/core/src/iterator.ts @@ -0,0 +1,262 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export namespace Iterable { + + export function is(thing: any): thing is Iterable { + return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function'; + } + + const _empty: Iterable = Object.freeze([]); + export function empty(): Iterable { + return _empty; + } + + export function* single(element: T): Iterable { + yield element; + } + + export function wrap(iterableOrElement: Iterable | T): Iterable { + if (is(iterableOrElement)) { + return iterableOrElement; + } else { + return single(iterableOrElement); + } + } + + export function from(iterable: Iterable | undefined | null): Iterable { + return iterable || _empty; + } + + export function* reverse(array: Array): Iterable { + for (let i = array.length - 1; i >= 0; i--) { + yield array[i]; + } + } + + export function isEmpty(iterable: Iterable | undefined | null): boolean { + return !iterable || iterable[Symbol.iterator]().next().done === true; + } + + export function first(iterable: Iterable): T | undefined { + return iterable[Symbol.iterator]().next().value; + } + + export function some(iterable: Iterable, predicate: (t: T, i: number) => unknown): boolean { + let i = 0; + for (const element of iterable) { + if (predicate(element, i++)) { + return true; + } + } + return false; + } + + export function find(iterable: Iterable, predicate: (t: T) => t is R): R | undefined; + export function find(iterable: Iterable, predicate: (t: T) => boolean): T | undefined; + export function find(iterable: Iterable, predicate: (t: T) => boolean): T | undefined { + for (const element of iterable) { + if (predicate(element)) { + return element; + } + } + + return undefined; + } + + export function filter(iterable: Iterable, predicate: (t: T) => t is R): Iterable; + export function filter(iterable: Iterable, predicate: (t: T) => boolean): Iterable; + export function* filter(iterable: Iterable, predicate: (t: T) => boolean): Iterable { + for (const element of iterable) { + if (predicate(element)) { + yield element; + } + } + } + + export function* map(iterable: Iterable, fn: (t: T, index: number) => R): Iterable { + let index = 0; + for (const element of iterable) { + yield fn(element, index++); + } + } + + export function* flatMap(iterable: Iterable, fn: (t: T, index: number) => Iterable): Iterable { + let index = 0; + for (const element of iterable) { + yield* fn(element, index++); + } + } + + export function* concat(...iterables: Iterable[]): Iterable { + for (const iterable of iterables) { + yield* iterable; + } + } + + export function reduce(iterable: Iterable, reducer: (previousValue: R, currentValue: T) => R, initialValue: R): R { + let value = initialValue; + for (const element of iterable) { + value = reducer(value, element); + } + return value; + } + + /** + * Returns an iterable slice of the array, with the same semantics as `array.slice()`. + */ + export function* slice(arr: ReadonlyArray, from: number, to = arr.length): Iterable { + if (from < -arr.length) { + from = 0; + } + if (from < 0) { + from += arr.length; + } + + if (to < 0) { + to += arr.length; + } else if (to > arr.length) { + to = arr.length; + } + + for (; from < to; from++) { + yield arr[from]; + } + } + + /** + * Consumes `atMost` elements from iterable and returns the consumed elements, + * and an iterable for the rest of the elements. + */ + export function consume(iterable: Iterable, atMost: number = Number.POSITIVE_INFINITY): [T[], Iterable] { + const consumed: T[] = []; + + if (atMost === 0) { + return [consumed, iterable]; + } + + const iterator = iterable[Symbol.iterator](); + + for (let i = 0; i < atMost; i++) { + const next = iterator.next(); + + if (next.done) { + return [consumed, Iterable.empty()]; + } + + consumed.push(next.value); + } + + return [consumed, { [Symbol.iterator]() { return iterator; } }]; + } + + export async function asyncToArray(iterable: AsyncIterable): Promise { + const result: T[] = []; + for await (const item of iterable) { + result.push(item); + } + return Promise.resolve(result); + } +} + +export interface IIterator { + next(): T; +} + +export class ArrayIterator implements IIterator { + + private items: T[]; + protected start: number; + protected end: number; + protected index: number; + + constructor(items: T[], start: number = 0, end: number = items.length) { + this.items = items; + this.start = start; + this.end = end; + this.index = start - 1; + } + + public first(): T { + this.index = this.start; + return this.current(); + } + + public next(): T { + this.index = Math.min(this.index + 1, this.end); + return this.current(); + } + + protected current(): T { + if (this.index === this.start - 1 || this.index === this.end) { + return null; + } + + return this.items[this.index]; + } +} + +export class ArrayNavigator extends ArrayIterator implements INavigator { + + constructor(items: T[], start: number = 0, end: number = items.length) { + super(items, start, end); + } + + public current(): T { + return super.current(); + } + + public previous(): T { + this.index = Math.max(this.index - 1, this.start - 1); + return this.current(); + } + + public first(): T { + this.index = this.start; + return this.current(); + } + + public last(): T { + this.index = this.end - 1; + return this.current(); + } + + public parent(): T { + return null; + } + +} + +export class MappedIterator implements IIterator { + + constructor(protected iterator: IIterator, protected fn: (item: T) => R) { + // noop + } + + next() { return this.fn(this.iterator.next()); } +} + +export interface INavigator extends IIterator { + current(): T; + previous(): T; + parent(): T; + first(): T; + last(): T; + next(): T; +} + +export class MappedNavigator extends MappedIterator implements INavigator { + + constructor(protected navigator: INavigator, fn: (item: T) => R) { + super(navigator, fn); + } + + current() { return this.fn(this.navigator.current()); } + previous() { return this.fn(this.navigator.previous()); } + parent() { return this.fn(this.navigator.parent()); } + first() { return this.fn(this.navigator.first()); } + last() { return this.fn(this.navigator.last()); } + next() { return this.fn(this.navigator.next()); } +} \ No newline at end of file diff --git a/packages/core/src/labels.ts b/packages/core/src/labels.ts new file mode 100644 index 00000000..e6c85eed --- /dev/null +++ b/packages/core/src/labels.ts @@ -0,0 +1,463 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { hasDriveLetter, toSlashes } from './extpath.js'; +import { posix, sep, win32 } from './path.js'; +import { isMacintosh, isWindows, OperatingSystem, OS } from './platform.js'; +import { extUri, extUriIgnorePathCase } from './resources.js'; +import { rtrim, startsWithIgnoreCase } from './strings.js'; +import { URI } from './uri.js'; + +export interface IPathLabelFormatting { + + /** + * The OS the path label is from to produce a label + * that matches OS expectations. + */ + readonly os: OperatingSystem; + + /** + * Whether to add a `~` when the path is in the + * user home directory. + * + * Note: this only applies to Linux, macOS but not + * Windows. + */ + readonly tildify?: IUserHomeProvider; + + /** + * Whether to convert to a relative path if the path + * is within any of the opened workspace folders. + */ + readonly relative?: IRelativePathProvider; +} + +export interface IRelativePathProvider { + + /** + * Whether to not add a prefix when in multi-root workspace. + */ + readonly noPrefix?: boolean; + + getWorkspace(): { folders: { uri: URI; name?: string }[] }; + getWorkspaceFolder(resource: URI): { uri: URI; name?: string } | null; +} + +export interface IUserHomeProvider { + userHome: URI; +} + +export function getPathLabel(resource: URI, formatting: IPathLabelFormatting): string { + const { os, tildify: tildifier, relative: relatifier } = formatting; + + // return early with a relative path if we can resolve one + if (relatifier) { + const relativePath = getRelativePathLabel(resource, relatifier, os); + if (typeof relativePath === 'string') { + return relativePath; + } + } + + // otherwise try to resolve a absolute path label and + // apply target OS standard path separators if target + // OS differs from actual OS we are running in + let absolutePath = resource.fsPath; + if (os === OperatingSystem.Windows && !isWindows) { + absolutePath = absolutePath.replace(/\//g, '\\'); + } else if (os !== OperatingSystem.Windows && isWindows) { + absolutePath = absolutePath.replace(/\\/g, '/'); + } + + // macOS/Linux: tildify with provided user home directory + if (os !== OperatingSystem.Windows && tildifier?.userHome) { + const userHome = tildifier.userHome.fsPath; + + // This is a bit of a hack, but in order to figure out if the + // resource is in the user home, we need to make sure to convert it + // to a user home resource. We cannot assume that the resource is + // already a user home resource. + let userHomeCandidate: string; + if (resource.scheme !== tildifier.userHome.scheme && resource.path[0] === posix.sep && resource.path[1] !== posix.sep) { + userHomeCandidate = tildifier.userHome.with({ path: resource.path }).fsPath; + } else { + userHomeCandidate = absolutePath; + } + + absolutePath = tildify(userHomeCandidate, userHome, os); + } + + // normalize + const pathLib = os === OperatingSystem.Windows ? win32 : posix; + return pathLib.normalize(normalizeDriveLetter(absolutePath, os === OperatingSystem.Windows)); +} + +function getRelativePathLabel(resource: URI, relativePathProvider: IRelativePathProvider, os: OperatingSystem): string | undefined { + const pathLib = os === OperatingSystem.Windows ? win32 : posix; + const extUriLib = os === OperatingSystem.Linux ? extUri : extUriIgnorePathCase; + + const workspace = relativePathProvider.getWorkspace(); + const firstFolder = workspace.folders.at(0); + if (!firstFolder) { + return undefined; + } + + // This is a bit of a hack, but in order to figure out the folder + // the resource belongs to, we need to make sure to convert it + // to a workspace resource. We cannot assume that the resource is + // already matching the workspace. + if (resource.scheme !== firstFolder.uri.scheme && resource.path[0] === posix.sep && resource.path[1] !== posix.sep) { + resource = firstFolder.uri.with({ path: resource.path }); + } + + const folder = relativePathProvider.getWorkspaceFolder(resource); + if (!folder) { + return undefined; + } + + let relativePathLabel: string | undefined = undefined; + if (extUriLib.isEqual(folder.uri, resource)) { + relativePathLabel = ''; // no label if paths are identical + } else { + relativePathLabel = extUriLib.relativePath(folder.uri, resource) ?? ''; + } + + // normalize + if (relativePathLabel) { + relativePathLabel = pathLib.normalize(relativePathLabel); + } + + // always show root basename if there are multiple folders + if (workspace.folders.length > 1 && !relativePathProvider.noPrefix) { + const rootName = folder.name ? folder.name : extUriLib.basenameOrAuthority(folder.uri); + relativePathLabel = relativePathLabel ? `${rootName} • ${relativePathLabel}` : rootName; + } + + return relativePathLabel; +} + +export function normalizeDriveLetter(path: string, isWindowsOS: boolean = isWindows): string { + if (hasDriveLetter(path, isWindowsOS)) { + return path.charAt(0).toUpperCase() + path.slice(1); + } + + return path; +} + +let normalizedUserHomeCached: { original: string; normalized: string } = Object.create(null); +export function tildify(path: string, userHome: string, os = OS): string { + if (os === OperatingSystem.Windows || !path || !userHome) { + return path; // unsupported on Windows + } + + let normalizedUserHome = normalizedUserHomeCached.original === userHome ? normalizedUserHomeCached.normalized : undefined; + if (!normalizedUserHome) { + normalizedUserHome = userHome; + if (isWindows) { + normalizedUserHome = toSlashes(normalizedUserHome); // make sure that the path is POSIX normalized on Windows + } + normalizedUserHome = `${rtrim(normalizedUserHome, posix.sep)}${posix.sep}`; + normalizedUserHomeCached = { original: userHome, normalized: normalizedUserHome }; + } + + let normalizedPath = path; + if (isWindows) { + normalizedPath = toSlashes(normalizedPath); // make sure that the path is POSIX normalized on Windows + } + + // Linux: case sensitive, macOS: case insensitive + if (os === OperatingSystem.Linux ? normalizedPath.startsWith(normalizedUserHome) : startsWithIgnoreCase(normalizedPath, normalizedUserHome)) { + return `~/${normalizedPath.substr(normalizedUserHome.length)}`; + } + + return path; +} + +export function untildify(path: string, userHome: string): string { + return path.replace(/^~($|\/|\\)/, `${userHome}$1`); +} + +/** + * Shortens the paths but keeps them easy to distinguish. + * Replaces not important parts with ellipsis. + * Every shorten path matches only one original path and vice versa. + * + * Algorithm for shortening paths is as follows: + * 1. For every path in list, find unique substring of that path. + * 2. Unique substring along with ellipsis is shortened path of that path. + * 3. To find unique substring of path, consider every segment of length from 1 to path.length of path from end of string + * and if present segment is not substring to any other paths then present segment is unique path, + * else check if it is not present as suffix of any other path and present segment is suffix of path itself, + * if it is true take present segment as unique path. + * 4. Apply ellipsis to unique segment according to whether segment is present at start/in-between/end of path. + * + * Example 1 + * 1. consider 2 paths i.e. ['a\\b\\c\\d', 'a\\f\\b\\c\\d'] + * 2. find unique path of first path, + * a. 'd' is present in path2 and is suffix of path2, hence not unique of present path. + * b. 'c' is present in path2 and 'c' is not suffix of present path, similarly for 'b' and 'a' also. + * c. 'd\\c' is suffix of path2. + * d. 'b\\c' is not suffix of present path. + * e. 'a\\b' is not present in path2, hence unique path is 'a\\b...'. + * 3. for path2, 'f' is not present in path1 hence unique is '...\\f\\...'. + * + * Example 2 + * 1. consider 2 paths i.e. ['a\\b', 'a\\b\\c']. + * a. Even if 'b' is present in path2, as 'b' is suffix of path1 and is not suffix of path2, unique path will be '...\\b'. + * 2. for path2, 'c' is not present in path1 hence unique path is '..\\c'. + */ +const ellipsis = '\u2026'; +const unc = '\\\\'; +const home = '~'; +export function shorten(paths: string[], pathSeparator: string = sep): string[] { + const shortenedPaths: string[] = new Array(paths.length); + + // for every path + let match = false; + for (let pathIndex = 0; pathIndex < paths.length; pathIndex++) { + const originalPath = paths[pathIndex]; + + if (originalPath === '') { + shortenedPaths[pathIndex] = `.${pathSeparator}`; + continue; + } + + if (!originalPath) { + shortenedPaths[pathIndex] = originalPath; + continue; + } + + match = true; + + // trim for now and concatenate unc path (e.g. \\network) or root path (/etc, ~/etc) later + let prefix = ''; + let trimmedPath = originalPath; + if (trimmedPath.indexOf(unc) === 0) { + prefix = trimmedPath.substr(0, trimmedPath.indexOf(unc) + unc.length); + trimmedPath = trimmedPath.substr(trimmedPath.indexOf(unc) + unc.length); + } else if (trimmedPath.indexOf(pathSeparator) === 0) { + prefix = trimmedPath.substr(0, trimmedPath.indexOf(pathSeparator) + pathSeparator.length); + trimmedPath = trimmedPath.substr(trimmedPath.indexOf(pathSeparator) + pathSeparator.length); + } else if (trimmedPath.indexOf(home) === 0) { + prefix = trimmedPath.substr(0, trimmedPath.indexOf(home) + home.length); + trimmedPath = trimmedPath.substr(trimmedPath.indexOf(home) + home.length); + } + + // pick the first shortest subpath found + const segments: string[] = trimmedPath.split(pathSeparator); + for (let subpathLength = 1; match && subpathLength <= segments.length; subpathLength++) { + for (let start = segments.length - subpathLength; match && start >= 0; start--) { + match = false; + let subpath = segments.slice(start, start + subpathLength).join(pathSeparator); + + // that is unique to any other path + for (let otherPathIndex = 0; !match && otherPathIndex < paths.length; otherPathIndex++) { + + // suffix subpath treated specially as we consider no match 'x' and 'x/...' + if (otherPathIndex !== pathIndex && paths[otherPathIndex] && paths[otherPathIndex].indexOf(subpath) > -1) { + const isSubpathEnding: boolean = (start + subpathLength === segments.length); + + // Adding separator as prefix for subpath, such that 'endsWith(src, trgt)' considers subpath as directory name instead of plain string. + // prefix is not added when either subpath is root directory or path[otherPathIndex] does not have multiple directories. + const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(pathSeparator) > -1) ? pathSeparator + subpath : subpath; + const isOtherPathEnding: boolean = paths[otherPathIndex].endsWith(subpathWithSep); + + match = !isSubpathEnding || isOtherPathEnding; + } + } + + // found unique subpath + if (!match) { + let result = ''; + + // preserve disk drive or root prefix + if (segments[0].endsWith(':') || prefix !== '') { + if (start === 1) { + // extend subpath to include disk drive prefix + start = 0; + subpathLength++; + subpath = segments[0] + pathSeparator + subpath; + } + + if (start > 0) { + result = segments[0] + pathSeparator; + } + + result = prefix + result; + } + + // add ellipsis at the beginning if needed + if (start > 0) { + result = result + ellipsis + pathSeparator; + } + + result = result + subpath; + + // add ellipsis at the end if needed + if (start + subpathLength < segments.length) { + result = result + pathSeparator + ellipsis; + } + + shortenedPaths[pathIndex] = result; + } + } + } + + if (match) { + shortenedPaths[pathIndex] = originalPath; // use original path if no unique subpaths found + } + } + + return shortenedPaths; +} + +export interface ISeparator { + label: string; +} + +enum Type { + TEXT, + VARIABLE, + SEPARATOR +} + +interface ISegment { + value: string; + type: Type; +} + +/** + * Helper to insert values for specific template variables into the string. E.g. "this $(is) a $(template)" can be + * passed to this function together with an object that maps "is" and "template" to strings to have them replaced. + * @param value string to which template is applied + * @param values the values of the templates to use + */ +export function template(template: string, values: { [key: string]: string | ISeparator | undefined | null } = Object.create(null)): string { + const segments: ISegment[] = []; + + let inVariable = false; + let curVal = ''; + for (const char of template) { + // Beginning of variable + if (char === '$' || (inVariable && char === '{')) { + if (curVal) { + segments.push({ value: curVal, type: Type.TEXT }); + } + + curVal = ''; + inVariable = true; + } + + // End of variable + else if (char === '}' && inVariable) { + const resolved = values[curVal]; + + // Variable + if (typeof resolved === 'string') { + if (resolved.length) { + segments.push({ value: resolved, type: Type.VARIABLE }); + } + } + + // Separator + else if (resolved) { + const prevSegment = segments[segments.length - 1]; + if (!prevSegment || prevSegment.type !== Type.SEPARATOR) { + segments.push({ value: resolved.label, type: Type.SEPARATOR }); // prevent duplicate separators + } + } + + curVal = ''; + inVariable = false; + } + + // Text or Variable Name + else { + curVal += char; + } + } + + // Tail + if (curVal && !inVariable) { + segments.push({ value: curVal, type: Type.TEXT }); + } + + return segments.filter((segment, index) => { + + // Only keep separator if we have values to the left and right + if (segment.type === Type.SEPARATOR) { + const left = segments[index - 1]; + const right = segments[index + 1]; + + return [left, right].every(segment => segment && (segment.type === Type.VARIABLE || segment.type === Type.TEXT) && segment.value.length > 0); + } + + // accept any TEXT and VARIABLE + return true; + }).map(segment => segment.value).join(''); +} + +/** + * Handles mnemonics for menu items. Depending on OS: + * - Windows: Supported via & character (replace && with &) + * - Linux: Supported via & character (replace && with &) + * - macOS: Unsupported (replace && with empty string) + */ +export function mnemonicMenuLabel(label: string, forceDisableMnemonics?: boolean): string { + if (isMacintosh || forceDisableMnemonics) { + return label.replace(/\(&&\w\)|&&/g, '').replace(/&/g, isMacintosh ? '&' : '&&'); + } + + return label.replace(/&&|&/g, m => m === '&' ? '&&' : '&'); +} + +/** + * Handles mnemonics for buttons. Depending on OS: + * - Windows: Supported via & character (replace && with & and & with && for escaping) + * - Linux: Supported via _ character (replace && with _) + * - macOS: Unsupported (replace && with empty string) + */ +export function mnemonicButtonLabel(label: string, forceDisableMnemonics?: boolean): string { + if (isMacintosh || forceDisableMnemonics) { + return label.replace(/\(&&\w\)|&&/g, ''); + } + + if (isWindows) { + return label.replace(/&&|&/g, m => m === '&' ? '&&' : '&'); + } + + return label.replace(/&&/g, '_'); +} + +export function unmnemonicLabel(label: string): string { + return label.replace(/&/g, '&&'); +} + +/** + * Splits a recent label in name and parent path, supporting both '/' and '\' and workspace suffixes. + * If the location is remote, the remote name is included in the name part. + */ +export function splitRecentLabel(recentLabel: string): { name: string; parentPath: string } { + if (recentLabel.endsWith(']')) { + // label with workspace suffix + const lastIndexOfSquareBracket = recentLabel.lastIndexOf(' [', recentLabel.length - 2); + if (lastIndexOfSquareBracket !== -1) { + const split = splitName(recentLabel.substring(0, lastIndexOfSquareBracket)); + const remoteNameWithSpace = recentLabel.substring(lastIndexOfSquareBracket); + return { name: split.name + remoteNameWithSpace, parentPath: split.parentPath }; + } + } + return splitName(recentLabel); +} + +function splitName(fullPath: string): { name: string; parentPath: string } { + const p = fullPath.indexOf('/') !== -1 ? posix : win32; + const name = p.basename(fullPath); + const parentPath = p.dirname(fullPath); + if (name.length) { + return { name, parentPath }; + } + // only the root segment + return { name: parentPath, parentPath: '' }; +} diff --git a/packages/core/src/lazy.ts b/packages/core/src/lazy.ts new file mode 100644 index 00000000..feda5ab3 --- /dev/null +++ b/packages/core/src/lazy.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export class Lazy { + + private _didRun: boolean = false; + private _value?: T; + private _error: Error | undefined; + + constructor( + private readonly executor: () => T, + ) { } + + /** + * True if the lazy value has been resolved. + */ + get hasValue() { return this._didRun; } + + /** + * Get the wrapped value. + * + * This will force evaluation of the lazy value if it has not been resolved yet. Lazy values are only + * resolved once. `getValue` will re-throw exceptions that are hit while resolving the value + */ + get value(): T { + if (!this._didRun) { + try { + this._value = this.executor(); + } catch (err) { + this._error = err; + } finally { + this._didRun = true; + } + } + if (this._error) { + throw this._error; + } + return this._value!; + } + + /** + * Get the wrapped value without forcing evaluation. + */ + get rawValue(): T | undefined { return this._value; } +} diff --git a/packages/core/src/lifecycle.ts b/packages/core/src/lifecycle.ts new file mode 100644 index 00000000..c2d2942b --- /dev/null +++ b/packages/core/src/lifecycle.ts @@ -0,0 +1,829 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { compareBy, numberComparator } from './arrays.js'; +import { groupBy } from './collections.js'; +import { SetMap } from './map.js'; +import { createSingleCallFunction } from './functional.js'; +import { Iterable } from './iterator.js'; + +// #region Disposable Tracking + +/** + * Enables logging of potentially leaked disposables. + * + * A disposable is considered leaked if it is not disposed or not registered as the child of + * another disposable. This tracking is very simple an only works for classes that either + * extend Disposable or use a DisposableStore. This means there are a lot of false positives. + */ +const TRACK_DISPOSABLES = false; +let disposableTracker: IDisposableTracker | null = null; + +export interface IDisposableTracker { + /** + * Is called on construction of a disposable. + */ + trackDisposable(disposable: IDisposable): void; + + /** + * Is called when a disposable is registered as child of another disposable (e.g. {@link DisposableStore}). + * If parent is `null`, the disposable is removed from its former parent. + */ + setParent(child: IDisposable, parent: IDisposable | null): void; + + /** + * Is called after a disposable is disposed. + */ + markAsDisposed(disposable: IDisposable): void; + + /** + * Indicates that the given object is a singleton which does not need to be disposed. + */ + markAsSingleton(disposable: IDisposable): void; +} + +export class GCBasedDisposableTracker implements IDisposableTracker { + + private readonly _registry = new FinalizationRegistry(heldValue => { + console.warn(`[LEAKED DISPOSABLE] ${heldValue}`); + }); + + trackDisposable(disposable: IDisposable): void { + const stack = new Error('CREATED via:').stack!; + this._registry.register(disposable, stack, disposable); + } + + setParent(child: IDisposable, parent: IDisposable | null): void { + if (parent) { + this._registry.unregister(child); + } else { + this.trackDisposable(child); + } + } + + markAsDisposed(disposable: IDisposable): void { + this._registry.unregister(disposable); + } + + markAsSingleton(disposable: IDisposable): void { + this._registry.unregister(disposable); + } +} + +export interface DisposableInfo { + value: IDisposable; + source: string | null; + parent: IDisposable | null; + isSingleton: boolean; + idx: number; +} + +export class DisposableTracker implements IDisposableTracker { + private static idx = 0; + + private readonly livingDisposables = new Map(); + + private getDisposableData(d: IDisposable): DisposableInfo { + let val = this.livingDisposables.get(d); + if (!val) { + val = { parent: null, source: null, isSingleton: false, value: d, idx: DisposableTracker.idx++ }; + this.livingDisposables.set(d, val); + } + return val; + } + + trackDisposable(d: IDisposable): void { + const data = this.getDisposableData(d); + if (!data.source) { + data.source = + new Error().stack!; + } + } + + setParent(child: IDisposable, parent: IDisposable | null): void { + const data = this.getDisposableData(child); + data.parent = parent; + } + + markAsDisposed(x: IDisposable): void { + this.livingDisposables.delete(x); + } + + markAsSingleton(disposable: IDisposable): void { + this.getDisposableData(disposable).isSingleton = true; + } + + private getRootParent(data: DisposableInfo, cache: Map): DisposableInfo { + const cacheValue = cache.get(data); + if (cacheValue) { + return cacheValue; + } + + const result = data.parent ? this.getRootParent(this.getDisposableData(data.parent), cache) : data; + cache.set(data, result); + return result; + } + + getTrackedDisposables(): IDisposable[] { + const rootParentCache = new Map(); + + const leaking = [...this.livingDisposables.entries()] + .filter(([, v]) => v.source !== null && !this.getRootParent(v, rootParentCache).isSingleton) + .flatMap(([k]) => k); + + return leaking; + } + + computeLeakingDisposables(maxReported = 10, preComputedLeaks?: DisposableInfo[]): { leaks: DisposableInfo[]; details: string } | undefined { + let uncoveredLeakingObjs: DisposableInfo[] | undefined; + if (preComputedLeaks) { + uncoveredLeakingObjs = preComputedLeaks; + } else { + const rootParentCache = new Map(); + + const leakingObjects = [...this.livingDisposables.values()] + .filter((info) => info.source !== null && !this.getRootParent(info, rootParentCache).isSingleton); + + if (leakingObjects.length === 0) { + return; + } + const leakingObjsSet = new Set(leakingObjects.map(o => o.value)); + + // Remove all objects that are a child of other leaking objects. Assumes there are no cycles. + uncoveredLeakingObjs = leakingObjects.filter(l => { + return !(l.parent && leakingObjsSet.has(l.parent)); + }); + + if (uncoveredLeakingObjs.length === 0) { + throw new Error('There are cyclic diposable chains!'); + } + } + + if (!uncoveredLeakingObjs) { + return undefined; + } + + function getStackTracePath(leaking: DisposableInfo): string[] { + function removePrefix(array: string[], linesToRemove: (string | RegExp)[]) { + while (array.length > 0 && linesToRemove.some(regexp => typeof regexp === 'string' ? regexp === array[0] : array[0].match(regexp))) { + array.shift(); + } + } + + const lines = leaking.source!.split('\n').map(p => p.trim().replace('at ', '')).filter(l => l !== ''); + removePrefix(lines, ['Error', /^trackDisposable \(.*\)$/, /^DisposableTracker.trackDisposable \(.*\)$/]); + return lines.reverse(); + } + + const stackTraceStarts = new SetMap(); + for (const leaking of uncoveredLeakingObjs) { + const stackTracePath = getStackTracePath(leaking); + for (let i = 0; i <= stackTracePath.length; i++) { + stackTraceStarts.add(stackTracePath.slice(0, i).join('\n'), leaking); + } + } + + // Put earlier leaks first + uncoveredLeakingObjs.sort(compareBy(l => l.idx, numberComparator)); + + let message = ''; + + let i = 0; + for (const leaking of uncoveredLeakingObjs.slice(0, maxReported)) { + i++; + const stackTracePath = getStackTracePath(leaking); + const stackTraceFormattedLines:any = []; + + for (let i = 0; i < stackTracePath.length; i++) { + let line = stackTracePath[i]; + const starts = stackTraceStarts.get(stackTracePath.slice(0, i + 1).join('\n')); + line = `(shared with ${starts.size}/${uncoveredLeakingObjs.length} leaks) at ${line}`; + + const prevStarts = stackTraceStarts.get(stackTracePath.slice(0, i).join('\n')); + const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v); + delete continuations[stackTracePath[i]]; + for (const [cont, set] of Object.entries(continuations)) { + stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`); + } + + stackTraceFormattedLines.unshift(line); + } + + message += `\n\n\n==================== Leaking disposable ${i}/${uncoveredLeakingObjs.length}: ${leaking.value.constructor.name} ====================\n${stackTraceFormattedLines.join('\n')}\n============================================================\n\n`; + } + + if (uncoveredLeakingObjs.length > maxReported) { + message += `\n\n\n... and ${uncoveredLeakingObjs.length - maxReported} more leaking disposables\n\n`; + } + + return { leaks: uncoveredLeakingObjs, details: message }; + } +} + +export function setDisposableTracker(tracker: IDisposableTracker | null): void { + disposableTracker = tracker; +} + +if (TRACK_DISPOSABLES) { + const __is_disposable_tracked__ = '__is_disposable_tracked__'; + setDisposableTracker(new class implements IDisposableTracker { + trackDisposable(x: IDisposable): void { + const stack = new Error('Potentially leaked disposable').stack!; + setTimeout(() => { + if (!(x as any)[__is_disposable_tracked__]) { + console.log(stack); + } + }, 3000); + } + + setParent(child: IDisposable, parent: IDisposable | null): void { + if (child && child !== Disposable.None) { + try { + (child as any)[__is_disposable_tracked__] = true; + } catch { + // noop + } + } + } + + markAsDisposed(disposable: IDisposable): void { + if (disposable && disposable !== Disposable.None) { + try { + (disposable as any)[__is_disposable_tracked__] = true; + } catch { + // noop + } + } + } + markAsSingleton(disposable: IDisposable): void { } + }); +} + +export function trackDisposable(x: T): T { + disposableTracker?.trackDisposable(x); + return x; +} + +export function markAsDisposed(disposable: IDisposable): void { + disposableTracker?.markAsDisposed(disposable); +} + +function setParentOfDisposable(child: IDisposable, parent: IDisposable | null): void { + disposableTracker?.setParent(child, parent); +} + +function setParentOfDisposables(children: IDisposable[], parent: IDisposable | null): void { + if (!disposableTracker) { + return; + } + for (const child of children) { + disposableTracker.setParent(child, parent); + } +} + +/** + * Indicates that the given object is a singleton which does not need to be disposed. +*/ +export function markAsSingleton(singleton: T): T { + disposableTracker?.markAsSingleton(singleton); + return singleton; +} + +// #endregion + +/** + * An object that performs a cleanup operation when `.dispose()` is called. + * + * Some examples of how disposables are used: + * + * - An event listener that removes itself when `.dispose()` is called. + * - A resource such as a file system watcher that cleans up the resource when `.dispose()` is called. + * - The return value from registering a provider. When `.dispose()` is called, the provider is unregistered. + */ +export interface IDisposable { + dispose(): void; +} + +/** + * Check if `thing` is {@link IDisposable disposable}. + */ +export function isDisposable(thing: E): thing is E & IDisposable { + return typeof thing === 'object' && thing !== null && typeof (thing).dispose === 'function' && (thing).dispose.length === 0; +} + +/** + * Disposes of the value(s) passed in. + */ +export function dispose(disposable: T): T; +export function dispose(disposable: T | undefined): T | undefined; +export function dispose = Iterable>(disposables: A): A; +export function dispose(disposables: Array): Array; +export function dispose(disposables: ReadonlyArray): ReadonlyArray; +export function dispose(arg: T | Iterable | undefined): any { + if (Iterable.is(arg)) { + const errors: any[] = []; + + for (const d of arg) { + if (d) { + try { + d.dispose(); + } catch (e) { + errors.push(e); + } + } + } + + if (errors.length === 1) { + throw errors[0]; + } else if (errors.length > 1) { + throw new AggregateError(errors, 'Encountered errors while disposing of store'); + } + + return Array.isArray(arg) ? [] : arg; + } else if (arg) { + arg.dispose(); + return arg; + } +} + +export function disposeIfDisposable(disposables: Array): Array { + for (const d of disposables) { + if (isDisposable(d)) { + d.dispose(); + } + } + return []; +} + +/** + * Combine multiple disposable values into a single {@link IDisposable}. + */ +export function combinedDisposable(...disposables: IDisposable[]): IDisposable { + const parent = toDisposable(() => dispose(disposables)); + setParentOfDisposables(disposables, parent); + return parent; +} + +/** + * Turn a function that implements dispose into an {@link IDisposable}. + * + * @param fn Clean up function, guaranteed to be called only **once**. + */ +export function toDisposable(fn: () => void): IDisposable { + const self = trackDisposable({ + dispose: createSingleCallFunction(() => { + markAsDisposed(self); + fn(); + }) + }); + return self; +} + +/** + * Manages a collection of disposable values. + * + * This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an + * `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a + * store that has already been disposed of. + */ +export class DisposableStore implements IDisposable { + + static DISABLE_DISPOSED_WARNING = false; + + private readonly _toDispose = new Set(); + private _isDisposed = false; + + constructor() { + trackDisposable(this); + } + + /** + * Dispose of all registered disposables and mark this object as disposed. + * + * Any future disposables added to this object will be disposed of on `add`. + */ + public dispose(): void { + if (this._isDisposed) { + return; + } + + markAsDisposed(this); + this._isDisposed = true; + this.clear(); + } + + /** + * @return `true` if this object has been disposed of. + */ + public get isDisposed(): boolean { + return this._isDisposed; + } + + /** + * Dispose of all registered disposables but do not mark this object as disposed. + */ + public clear(): void { + if (this._toDispose.size === 0) { + return; + } + + try { + dispose(this._toDispose); + } finally { + this._toDispose.clear(); + } + } + + /** + * Add a new {@link IDisposable disposable} to the collection. + */ + public add(o: T): T { + if (!o) { + return o; + } + if ((o as unknown as DisposableStore) === this) { + throw new Error('Cannot register a disposable on itself!'); + } + + setParentOfDisposable(o, this); + if (this._isDisposed) { + if (!DisposableStore.DISABLE_DISPOSED_WARNING) { + console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack); + } + } else { + this._toDispose.add(o); + } + + return o; + } + + /** + * Deletes a disposable from store and disposes of it. This will not throw or warn and proceed to dispose the + * disposable even when the disposable is not part in the store. + */ + public delete(o: T): void { + if (!o) { + return; + } + if ((o as unknown as DisposableStore) === this) { + throw new Error('Cannot dispose a disposable on itself!'); + } + this._toDispose.delete(o); + o.dispose(); + } + + /** + * Deletes the value from the store, but does not dispose it. + */ + public deleteAndLeak(o: T): void { + if (!o) { + return; + } + if (this._toDispose.has(o)) { + this._toDispose.delete(o); + setParentOfDisposable(o, null); + } + } +} + +/** + * Abstract base class for a {@link IDisposable disposable} object. + * + * Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of. + */ +export abstract class Disposable implements IDisposable { + + /** + * A disposable that does nothing when it is disposed of. + * + * TODO: This should not be a static property. + */ + static readonly None = Object.freeze({ dispose() { } }); + + protected readonly _store = new DisposableStore(); + + constructor() { + trackDisposable(this); + setParentOfDisposable(this._store, this); + } + + public dispose(): void { + markAsDisposed(this); + + this._store.dispose(); + } + + /** + * Adds `o` to the collection of disposables managed by this object. + */ + protected _register(o: T): T { + if ((o as unknown as Disposable) === this) { + throw new Error('Cannot register a disposable on itself!'); + } + return this._store.add(o); + } +} + +/** + * Manages the lifecycle of a disposable value that may be changed. + * + * This ensures that when the disposable value is changed, the previously held disposable is disposed of. You can + * also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up. + */ +export class MutableDisposable implements IDisposable { + private _value?: T; + private _isDisposed = false; + + constructor() { + trackDisposable(this); + } + + get value(): T | undefined { + return this._isDisposed ? undefined : this._value; + } + + set value(value: T | undefined) { + if (this._isDisposed || value === this._value) { + return; + } + + this._value?.dispose(); + if (value) { + setParentOfDisposable(value, this); + } + this._value = value; + } + + /** + * Resets the stored value and disposed of the previously stored value. + */ + clear(): void { + this.value = undefined; + } + + dispose(): void { + this._isDisposed = true; + markAsDisposed(this); + this._value?.dispose(); + this._value = undefined; + } + + /** + * Clears the value, but does not dispose it. + * The old value is returned. + */ + clearAndLeak(): T | undefined { + const oldValue = this._value; + this._value = undefined; + if (oldValue) { + setParentOfDisposable(oldValue, null); + } + return oldValue; + } +} + +/** + * Manages the lifecycle of a disposable value that may be changed like {@link MutableDisposable}, but the value must + * exist and cannot be undefined. + */ +export class MandatoryMutableDisposable implements IDisposable { + private readonly _disposable = new MutableDisposable(); + private _isDisposed = false; + + constructor(initialValue: T) { + this._disposable.value = initialValue; + } + + get value(): T { + return this._disposable.value!; + } + + set value(value: T) { + if (this._isDisposed || value === this._disposable.value) { + return; + } + this._disposable.value = value; + } + + dispose() { + this._isDisposed = true; + this._disposable.dispose(); + } +} + +export class RefCountedDisposable { + + private _counter: number = 1; + + constructor( + private readonly _disposable: IDisposable, + ) { } + + acquire() { + this._counter++; + return this; + } + + release() { + if (--this._counter === 0) { + this._disposable.dispose(); + } + return this; + } +} + +/** + * A safe disposable can be `unset` so that a leaked reference (listener) + * can be cut-off. + */ +export class SafeDisposable implements IDisposable { + + dispose: () => void = () => { }; + unset: () => void = () => { }; + isset: () => boolean = () => false; + + constructor() { + trackDisposable(this); + } + + set(fn: Function) { + let callback: Function | undefined = fn; + this.unset = () => callback = undefined; + this.isset = () => callback !== undefined; + this.dispose = () => { + if (callback) { + callback(); + callback = undefined; + markAsDisposed(this); + } + }; + return this; + } +} + +export interface IReference extends IDisposable { + readonly object: T; +} + +export abstract class ReferenceCollection { + + private readonly references: Map = new Map(); + + acquire(key: string, ...args: any[]): IReference { + let reference = this.references.get(key); + + if (!reference) { + reference = { counter: 0, object: this.createReferencedObject(key, ...args) }; + this.references.set(key, reference); + } + + const { object } = reference; + const dispose = createSingleCallFunction(() => { + if (--reference.counter === 0) { + this.destroyReferencedObject(key, reference.object); + this.references.delete(key); + } + }); + + reference.counter++; + + return { object, dispose }; + } + + protected abstract createReferencedObject(key: string, ...args: any[]): T; + protected abstract destroyReferencedObject(key: string, object: T): void; +} + +/** + * Unwraps a reference collection of promised values. Makes sure + * references are disposed whenever promises get rejected. + */ +export class AsyncReferenceCollection { + + constructor(private referenceCollection: ReferenceCollection>) { } + + async acquire(key: string, ...args: any[]): Promise> { + const ref = this.referenceCollection.acquire(key, ...args); + + try { + const object = await ref.object; + + return { + object, + dispose: () => ref.dispose() + }; + } catch (error) { + ref.dispose(); + throw error; + } + } +} + +export class ImmortalReference implements IReference { + constructor(public object: T) { } + dispose(): void { /* noop */ } +} + +export function disposeOnReturn(fn: (store: DisposableStore) => void): void { + const store = new DisposableStore(); + try { + fn(store); + } finally { + store.dispose(); + } +} + +/** + * A map the manages the lifecycle of the values that it stores. + */ +export class DisposableMap implements IDisposable { + + private readonly _store = new Map(); + private _isDisposed = false; + + constructor() { + trackDisposable(this); + } + + /** + * Disposes of all stored values and mark this object as disposed. + * + * Trying to use this object after it has been disposed of is an error. + */ + dispose(): void { + markAsDisposed(this); + this._isDisposed = true; + this.clearAndDisposeAll(); + } + + /** + * Disposes of all stored values and clear the map, but DO NOT mark this object as disposed. + */ + clearAndDisposeAll(): void { + if (!this._store.size) { + return; + } + + try { + dispose(this._store.values()); + } finally { + this._store.clear(); + } + } + + has(key: K): boolean { + return this._store.has(key); + } + + get size(): number { + return this._store.size; + } + + get(key: K): V | undefined { + return this._store.get(key); + } + + set(key: K, value: V, skipDisposeOnOverwrite = false): void { + if (this._isDisposed) { + console.warn(new Error('Trying to add a disposable to a DisposableMap that has already been disposed of. The added object will be leaked!').stack); + } + + if (!skipDisposeOnOverwrite) { + this._store.get(key)?.dispose(); + } + + this._store.set(key, value); + } + + /** + * Delete the value stored for `key` from this map and also dispose of it. + */ + deleteAndDispose(key: K): void { + this._store.get(key)?.dispose(); + this._store.delete(key); + } + + /** + * Delete the value stored for `key` from this map but return it. The caller is + * responsible for disposing of the value. + */ + deleteAndLeak(key: K): V | undefined { + const value = this._store.get(key); + this._store.delete(key); + return value; + } + + keys(): IterableIterator { + return this._store.keys(); + } + + values(): IterableIterator { + return this._store.values(); + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this._store[Symbol.iterator](); + } +} diff --git a/packages/core/src/linkedList.ts b/packages/core/src/linkedList.ts new file mode 100644 index 00000000..735f9d0c --- /dev/null +++ b/packages/core/src/linkedList.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +class Node { + + static readonly Undefined = new Node(undefined); + + element: E; + next: Node; + prev: Node; + + constructor(element: E) { + this.element = element; + this.next = Node.Undefined; + this.prev = Node.Undefined; + } +} + +export class LinkedList { + + private _first: Node = Node.Undefined; + private _last: Node = Node.Undefined; + private _size: number = 0; + + get size(): number { + return this._size; + } + + isEmpty(): boolean { + return this._first === Node.Undefined; + } + + clear(): void { + let node = this._first; + while (node !== Node.Undefined) { + const next = node.next; + node.prev = Node.Undefined; + node.next = Node.Undefined; + node = next; + } + + this._first = Node.Undefined; + this._last = Node.Undefined; + this._size = 0; + } + + unshift(element: E): () => void { + return this._insert(element, false); + } + + push(element: E): () => void { + return this._insert(element, true); + } + + private _insert(element: E, atTheEnd: boolean): () => void { + const newNode = new Node(element); + if (this._first === Node.Undefined) { + this._first = newNode; + this._last = newNode; + + } else if (atTheEnd) { + // push + const oldLast = this._last; + this._last = newNode; + newNode.prev = oldLast; + oldLast.next = newNode; + + } else { + // unshift + const oldFirst = this._first; + this._first = newNode; + newNode.next = oldFirst; + oldFirst.prev = newNode; + } + this._size += 1; + + let didRemove = false; + return () => { + if (!didRemove) { + didRemove = true; + this._remove(newNode); + } + }; + } + + shift(): E | undefined { + if (this._first === Node.Undefined) { + return undefined; + } else { + const res = this._first.element; + this._remove(this._first); + return res; + } + } + + pop(): E | undefined { + if (this._last === Node.Undefined) { + return undefined; + } else { + const res = this._last.element; + this._remove(this._last); + return res; + } + } + + private _remove(node: Node): void { + if (node.prev !== Node.Undefined && node.next !== Node.Undefined) { + // middle + const anchor = node.prev; + anchor.next = node.next; + node.next.prev = anchor; + + } else if (node.prev === Node.Undefined && node.next === Node.Undefined) { + // only node + this._first = Node.Undefined; + this._last = Node.Undefined; + + } else if (node.next === Node.Undefined) { + // last + this._last = this._last.prev!; + this._last.next = Node.Undefined; + + } else if (node.prev === Node.Undefined) { + // first + this._first = this._first.next!; + this._first.prev = Node.Undefined; + } + + // done + this._size -= 1; + } + + *[Symbol.iterator](): Iterator { + let node = this._first; + while (node !== Node.Undefined) { + yield node.element; + node = node.next; + } + } +} diff --git a/packages/core/src/map.ts b/packages/core/src/map.ts new file mode 100644 index 00000000..7fe0246c --- /dev/null +++ b/packages/core/src/map.ts @@ -0,0 +1,952 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from './uri.js'; + +export function getOrSet(map: Map, key: K, value: V): V { + let result = map.get(key); + if (result === undefined) { + result = value; + map.set(key, result); + } + + return result; +} + +export function mapToString(map: Map): string { + const entries: string[] = []; + map.forEach((value, key) => { + entries.push(`${key} => ${value}`); + }); + + return `Map(${map.size}) {${entries.join(', ')}}`; +} + +export function setToString(set: Set): string { + const entries: K[] = []; + set.forEach(value => { + entries.push(value); + }); + + return `Set(${set.size}) {${entries.join(', ')}}`; +} + +interface ResourceMapKeyFn { + (resource: URI): string; +} + +class ResourceMapEntry { + constructor(readonly uri: URI, readonly value: T) { } +} + +function isEntries(arg: ResourceMap | ResourceMapKeyFn | readonly (readonly [URI, T])[] | undefined): arg is readonly (readonly [URI, T])[] { + return Array.isArray(arg); +} + +export class ResourceMap implements Map { + + private static readonly defaultToKey = (resource: URI) => resource.toString(); + + readonly [Symbol.toStringTag] = 'ResourceMap'; + + private readonly map: Map>; + private readonly toKey: ResourceMapKeyFn; + + /** + * + * @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util + */ + constructor(toKey?: ResourceMapKeyFn); + + /** + * + * @param other Another resource which this maps is created from + * @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util + */ + constructor(other?: ResourceMap, toKey?: ResourceMapKeyFn); + + /** + * + * @param other Another resource which this maps is created from + * @param toKey Custom uri identity function, e.g use an existing `IExtUri#getComparison`-util + */ + constructor(entries?: readonly (readonly [URI, T])[], toKey?: ResourceMapKeyFn); + + constructor(arg?: ResourceMap | ResourceMapKeyFn | readonly (readonly [URI, T])[], toKey?: ResourceMapKeyFn) { + if (arg instanceof ResourceMap) { + this.map = new Map(arg.map); + this.toKey = toKey ?? ResourceMap.defaultToKey; + } else if (isEntries(arg)) { + this.map = new Map(); + this.toKey = toKey ?? ResourceMap.defaultToKey; + + for (const [resource, value] of arg) { + this.set(resource, value); + } + } else { + this.map = new Map(); + this.toKey = arg ?? ResourceMap.defaultToKey; + } + } + + set(resource: URI, value: T): this { + this.map.set(this.toKey(resource), new ResourceMapEntry(resource, value)); + return this; + } + + get(resource: URI): T | undefined { + return this.map.get(this.toKey(resource))?.value; + } + + has(resource: URI): boolean { + return this.map.has(this.toKey(resource)); + } + + get size(): number { + return this.map.size; + } + + clear(): void { + this.map.clear(); + } + + delete(resource: URI): boolean { + return this.map.delete(this.toKey(resource)); + } + + forEach(clb: (value: T, key: URI, map: Map) => void, thisArg?: any): void { + if (typeof thisArg !== 'undefined') { + clb = clb.bind(thisArg); + } + for (const [_, entry] of this.map) { + clb(entry.value, entry.uri, this); + } + } + + *values(): IterableIterator { + for (const entry of this.map.values()) { + yield entry.value; + } + } + + *keys(): IterableIterator { + for (const entry of this.map.values()) { + yield entry.uri; + } + } + + *entries(): IterableIterator<[URI, T]> { + for (const entry of this.map.values()) { + yield [entry.uri, entry.value]; + } + } + + *[Symbol.iterator](): IterableIterator<[URI, T]> { + for (const [, entry] of this.map) { + yield [entry.uri, entry.value]; + } + } +} + +export class ResourceSet implements Set { + + readonly [Symbol.toStringTag]: string = 'ResourceSet'; + + private readonly _map: ResourceMap; + + constructor(toKey?: ResourceMapKeyFn); + constructor(entries: readonly URI[], toKey?: ResourceMapKeyFn); + constructor(entriesOrKey?: readonly URI[] | ResourceMapKeyFn, toKey?: ResourceMapKeyFn) { + if (!entriesOrKey || typeof entriesOrKey === 'function') { + this._map = new ResourceMap(entriesOrKey as ResourceMapKeyFn); + } else { + this._map = new ResourceMap(toKey); + entriesOrKey.forEach(this.add, this); + } + } + + + get size(): number { + return this._map.size; + } + + add(value: URI): this { + this._map.set(value, value); + return this; + } + + clear(): void { + this._map.clear(); + } + + delete(value: URI): boolean { + return this._map.delete(value); + } + + forEach(callbackfn: (value: URI, value2: URI, set: Set) => void, thisArg?: any): void { + this._map.forEach((_value, key) => callbackfn.call(thisArg, key, key, this)); + } + + has(value: URI): boolean { + return this._map.has(value); + } + + entries(): IterableIterator<[URI, URI]> { + return this._map.entries(); + } + + keys(): IterableIterator { + return this._map.keys(); + } + + values(): IterableIterator { + return this._map.keys(); + } + + [Symbol.iterator](): IterableIterator { + return this.keys(); + } +} + + +interface Item { + previous: Item | undefined; + next: Item | undefined; + key: K; + value: V; +} + +export const enum Touch { + None = 0, + AsOld = 1, + AsNew = 2 +} + +export class LinkedMap implements Map { + + readonly [Symbol.toStringTag] = 'LinkedMap'; + + private _map: Map>; + private _head: Item | undefined; + private _tail: Item | undefined; + private _size: number; + + private _state: number; + + constructor() { + this._map = new Map>(); + this._head = undefined; + this._tail = undefined; + this._size = 0; + this._state = 0; + } + + clear(): void { + this._map.clear(); + this._head = undefined; + this._tail = undefined; + this._size = 0; + this._state++; + } + + isEmpty(): boolean { + return !this._head && !this._tail; + } + + get size(): number { + return this._size; + } + + get first(): V | undefined { + return this._head?.value; + } + + get last(): V | undefined { + return this._tail?.value; + } + + has(key: K): boolean { + return this._map.has(key); + } + + get(key: K, touch: Touch = Touch.None): V | undefined { + const item = this._map.get(key); + if (!item) { + return undefined; + } + if (touch !== Touch.None) { + this.touch(item, touch); + } + return item.value; + } + + set(key: K, value: V, touch: Touch = Touch.None): this { + let item = this._map.get(key); + if (item) { + item.value = value; + if (touch !== Touch.None) { + this.touch(item, touch); + } + } else { + item = { key, value, next: undefined, previous: undefined }; + switch (touch) { + case Touch.None: + this.addItemLast(item); + break; + case Touch.AsOld: + this.addItemFirst(item); + break; + case Touch.AsNew: + this.addItemLast(item); + break; + default: + this.addItemLast(item); + break; + } + this._map.set(key, item); + this._size++; + } + return this; + } + + delete(key: K): boolean { + return !!this.remove(key); + } + + remove(key: K): V | undefined { + const item = this._map.get(key); + if (!item) { + return undefined; + } + this._map.delete(key); + this.removeItem(item); + this._size--; + return item.value; + } + + shift(): V | undefined { + if (!this._head && !this._tail) { + return undefined; + } + if (!this._head || !this._tail) { + throw new Error('Invalid list'); + } + const item = this._head; + this._map.delete(item.key); + this.removeItem(item); + this._size--; + return item.value; + } + + forEach(callbackfn: (value: V, key: K, map: LinkedMap) => void, thisArg?: any): void { + const state = this._state; + let current = this._head; + while (current) { + if (thisArg) { + callbackfn.bind(thisArg)(current.value, current.key, this); + } else { + callbackfn(current.value, current.key, this); + } + if (this._state !== state) { + throw new Error(`LinkedMap got modified during iteration.`); + } + current = current.next; + } + } + + keys(): IterableIterator { + const map = this; + const state = this._state; + let current = this._head; + const iterator: IterableIterator = { + [Symbol.iterator]() { + return iterator; + }, + next(): IteratorResult { + if (map._state !== state) { + throw new Error(`LinkedMap got modified during iteration.`); + } + if (current) { + const result = { value: current.key, done: false }; + current = current.next; + return result; + } else { + return { value: undefined, done: true }; + } + } + }; + return iterator; + } + + values(): IterableIterator { + const map = this; + const state = this._state; + let current = this._head; + const iterator: IterableIterator = { + [Symbol.iterator]() { + return iterator; + }, + next(): IteratorResult { + if (map._state !== state) { + throw new Error(`LinkedMap got modified during iteration.`); + } + if (current) { + const result = { value: current.value, done: false }; + current = current.next; + return result; + } else { + return { value: undefined, done: true }; + } + } + }; + return iterator; + } + + entries(): IterableIterator<[K, V]> { + const map = this; + const state = this._state; + let current = this._head; + const iterator: IterableIterator<[K, V]> = { + [Symbol.iterator]() { + return iterator; + }, + next(): IteratorResult<[K, V]> { + if (map._state !== state) { + throw new Error(`LinkedMap got modified during iteration.`); + } + if (current) { + const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false }; + current = current.next; + return result; + } else { + return { value: undefined, done: true }; + } + } + }; + return iterator; + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this.entries(); + } + + protected trimOld(newSize: number) { + if (newSize >= this.size) { + return; + } + if (newSize === 0) { + this.clear(); + return; + } + let current = this._head; + let currentSize = this.size; + while (current && currentSize > newSize) { + this._map.delete(current.key); + current = current.next; + currentSize--; + } + this._head = current; + this._size = currentSize; + if (current) { + current.previous = undefined; + } + this._state++; + } + + protected trimNew(newSize: number) { + if (newSize >= this.size) { + return; + } + if (newSize === 0) { + this.clear(); + return; + } + let current = this._tail; + let currentSize = this.size; + while (current && currentSize > newSize) { + this._map.delete(current.key); + current = current.previous; + currentSize--; + } + this._tail = current; + this._size = currentSize; + if (current) { + current.next = undefined; + } + this._state++; + } + + private addItemFirst(item: Item): void { + // First time Insert + if (!this._head && !this._tail) { + this._tail = item; + } else if (!this._head) { + throw new Error('Invalid list'); + } else { + item.next = this._head; + this._head.previous = item; + } + this._head = item; + this._state++; + } + + private addItemLast(item: Item): void { + // First time Insert + if (!this._head && !this._tail) { + this._head = item; + } else if (!this._tail) { + throw new Error('Invalid list'); + } else { + item.previous = this._tail; + this._tail.next = item; + } + this._tail = item; + this._state++; + } + + private removeItem(item: Item): void { + if (item === this._head && item === this._tail) { + this._head = undefined; + this._tail = undefined; + } + else if (item === this._head) { + // This can only happen if size === 1 which is handled + // by the case above. + if (!item.next) { + throw new Error('Invalid list'); + } + item.next.previous = undefined; + this._head = item.next; + } + else if (item === this._tail) { + // This can only happen if size === 1 which is handled + // by the case above. + if (!item.previous) { + throw new Error('Invalid list'); + } + item.previous.next = undefined; + this._tail = item.previous; + } + else { + const next = item.next; + const previous = item.previous; + if (!next || !previous) { + throw new Error('Invalid list'); + } + next.previous = previous; + previous.next = next; + } + item.next = undefined; + item.previous = undefined; + this._state++; + } + + private touch(item: Item, touch: Touch): void { + if (!this._head || !this._tail) { + throw new Error('Invalid list'); + } + if ((touch !== Touch.AsOld && touch !== Touch.AsNew)) { + return; + } + + if (touch === Touch.AsOld) { + if (item === this._head) { + return; + } + + const next = item.next; + const previous = item.previous; + + // Unlink the item + if (item === this._tail) { + // previous must be defined since item was not head but is tail + // So there are more than on item in the map + previous!.next = undefined; + this._tail = previous; + } + else { + // Both next and previous are not undefined since item was neither head nor tail. + next!.previous = previous; + previous!.next = next; + } + + // Insert the node at head + item.previous = undefined; + item.next = this._head; + this._head.previous = item; + this._head = item; + this._state++; + } else if (touch === Touch.AsNew) { + if (item === this._tail) { + return; + } + + const next = item.next; + const previous = item.previous; + + // Unlink the item. + if (item === this._head) { + // next must be defined since item was not tail but is head + // So there are more than on item in the map + next!.previous = undefined; + this._head = next; + } else { + // Both next and previous are not undefined since item was neither head nor tail. + next!.previous = previous; + previous!.next = next; + } + item.next = undefined; + item.previous = this._tail; + this._tail.next = item; + this._tail = item; + this._state++; + } + } + + toJSON(): [K, V][] { + const data: [K, V][] = []; + + this.forEach((value, key) => { + data.push([key, value]); + }); + + return data; + } + + fromJSON(data: [K, V][]): void { + this.clear(); + + for (const [key, value] of data) { + this.set(key, value); + } + } +} + +abstract class Cache extends LinkedMap { + + protected _limit: number; + protected _ratio: number; + + constructor(limit: number, ratio: number = 1) { + super(); + this._limit = limit; + this._ratio = Math.min(Math.max(0, ratio), 1); + } + + get limit(): number { + return this._limit; + } + + set limit(limit: number) { + this._limit = limit; + this.checkTrim(); + } + + get ratio(): number { + return this._ratio; + } + + set ratio(ratio: number) { + this._ratio = Math.min(Math.max(0, ratio), 1); + this.checkTrim(); + } + + override get(key: K, touch: Touch = Touch.AsNew): V | undefined { + return super.get(key, touch); + } + + peek(key: K): V | undefined { + return super.get(key, Touch.None); + } + + override set(key: K, value: V): this { + super.set(key, value, Touch.AsNew); + return this; + } + + protected checkTrim() { + if (this.size > this._limit) { + this.trim(Math.round(this._limit * this._ratio)); + } + } + + protected abstract trim(newSize: number): void; +} + +export class LRUCache extends Cache { + + constructor(limit: number, ratio: number = 1) { + super(limit, ratio); + } + + protected override trim(newSize: number) { + this.trimOld(newSize); + } + + override set(key: K, value: V): this { + super.set(key, value); + this.checkTrim(); + return this; + } +} + +export class MRUCache extends Cache { + + constructor(limit: number, ratio: number = 1) { + super(limit, ratio); + } + + protected override trim(newSize: number) { + this.trimNew(newSize); + } + + override set(key: K, value: V): this { + if (this._limit <= this.size && !this.has(key)) { + this.trim(Math.round(this._limit * this._ratio) - 1); + } + + super.set(key, value); + return this; + } +} + +export class CounterSet { + + private map = new Map(); + + add(value: T): CounterSet { + this.map.set(value, (this.map.get(value) || 0) + 1); + return this; + } + + delete(value: T): boolean { + let counter = this.map.get(value) || 0; + + if (counter === 0) { + return false; + } + + counter--; + + if (counter === 0) { + this.map.delete(value); + } else { + this.map.set(value, counter); + } + + return true; + } + + has(value: T): boolean { + return this.map.has(value); + } +} + +/** + * A map that allows access both by keys and values. + * **NOTE**: values need to be unique. + */ +export class BidirectionalMap { + + private readonly _m1 = new Map(); + private readonly _m2 = new Map(); + + constructor(entries?: readonly (readonly [K, V])[]) { + if (entries) { + for (const [key, value] of entries) { + this.set(key, value); + } + } + } + + clear(): void { + this._m1.clear(); + this._m2.clear(); + } + + set(key: K, value: V): void { + this._m1.set(key, value); + this._m2.set(value, key); + } + + get(key: K): V | undefined { + return this._m1.get(key); + } + + getKey(value: V): K | undefined { + return this._m2.get(value); + } + + delete(key: K): boolean { + const value = this._m1.get(key); + if (value === undefined) { + return false; + } + this._m1.delete(key); + this._m2.delete(value); + return true; + } + + forEach(callbackfn: (value: V, key: K, map: BidirectionalMap) => void, thisArg?: any): void { + this._m1.forEach((value, key) => { + callbackfn.call(thisArg, value, key, this); + }); + } + + keys(): IterableIterator { + return this._m1.keys(); + } + + values(): IterableIterator { + return this._m1.values(); + } +} + +export class SetMap { + + private map = new Map>(); + + add(key: K, value: V): void { + let values = this.map.get(key); + + if (!values) { + values = new Set(); + this.map.set(key, values); + } + + values.add(value); + } + + delete(key: K, value: V): void { + const values = this.map.get(key); + + if (!values) { + return; + } + + values.delete(value); + + if (values.size === 0) { + this.map.delete(key); + } + } + + forEach(key: K, fn: (value: V) => void): void { + const values = this.map.get(key); + + if (!values) { + return; + } + + values.forEach(fn); + } + + get(key: K): ReadonlySet { + const values = this.map.get(key); + if (!values) { + return new Set(); + } + return values; + } +} + +export function mapsStrictEqualIgnoreOrder(a: Map, b: Map): boolean { + if (a === b) { + return true; + } + + if (a.size !== b.size) { + return false; + } + + for (const [key, value] of a) { + if (!b.has(key) || b.get(key) !== value) { + return false; + } + } + + for (const [key] of b) { + if (!a.has(key)) { + return false; + } + } + + return true; +} + +/** + * A map that is addressable with an arbitrary number of keys. This is useful in high performance + * scenarios where creating a composite key whenever the data is accessed is too expensive. For + * example for a very hot function, constructing a string like `first-second-third` for every call + * will cause a significant hit to performance. + */ +export class NKeyMap { + private _data: Map = new Map(); + + /** + * Sets a value on the map. Note that unlike a standard `Map`, the first argument is the value. + * This is because the spread operator is used for the keys and must be last.. + * @param value The value to set. + * @param keys The keys for the value. + */ + public set(value: TValue, ...keys: [...TKeys]): void { + let currentMap = this._data; + for (let i = 0; i < keys.length - 1; i++) { + if (!currentMap.has(keys[i])) { + currentMap.set(keys[i], new Map()); + } + currentMap = currentMap.get(keys[i]); + } + currentMap.set(keys[keys.length - 1], value); + } + + public get(...keys: [...TKeys]): TValue | undefined { + let currentMap = this._data; + for (let i = 0; i < keys.length - 1; i++) { + if (!currentMap.has(keys[i])) { + return undefined; + } + currentMap = currentMap.get(keys[i]); + } + return currentMap.get(keys[keys.length - 1]); + } + + public clear(): void { + this._data.clear(); + } + + public *values(): IterableIterator { + function* iterate(map: Map): IterableIterator { + for (const value of map.values()) { + if (value instanceof Map) { + yield* iterate(value); + } else { + yield value; + } + } + } + yield* iterate(this._data); + } + + /** + * Get a textual representation of the map for debugging purposes. + */ + public toString(): string { + const printMap = (map: Map, depth: number): string => { + let result = ''; + for (const [key, value] of map) { + result += `${' '.repeat(depth)}${key}: `; + if (value instanceof Map) { + result += '\n' + printMap(value, depth + 1); + } else { + result += `${value}\n`; + } + } + return result; + }; + + return printMap(this._data, 0); + } +} diff --git a/packages/core/src/marshallingIds.ts b/packages/core/src/marshallingIds.ts new file mode 100644 index 00000000..c37a8885 --- /dev/null +++ b/packages/core/src/marshallingIds.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const enum MarshalledId { + Uri = 1, + Regexp, + ScmResource, + ScmResourceGroup, + ScmProvider, + CommentController, + CommentThread, + CommentThreadInstance, + CommentThreadReply, + CommentNode, + CommentThreadNode, + TimelineActionContext, + NotebookCellActionContext, + NotebookActionContext, + TerminalContext, + TestItemContext, + Date, + TestMessageMenuArgs, + ChatViewContext, + LanguageModelToolResult, + LanguageModelTextPart, + LanguageModelPromptTsxPart, +} diff --git a/packages/core/src/mime.ts b/packages/core/src/mime.ts new file mode 100644 index 00000000..8aff6daf --- /dev/null +++ b/packages/core/src/mime.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { extname } from './path.js'; + +export const Mimes = Object.freeze({ + text: 'text/plain', + binary: 'application/octet-stream', + unknown: 'application/unknown', + markdown: 'text/markdown', + latex: 'text/latex', + uriList: 'text/uri-list', +}); + +interface MapExtToMediaMimes { + [index: string]: string; +} + +const mapExtToTextMimes: MapExtToMediaMimes = { + '.css': 'text/css', + '.csv': 'text/csv', + '.htm': 'text/html', + '.html': 'text/html', + '.ics': 'text/calendar', + '.js': 'text/javascript', + '.mjs': 'text/javascript', + '.txt': 'text/plain', + '.xml': 'text/xml' +}; + +// Known media mimes that we can handle +const mapExtToMediaMimes: MapExtToMediaMimes = { + '.aac': 'audio/x-aac', + '.avi': 'video/x-msvideo', + '.bmp': 'image/bmp', + '.flv': 'video/x-flv', + '.gif': 'image/gif', + '.ico': 'image/x-icon', + '.jpe': 'image/jpg', + '.jpeg': 'image/jpg', + '.jpg': 'image/jpg', + '.m1v': 'video/mpeg', + '.m2a': 'audio/mpeg', + '.m2v': 'video/mpeg', + '.m3a': 'audio/mpeg', + '.mid': 'audio/midi', + '.midi': 'audio/midi', + '.mk3d': 'video/x-matroska', + '.mks': 'video/x-matroska', + '.mkv': 'video/x-matroska', + '.mov': 'video/quicktime', + '.movie': 'video/x-sgi-movie', + '.mp2': 'audio/mpeg', + '.mp2a': 'audio/mpeg', + '.mp3': 'audio/mpeg', + '.mp4': 'video/mp4', + '.mp4a': 'audio/mp4', + '.mp4v': 'video/mp4', + '.mpe': 'video/mpeg', + '.mpeg': 'video/mpeg', + '.mpg': 'video/mpeg', + '.mpg4': 'video/mp4', + '.mpga': 'audio/mpeg', + '.oga': 'audio/ogg', + '.ogg': 'audio/ogg', + '.opus': 'audio/opus', + '.ogv': 'video/ogg', + '.png': 'image/png', + '.psd': 'image/vnd.adobe.photoshop', + '.qt': 'video/quicktime', + '.spx': 'audio/ogg', + '.svg': 'image/svg+xml', + '.tga': 'image/x-tga', + '.tif': 'image/tiff', + '.tiff': 'image/tiff', + '.wav': 'audio/x-wav', + '.webm': 'video/webm', + '.webp': 'image/webp', + '.wma': 'audio/x-ms-wma', + '.wmv': 'video/x-ms-wmv', + '.woff': 'application/font-woff', +}; + +export function getMediaOrTextMime(path: string): string | undefined { + const ext = extname(path); + const textMime = mapExtToTextMimes[ext.toLowerCase()]; + if (textMime !== undefined) { + return textMime; + } else { + return getMediaMime(path); + } +} + +export function getMediaMime(path: string): string | undefined { + const ext = extname(path); + return mapExtToMediaMimes[ext.toLowerCase()]; +} + +export function getExtensionForMimeType(mimeType: string): string | undefined { + for (const extension in mapExtToMediaMimes) { + if (mapExtToMediaMimes[extension] === mimeType) { + return extension; + } + } + + return undefined; +} + +const _simplePattern = /^(.+)\/(.+?)(;.+)?$/; + +export function normalizeMimeType(mimeType: string): string; +export function normalizeMimeType(mimeType: string, strict: true): string | undefined; +export function normalizeMimeType(mimeType: string, strict?: true): string | undefined { + + const match = _simplePattern.exec(mimeType); + if (!match) { + return strict + ? undefined + : mimeType; + } + // https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 + // media and subtype must ALWAYS be lowercase, parameter not + return `${match[1].toLowerCase()}/${match[2].toLowerCase()}${match[3] ?? ''}`; +} diff --git a/packages/core/src/network.ts b/packages/core/src/network.ts new file mode 100644 index 00000000..b985273d --- /dev/null +++ b/packages/core/src/network.ts @@ -0,0 +1,408 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as errors from './errors.js'; +import * as platform from './platform.js'; +import { equalsIgnoreCase, startsWithIgnoreCase } from './strings.js'; +import { URI } from './uri.js'; +import * as paths from './path.js'; + +export namespace Schemas { + + /** + * A schema that is used for models that exist in memory + * only and that have no correspondence on a server or such. + */ + export const inMemory = 'inmemory'; + + /** + * A schema that is used for setting files + */ + export const vscode = 'vscode'; + + /** + * A schema that is used for internal private files + */ + export const internal = 'private'; + + /** + * A walk-through document. + */ + export const walkThrough = 'walkThrough'; + + /** + * An embedded code snippet. + */ + export const walkThroughSnippet = 'walkThroughSnippet'; + + export const http = 'http'; + + export const https = 'https'; + + export const file = 'file'; + + export const mailto = 'mailto'; + + export const untitled = 'untitled'; + + export const data = 'data'; + + export const command = 'command'; + + export const vscodeRemote = 'vscode-remote'; + + export const vscodeRemoteResource = 'vscode-remote-resource'; + + export const vscodeManagedRemoteResource = 'vscode-managed-remote-resource'; + + export const vscodeUserData = 'vscode-userdata'; + + export const vscodeCustomEditor = 'vscode-custom-editor'; + + export const vscodeNotebookCell = 'vscode-notebook-cell'; + export const vscodeNotebookCellMetadata = 'vscode-notebook-cell-metadata'; + export const vscodeNotebookCellMetadataDiff = 'vscode-notebook-cell-metadata-diff'; + export const vscodeNotebookCellOutput = 'vscode-notebook-cell-output'; + export const vscodeNotebookCellOutputDiff = 'vscode-notebook-cell-output-diff'; + export const vscodeNotebookMetadata = 'vscode-notebook-metadata'; + export const vscodeInteractiveInput = 'vscode-interactive-input'; + + export const vscodeSettings = 'vscode-settings'; + + export const vscodeWorkspaceTrust = 'vscode-workspace-trust'; + + export const vscodeTerminal = 'vscode-terminal'; + + /** Scheme used for code blocks in chat. */ + export const vscodeChatCodeBlock = 'vscode-chat-code-block'; + + /** Scheme used for LHS of code compare (aka diff) blocks in chat. */ + export const vscodeChatCodeCompareBlock = 'vscode-chat-code-compare-block'; + + /** Scheme used for the chat input editor. */ + export const vscodeChatSesssion = 'vscode-chat-editor'; + + /** + * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors) + */ + export const webviewPanel = 'webview-panel'; + + /** + * Scheme used for loading the wrapper html and script in webviews. + */ + export const vscodeWebview = 'vscode-webview'; + + /** + * Scheme used for extension pages + */ + export const extension = 'extension'; + + /** + * Scheme used as a replacement of `file` scheme to load + * files with our custom protocol handler (desktop only). + */ + export const vscodeFileResource = 'vscode-file'; + + /** + * Scheme used for temporary resources + */ + export const tmp = 'tmp'; + + /** + * Scheme used vs live share + */ + export const vsls = 'vsls'; + + /** + * Scheme used for the Source Control commit input's text document + */ + export const vscodeSourceControl = 'vscode-scm'; + + /** + * Scheme used for input box for creating comments. + */ + export const commentsInput = 'comment'; + + /** + * Scheme used for special rendering of settings in the release notes + */ + export const codeSetting = 'code-setting'; + + /** + * Scheme used for output panel resources + */ + export const outputChannel = 'output'; + + /** + * Scheme used for the accessible view + */ + export const accessibleView = 'accessible-view'; +} + +export function matchesScheme(target: URI | string, scheme: string): boolean { + if (URI.isUri(target)) { + return equalsIgnoreCase(target.scheme, scheme); + } else { + return startsWithIgnoreCase(target, scheme + ':'); + } +} + +export function matchesSomeScheme(target: URI | string, ...schemes: string[]): boolean { + return schemes.some(scheme => matchesScheme(target, scheme)); +} + +export const connectionTokenCookieName = 'vscode-tkn'; +export const connectionTokenQueryName = 'tkn'; + +class RemoteAuthoritiesImpl { + private readonly _hosts: { [authority: string]: string | undefined } = Object.create(null); + private readonly _ports: { [authority: string]: number | undefined } = Object.create(null); + private readonly _connectionTokens: { [authority: string]: string | undefined } = Object.create(null); + private _preferredWebSchema: 'http' | 'https' = 'http'; + private _delegate: ((uri: URI) => URI) | null = null; + private _serverRootPath: string = '/'; + + setPreferredWebSchema(schema: 'http' | 'https') { + this._preferredWebSchema = schema; + } + + setDelegate(delegate: (uri: URI) => URI): void { + this._delegate = delegate; + } + + setServerRootPath(product: { quality?: string; commit?: string }, serverBasePath: string | undefined): void { + this._serverRootPath = paths.posix.join(serverBasePath ?? '/', getServerProductSegment(product)); + } + + getServerRootPath(): string { + return this._serverRootPath; + } + + private get _remoteResourcesPath(): string { + return paths.posix.join(this._serverRootPath, Schemas.vscodeRemoteResource); + } + + set(authority: string, host: string, port: number): void { + this._hosts[authority] = host; + this._ports[authority] = port; + } + + setConnectionToken(authority: string, connectionToken: string): void { + this._connectionTokens[authority] = connectionToken; + } + + getPreferredWebSchema(): 'http' | 'https' { + return this._preferredWebSchema; + } + + rewrite(uri: URI): URI { + if (this._delegate) { + try { + return this._delegate(uri); + } catch (err) { + errors.onUnexpectedError(err); + return uri; + } + } + const authority = uri.authority; + let host = this._hosts[authority]; + if (host && host.indexOf(':') !== -1 && host.indexOf('[') === -1) { + host = `[${host}]`; + } + const port = this._ports[authority]; + const connectionToken = this._connectionTokens[authority]; + let query = `path=${encodeURIComponent(uri.path)}`; + if (typeof connectionToken === 'string') { + query += `&${connectionTokenQueryName}=${encodeURIComponent(connectionToken)}`; + } + return URI.from({ + scheme: platform.isWeb ? this._preferredWebSchema : Schemas.vscodeRemoteResource, + authority: `${host}:${port}`, + path: this._remoteResourcesPath, + query + }); + } +} + +export const RemoteAuthorities = new RemoteAuthoritiesImpl(); + +export function getServerProductSegment(product: { quality?: string; commit?: string }) { + return `${product.quality ?? 'oss'}-${product.commit ?? 'dev'}`; +} + +/** + * A string pointing to a path inside the app. It should not begin with ./ or ../ + */ +export type AppResourcePath = ( + `a${string}` | `b${string}` | `c${string}` | `d${string}` | `e${string}` | `f${string}` + | `g${string}` | `h${string}` | `i${string}` | `j${string}` | `k${string}` | `l${string}` + | `m${string}` | `n${string}` | `o${string}` | `p${string}` | `q${string}` | `r${string}` + | `s${string}` | `t${string}` | `u${string}` | `v${string}` | `w${string}` | `x${string}` + | `y${string}` | `z${string}` +); + +export const builtinExtensionsPath: AppResourcePath = 'vs/../../extensions'; +export const nodeModulesPath: AppResourcePath = 'vs/../../node_modules'; +export const nodeModulesAsarPath: AppResourcePath = 'vs/../../node_modules.asar'; +export const nodeModulesAsarUnpackedPath: AppResourcePath = 'vs/../../node_modules.asar.unpacked'; + +export const VSCODE_AUTHORITY = 'vscode-app'; + +class FileAccessImpl { + + private static readonly FALLBACK_AUTHORITY = VSCODE_AUTHORITY; + + /** + * Returns a URI to use in contexts where the browser is responsible + * for loading (e.g. fetch()) or when used within the DOM. + * + * **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context. + */ + asBrowserUri(resourcePath: AppResourcePath | ''): URI { + const uri = this.toUri(resourcePath); + return this.uriToBrowserUri(uri); + } + + /** + * Returns a URI to use in contexts where the browser is responsible + * for loading (e.g. fetch()) or when used within the DOM. + * + * **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context. + */ + uriToBrowserUri(uri: URI): URI { + // Handle remote URIs via `RemoteAuthorities` + if (uri.scheme === Schemas.vscodeRemote) { + return RemoteAuthorities.rewrite(uri); + } + + // Convert to `vscode-file` resource.. + if ( + // ...only ever for `file` resources + uri.scheme === Schemas.file && + ( + // ...and we run in native environments + platform.isNative || + // ...or web worker extensions on desktop + (platform.webWorkerOrigin === `${Schemas.vscodeFileResource}://${FileAccessImpl.FALLBACK_AUTHORITY}`) + ) + ) { + return uri.with({ + scheme: Schemas.vscodeFileResource, + // We need to provide an authority here so that it can serve + // as origin for network and loading matters in chromium. + // If the URI is not coming with an authority already, we + // add our own + authority: uri.authority || FileAccessImpl.FALLBACK_AUTHORITY, + query: null, + fragment: null + }); + } + + return uri; + } + + /** + * Returns the `file` URI to use in contexts where node.js + * is responsible for loading. + */ + asFileUri(resourcePath: AppResourcePath | ''): URI { + const uri = this.toUri(resourcePath); + return this.uriToFileUri(uri); + } + + /** + * Returns the `file` URI to use in contexts where node.js + * is responsible for loading. + */ + uriToFileUri(uri: URI): URI { + // Only convert the URI if it is `vscode-file:` scheme + if (uri.scheme === Schemas.vscodeFileResource) { + return uri.with({ + scheme: Schemas.file, + // Only preserve the `authority` if it is different from + // our fallback authority. This ensures we properly preserve + // Windows UNC paths that come with their own authority. + authority: uri.authority !== FileAccessImpl.FALLBACK_AUTHORITY ? uri.authority : null, + query: null, + fragment: null + }); + } + + return uri; + } + + private toUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI { + if (URI.isUri(uriOrModule)) { + return uriOrModule; + } + + if (globalThis._VSCODE_FILE_ROOT) { + const rootUriOrPath = globalThis._VSCODE_FILE_ROOT; + + // File URL (with scheme) + if (/^\w[\w\d+.-]*:\/\//.test(rootUriOrPath)) { + return URI.joinPath(URI.parse(rootUriOrPath, true), uriOrModule); + } + + // File Path (no scheme) + const modulePath = paths.join(rootUriOrPath, uriOrModule); + return URI.file(modulePath); + } + + return URI.parse(moduleIdToUrl!.toUrl(uriOrModule)); + } +} + +export const FileAccess = new FileAccessImpl(); + + +export namespace COI { + + const coiHeaders = new Map<'3' | '2' | '1' | string, Record>([ + ['1', { 'Cross-Origin-Opener-Policy': 'same-origin' }], + ['2', { 'Cross-Origin-Embedder-Policy': 'require-corp' }], + ['3', { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' }], + ]); + + export const CoopAndCoep = Object.freeze(coiHeaders.get('3')); + + const coiSearchParamName = 'vscode-coi'; + + /** + * Extract desired headers from `vscode-coi` invocation + */ + export function getHeadersFromQuery(url: string | URI | URL): Record | undefined { + let params: URLSearchParams | undefined; + if (typeof url === 'string') { + params = new URL(url).searchParams; + } else if (url instanceof URL) { + params = url.searchParams; + } else if (URI.isUri(url)) { + params = new URL(url.toString(true)).searchParams; + } + const value = params?.get(coiSearchParamName); + if (!value) { + return undefined; + } + return coiHeaders.get(value); + } + + /** + * Add the `vscode-coi` query attribute based on wanting `COOP` and `COEP`. Will be a noop when `crossOriginIsolated` + * isn't enabled the current context + */ + export function addSearchParam(urlOrSearch: URLSearchParams | Record, coop: boolean, coep: boolean): void { + if (!(globalThis).crossOriginIsolated) { + // depends on the current context being COI + return; + } + const value = coop && coep ? '3' : coep ? '2' : '1'; + if (urlOrSearch instanceof URLSearchParams) { + urlOrSearch.set(coiSearchParamName, value); + } else { + (>urlOrSearch)[coiSearchParamName] = value; + } + } +} diff --git a/packages/core/src/nls.messages.ts b/packages/core/src/nls.messages.ts new file mode 100644 index 00000000..dd9fffed --- /dev/null +++ b/packages/core/src/nls.messages.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* + * This module exists so that the AMD build of the monaco editor can replace this with an async loader plugin. + * If you add new functions to this module make sure that they are also provided in the AMD build of the monaco editor. + * + * TODO@esm remove me once we no longer ship an AMD build. + */ + +export function getNLSMessages(): string[] { + return globalThis._VSCODE_NLS_MESSAGES; +} + +export function getNLSLanguage(): string | undefined { + return globalThis._VSCODE_NLS_LANGUAGE; +} diff --git a/packages/core/src/nls.ts b/packages/core/src/nls.ts new file mode 100644 index 00000000..eca18561 --- /dev/null +++ b/packages/core/src/nls.ts @@ -0,0 +1,240 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// eslint-disable-next-line local/code-import-patterns +import { getNLSLanguage, getNLSMessages } from './nls.messages.js'; +// eslint-disable-next-line local/code-import-patterns +export { getNLSLanguage, getNLSMessages } from './nls.messages.js'; + +const isPseudo = getNLSLanguage() === 'pseudo' || (typeof document !== 'undefined' && document.location && typeof document.location.hash === 'string' && document.location.hash.indexOf('pseudo=true') >= 0); + +export interface ILocalizeInfo { + key: string; + comment: string[]; +} + +export interface ILocalizedString { + original: string; + value: string; +} + +function _format(message: string, args: (string | number | boolean | undefined | null)[]): string { + let result: string; + + if (args.length === 0) { + result = message; + } else { + result = message.replace(/\{(\d+)\}/g, (match, rest) => { + const index = rest[0]; + const arg = args[index]; + let result = match; + if (typeof arg === 'string') { + result = arg; + } else if (typeof arg === 'number' || typeof arg === 'boolean' || arg === void 0 || arg === null) { + result = String(arg); + } + return result; + }); + } + + if (isPseudo) { + // FF3B and FF3D is the Unicode zenkaku representation for [ and ] + result = '\uFF3B' + result.replace(/[aouei]/g, '$&$&') + '\uFF3D'; + } + + return result; +} + +/** + * Marks a string to be localized. Returns the localized string. + * + * @param info The {@linkcode ILocalizeInfo} which describes the id and comments associated with the localized string. + * @param message The string to localize + * @param args The arguments to the string + * + * @note `message` can contain `{n}` notation where it is replaced by the nth value in `...args` + * @example `localize({ key: 'sayHello', comment: ['Welcomes user'] }, 'hello {0}', name)` + * + * @returns string The localized string. + */ +export function localize(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string; + +/** + * Marks a string to be localized. Returns the localized string. + * + * @param key The key to use for localizing the string + * @param message The string to localize + * @param args The arguments to the string + * + * @note `message` can contain `{n}` notation where it is replaced by the nth value in `...args` + * @example For example, `localize('sayHello', 'hello {0}', name)` + * + * @returns string The localized string. + */ +export function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string; + +/** + * @skipMangle + */ +export function localize(data: ILocalizeInfo | string /* | number when built */, message: string /* | null when built */, ...args: (string | number | boolean | undefined | null)[]): string { + if (typeof data === 'number') { + return _format(lookupMessage(data, message), args); + } + return _format(message, args); +} + +/** + * Only used when built: Looks up the message in the global NLS table. + * This table is being made available as a global through bootstrapping + * depending on the target context. + */ +function lookupMessage(index: number, fallback: string | null): string { + const message = getNLSMessages()?.[index]; + if (typeof message !== 'string') { + if (typeof fallback === 'string') { + return fallback; + } + throw new Error(`!!! NLS MISSING: ${index} !!!`); + } + return message; +} + +/** + * Marks a string to be localized. Returns an {@linkcode ILocalizedString} + * which contains the localized string and the original string. + * + * @param info The {@linkcode ILocalizeInfo} which describes the id and comments associated with the localized string. + * @param message The string to localize + * @param args The arguments to the string + * + * @note `message` can contain `{n}` notation where it is replaced by the nth value in `...args` + * @example `localize2({ key: 'sayHello', comment: ['Welcomes user'] }, 'hello {0}', name)` + * + * @returns ILocalizedString which contains the localized string and the original string. + */ +export function localize2(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString; + +/** + * Marks a string to be localized. Returns an {@linkcode ILocalizedString} + * which contains the localized string and the original string. + * + * @param key The key to use for localizing the string + * @param message The string to localize + * @param args The arguments to the string + * + * @note `message` can contain `{n}` notation where it is replaced by the nth value in `...args` + * @example `localize('sayHello', 'hello {0}', name)` + * + * @returns ILocalizedString which contains the localized string and the original string. + */ +export function localize2(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString; + +/** + * @skipMangle + */ +export function localize2(data: ILocalizeInfo | string /* | number when built */, originalMessage: string, ...args: (string | number | boolean | undefined | null)[]): ILocalizedString { + let message: string; + if (typeof data === 'number') { + message = lookupMessage(data, originalMessage); + } else { + message = originalMessage; + } + + const value = _format(message, args); + + return { + value, + original: originalMessage === message ? value : _format(originalMessage, args) + }; +} + +export interface INLSLanguagePackConfiguration { + + /** + * The path to the translations config file that contains pointers to + * all message bundles for `main` and extensions. + */ + readonly translationsConfigFile: string; + + /** + * The path to the file containing the translations for this language + * pack as flat string array. + */ + readonly messagesFile: string; + + /** + * The path to the file that can be used to signal a corrupt language + * pack, for example when reading the `messagesFile` fails. This will + * instruct the application to re-create the cache on next startup. + */ + readonly corruptMarkerFile: string; +} + +export interface INLSConfiguration { + + /** + * Locale as defined in `argv.json` or `app.getLocale()`. + */ + readonly userLocale: string; + + /** + * Locale as defined by the OS (e.g. `app.getPreferredSystemLanguages()`). + */ + readonly osLocale: string; + + /** + * The actual language of the UI that ends up being used considering `userLocale` + * and `osLocale`. + */ + readonly resolvedLanguage: string; + + /** + * Defined if a language pack is used that is not the + * default english language pack. This requires a language + * pack to be installed as extension. + */ + readonly languagePack?: INLSLanguagePackConfiguration; + + /** + * The path to the file containing the default english messages + * as flat string array. The file is only present in built + * versions of the application. + */ + readonly defaultMessagesFile: string; + + /** + * Below properties are deprecated and only there to continue support + * for `vscode-nls` module that depends on them. + * Refs https://github.com/microsoft/vscode-nls/blob/main/src/node/main.ts#L36-L46 + */ + /** @deprecated */ + readonly locale: string; + /** @deprecated */ + readonly availableLanguages: Record; + /** @deprecated */ + readonly _languagePackSupport?: boolean; + /** @deprecated */ + readonly _languagePackId?: string; + /** @deprecated */ + readonly _translationsConfigFile?: string; + /** @deprecated */ + readonly _cacheRoot?: string; + /** @deprecated */ + readonly _resolvedLanguagePackCoreLocation?: string; + /** @deprecated */ + readonly _corruptedFile?: string; +} + +export interface ILanguagePack { + readonly hash: string; + readonly label: string | undefined; + readonly extensions: { + readonly extensionIdentifier: { readonly id: string; readonly uuid?: string }; + readonly version: string; + }[]; + readonly translations: Record; +} + +export type ILanguagePacks = Record; diff --git a/packages/core/src/objects.ts b/packages/core/src/objects.ts new file mode 100644 index 00000000..a4adb0df --- /dev/null +++ b/packages/core/src/objects.ts @@ -0,0 +1,275 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isTypedArray, isObject, isUndefinedOrNull } from './primitives.js'; + +export function deepClone(obj: T): T { + if (!obj || typeof obj !== 'object') { + return obj; + } + if (obj instanceof RegExp) { + return obj; + } + const result: any = Array.isArray(obj) ? [] : {}; + Object.entries(obj).forEach(([key, value]) => { + result[key] = value && typeof value === 'object' ? deepClone(value) : value; + }); + return result; +} + +export function deepFreeze(obj: T): T { + if (!obj || typeof obj !== 'object') { + return obj; + } + const stack: any[] = [obj]; + while (stack.length > 0) { + const obj = stack.shift(); + Object.freeze(obj); + for (const key in obj) { + if (_hasOwnProperty.call(obj, key)) { + const prop = obj[key]; + if (typeof prop === 'object' && !Object.isFrozen(prop) && !isTypedArray(prop)) { + stack.push(prop); + } + } + } + } + return obj; +} + +const _hasOwnProperty = Object.prototype.hasOwnProperty; + + +export function cloneAndChange(obj: any, changer: (orig: any) => any): any { + return _cloneAndChange(obj, changer, new Set()); +} + +function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set): any { + if (isUndefinedOrNull(obj)) { + return obj; + } + + const changed = changer(obj); + if (typeof changed !== 'undefined') { + return changed; + } + + if (Array.isArray(obj)) { + const r1: any[] = []; + for (const e of obj) { + r1.push(_cloneAndChange(e, changer, seen)); + } + return r1; + } + + if (isObject(obj)) { + if (seen.has(obj)) { + throw new Error('Cannot clone recursive data-structure'); + } + seen.add(obj); + const r2 = {}; + for (const i2 in obj) { + if (_hasOwnProperty.call(obj, i2)) { + (r2 as any)[i2] = _cloneAndChange(obj[i2], changer, seen); + } + } + seen.delete(obj); + return r2; + } + + return obj; +} + +/** + * Copies all properties of source into destination. The optional parameter "overwrite" allows to control + * if existing properties on the destination should be overwritten or not. Defaults to true (overwrite). + */ +export function mixin(destination: any, source: any, overwrite: boolean = true): any { + if (!isObject(destination)) { + return source; + } + + if (isObject(source)) { + Object.keys(source).forEach(key => { + if (key in destination) { + if (overwrite) { + if (isObject(destination[key]) && isObject(source[key])) { + mixin(destination[key], source[key], overwrite); + } else { + destination[key] = source[key]; + } + } + } else { + destination[key] = source[key]; + } + }); + } + return destination; +} + +export function equals(one: any, other: any): boolean { + if (one === other) { + return true; + } + if (one === null || one === undefined || other === null || other === undefined) { + return false; + } + if (typeof one !== typeof other) { + return false; + } + if (typeof one !== 'object') { + return false; + } + if ((Array.isArray(one)) !== (Array.isArray(other))) { + return false; + } + + let i: number; + let key: string; + + if (Array.isArray(one)) { + if (one.length !== other.length) { + return false; + } + for (i = 0; i < one.length; i++) { + if (!equals(one[i], other[i])) { + return false; + } + } + } else { + const oneKeys: string[] = []; + + for (key in one) { + oneKeys.push(key); + } + oneKeys.sort(); + const otherKeys: string[] = []; + for (key in other) { + otherKeys.push(key); + } + otherKeys.sort(); + if (!equals(oneKeys, otherKeys)) { + return false; + } + for (i = 0; i < oneKeys.length; i++) { + if (!equals(one[oneKeys[i]], other[oneKeys[i]])) { + return false; + } + } + } + return true; +} + +/** + * Calls `JSON.Stringify` with a replacer to break apart any circular references. + * This prevents `JSON`.stringify` from throwing the exception + * "Uncaught TypeError: Converting circular structure to JSON" + */ +export function safeStringify(obj: any): string { + const seen = new Set(); + return JSON.stringify(obj, (key, value) => { + if (isObject(value) || Array.isArray(value)) { + if (seen.has(value)) { + return '[Circular]'; + } else { + seen.add(value); + } + } + if (typeof value === 'bigint') { + return `[BigInt ${value.toString()}]`; + } + return value; + }); +} + +type obj = { [key: string]: any }; +/** + * Returns an object that has keys for each value that is different in the base object. Keys + * that do not exist in the target but in the base object are not considered. + * + * Note: This is not a deep-diffing method, so the values are strictly taken into the resulting + * object if they differ. + * + * @param base the object to diff against + * @param obj the object to use for diffing + */ +export function distinct(base: obj, target: obj): obj { + const result = Object.create(null); + + if (!base || !target) { + return result; + } + + const targetKeys = Object.keys(target); + targetKeys.forEach(k => { + const baseValue = base[k]; + const targetValue = target[k]; + + if (!equals(baseValue, targetValue)) { + result[k] = targetValue; + } + }); + + return result; +} + +export function getCaseInsensitive(target: obj, key: string): unknown { + const lowercaseKey = key.toLowerCase(); + const equivalentKey = Object.keys(target).find(k => k.toLowerCase() === lowercaseKey); + return equivalentKey ? target[equivalentKey] : target[key]; +} + +export function filter(obj: obj, predicate: (key: string, value: any) => boolean): obj { + const result = Object.create(null); + for (const [key, value] of Object.entries(obj)) { + if (predicate(key, value)) { + result[key] = value; + } + } + return result; +} + +export function getAllPropertyNames(obj: object): string[] { + let res: string[] = []; + while (Object.prototype !== obj) { + res = res.concat(Object.getOwnPropertyNames(obj)); + obj = Object.getPrototypeOf(obj); + } + return res; +} + +export function getAllMethodNames(obj: object): string[] { + const methods: string[] = []; + for (const prop of getAllPropertyNames(obj)) { + if (typeof (obj as any)[prop] === 'function') { + methods.push(prop); + } + } + return methods; +} + +export function createProxyObject(methodNames: string[], invoke: (method: string, args: unknown[]) => unknown): T { + const createProxyMethod = (method: string): () => unknown => { + return function () { + const args = Array.prototype.slice.call(arguments, 0); + return invoke(method, args); + }; + }; + + // eslint-disable-next-line local/code-no-dangerous-type-assertions + const result = {} as T; + for (const methodName of methodNames) { + (result)[methodName] = createProxyMethod(methodName); + } + return result; +} + +export function mapValues(obj: T, fn: (value: T[keyof T], key: string) => R): { [K in keyof T]: R } { + const result: { [key: string]: R } = {}; + for (const [key, value] of Object.entries(obj)) { + result[key] = fn(value, key); + } + return result as { [K in keyof T]: R }; +} diff --git a/packages/core/src/observable.ts b/packages/core/src/observable.ts new file mode 100644 index 00000000..0c831ace --- /dev/null +++ b/packages/core/src/observable.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// This is a facade for the observable implementation. Only import from here! + +export * from './observableInternal/index.js'; diff --git a/packages/core/src/observableInternal/api.ts b/packages/core/src/observableInternal/api.ts new file mode 100644 index 00000000..73b86e31 --- /dev/null +++ b/packages/core/src/observableInternal/api.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ISettableObservable, ObservableValue } from './base.js'; +import { DebugNameData, IDebugNameData } from './debugName.js'; +import { EqualityComparer, strictEquals } from './commonFacade/deps.js'; +import { LazyObservableValue } from './lazyObservableValue.js'; + +export function observableValueOpts( + options: IDebugNameData & { + equalsFn?: EqualityComparer; + lazy?: boolean; + }, + initialValue: T +): ISettableObservable { + if (options.lazy) { + return new LazyObservableValue( + new DebugNameData(options.owner, options.debugName, undefined), + initialValue, + options.equalsFn ?? strictEquals, + ); + } + return new ObservableValue( + new DebugNameData(options.owner, options.debugName, undefined), + initialValue, + options.equalsFn ?? strictEquals, + ); +} diff --git a/packages/core/src/observableInternal/autorun.ts b/packages/core/src/observableInternal/autorun.ts new file mode 100644 index 00000000..b77fcc36 --- /dev/null +++ b/packages/core/src/observableInternal/autorun.ts @@ -0,0 +1,327 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChangeContext, IObservable, IObservableWithChange, IObserver, IReader } from './base.js'; +import { DebugNameData, IDebugNameData } from './debugName.js'; +import { assertFn, BugIndicatingError, DisposableStore, IDisposable, markAsDisposed, onBugIndicatingError, toDisposable, trackDisposable } from './commonFacade/deps.js'; +import { getLogger } from './logging.js'; + +/** + * Runs immediately and whenever a transaction ends and an observed observable changed. + * {@link fn} should start with a JS Doc using `@description` to name the autorun. + */ +export function autorun(fn: (reader: IReader) => void): IDisposable { + return new AutorunObserver( + new DebugNameData(undefined, undefined, fn), + fn, + undefined, + undefined + ); +} + +/** + * Runs immediately and whenever a transaction ends and an observed observable changed. + * {@link fn} should start with a JS Doc using `@description` to name the autorun. + */ +export function autorunOpts(options: IDebugNameData & {}, fn: (reader: IReader) => void): IDisposable { + return new AutorunObserver( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), + fn, + undefined, + undefined + ); +} + +/** + * Runs immediately and whenever a transaction ends and an observed observable changed. + * {@link fn} should start with a JS Doc using `@description` to name the autorun. + * + * Use `createEmptyChangeSummary` to create a "change summary" that can collect the changes. + * Use `handleChange` to add a reported change to the change summary. + * The run function is given the last change summary. + * The change summary is discarded after the run function was called. + * + * @see autorun + */ +export function autorunHandleChanges( + options: IDebugNameData & { + createEmptyChangeSummary?: () => TChangeSummary; + handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; + }, + fn: (reader: IReader, changeSummary: TChangeSummary) => void +): IDisposable { + return new AutorunObserver( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? fn), + fn, + options.createEmptyChangeSummary, + options.handleChange + ); +} + +/** + * @see autorunHandleChanges (but with a disposable store that is cleared before the next run or on dispose) + */ +export function autorunWithStoreHandleChanges( + options: IDebugNameData & { + createEmptyChangeSummary?: () => TChangeSummary; + handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; + }, + fn: (reader: IReader, changeSummary: TChangeSummary, store: DisposableStore) => void +): IDisposable { + const store = new DisposableStore(); + const disposable = autorunHandleChanges( + { + owner: options.owner, + debugName: options.debugName, + debugReferenceFn: options.debugReferenceFn ?? fn, + createEmptyChangeSummary: options.createEmptyChangeSummary, + handleChange: options.handleChange, + }, + (reader, changeSummary) => { + store.clear(); + fn(reader, changeSummary, store); + } + ); + return toDisposable(() => { + disposable.dispose(); + store.dispose(); + }); +} + +/** + * @see autorun (but with a disposable store that is cleared before the next run or on dispose) + */ +export function autorunWithStore(fn: (reader: IReader, store: DisposableStore) => void): IDisposable { + const store = new DisposableStore(); + const disposable = autorunOpts( + { + owner: undefined, + debugName: undefined, + debugReferenceFn: fn, + }, + reader => { + store.clear(); + fn(reader, store); + } + ); + return toDisposable(() => { + disposable.dispose(); + store.dispose(); + }); +} + +export function autorunDelta( + observable: IObservable, + handler: (args: { lastValue: T | undefined; newValue: T }) => void +): IDisposable { + let _lastValue: T | undefined; + return autorunOpts({ debugReferenceFn: handler }, (reader) => { + const newValue = observable.read(reader); + const lastValue = _lastValue; + _lastValue = newValue; + handler({ lastValue, newValue }); + }); +} + +export function autorunIterableDelta( + getValue: (reader: IReader) => Iterable, + handler: (args: { addedValues: T[]; removedValues: T[] }) => void, + getUniqueIdentifier: (value: T) => unknown = v => v, +) { + const lastValues = new Map(); + return autorunOpts({ debugReferenceFn: getValue }, (reader) => { + const newValues = new Map(); + const removedValues = new Map(lastValues); + for (const value of getValue(reader)) { + const id = getUniqueIdentifier(value); + if (lastValues.has(id)) { + removedValues.delete(id); + } else { + newValues.set(id, value); + lastValues.set(id, value); + } + } + for (const id of removedValues.keys()) { + lastValues.delete(id); + } + + if (newValues.size || removedValues.size) { + handler({ addedValues: [...newValues.values()], removedValues: [...removedValues.values()] }); + } + }); +} + + + +const enum AutorunState { + /** + * A dependency could have changed. + * We need to explicitly ask them if at least one dependency changed. + */ + dependenciesMightHaveChanged = 1, + + /** + * A dependency changed and we need to recompute. + */ + stale = 2, + upToDate = 3, +} + +export class AutorunObserver implements IObserver, IReader, IDisposable { + private state = AutorunState.stale; + private updateCount = 0; + private disposed = false; + private dependencies = new Set>(); + private dependenciesToBeRemoved = new Set>(); + private changeSummary: TChangeSummary | undefined; + + public get debugName(): string { + return this._debugNameData.getDebugName(this) ?? '(anonymous)'; + } + + constructor( + public readonly _debugNameData: DebugNameData, + public readonly _runFn: (reader: IReader, changeSummary: TChangeSummary) => void, + private readonly createChangeSummary: (() => TChangeSummary) | undefined, + private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, + ) { + this.changeSummary = this.createChangeSummary?.(); + getLogger()?.handleAutorunCreated(this); + this._runIfNeeded(); + + trackDisposable(this); + } + + public dispose(): void { + this.disposed = true; + for (const o of this.dependencies) { + o.removeObserver(this); + } + this.dependencies.clear(); + + markAsDisposed(this); + } + + private _runIfNeeded() { + if (this.state === AutorunState.upToDate) { + return; + } + + const emptySet = this.dependenciesToBeRemoved; + this.dependenciesToBeRemoved = this.dependencies; + this.dependencies = emptySet; + + this.state = AutorunState.upToDate; + + const isDisposed = this.disposed; + try { + if (!isDisposed) { + getLogger()?.handleAutorunTriggered(this); + const changeSummary = this.changeSummary!; + try { + this.changeSummary = this.createChangeSummary?.(); + this._isReaderValid = true; + this._runFn(this, changeSummary); + } catch (e) { + onBugIndicatingError(e); + } finally { + this._isReaderValid = false; + } + } + } finally { + if (!isDisposed) { + getLogger()?.handleAutorunFinished(this); + } + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.dependenciesToBeRemoved) { + o.removeObserver(this); + } + this.dependenciesToBeRemoved.clear(); + } + } + + public toString(): string { + return `Autorun<${this.debugName}>`; + } + + // IObserver implementation + public beginUpdate(): void { + if (this.state === AutorunState.upToDate) { + this.state = AutorunState.dependenciesMightHaveChanged; + } + this.updateCount++; + } + + public endUpdate(): void { + try { + if (this.updateCount === 1) { + do { + if (this.state === AutorunState.dependenciesMightHaveChanged) { + this.state = AutorunState.upToDate; + for (const d of this.dependencies) { + d.reportChanges(); + if (this.state as AutorunState === AutorunState.stale) { + // The other dependencies will refresh on demand + break; + } + } + } + + this._runIfNeeded(); + } while (this.state !== AutorunState.upToDate); + } + } finally { + this.updateCount--; + } + + assertFn(() => this.updateCount >= 0); + } + + public handlePossibleChange(observable: IObservable): void { + if (this.state === AutorunState.upToDate && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + this.state = AutorunState.dependenciesMightHaveChanged; + } + } + + public handleChange(observable: IObservableWithChange, change: TChange): void { + if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + try { + const shouldReact = this._handleChange ? this._handleChange({ + changedObservable: observable, + change, + didChange: (o): this is any => o === observable as any, + }, this.changeSummary!) : true; + if (shouldReact) { + this.state = AutorunState.stale; + } + } catch (e) { + onBugIndicatingError(e); + } + } + } + + // IReader implementation + private _isReaderValid = false; + + public readObservable(observable: IObservable): T { + if (!this._isReaderValid) { throw new BugIndicatingError('The reader object cannot be used outside its compute function!'); } + + // In case the run action disposes the autorun + if (this.disposed) { + return observable.get(); + } + + observable.addObserver(this); + const value = observable.get(); + this.dependencies.add(observable); + this.dependenciesToBeRemoved.delete(observable); + return value; + } +} + +export namespace autorun { + export const Observer = AutorunObserver; +} diff --git a/packages/core/src/observableInternal/base.ts b/packages/core/src/observableInternal/base.ts new file mode 100644 index 00000000..1bf6a23d --- /dev/null +++ b/packages/core/src/observableInternal/base.ts @@ -0,0 +1,522 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DebugNameData, DebugOwner, getFunctionName } from './debugName.js'; +import { DisposableStore, EqualityComparer, IDisposable, strictEquals } from './commonFacade/deps.js'; +import type { derivedOpts } from './derived.js'; +import { getLogger, logObservable } from './logging.js'; +import { keepObserved, recomputeInitiallyAndOnChange } from './utils.js'; + +/** + * Represents an observable value. + * + * @template T The type of the values the observable can hold. + */ +export interface IObservable extends IObservableWithChange { } + +/** + * Represents an observable value. + * + * @template T The type of the values the observable can hold. + * @template TChange The type used to describe value changes + * (usually `void` and only used in advanced scenarios). + * While observers can miss temporary values of an observable, + * they will receive all change values (as long as they are subscribed)! + */ +export interface IObservableWithChange { + /** + * Returns the current value. + * + * Calls {@link IObserver.handleChange} if the observable notices that the value changed. + * Must not be called from {@link IObserver.handleChange}! + */ + get(): T; + + /** + * Forces the observable to check for changes and report them. + * + * Has the same effect as calling {@link IObservable.get}, but does not force the observable + * to actually construct the value, e.g. if change deltas are used. + * Calls {@link IObserver.handleChange} if the observable notices that the value changed. + * Must not be called from {@link IObserver.handleChange}! + */ + reportChanges(): void; + + /** + * Adds the observer to the set of subscribed observers. + * This method is idempotent. + */ + addObserver(observer: IObserver): void; + + /** + * Removes the observer from the set of subscribed observers. + * This method is idempotent. + */ + removeObserver(observer: IObserver): void; + + /** + * Reads the current value and subscribes the reader to this observable. + * + * Calls {@link IReader.readObservable} if a reader is given, otherwise {@link IObservable.get} + * (see {@link ConvenientObservable.read} for the implementation). + */ + read(reader: IReader | undefined): T; + + /** + * Creates a derived observable that depends on this observable. + * Use the reader to read other observables + * (see {@link ConvenientObservable.map} for the implementation). + */ + map(fn: (value: T, reader: IReader) => TNew): IObservable; + map(owner: object, fn: (value: T, reader: IReader) => TNew): IObservable; + + flatten(this: IObservable>): IObservable; + + /** + * ONLY FOR DEBUGGING! + * Logs computations of this derived. + */ + log(): IObservableWithChange; + + /** + * Makes sure this value is computed eagerly. + */ + recomputeInitiallyAndOnChange(store: DisposableStore, handleValue?: (value: T) => void): IObservable; + + /** + * Makes sure this value is cached. + */ + keepObserved(store: DisposableStore): IObservable; + + /** + * A human-readable name for debugging purposes. + */ + readonly debugName: string; + + /** + * This property captures the type of the change object. Do not use it at runtime! + */ + readonly TChange: TChange; +} + +export interface IReader { + /** + * Reads the value of an observable and subscribes to it. + */ + readObservable(observable: IObservableWithChange): T; +} + +/** + * Represents an observer that can be subscribed to an observable. + * + * If an observer is subscribed to an observable and that observable didn't signal + * a change through one of the observer methods, the observer can assume that the + * observable didn't change. + * If an observable reported a possible change, {@link IObservable.reportChanges} forces + * the observable to report an actual change if there was one. + */ +export interface IObserver { + /** + * Signals that the given observable might have changed and a transaction potentially modifying that observable started. + * Before the given observable can call this method again, is must call {@link IObserver.endUpdate}. + * + * Implementations must not get/read the value of other observables, as they might not have received this event yet! + * The method {@link IObservable.reportChanges} can be used to force the observable to report the changes. + */ + beginUpdate(observable: IObservable): void; + + /** + * Signals that the transaction that potentially modified the given observable ended. + * This is a good place to react to (potential) changes. + */ + endUpdate(observable: IObservable): void; + + /** + * Signals that the given observable might have changed. + * The method {@link IObservable.reportChanges} can be used to force the observable to report the changes. + * + * Implementations must not get/read the value of other observables, as they might not have received this event yet! + * The change should be processed lazily or in {@link IObserver.endUpdate}. + */ + handlePossibleChange(observable: IObservable): void; + + /** + * Signals that the given {@link observable} changed. + * + * Implementations must not get/read the value of other observables, as they might not have received this event yet! + * The change should be processed lazily or in {@link IObserver.endUpdate}. + * + * @param change Indicates how or why the value changed. + */ + handleChange(observable: IObservableWithChange, change: TChange): void; +} + +export interface ISettable { + /** + * Sets the value of the observable. + * Use a transaction to batch multiple changes (with a transaction, observers only react at the end of the transaction). + * + * @param transaction When given, value changes are handled on demand or when the transaction ends. + * @param change Describes how or why the value changed. + */ + set(value: T, transaction: ITransaction | undefined, change: TChange): void; +} + +export interface ITransaction { + /** + * Calls {@link Observer.beginUpdate} immediately + * and {@link Observer.endUpdate} when the transaction ends. + */ + updateObserver(observer: IObserver, observable: IObservableWithChange): void; +} + +let _recomputeInitiallyAndOnChange: typeof recomputeInitiallyAndOnChange; +export function _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange: typeof _recomputeInitiallyAndOnChange) { + _recomputeInitiallyAndOnChange = recomputeInitiallyAndOnChange; +} + +let _keepObserved: typeof keepObserved; +export function _setKeepObserved(keepObserved: typeof _keepObserved) { + _keepObserved = keepObserved; +} + + +let _derived: typeof derivedOpts; +/** + * @internal + * This is to allow splitting files. +*/ +export function _setDerivedOpts(derived: typeof _derived) { + _derived = derived; +} + +export abstract class ConvenientObservable implements IObservableWithChange { + get TChange(): TChange { return null!; } + + public abstract get(): T; + + public reportChanges(): void { + this.get(); + } + + public abstract addObserver(observer: IObserver): void; + public abstract removeObserver(observer: IObserver): void; + + /** @sealed */ + public read(reader: IReader | undefined): T { + if (reader) { + return reader.readObservable(this); + } else { + return this.get(); + } + } + + /** @sealed */ + public map(fn: (value: T, reader: IReader) => TNew): IObservable; + public map(owner: DebugOwner, fn: (value: T, reader: IReader) => TNew): IObservable; + public map(fnOrOwner: DebugOwner | ((value: T, reader: IReader) => TNew), fnOrUndefined?: (value: T, reader: IReader) => TNew): IObservable { + const owner = fnOrUndefined === undefined ? undefined : fnOrOwner as DebugOwner; + const fn = fnOrUndefined === undefined ? fnOrOwner as (value: T, reader: IReader) => TNew : fnOrUndefined; + + return _derived( + { + owner, + debugName: () => { + const name = getFunctionName(fn); + if (name !== undefined) { + return name; + } + + // regexp to match `x => x.y` or `x => x?.y` where x and y can be arbitrary identifiers (uses backref): + const regexp = /^\s*\(?\s*([a-zA-Z_$][a-zA-Z_$0-9]*)\s*\)?\s*=>\s*\1(?:\??)\.([a-zA-Z_$][a-zA-Z_$0-9]*)\s*$/; + const match = regexp.exec(fn.toString()); + if (match) { + return `${this.debugName}.${match[2]}`; + } + if (!owner) { + return `${this.debugName} (mapped)`; + } + return undefined; + }, + debugReferenceFn: fn, + }, + (reader) => fn(this.read(reader), reader), + ); + } + + public log(): IObservableWithChange { + logObservable(this); + return this; + } + + /** + * @sealed + * Converts an observable of an observable value into a direct observable of the value. + */ + public flatten(this: IObservable>): IObservable { + return _derived( + { + owner: undefined, + debugName: () => `${this.debugName} (flattened)`, + }, + (reader) => this.read(reader).read(reader), + ); + } + + public recomputeInitiallyAndOnChange(store: DisposableStore, handleValue?: (value: T) => void): IObservable { + store.add(_recomputeInitiallyAndOnChange!(this, handleValue)); + return this; + } + + /** + * Ensures that this observable is observed. This keeps the cache alive. + * However, in case of deriveds, it does not force eager evaluation (only when the value is read/get). + * Use `recomputeInitiallyAndOnChange` for eager evaluation. + */ + public keepObserved(store: DisposableStore): IObservable { + store.add(_keepObserved!(this)); + return this; + } + + public abstract get debugName(): string; + + protected get debugValue() { + return this.get(); + } +} + +export abstract class BaseObservable extends ConvenientObservable { + protected readonly observers = new Set(); + + public addObserver(observer: IObserver): void { + const len = this.observers.size; + this.observers.add(observer); + if (len === 0) { + this.onFirstObserverAdded(); + } + } + + public removeObserver(observer: IObserver): void { + const deleted = this.observers.delete(observer); + if (deleted && this.observers.size === 0) { + this.onLastObserverRemoved(); + } + } + + protected onFirstObserverAdded(): void { } + protected onLastObserverRemoved(): void { } +} + +/** + * Starts a transaction in which many observables can be changed at once. + * {@link fn} should start with a JS Doc using `@description` to give the transaction a debug name. + * Reaction run on demand or when the transaction ends. + */ + +export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => string): void { + const tx = new TransactionImpl(fn, getDebugName); + try { + fn(tx); + } finally { + tx.finish(); + } +} + +let _globalTransaction: ITransaction | undefined = undefined; + +export function globalTransaction(fn: (tx: ITransaction) => void) { + if (_globalTransaction) { + fn(_globalTransaction); + } else { + const tx = new TransactionImpl(fn, undefined); + _globalTransaction = tx; + try { + fn(tx); + } finally { + tx.finish(); // During finish, more actions might be added to the transaction. + // Which is why we only clear the global transaction after finish. + _globalTransaction = undefined; + } + } +} + +export async function asyncTransaction(fn: (tx: ITransaction) => Promise, getDebugName?: () => string): Promise { + const tx = new TransactionImpl(fn, getDebugName); + try { + await fn(tx); + } finally { + tx.finish(); + } +} + +/** + * Allows to chain transactions. + */ +export function subtransaction(tx: ITransaction | undefined, fn: (tx: ITransaction) => void, getDebugName?: () => string): void { + if (!tx) { + transaction(fn, getDebugName); + } else { + fn(tx); + } +} + +export class TransactionImpl implements ITransaction { + private updatingObservers: { observer: IObserver; observable: IObservable }[] | null = []; + + constructor(public readonly _fn: Function, private readonly _getDebugName?: () => string) { + getLogger()?.handleBeginTransaction(this); + } + + public getDebugName(): string | undefined { + if (this._getDebugName) { + return this._getDebugName(); + } + return getFunctionName(this._fn); + } + + public updateObserver(observer: IObserver, observable: IObservable): void { + // When this gets called while finish is active, they will still get considered + this.updatingObservers!.push({ observer, observable }); + observer.beginUpdate(observable); + } + + public finish(): void { + const updatingObservers = this.updatingObservers!; + for (let i = 0; i < updatingObservers.length; i++) { + const { observer, observable } = updatingObservers[i]; + observer.endUpdate(observable); + } + // Prevent anyone from updating observers from now on. + this.updatingObservers = null; + getLogger()?.handleEndTransaction(); + } +} + +/** + * A settable observable. + */ +export interface ISettableObservable extends IObservableWithChange, ISettable { +} + +/** + * Creates an observable value. + * Observers get informed when the value changes. + * @template TChange An arbitrary type to describe how or why the value changed. Defaults to `void`. + * Observers will receive every single change value. + */ +export function observableValue(name: string, initialValue: T): ISettableObservable; +export function observableValue(owner: object, initialValue: T): ISettableObservable; +export function observableValue(nameOrOwner: string | object, initialValue: T): ISettableObservable { + let debugNameData: DebugNameData; + if (typeof nameOrOwner === 'string') { + debugNameData = new DebugNameData(undefined, nameOrOwner, undefined); + } else { + debugNameData = new DebugNameData(nameOrOwner, undefined, undefined); + } + return new ObservableValue(debugNameData, initialValue, strictEquals); +} + +export class ObservableValue + extends BaseObservable + implements ISettableObservable { + protected _value: T; + + get debugName() { + return this._debugNameData.getDebugName(this) ?? 'ObservableValue'; + } + + constructor( + private readonly _debugNameData: DebugNameData, + initialValue: T, + private readonly _equalityComparator: EqualityComparer, + ) { + super(); + this._value = initialValue; + } + public override get(): T { + return this._value; + } + + public set(value: T, tx: ITransaction | undefined, change: TChange): void { + if (change === undefined && this._equalityComparator(this._value, value)) { + return; + } + + let _tx: TransactionImpl | undefined; + if (!tx) { + tx = _tx = new TransactionImpl(() => { }, () => `Setting ${this.debugName}`); + } + try { + const oldValue = this._value; + this._setValue(value); + getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true, hadValue: true }); + + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, change); + } + } finally { + if (_tx) { + _tx.finish(); + } + } + } + + override toString(): string { + return `${this.debugName}: ${this._value}`; + } + + protected _setValue(newValue: T): void { + this._value = newValue; + } +} + +/** + * A disposable observable. When disposed, its value is also disposed. + * When a new value is set, the previous value is disposed. + */ +export function disposableObservableValue(nameOrOwner: string | object, initialValue: T): ISettableObservable & IDisposable { + let debugNameData: DebugNameData; + if (typeof nameOrOwner === 'string') { + debugNameData = new DebugNameData(undefined, nameOrOwner, undefined); + } else { + debugNameData = new DebugNameData(nameOrOwner, undefined, undefined); + } + return new DisposableObservableValue(debugNameData, initialValue, strictEquals); +} + +export class DisposableObservableValue extends ObservableValue implements IDisposable { + protected override _setValue(newValue: T): void { + if (this._value === newValue) { + return; + } + if (this._value) { + this._value.dispose(); + } + this._value = newValue; + } + + public dispose(): void { + this._value?.dispose(); + } +} + +export interface IChangeTracker { + /** + * Returns if this change should cause an invalidation. + * Implementations can record changes. + */ + handleChange(context: IChangeContext): boolean; +} + +export interface IChangeContext { + readonly changedObservable: IObservableWithChange; + readonly change: unknown; + + /** + * Returns if the given observable caused the change. + */ + didChange(observable: IObservableWithChange): this is { change: TChange }; +} diff --git a/packages/core/src/observableInternal/commonFacade/cancellation.ts b/packages/core/src/observableInternal/commonFacade/cancellation.ts new file mode 100644 index 00000000..bc5a921a --- /dev/null +++ b/packages/core/src/observableInternal/commonFacade/cancellation.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export { CancellationError } from '../../errors.js'; +export { CancellationToken, CancellationTokenSource } from '../../cancellation.js'; diff --git a/packages/core/src/observableInternal/commonFacade/deps.ts b/packages/core/src/observableInternal/commonFacade/deps.ts new file mode 100644 index 00000000..d73340cb --- /dev/null +++ b/packages/core/src/observableInternal/commonFacade/deps.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export { assertFn } from '../../assert.js'; +export { type EqualityComparer, strictEquals } from '../../equals.js'; +export { BugIndicatingError, onBugIndicatingError } from '../../errors.js'; +export { Event, type IValueWithChangeEvent } from '../../event.js'; +export { DisposableStore, type IDisposable, markAsDisposed, toDisposable, trackDisposable } from '../../lifecycle.js'; diff --git a/packages/core/src/observableInternal/debugName.ts b/packages/core/src/observableInternal/debugName.ts new file mode 100644 index 00000000..d780a162 --- /dev/null +++ b/packages/core/src/observableInternal/debugName.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface IDebugNameData { + /** + * The owner object of an observable. + * Used for debugging only, such as computing a name for the observable by iterating over the fields of the owner. + */ + readonly owner?: DebugOwner | undefined; + + /** + * A string or function that returns a string that represents the name of the observable. + * Used for debugging only. + */ + readonly debugName?: DebugNameSource | undefined; + + /** + * A function that points to the defining function of the object. + * Used for debugging only. + */ + readonly debugReferenceFn?: Function | undefined; +} + +export class DebugNameData { + constructor( + public readonly owner: DebugOwner | undefined, + public readonly debugNameSource: DebugNameSource | undefined, + public readonly referenceFn: Function | undefined, + ) { } + + public getDebugName(target: object): string | undefined { + return getDebugName(target, this); + } +} + +/** + * The owning object of an observable. + * Is only used for debugging purposes, such as computing a name for the observable by iterating over the fields of the owner. + */ +export type DebugOwner = object | undefined; +export type DebugNameSource = string | (() => string | undefined); + +const countPerName = new Map(); +const cachedDebugName = new WeakMap(); + +export function getDebugName(target: object, data: DebugNameData): string | undefined { + const cached = cachedDebugName.get(target); + if (cached) { + return cached; + } + + const dbgName = computeDebugName(target, data); + if (dbgName) { + let count = countPerName.get(dbgName) ?? 0; + count++; + countPerName.set(dbgName, count); + const result = count === 1 ? dbgName : `${dbgName}#${count}`; + cachedDebugName.set(target, result); + return result; + } + return undefined; +} + +function computeDebugName(self: object, data: DebugNameData): string | undefined { + const cached = cachedDebugName.get(self); + if (cached) { + return cached; + } + + const ownerStr = data.owner ? formatOwner(data.owner) + `.` : ''; + + let result: string | undefined; + const debugNameSource = data.debugNameSource; + if (debugNameSource !== undefined) { + if (typeof debugNameSource === 'function') { + result = debugNameSource(); + if (result !== undefined) { + return ownerStr + result; + } + } else { + return ownerStr + debugNameSource; + } + } + + const referenceFn = data.referenceFn; + if (referenceFn !== undefined) { + result = getFunctionName(referenceFn); + if (result !== undefined) { + return ownerStr + result; + } + } + + if (data.owner !== undefined) { + const key = findKey(data.owner, self); + if (key !== undefined) { + return ownerStr + key; + } + } + return undefined; +} + +function findKey(obj: object, value: object): string | undefined { + for (const key in obj) { + if ((obj as any)[key] === value) { + return key; + } + } + return undefined; +} + +const countPerClassName = new Map(); +const ownerId = new WeakMap(); + +function formatOwner(owner: object): string { + const id = ownerId.get(owner); + if (id) { + return id; + } + const className = getClassName(owner); + let count = countPerClassName.get(className) ?? 0; + count++; + countPerClassName.set(className, count); + const result = count === 1 ? className : `${className}#${count}`; + ownerId.set(owner, result); + return result; +} + +function getClassName(obj: object): string { + const ctor = obj.constructor; + if (ctor) { + return ctor.name; + } + return 'Object'; +} + +export function getFunctionName(fn: Function): string | undefined { + const fnSrc = fn.toString(); + // Pattern: /** @description ... */ + const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; + const match = regexp.exec(fnSrc); + const result = match ? match[1] : undefined; + return result?.trim(); +} diff --git a/packages/core/src/observableInternal/derived.ts b/packages/core/src/observableInternal/derived.ts new file mode 100644 index 00000000..d059acf3 --- /dev/null +++ b/packages/core/src/observableInternal/derived.ts @@ -0,0 +1,495 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BaseObservable, IChangeContext, IObservable, IObservableWithChange, IObserver, IReader, ISettableObservable, ITransaction, _setDerivedOpts, } from './base.js'; +import { DebugNameData, DebugOwner, IDebugNameData } from './debugName.js'; +import { BugIndicatingError, DisposableStore, EqualityComparer, IDisposable, assertFn, onBugIndicatingError, strictEquals } from './commonFacade/deps.js'; +import { getLogger } from './logging.js'; + +/** + * Creates an observable that is derived from other observables. + * The value is only recomputed when absolutely needed. + * + * {@link computeFn} should start with a JS Doc using `@description` to name the derived. + */ +export function derived(computeFn: (reader: IReader) => T): IObservable; +export function derived(owner: DebugOwner, computeFn: (reader: IReader) => T): IObservable; +export function derived(computeFnOrOwner: ((reader: IReader) => T) | DebugOwner, computeFn?: ((reader: IReader) => T) | undefined): IObservable { + if (computeFn !== undefined) { + return new Derived( + new DebugNameData(computeFnOrOwner, undefined, computeFn), + computeFn, + undefined, + undefined, + undefined, + strictEquals + ); + } + return new Derived( + new DebugNameData(undefined, undefined, computeFnOrOwner as any), + computeFnOrOwner as any, + undefined, + undefined, + undefined, + strictEquals + ); +} + +export function derivedWithSetter(owner: DebugOwner | undefined, computeFn: (reader: IReader) => T, setter: (value: T, transaction: ITransaction | undefined) => void): ISettableObservable { + return new DerivedWithSetter( + new DebugNameData(owner, undefined, computeFn), + computeFn, + undefined, + undefined, + undefined, + strictEquals, + setter, + ); +} + +export function derivedOpts( + options: IDebugNameData & { + equalsFn?: EqualityComparer; + onLastObserverRemoved?: (() => void); + }, + computeFn: (reader: IReader) => T +): IObservable { + return new Derived( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn), + computeFn, + undefined, + undefined, + options.onLastObserverRemoved, + options.equalsFn ?? strictEquals + ); +} + +_setDerivedOpts(derivedOpts); + +/** + * Represents an observable that is derived from other observables. + * The value is only recomputed when absolutely needed. + * + * {@link computeFn} should start with a JS Doc using `@description` to name the derived. + * + * Use `createEmptyChangeSummary` to create a "change summary" that can collect the changes. + * Use `handleChange` to add a reported change to the change summary. + * The compute function is given the last change summary. + * The change summary is discarded after the compute function was called. + * + * @see derived + */ +export function derivedHandleChanges( + options: IDebugNameData & { + createEmptyChangeSummary: () => TChangeSummary; + handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; + equalityComparer?: EqualityComparer; + }, + computeFn: (reader: IReader, changeSummary: TChangeSummary) => T +): IObservable { + return new Derived( + new DebugNameData(options.owner, options.debugName, undefined), + computeFn, + options.createEmptyChangeSummary, + options.handleChange, + undefined, + options.equalityComparer ?? strictEquals + ); +} + +export function derivedWithStore(computeFn: (reader: IReader, store: DisposableStore) => T): IObservable; +export function derivedWithStore(owner: object, computeFn: (reader: IReader, store: DisposableStore) => T): IObservable; +export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: DisposableStore) => T) | object, computeFnOrUndefined?: ((reader: IReader, store: DisposableStore) => T)): IObservable { + let computeFn: (reader: IReader, store: DisposableStore) => T; + let owner: DebugOwner; + if (computeFnOrUndefined === undefined) { + computeFn = computeFnOrOwner as any; + owner = undefined; + } else { + owner = computeFnOrOwner; + computeFn = computeFnOrUndefined as any; + } + + const store = new DisposableStore(); + return new Derived( + new DebugNameData(owner, undefined, computeFn), + r => { + store.clear(); + return computeFn(r, store); + }, undefined, + undefined, + () => store.dispose(), + strictEquals + ); +} + +export function derivedDisposable(computeFn: (reader: IReader) => T): IObservable; +export function derivedDisposable(owner: DebugOwner, computeFn: (reader: IReader) => T): IObservable; +export function derivedDisposable(computeFnOrOwner: ((reader: IReader) => T) | DebugOwner, computeFnOrUndefined?: ((reader: IReader) => T)): IObservable { + let computeFn: (reader: IReader) => T; + let owner: DebugOwner; + if (computeFnOrUndefined === undefined) { + computeFn = computeFnOrOwner as any; + owner = undefined; + } else { + owner = computeFnOrOwner; + computeFn = computeFnOrUndefined as any; + } + + let store: DisposableStore | undefined = undefined; + return new Derived( + new DebugNameData(owner, undefined, computeFn), + r => { + if (!store) { + store = new DisposableStore(); + } else { + store.clear(); + } + const result = computeFn(r); + if (result) { + store.add(result); + } + return result; + }, undefined, + undefined, + () => { + if (store) { + store.dispose(); + store = undefined; + } + }, + strictEquals + ); +} + +const enum DerivedState { + /** Initial state, no previous value, recomputation needed */ + initial = 0, + + /** + * A dependency could have changed. + * We need to explicitly ask them if at least one dependency changed. + */ + dependenciesMightHaveChanged = 1, + + /** + * A dependency changed and we need to recompute. + * After recomputation, we need to check the previous value to see if we changed as well. + */ + stale = 2, + + /** + * No change reported, our cached value is up to date. + */ + upToDate = 3, +} + +export class Derived extends BaseObservable implements IReader, IObserver { + private state = DerivedState.initial; + private value: T | undefined = undefined; + private updateCount = 0; + private dependencies = new Set>(); + private dependenciesToBeRemoved = new Set>(); + private changeSummary: TChangeSummary | undefined = undefined; + private _isUpdating = false; + private _isComputing = false; + + public override get debugName(): string { + return this._debugNameData.getDebugName(this) ?? '(anonymous)'; + } + + constructor( + public readonly _debugNameData: DebugNameData, + public readonly _computeFn: (reader: IReader, changeSummary: TChangeSummary) => T, + private readonly createChangeSummary: (() => TChangeSummary) | undefined, + private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, + private readonly _handleLastObserverRemoved: (() => void) | undefined = undefined, + private readonly _equalityComparator: EqualityComparer, + ) { + super(); + this.changeSummary = this.createChangeSummary?.(); + getLogger()?.handleDerivedCreated(this); + } + + protected override onLastObserverRemoved(): void { + /** + * We are not tracking changes anymore, thus we have to assume + * that our cache is invalid. + */ + this.state = DerivedState.initial; + this.value = undefined; + getLogger()?.handleDerivedCleared(this); + for (const d of this.dependencies) { + d.removeObserver(this); + } + this.dependencies.clear(); + + this._handleLastObserverRemoved?.(); + } + + public override get(): T { + if (this._isComputing) { + throw new BugIndicatingError('Cyclic deriveds are not supported yet!'); + } + + if (this.observers.size === 0) { + let result; + // Without observers, we don't know when to clean up stuff. + // Thus, we don't cache anything to prevent memory leaks. + try { + this._isReaderValid = true; + result = this._computeFn(this, this.createChangeSummary?.()!); + } finally { + this._isReaderValid = false; + } + // Clear new dependencies + this.onLastObserverRemoved(); + return result; + + } else { + do { + // We might not get a notification for a dependency that changed while it is updating, + // thus we also have to ask all our depedencies if they changed in this case. + if (this.state === DerivedState.dependenciesMightHaveChanged) { + for (const d of this.dependencies) { + /** might call {@link handleChange} indirectly, which could make us stale */ + d.reportChanges(); + + if (this.state as DerivedState === DerivedState.stale) { + // The other dependencies will refresh on demand, so early break + break; + } + } + } + + // We called report changes of all dependencies. + // If we are still not stale, we can assume to be up to date again. + if (this.state === DerivedState.dependenciesMightHaveChanged) { + this.state = DerivedState.upToDate; + } + + this._recomputeIfNeeded(); + // In case recomputation changed one of our dependencies, we need to recompute again. + } while (this.state !== DerivedState.upToDate); + return this.value!; + } + } + + private _recomputeIfNeeded() { + if (this.state === DerivedState.upToDate) { + return; + } + const emptySet = this.dependenciesToBeRemoved; + this.dependenciesToBeRemoved = this.dependencies; + this.dependencies = emptySet; + + const hadValue = this.state !== DerivedState.initial; + const oldValue = this.value; + this.state = DerivedState.upToDate; + + let didChange = false; + + this._isComputing = false; // TODO@hediet: Set to true and investigate diff editor scrolling issues! (also see test.skip('catches cyclic dependencies') + + try { + const changeSummary = this.changeSummary!; + this.changeSummary = this.createChangeSummary?.(); + try { + this._isReaderValid = true; + /** might call {@link handleChange} indirectly, which could invalidate us */ + this.value = this._computeFn(this, changeSummary); + } finally { + this._isReaderValid = false; + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.dependenciesToBeRemoved) { + o.removeObserver(this); + } + this.dependenciesToBeRemoved.clear(); + } + + didChange = hadValue && !(this._equalityComparator(oldValue!, this.value)); + + getLogger()?.handleDerivedRecomputed(this, { + oldValue, + newValue: this.value, + change: undefined, + didChange, + hadValue, + }); + } catch (e) { + onBugIndicatingError(e); + } + + this._isComputing = false; + + if (didChange) { + for (const r of this.observers) { + r.handleChange(this, undefined); + } + } + } + + public override toString(): string { + return `LazyDerived<${this.debugName}>`; + } + + // IObserver Implementation + + public beginUpdate(_observable: IObservable): void { + if (this._isUpdating) { + throw new BugIndicatingError('Cyclic deriveds are not supported yet!'); + } + + this.updateCount++; + this._isUpdating = true; + try { + const propagateBeginUpdate = this.updateCount === 1; + if (this.state === DerivedState.upToDate) { + this.state = DerivedState.dependenciesMightHaveChanged; + // If we propagate begin update, that will already signal a possible change. + if (!propagateBeginUpdate) { + for (const r of this.observers) { + r.handlePossibleChange(this); + } + } + } + if (propagateBeginUpdate) { + for (const r of this.observers) { + r.beginUpdate(this); // This signals a possible change + } + } + } finally { + this._isUpdating = false; + } + } + + private _removedObserverToCallEndUpdateOn: Set | null = null; + + public endUpdate(_observable: IObservable): void { + this.updateCount--; + if (this.updateCount === 0) { + // End update could change the observer list. + const observers = [...this.observers]; + for (const r of observers) { + r.endUpdate(this); + } + if (this._removedObserverToCallEndUpdateOn) { + const observers = [...this._removedObserverToCallEndUpdateOn]; + this._removedObserverToCallEndUpdateOn = null; + for (const r of observers) { + r.endUpdate(this); + } + } + } + assertFn(() => this.updateCount >= 0); + } + + public handlePossibleChange(observable: IObservable): void { + // In all other states, observers already know that we might have changed. + if (this.state === DerivedState.upToDate && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + this.state = DerivedState.dependenciesMightHaveChanged; + for (const r of this.observers) { + r.handlePossibleChange(this); + } + } + } + + public handleChange(observable: IObservableWithChange, change: TChange): void { + if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + let shouldReact = false; + try { + shouldReact = this._handleChange ? this._handleChange({ + changedObservable: observable, + change, + didChange: (o): this is any => o === observable as any, + }, this.changeSummary!) : true; + } catch (e) { + onBugIndicatingError(e); + } + + const wasUpToDate = this.state === DerivedState.upToDate; + if (shouldReact && (this.state === DerivedState.dependenciesMightHaveChanged || wasUpToDate)) { + this.state = DerivedState.stale; + if (wasUpToDate) { + for (const r of this.observers) { + r.handlePossibleChange(this); + } + } + } + } + } + + // IReader Implementation + private _isReaderValid = false; + + public readObservable(observable: IObservable): T { + if (!this._isReaderValid) { throw new BugIndicatingError('The reader object cannot be used outside its compute function!'); } + + // Subscribe before getting the value to enable caching + observable.addObserver(this); + /** This might call {@link handleChange} indirectly, which could invalidate us */ + const value = observable.get(); + // Which is why we only add the observable to the dependencies now. + this.dependencies.add(observable); + this.dependenciesToBeRemoved.delete(observable); + return value; + } + + public override addObserver(observer: IObserver): void { + const shouldCallBeginUpdate = !this.observers.has(observer) && this.updateCount > 0; + super.addObserver(observer); + + if (shouldCallBeginUpdate) { + if (this._removedObserverToCallEndUpdateOn && this._removedObserverToCallEndUpdateOn.has(observer)) { + this._removedObserverToCallEndUpdateOn.delete(observer); + } else { + observer.beginUpdate(this); + } + } + } + + public override removeObserver(observer: IObserver): void { + if (this.observers.has(observer) && this.updateCount > 0) { + if (!this._removedObserverToCallEndUpdateOn) { + this._removedObserverToCallEndUpdateOn = new Set(); + } + this._removedObserverToCallEndUpdateOn.add(observer); + } + super.removeObserver(observer); + } + + public override log(): IObservableWithChange { + if (!getLogger()) { + super.log(); + getLogger()?.handleDerivedCreated(this); + } else { + super.log(); + } + return this; + } +} + + +export class DerivedWithSetter extends Derived implements ISettableObservable { + constructor( + debugNameData: DebugNameData, + computeFn: (reader: IReader, changeSummary: TChangeSummary) => T, + createChangeSummary: (() => TChangeSummary) | undefined, + handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, + handleLastObserverRemoved: (() => void) | undefined = undefined, + equalityComparator: EqualityComparer, + public readonly set: (value: T, tx: ITransaction | undefined) => void, + ) { + super( + debugNameData, + computeFn, + createChangeSummary, + handleChange, + handleLastObserverRemoved, + equalityComparator, + ); + } +} diff --git a/packages/core/src/observableInternal/index.ts b/packages/core/src/observableInternal/index.ts new file mode 100644 index 00000000..cd0c690e --- /dev/null +++ b/packages/core/src/observableInternal/index.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// This is a facade for the observable implementation. Only import from here! + +export { observableValueOpts } from './api.js'; +export { autorun, autorunDelta, autorunHandleChanges, autorunOpts, autorunWithStore, autorunWithStoreHandleChanges } from './autorun.js'; +export { asyncTransaction, disposableObservableValue, globalTransaction, observableValue, subtransaction, transaction, TransactionImpl, type IChangeContext, type IChangeTracker, type IObservable, type IObservableWithChange, type IObserver, type IReader, type ISettable, type ISettableObservable, type ITransaction, } from './base.js'; +export { derived, derivedDisposable, derivedHandleChanges, derivedOpts, derivedWithSetter, derivedWithStore } from './derived.js'; +export { ObservableLazy, ObservableLazyPromise, ObservablePromise, PromiseResult, } from './promise.js'; +export { derivedWithCancellationToken, waitForState } from './utilsCancellation.js'; +export { constObservable, debouncedObservable, derivedConstOnceDefined, derivedObservableWithCache, derivedObservableWithWritableCache, keepObserved, latestChangedValue, mapObservableArrayCached, observableFromEvent, observableFromEventOpts, observableFromPromise, observableFromValueWithChangeEvent, observableSignal, observableSignalFromEvent, recomputeInitiallyAndOnChange, runOnChange, runOnChangeWithStore, signalFromObservable, ValueWithChangeEventFromObservable, wasEventTriggeredRecently, type IObservableSignal, } from './utils.js'; +export { type DebugOwner } from './debugName.js'; + +import { + ConsoleObservableLogger, + setLogger +} from './logging.js'; + +// Remove "//" in the next line to enable logging +const enableLogging = false + // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this + ; + +if (enableLogging) { + setLogger(new ConsoleObservableLogger()); +} diff --git a/packages/core/src/observableInternal/lazyObservableValue.ts b/packages/core/src/observableInternal/lazyObservableValue.ts new file mode 100644 index 00000000..56d15c6c --- /dev/null +++ b/packages/core/src/observableInternal/lazyObservableValue.ts @@ -0,0 +1,148 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EqualityComparer } from './commonFacade/deps.js'; +import { BaseObservable, IObserver, ISettableObservable, ITransaction, TransactionImpl } from './base.js'; +import { DebugNameData } from './debugName.js'; +import { getLogger } from './logging.js'; + +/** + * Holds off updating observers until the value is actually read. +*/ +export class LazyObservableValue + extends BaseObservable + implements ISettableObservable { + protected _value: T; + private _isUpToDate = true; + private readonly _deltas: TChange[] = []; + + get debugName() { + return this._debugNameData.getDebugName(this) ?? 'LazyObservableValue'; + } + + constructor( + private readonly _debugNameData: DebugNameData, + initialValue: T, + private readonly _equalityComparator: EqualityComparer, + ) { + super(); + this._value = initialValue; + } + + public override get(): T { + this._update(); + return this._value; + } + + private _update(): void { + if (this._isUpToDate) { + return; + } + this._isUpToDate = true; + + if (this._deltas.length > 0) { + for (const change of this._deltas) { + getLogger()?.handleObservableChanged(this, { change, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true }); + for (const observer of this.observers) { + observer.handleChange(this, change); + } + } + this._deltas.length = 0; + } else { + getLogger()?.handleObservableChanged(this, { change: undefined, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true }); + for (const observer of this.observers) { + observer.handleChange(this, undefined); + } + } + } + + private _updateCounter = 0; + + private _beginUpdate(): void { + this._updateCounter++; + if (this._updateCounter === 1) { + for (const observer of this.observers) { + observer.beginUpdate(this); + } + } + } + + private _endUpdate(): void { + this._updateCounter--; + if (this._updateCounter === 0) { + this._update(); + + // End update could change the observer list. + const observers = [...this.observers]; + for (const r of observers) { + r.endUpdate(this); + } + } + } + + public override addObserver(observer: IObserver): void { + const shouldCallBeginUpdate = !this.observers.has(observer) && this._updateCounter > 0; + super.addObserver(observer); + + if (shouldCallBeginUpdate) { + observer.beginUpdate(this); + } + } + + public override removeObserver(observer: IObserver): void { + const shouldCallEndUpdate = this.observers.has(observer) && this._updateCounter > 0; + super.removeObserver(observer); + + if (shouldCallEndUpdate) { + // Calling end update after removing the observer makes sure endUpdate cannot be called twice here. + observer.endUpdate(this); + } + } + + public set(value: T, tx: ITransaction | undefined, change: TChange): void { + if (change === undefined && this._equalityComparator(this._value, value)) { + return; + } + + let _tx: TransactionImpl | undefined; + if (!tx) { + tx = _tx = new TransactionImpl(() => { }, () => `Setting ${this.debugName}`); + } + try { + this._isUpToDate = false; + this._setValue(value); + if (change !== undefined) { + this._deltas.push(change); + } + + tx.updateObserver({ + beginUpdate: () => this._beginUpdate(), + endUpdate: () => this._endUpdate(), + handleChange: (observable, change) => { }, + handlePossibleChange: (observable) => { }, + }, this); + + if (this._updateCounter > 1) { + // We already started begin/end update, so we need to manually call handlePossibleChange + for (const observer of this.observers) { + observer.handlePossibleChange(this); + } + } + + } finally { + if (_tx) { + _tx.finish(); + } + } + } + + override toString(): string { + return `${this.debugName}: ${this._value}`; + } + + protected _setValue(newValue: T): void { + this._value = newValue; + } +} diff --git a/packages/core/src/observableInternal/logging.ts b/packages/core/src/observableInternal/logging.ts new file mode 100644 index 00000000..83214984 --- /dev/null +++ b/packages/core/src/observableInternal/logging.ts @@ -0,0 +1,407 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AutorunObserver } from './autorun.js'; +import { IObservable, TransactionImpl } from './base.js'; +import { Derived } from './derived.js'; +import { FromEventObservable } from './utils.js'; + +let globalObservableLogger: IObservableLogger | undefined; + +export function setLogger(logger: IObservableLogger): void { + globalObservableLogger = logger; +} + +export function getLogger(): IObservableLogger | undefined { + return globalObservableLogger; +} + +export function logObservable(obs: IObservable): void { + if (!globalObservableLogger) { + const l = new ConsoleObservableLogger(); + l.addFilteredObj(obs); + setLogger(l); + } else { + if (globalObservableLogger instanceof ConsoleObservableLogger) { + (globalObservableLogger as ConsoleObservableLogger).addFilteredObj(obs); + } + } +} + +interface IChangeInformation { + oldValue: unknown; + newValue: unknown; + change: unknown; + didChange: boolean; + hadValue: boolean; +} + +export interface IObservableLogger { + handleObservableChanged(observable: IObservable, info: IChangeInformation): void; + handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void; + + handleAutorunCreated(autorun: AutorunObserver): void; + handleAutorunTriggered(autorun: AutorunObserver): void; + handleAutorunFinished(autorun: AutorunObserver): void; + + handleDerivedCreated(observable: Derived): void; + handleDerivedRecomputed(observable: Derived, info: IChangeInformation): void; + handleDerivedCleared(observable: Derived): void; + + handleBeginTransaction(transaction: TransactionImpl): void; + handleEndTransaction(): void; +} + +export class ConsoleObservableLogger implements IObservableLogger { + private indentation = 0; + + private _filteredObjects: Set | undefined; + + public addFilteredObj(obj: unknown): void { + if (!this._filteredObjects) { + this._filteredObjects = new Set(); + } + this._filteredObjects.add(obj); + } + + private _isIncluded(obj: unknown): boolean { + return this._filteredObjects?.has(obj) ?? true; + } + + private textToConsoleArgs(text: ConsoleText): unknown[] { + return consoleTextToArgs([ + normalText(repeat('| ', this.indentation)), + text, + ]); + } + + private formatInfo(info: IChangeInformation): ConsoleText[] { + if (!info.hadValue) { + return [ + normalText(` `), + styled(formatValue(info.newValue, 60), { + color: 'green', + }), + normalText(` (initial)`), + ]; + } + return info.didChange + ? [ + normalText(` `), + styled(formatValue(info.oldValue, 70), { + color: 'red', + strikeThrough: true, + }), + normalText(` `), + styled(formatValue(info.newValue, 60), { + color: 'green', + }), + ] + : [normalText(` (unchanged)`)]; + } + + handleObservableChanged(observable: IObservable, info: IChangeInformation): void { + if (!this._isIncluded(observable)) { return; } + console.log(...this.textToConsoleArgs([ + formatKind('observable value changed'), + styled(observable.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + ])); + } + + private readonly changedObservablesSets = new WeakMap>>(); + + formatChanges(changes: Set>): ConsoleText | undefined { + if (changes.size === 0) { + return undefined; + } + return styled( + ' (changed deps: ' + + [...changes].map((o) => o.debugName).join(', ') + + ')', + { color: 'gray' } + ); + } + + handleDerivedCreated(derived: Derived): void { + const existingHandleChange = derived.handleChange; + this.changedObservablesSets.set(derived, new Set()); + derived.handleChange = (observable, change) => { + this.changedObservablesSets.get(derived)!.add(observable); + return existingHandleChange.apply(derived, [observable, change]); + }; + + const debugTrackUpdating = false; + if (debugTrackUpdating) { + const updating: IObservable[] = []; + (derived as any).__debugUpdating = updating; + + const existingBeginUpdate = derived.beginUpdate; + derived.beginUpdate = (obs) => { + updating.push(obs); + return existingBeginUpdate.apply(derived, [obs]); + }; + + const existingEndUpdate = derived.endUpdate; + derived.endUpdate = (obs) => { + const idx = updating.indexOf(obs); + if (idx === -1) { + console.error('endUpdate called without beginUpdate', derived.debugName, obs.debugName); + } + updating.splice(idx, 1); + return existingEndUpdate.apply(derived, [obs]); + }; + } + } + + handleDerivedRecomputed(derived: Derived, info: IChangeInformation): void { + if (!this._isIncluded(derived)) { return; } + + const changedObservables = this.changedObservablesSets.get(derived); + if (!changedObservables) { return; } + console.log(...this.textToConsoleArgs([ + formatKind('derived recomputed'), + styled(derived.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + this.formatChanges(changedObservables), + { data: [{ fn: derived._debugNameData.referenceFn ?? derived._computeFn }] } + ])); + changedObservables.clear(); + } + + handleDerivedCleared(derived: Derived): void { + if (!this._isIncluded(derived)) { return; } + + console.log(...this.textToConsoleArgs([ + formatKind('derived cleared'), + styled(derived.debugName, { color: 'BlueViolet' }), + ])); + } + + handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void { + if (!this._isIncluded(observable)) { return; } + + console.log(...this.textToConsoleArgs([ + formatKind('observable from event triggered'), + styled(observable.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + { data: [{ fn: observable._getValue }] } + ])); + } + + handleAutorunCreated(autorun: AutorunObserver): void { + if (!this._isIncluded(autorun)) { return; } + + const existingHandleChange = autorun.handleChange; + this.changedObservablesSets.set(autorun, new Set()); + autorun.handleChange = (observable, change) => { + this.changedObservablesSets.get(autorun)!.add(observable); + return existingHandleChange.apply(autorun, [observable, change]); + }; + } + + handleAutorunTriggered(autorun: AutorunObserver): void { + const changedObservables = this.changedObservablesSets.get(autorun); + if (!changedObservables) { return; } + + if (this._isIncluded(autorun)) { + console.log(...this.textToConsoleArgs([ + formatKind('autorun'), + styled(autorun.debugName, { color: 'BlueViolet' }), + this.formatChanges(changedObservables), + { data: [{ fn: autorun._debugNameData.referenceFn ?? autorun._runFn }] } + ])); + } + changedObservables.clear(); + this.indentation++; + } + + handleAutorunFinished(autorun: AutorunObserver): void { + this.indentation--; + } + + handleBeginTransaction(transaction: TransactionImpl): void { + let transactionName = transaction.getDebugName(); + if (transactionName === undefined) { + transactionName = ''; + } + if (this._isIncluded(transaction)) { + console.log(...this.textToConsoleArgs([ + formatKind('transaction'), + styled(transactionName, { color: 'BlueViolet' }), + { data: [{ fn: transaction._fn }] } + ])); + } + this.indentation++; + } + + handleEndTransaction(): void { + this.indentation--; + } +} + +type ConsoleText = + | (ConsoleText | undefined)[] + | { text: string; style: string; data?: unknown[] } + | { data: unknown[] }; + +function consoleTextToArgs(text: ConsoleText): unknown[] { + const styles = new Array(); + const data: unknown[] = []; + let firstArg = ''; + + function process(t: ConsoleText): void { + if ('length' in t) { + for (const item of t) { + if (item) { + process(item); + } + } + } else if ('text' in t) { + firstArg += `%c${t.text}`; + styles.push(t.style); + if (t.data) { + data.push(...t.data); + } + } else if ('data' in t) { + data.push(...t.data); + } + } + + process(text); + + const result = [firstArg, ...styles]; + result.push(...data); + return result; +} + +function normalText(text: string): ConsoleText { + return styled(text, { color: 'black' }); +} + +function formatKind(kind: string): ConsoleText { + return styled(padStr(`${kind}: `, 10), { color: 'black', bold: true }); +} + +function styled( + text: string, + options: { color: string; strikeThrough?: boolean; bold?: boolean } = { + color: 'black', + } +): ConsoleText { + function objToCss(styleObj: Record): string { + return Object.entries(styleObj).reduce( + (styleString, [propName, propValue]) => { + return `${styleString}${propName}:${propValue};`; + }, + '' + ); + } + + const style: Record = { + color: options.color, + }; + if (options.strikeThrough) { + style['text-decoration'] = 'line-through'; + } + if (options.bold) { + style['font-weight'] = 'bold'; + } + + return { + text, + style: objToCss(style), + }; +} + +function formatValue(value: unknown, availableLen: number): string { + switch (typeof value) { + case 'number': + return '' + value; + case 'string': + if (value.length + 2 <= availableLen) { + return `"${value}"`; + } + return `"${value.substr(0, availableLen - 7)}"+...`; + + case 'boolean': + return value ? 'true' : 'false'; + case 'undefined': + return 'undefined'; + case 'object': + if (value === null) { + return 'null'; + } + if (Array.isArray(value)) { + return formatArray(value, availableLen); + } + return formatObject(value, availableLen); + case 'symbol': + return value.toString(); + case 'function': + return `[[Function${value.name ? ' ' + value.name : ''}]]`; + default: + return '' + value; + } +} + +function formatArray(value: unknown[], availableLen: number): string { + let result = '[ '; + let first = true; + for (const val of value) { + if (!first) { + result += ', '; + } + if (result.length - 5 > availableLen) { + result += '...'; + break; + } + first = false; + result += `${formatValue(val, availableLen - result.length)}`; + } + result += ' ]'; + return result; +} + +function formatObject(value: object, availableLen: number): string { + if (typeof value.toString === 'function' && value.toString !== Object.prototype.toString) { + const val = value.toString(); + if (val.length <= availableLen) { + return val; + } + return val.substring(0, availableLen - 3) + '...'; + } + + let result = '{ '; + let first = true; + for (const [key, val] of Object.entries(value)) { + if (!first) { + result += ', '; + } + if (result.length - 5 > availableLen) { + result += '...'; + break; + } + first = false; + result += `${key}: ${formatValue(val, availableLen - result.length)}`; + } + result += ' }'; + return result; +} + +function repeat(str: string, count: number): string { + let result = ''; + for (let i = 1; i <= count; i++) { + result += str; + } + return result; +} + +function padStr(str: string, length: number): string { + while (str.length < length) { + str += ' '; + } + return str; +} diff --git a/packages/core/src/observableInternal/promise.ts b/packages/core/src/observableInternal/promise.ts new file mode 100644 index 00000000..e0a77304 --- /dev/null +++ b/packages/core/src/observableInternal/promise.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { IObservable, observableValue, transaction } from './base.js'; +import { derived } from './derived.js'; + +export class ObservableLazy { + private readonly _value = observableValue(this, undefined); + + /** + * The cached value. + * Does not force a computation of the value. + */ + public get cachedValue(): IObservable { return this._value; } + + constructor(private readonly _computeValue: () => T) { + } + + /** + * Returns the cached value. + * Computes the value if the value has not been cached yet. + */ + public getValue() { + let v = this._value.get(); + if (!v) { + v = this._computeValue(); + this._value.set(v, undefined); + } + return v; + } +} + +/** + * A promise whose state is observable. + */ +export class ObservablePromise { + public static fromFn(fn: () => Promise): ObservablePromise { + return new ObservablePromise(fn()); + } + + private readonly _value = observableValue | undefined>(this, undefined); + + /** + * The promise that this object wraps. + */ + public readonly promise: Promise; + + /** + * The current state of the promise. + * Is `undefined` if the promise didn't resolve yet. + */ + public readonly promiseResult: IObservable | undefined> = this._value; + + constructor(promise: Promise) { + this.promise = promise.then(value => { + transaction(tx => { + /** @description onPromiseResolved */ + this._value.set(new PromiseResult(value, undefined), tx); + }); + return value; + }, error => { + transaction(tx => { + /** @description onPromiseRejected */ + this._value.set(new PromiseResult(undefined, error), tx); + }); + throw error; + }); + } +} + +export class PromiseResult { + constructor( + /** + * The value of the resolved promise. + * Undefined if the promise rejected. + */ + public readonly data: T | undefined, + + /** + * The error in case of a rejected promise. + * Undefined if the promise resolved. + */ + public readonly error: unknown | undefined, + ) { + } + + /** + * Returns the value if the promise resolved, otherwise throws the error. + */ + public getDataOrThrow(): T { + if (this.error) { + throw this.error; + } + return this.data!; + } +} + +/** + * A lazy promise whose state is observable. + */ +export class ObservableLazyPromise { + private readonly _lazyValue = new ObservableLazy(() => new ObservablePromise(this._computePromise())); + + /** + * Does not enforce evaluation of the promise compute function. + * Is undefined if the promise has not been computed yet. + */ + public readonly cachedPromiseResult = derived(this, reader => this._lazyValue.cachedValue.read(reader)?.promiseResult.read(reader)); + + constructor(private readonly _computePromise: () => Promise) { + } + + public getPromise(): Promise { + return this._lazyValue.getValue().promise; + } +} diff --git a/packages/core/src/observableInternal/utils.ts b/packages/core/src/observableInternal/utils.ts new file mode 100644 index 00000000..7379a714 --- /dev/null +++ b/packages/core/src/observableInternal/utils.ts @@ -0,0 +1,664 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { autorun, autorunOpts, autorunWithStoreHandleChanges } from './autorun.js'; +import { BaseObservable, ConvenientObservable, IObservable, IObservableWithChange, IObserver, IReader, ITransaction, _setKeepObserved, _setRecomputeInitiallyAndOnChange, observableValue, subtransaction, transaction } from './base.js'; +import { DebugNameData, DebugOwner, IDebugNameData, getDebugName, } from './debugName.js'; +import { BugIndicatingError, DisposableStore, EqualityComparer, Event, IDisposable, IValueWithChangeEvent, strictEquals, toDisposable } from './commonFacade/deps.js'; +import { derived, derivedOpts } from './derived.js'; +import { getLogger } from './logging.js'; + +/** + * Represents an efficient observable whose value never changes. + */ +export function constObservable(value: T): IObservable { + return new ConstObservable(value); +} + +class ConstObservable extends ConvenientObservable { + constructor(private readonly value: T) { + super(); + } + + public override get debugName(): string { + return this.toString(); + } + + public get(): T { + return this.value; + } + public addObserver(observer: IObserver): void { + // NO OP + } + public removeObserver(observer: IObserver): void { + // NO OP + } + + override toString(): string { + return `Const: ${this.value}`; + } +} + + +export function observableFromPromise(promise: Promise): IObservable<{ value?: T }> { + const observable = observableValue<{ value?: T }>('promiseValue', {}); + promise.then((value) => { + observable.set({ value }, undefined); + }); + return observable; +} + + +export function observableFromEvent( + owner: DebugOwner, + event: Event, + getValue: (args: TArgs | undefined) => T, +): IObservable; +export function observableFromEvent( + event: Event, + getValue: (args: TArgs | undefined) => T, +): IObservable; +export function observableFromEvent(...args: + [owner: DebugOwner, event: Event, getValue: (args: any | undefined) => any] + | [event: Event, getValue: (args: any | undefined) => any] +): IObservable { + let owner; + let event; + let getValue; + if (args.length === 3) { + [owner, event, getValue] = args; + } else { + [event, getValue] = args; + } + return new FromEventObservable( + new DebugNameData(owner, undefined, getValue), + event, + getValue, + () => FromEventObservable.globalTransaction, + strictEquals + ); +} + +export function observableFromEventOpts( + options: IDebugNameData & { + equalsFn?: EqualityComparer; + }, + event: Event, + getValue: (args: TArgs | undefined) => T, +): IObservable { + return new FromEventObservable( + new DebugNameData(options.owner, options.debugName, options.debugReferenceFn ?? getValue), + event, + getValue, () => FromEventObservable.globalTransaction, options.equalsFn ?? strictEquals + ); +} + +export class FromEventObservable extends BaseObservable { + public static globalTransaction: ITransaction | undefined; + + private value: T | undefined; + private hasValue = false; + private subscription: IDisposable | undefined; + + constructor( + private readonly _debugNameData: DebugNameData, + private readonly event: Event, + public readonly _getValue: (args: TArgs | undefined) => T, + private readonly _getTransaction: () => ITransaction | undefined, + private readonly _equalityComparator: EqualityComparer + ) { + super(); + } + + private getDebugName(): string | undefined { + return this._debugNameData.getDebugName(this); + } + + public get debugName(): string { + const name = this.getDebugName(); + return 'From Event' + (name ? `: ${name}` : ''); + } + + protected override onFirstObserverAdded(): void { + this.subscription = this.event(this.handleEvent); + } + + private readonly handleEvent = (args: TArgs | undefined) => { + const newValue = this._getValue(args); + const oldValue = this.value; + + const didChange = !this.hasValue || !(this._equalityComparator(oldValue!, newValue)); + let didRunTransaction = false; + + if (didChange) { + this.value = newValue; + + if (this.hasValue) { + didRunTransaction = true; + subtransaction( + this._getTransaction(), + (tx) => { + getLogger()?.handleFromEventObservableTriggered(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); + + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, undefined); + } + }, + () => { + const name = this.getDebugName(); + return 'Event fired' + (name ? `: ${name}` : ''); + } + ); + } + this.hasValue = true; + } + + if (!didRunTransaction) { + getLogger()?.handleFromEventObservableTriggered(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); + } + }; + + protected override onLastObserverRemoved(): void { + this.subscription!.dispose(); + this.subscription = undefined; + this.hasValue = false; + this.value = undefined; + } + + public get(): T { + if (this.subscription) { + if (!this.hasValue) { + this.handleEvent(undefined); + } + return this.value!; + } else { + // no cache, as there are no subscribers to keep it updated + const value = this._getValue(undefined); + return value; + } + } +} + +export namespace observableFromEvent { + export const Observer = FromEventObservable; + + export function batchEventsGlobally(tx: ITransaction, fn: () => void): void { + let didSet = false; + if (FromEventObservable.globalTransaction === undefined) { + FromEventObservable.globalTransaction = tx; + didSet = true; + } + try { + fn(); + } finally { + if (didSet) { + FromEventObservable.globalTransaction = undefined; + } + } + } +} + +export function observableSignalFromEvent( + debugName: string, + event: Event +): IObservable { + return new FromEventObservableSignal(debugName, event); +} + +class FromEventObservableSignal extends BaseObservable { + private subscription: IDisposable | undefined; + + constructor( + public readonly debugName: string, + private readonly event: Event, + ) { + super(); + } + + protected override onFirstObserverAdded(): void { + this.subscription = this.event(this.handleEvent); + } + + private readonly handleEvent = () => { + transaction( + (tx) => { + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, undefined); + } + }, + () => this.debugName + ); + }; + + protected override onLastObserverRemoved(): void { + this.subscription!.dispose(); + this.subscription = undefined; + } + + public override get(): void { + // NO OP + } +} + +/** + * Creates a signal that can be triggered to invalidate observers. + * Signals don't have a value - when they are triggered they indicate a change. + * However, signals can carry a delta that is passed to observers. + */ +export function observableSignal(debugName: string): IObservableSignal; +export function observableSignal(owner: object): IObservableSignal; +export function observableSignal(debugNameOrOwner: string | object): IObservableSignal { + if (typeof debugNameOrOwner === 'string') { + return new ObservableSignal(debugNameOrOwner); + } else { + return new ObservableSignal(undefined, debugNameOrOwner); + } +} + +export interface IObservableSignal extends IObservableWithChange { + trigger(tx: ITransaction | undefined, change: TChange): void; +} + +class ObservableSignal extends BaseObservable implements IObservableSignal { + public get debugName() { + return new DebugNameData(this._owner, this._debugName, undefined).getDebugName(this) ?? 'Observable Signal'; + } + + public override toString(): string { + return this.debugName; + } + + constructor( + private readonly _debugName: string | undefined, + private readonly _owner?: object, + ) { + super(); + } + + public trigger(tx: ITransaction | undefined, change: TChange): void { + if (!tx) { + transaction(tx => { + this.trigger(tx, change); + }, () => `Trigger signal ${this.debugName}`); + return; + } + + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, change); + } + } + + public override get(): void { + // NO OP + } +} + +export function signalFromObservable(owner: DebugOwner | undefined, observable: IObservable): IObservable { + return derivedOpts({ + owner, + equalsFn: () => false, + }, reader => { + observable.read(reader); + }); +} + +/** + * @deprecated Use `debouncedObservable2` instead. + */ +export function debouncedObservable(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { + const debouncedObservable = observableValue('debounced', undefined); + + let timeout: any = undefined; + + disposableStore.add(autorun(reader => { + /** @description debounce */ + const value = observable.read(reader); + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + transaction(tx => { + debouncedObservable.set(value, tx); + }); + }, debounceMs); + + })); + + return debouncedObservable; +} + +/** + * Creates an observable that debounces the input observable. + */ +export function debouncedObservable2(observable: IObservable, debounceMs: number): IObservable { + let hasValue = false; + let lastValue: T | undefined; + + let timeout: any = undefined; + + return observableFromEvent(cb => { + const d = autorun(reader => { + const value = observable.read(reader); + + if (!hasValue) { + hasValue = true; + lastValue = value; + } else { + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + lastValue = value; + cb(); + }, debounceMs); + } + }); + return { + dispose() { + d.dispose(); + hasValue = false; + lastValue = undefined; + }, + }; + }, () => { + if (hasValue) { + return lastValue!; + } else { + return observable.get(); + } + }); +} + +export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { + const observable = observableValue('triggeredRecently', false); + + let timeout: any = undefined; + + disposableStore.add(event(() => { + observable.set(true, undefined); + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + observable.set(false, undefined); + }, timeoutMs); + })); + + return observable; +} + +/** + * This makes sure the observable is being observed and keeps its cache alive. + */ +export function keepObserved(observable: IObservable): IDisposable { + const o = new KeepAliveObserver(false, undefined); + observable.addObserver(o); + return toDisposable(() => { + observable.removeObserver(o); + }); +} + +_setKeepObserved(keepObserved); + +/** + * This converts the given observable into an autorun. + */ +export function recomputeInitiallyAndOnChange(observable: IObservable, handleValue?: (value: T) => void): IDisposable { + const o = new KeepAliveObserver(true, handleValue); + observable.addObserver(o); + if (handleValue) { + handleValue(observable.get()); + } else { + observable.reportChanges(); + } + + return toDisposable(() => { + observable.removeObserver(o); + }); +} + +_setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange); + +export class KeepAliveObserver implements IObserver { + private _counter = 0; + + constructor( + private readonly _forceRecompute: boolean, + private readonly _handleValue: ((value: any) => void) | undefined, + ) { } + + beginUpdate(observable: IObservable): void { + this._counter++; + } + + endUpdate(observable: IObservable): void { + this._counter--; + if (this._counter === 0 && this._forceRecompute) { + if (this._handleValue) { + this._handleValue(observable.get()); + } else { + observable.reportChanges(); + } + } + } + + handlePossibleChange(observable: IObservable): void { + // NO OP + } + + handleChange(observable: IObservableWithChange, change: TChange): void { + // NO OP + } +} + +export function derivedObservableWithCache(owner: DebugOwner, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable { + let lastValue: T | undefined = undefined; + const observable = derivedOpts({ owner, debugReferenceFn: computeFn }, reader => { + lastValue = computeFn(reader, lastValue); + return lastValue; + }); + return observable; +} + +export function derivedObservableWithWritableCache(owner: object, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable + & { clearCache(transaction: ITransaction): void; setCache(newValue: T | undefined, tx: ITransaction | undefined): void } { + let lastValue: T | undefined = undefined; + const onChange = observableSignal('derivedObservableWithWritableCache'); + const observable = derived(owner, reader => { + onChange.read(reader); + lastValue = computeFn(reader, lastValue); + return lastValue; + }); + return Object.assign(observable, { + clearCache: (tx: ITransaction) => { + lastValue = undefined; + onChange.trigger(tx); + }, + setCache: (newValue: T | undefined, tx: ITransaction | undefined) => { + lastValue = newValue; + onChange.trigger(tx); + } + }); +} + +/** + * When the items array changes, referential equal items are not mapped again. + */ +export function mapObservableArrayCached(owner: DebugOwner, items: IObservable, map: (input: TIn, store: DisposableStore) => TOut, keySelector?: (input: TIn) => TKey): IObservable { + let m = new ArrayMap(map, keySelector); + const self = derivedOpts({ + debugReferenceFn: map, + owner, + onLastObserverRemoved: () => { + m.dispose(); + m = new ArrayMap(map); + } + }, (reader) => { + m.setItems(items.read(reader)); + return m.getItems(); + }); + return self; +} + +class ArrayMap implements IDisposable { + private readonly _cache = new Map(); + private _items: TOut[] = []; + constructor( + private readonly _map: (input: TIn, store: DisposableStore) => TOut, + private readonly _keySelector?: (input: TIn) => TKey, + ) { + } + + public dispose(): void { + this._cache.forEach(entry => entry.store.dispose()); + this._cache.clear(); + } + + public setItems(items: readonly TIn[]): void { + const newItems: TOut[] = []; + const itemsToRemove = new Set(this._cache.keys()); + + for (const item of items) { + const key = this._keySelector ? this._keySelector(item) : item as unknown as TKey; + + let entry = this._cache.get(key); + if (!entry) { + const store = new DisposableStore(); + const out = this._map(item, store); + entry = { out, store }; + this._cache.set(key, entry); + } else { + itemsToRemove.delete(key); + } + newItems.push(entry.out); + } + + for (const item of itemsToRemove) { + const entry = this._cache.get(item)!; + entry.store.dispose(); + this._cache.delete(item); + } + + this._items = newItems; + } + + public getItems(): TOut[] { + return this._items; + } +} + +export class ValueWithChangeEventFromObservable implements IValueWithChangeEvent { + constructor(public readonly observable: IObservable) { + } + + get onDidChange(): Event { + return Event.fromObservableLight(this.observable); + } + + get value(): T { + return this.observable.get(); + } +} + +export function observableFromValueWithChangeEvent(owner: DebugOwner, value: IValueWithChangeEvent): IObservable { + if (value instanceof ValueWithChangeEventFromObservable) { + return value.observable; + } + return observableFromEvent(owner, value.onDidChange, () => value.value); +} + +/** + * Creates an observable that has the latest changed value of the given observables. + * Initially (and when not observed), it has the value of the last observable. + * When observed and any of the observables change, it has the value of the last changed observable. + * If multiple observables change in the same transaction, the last observable wins. +*/ +export function latestChangedValue[]>(owner: DebugOwner, observables: T): IObservable> { + if (observables.length === 0) { + throw new BugIndicatingError(); + } + + let hasLastChangedValue = false; + let lastChangedValue: any = undefined; + + const result = observableFromEvent(owner, cb => { + const store = new DisposableStore(); + for (const o of observables) { + store.add(autorunOpts({ debugName: () => getDebugName(result, new DebugNameData(owner, undefined, undefined)) + '.updateLastChangedValue' }, reader => { + hasLastChangedValue = true; + lastChangedValue = o.read(reader); + cb(); + })); + } + store.add({ + dispose() { + hasLastChangedValue = false; + lastChangedValue = undefined; + }, + }); + return store; + }, () => { + if (hasLastChangedValue) { + return lastChangedValue; + } else { + return observables[observables.length - 1].get(); + } + }); + return result; +} + +/** + * Works like a derived. + * However, if the value is not undefined, it is cached and will not be recomputed anymore. + * In that case, the derived will unsubscribe from its dependencies. +*/ +export function derivedConstOnceDefined(owner: DebugOwner, fn: (reader: IReader) => T): IObservable { + return derivedObservableWithCache(owner, (reader, lastValue) => lastValue ?? fn(reader)); +} + +type RemoveUndefined = T extends undefined ? never : T; + +export function runOnChange(observable: IObservableWithChange, cb: (value: T, previousValue: undefined | T, deltas: RemoveUndefined[]) => void): IDisposable { + let _previousValue: T | undefined; + return autorunWithStoreHandleChanges({ + createEmptyChangeSummary: () => ({ deltas: [] as RemoveUndefined[], didChange: false }), + handleChange: (context, changeSummary) => { + if (context.didChange(observable)) { + const e = context.change; + if (e !== undefined) { + changeSummary.deltas.push(e as RemoveUndefined); + } + changeSummary.didChange = true; + } + return true; + }, + }, (reader, changeSummary) => { + const value = observable.read(reader); + const previousValue = _previousValue; + if (changeSummary.didChange) { + _previousValue = value; + cb(value, previousValue, changeSummary.deltas); + } + }); +} + +export function runOnChangeWithStore(observable: IObservableWithChange, cb: (value: T, previousValue: undefined | T, deltas: RemoveUndefined[], store: DisposableStore) => void): IDisposable { + const store = new DisposableStore(); + const disposable = runOnChange(observable, (value, previousValue: undefined | T, deltas) => { + store.clear(); + cb(value, previousValue, deltas, store); + }); + return { + dispose() { + disposable.dispose(); + store.dispose(); + } + }; +} diff --git a/packages/core/src/observableInternal/utilsCancellation.ts b/packages/core/src/observableInternal/utilsCancellation.ts new file mode 100644 index 00000000..4e1eb970 --- /dev/null +++ b/packages/core/src/observableInternal/utilsCancellation.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IReader, IObservable } from './base.js'; +import { DebugOwner, DebugNameData } from './debugName.js'; +import { CancellationError, CancellationToken, CancellationTokenSource } from './commonFacade/cancellation.js'; +import { Derived } from './derived.js'; +import { strictEquals } from './commonFacade/deps.js'; +import { autorun } from './autorun.js'; + +/** + * Resolves the promise when the observables state matches the predicate. + */ +export function waitForState(observable: IObservable): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => state is TState, isError?: (state: T) => boolean | unknown | undefined, cancellationToken?: CancellationToken): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean, isError?: (state: T) => boolean | unknown | undefined, cancellationToken?: CancellationToken): Promise; +export function waitForState(observable: IObservable, predicate?: (state: T) => boolean, isError?: (state: T) => boolean | unknown | undefined, cancellationToken?: CancellationToken): Promise { + if (!predicate) { + predicate = state => state !== null && state !== undefined; + } + return new Promise((resolve, reject) => { + let isImmediateRun = true; + let shouldDispose = false; + const stateObs = observable.map(state => { + /** @description waitForState.state */ + return { + isFinished: predicate(state), + error: isError ? isError(state) : false, + state + }; + }); + const d = autorun(reader => { + /** @description waitForState */ + const { isFinished, error, state } = stateObs.read(reader); + if (isFinished || error) { + if (isImmediateRun) { + // The variable `d` is not initialized yet + shouldDispose = true; + } else { + d.dispose(); + } + if (error) { + reject(error === true ? state : error); + } else { + resolve(state); + } + } + }); + if (cancellationToken) { + const dc = cancellationToken.onCancellationRequested(() => { + d.dispose(); + dc.dispose(); + reject(new CancellationError()); + }); + if (cancellationToken.isCancellationRequested) { + d.dispose(); + dc.dispose(); + reject(new CancellationError()); + return; + } + } + isImmediateRun = false; + if (shouldDispose) { + d.dispose(); + } + }); +} + +export function derivedWithCancellationToken(computeFn: (reader: IReader, cancellationToken: CancellationToken) => T): IObservable; +export function derivedWithCancellationToken(owner: object, computeFn: (reader: IReader, cancellationToken: CancellationToken) => T): IObservable; +export function derivedWithCancellationToken(computeFnOrOwner: ((reader: IReader, cancellationToken: CancellationToken) => T) | object, computeFnOrUndefined?: ((reader: IReader, cancellationToken: CancellationToken) => T)): IObservable { + let computeFn: (reader: IReader, store: CancellationToken) => T; + let owner: DebugOwner; + if (computeFnOrUndefined === undefined) { + computeFn = computeFnOrOwner as any; + owner = undefined; + } else { + owner = computeFnOrOwner; + computeFn = computeFnOrUndefined as any; + } + + let cancellationTokenSource: CancellationTokenSource | undefined = undefined; + return new Derived( + new DebugNameData(owner, undefined, computeFn), + r => { + if (cancellationTokenSource) { + cancellationTokenSource.dispose(true); + } + cancellationTokenSource = new CancellationTokenSource(); + return computeFn(r, cancellationTokenSource.token); + }, undefined, + undefined, + () => cancellationTokenSource?.dispose(), + strictEquals + ); +} diff --git a/packages/core/src/path.ts b/packages/core/src/path.ts new file mode 100644 index 00000000..77b097bd --- /dev/null +++ b/packages/core/src/path.ts @@ -0,0 +1,1529 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace +// Copied from: https://github.com/nodejs/node/commits/v20.9.0/lib/path.js +// Excluding: the change that adds primordials +// (https://github.com/nodejs/node/commit/187a862d221dec42fa9a5c4214e7034d9092792f and others) + +/** + * Copyright Joyent, Inc. and other Node contributors. + * + * 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. + */ + +import * as process from './process.js'; + +const CHAR_UPPERCASE_A = 65;/* A */ +const CHAR_LOWERCASE_A = 97; /* a */ +const CHAR_UPPERCASE_Z = 90; /* Z */ +const CHAR_LOWERCASE_Z = 122; /* z */ +const CHAR_DOT = 46; /* . */ +const CHAR_FORWARD_SLASH = 47; /* / */ +const CHAR_BACKWARD_SLASH = 92; /* \ */ +const CHAR_COLON = 58; /* : */ +const CHAR_QUESTION_MARK = 63; /* ? */ + +class ErrorInvalidArgType extends Error { + code: 'ERR_INVALID_ARG_TYPE'; + constructor(name: string, expected: string, actual: unknown) { + // determiner: 'must be' or 'must not be' + let determiner; + if (typeof expected === 'string' && expected.indexOf('not ') === 0) { + determiner = 'must not be'; + expected = expected.replace(/^not /, ''); + } else { + determiner = 'must be'; + } + + const type = name.indexOf('.') !== -1 ? 'property' : 'argument'; + let msg = `The "${name}" ${type} ${determiner} of type ${expected}`; + + msg += `. Received type ${typeof actual}`; + super(msg); + + this.code = 'ERR_INVALID_ARG_TYPE'; + } +} + +function validateObject(pathObject: object, name: string) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new ErrorInvalidArgType(name, 'Object', pathObject); + } +} + +function validateString(value: string, name: string) { + if (typeof value !== 'string') { + throw new ErrorInvalidArgType(name, 'string', value); + } +} + +const platformIsWin32 = (process.platform === 'win32'); + +function isPathSeparator(code: number | undefined) { + return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; +} + +function isPosixPathSeparator(code: number | undefined) { + return code === CHAR_FORWARD_SLASH; +} + +function isWindowsDeviceRoot(code: number) { + return (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) || + (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z); +} + +// Resolves . and .. elements in a path with directory names +function normalizeString(path: string, allowAboveRoot: boolean, separator: string, isPathSeparator: (code?: number) => boolean) { + let res = ''; + let lastSegmentLength = 0; + let lastSlash = -1; + let dots = 0; + let code = 0; + for (let i = 0; i <= path.length; ++i) { + if (i < path.length) { + code = path.charCodeAt(i); + } + else if (isPathSeparator(code)) { + break; + } + else { + code = CHAR_FORWARD_SLASH; + } + + if (isPathSeparator(code)) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (dots === 2) { + if (res.length < 2 || lastSegmentLength !== 2 || + res.charCodeAt(res.length - 1) !== CHAR_DOT || + res.charCodeAt(res.length - 2) !== CHAR_DOT) { + if (res.length > 2) { + const lastSlashIndex = res.lastIndexOf(separator); + if (lastSlashIndex === -1) { + res = ''; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf(separator); + } + lastSlash = i; + dots = 0; + continue; + } else if (res.length !== 0) { + res = ''; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + res += res.length > 0 ? `${separator}..` : '..'; + lastSegmentLength = 2; + } + } else { + if (res.length > 0) { + res += `${separator}${path.slice(lastSlash + 1, i)}`; + } + else { + res = path.slice(lastSlash + 1, i); + } + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === CHAR_DOT && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; +} + +function formatExt(ext: string): string { + return ext ? `${ext[0] === '.' ? '' : '.'}${ext}` : ''; +} + +function _format(sep: string, pathObject: ParsedPath) { + validateObject(pathObject, 'pathObject'); + const dir = pathObject.dir || pathObject.root; + const base = pathObject.base || + `${pathObject.name || ''}${formatExt(pathObject.ext)}`; + if (!dir) { + return base; + } + return dir === pathObject.root ? `${dir}${base}` : `${dir}${sep}${base}`; +} + +export interface ParsedPath { + root: string; + dir: string; + base: string; + ext: string; + name: string; +} + +export interface IPath { + normalize(path: string): string; + isAbsolute(path: string): boolean; + join(...paths: string[]): string; + resolve(...pathSegments: string[]): string; + relative(from: string, to: string): string; + dirname(path: string): string; + basename(path: string, suffix?: string): string; + extname(path: string): string; + format(pathObject: ParsedPath): string; + parse(path: string): ParsedPath; + toNamespacedPath(path: string): string; + sep: '\\' | '/'; + delimiter: string; + win32: IPath | null; + posix: IPath | null; +} + +export const win32: IPath = { + // path.resolve([from ...], to) + resolve(...pathSegments: string[]): string { + let resolvedDevice = ''; + let resolvedTail = ''; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1; i--) { + let path; + if (i >= 0) { + path = pathSegments[i]; + validateString(path, `paths[${i}]`); + + // Skip empty entries + if (path.length === 0) { + continue; + } + } else if (resolvedDevice.length === 0) { + path = process.cwd(); + } else { + // Windows has the concept of drive-specific current working + // directories. If we've resolved a drive letter but not yet an + // absolute path, get cwd for that drive, or the process cwd if + // the drive cwd is not available. We're sure the device is not + // a UNC path at this points, because UNC paths are always absolute. + path = process.env[`=${resolvedDevice}`] || process.cwd(); + + // Verify that a cwd was found and that it actually points + // to our drive. If not, default to the drive's root. + if (path === undefined || + (path.slice(0, 2).toLowerCase() !== resolvedDevice.toLowerCase() && + path.charCodeAt(2) === CHAR_BACKWARD_SLASH)) { + path = `${resolvedDevice}\\`; + } + } + + const len = path.length; + let rootEnd = 0; + let device = ''; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len === 1) { + if (isPathSeparator(code)) { + // `path` contains just a path separator + rootEnd = 1; + isAbsolute = true; + } + } else if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an + // absolute path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len || j !== last) { + // We matched a UNC root + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + + if (device.length > 0) { + if (resolvedDevice.length > 0) { + if (device.toLowerCase() !== resolvedDevice.toLowerCase()) { + // This path points to another device so it is not applicable + continue; + } + } else { + resolvedDevice = device; + } + } + + if (resolvedAbsolute) { + if (resolvedDevice.length > 0) { + break; + } + } else { + resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; + resolvedAbsolute = isAbsolute; + if (isAbsolute && resolvedDevice.length > 0) { + break; + } + } + } + + // At this point the path should be resolved to a full absolute path, + // but handle relative paths to be safe (might happen when process.cwd() + // fails) + + // Normalize the tail path + resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', + isPathSeparator); + + return resolvedAbsolute ? + `${resolvedDevice}\\${resolvedTail}` : + `${resolvedDevice}${resolvedTail}` || '.'; + }, + + normalize(path: string): string { + validateString(path, 'path'); + const len = path.length; + if (len === 0) { + return '.'; + } + let rootEnd = 0; + let device; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len === 1) { + // `path` contains just a single char, exit early to avoid + // unnecessary work + return isPosixPathSeparator(code) ? '\\' : path; + } + if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an absolute + // path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len) { + // We matched a UNC root only + // Return the normalized version of the UNC root since there + // is nothing left to process + return `\\\\${firstPart}\\${path.slice(last)}\\`; + } + if (j !== last) { + // We matched a UNC root with leftovers + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2 && isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + + let tail = rootEnd < len ? + normalizeString(path.slice(rootEnd), !isAbsolute, '\\', isPathSeparator) : + ''; + if (tail.length === 0 && !isAbsolute) { + tail = '.'; + } + if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) { + tail += '\\'; + } + if (device === undefined) { + return isAbsolute ? `\\${tail}` : tail; + } + return isAbsolute ? `${device}\\${tail}` : `${device}${tail}`; + }, + + isAbsolute(path: string): boolean { + validateString(path, 'path'); + const len = path.length; + if (len === 0) { + return false; + } + + const code = path.charCodeAt(0); + return isPathSeparator(code) || + // Possible device root + (len > 2 && + isWindowsDeviceRoot(code) && + path.charCodeAt(1) === CHAR_COLON && + isPathSeparator(path.charCodeAt(2))); + }, + + join(...paths: string[]): string { + if (paths.length === 0) { + return '.'; + } + + let joined; + let firstPart: string | undefined; + for (let i = 0; i < paths.length; ++i) { + const arg = paths[i]; + validateString(arg, 'path'); + if (arg.length > 0) { + if (joined === undefined) { + joined = firstPart = arg; + } + else { + joined += `\\${arg}`; + } + } + } + + if (joined === undefined) { + return '.'; + } + + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for a UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at a UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as a UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\\') + let needsReplace = true; + let slashCount = 0; + if (typeof firstPart === 'string' && isPathSeparator(firstPart.charCodeAt(0))) { + ++slashCount; + const firstLen = firstPart.length; + if (firstLen > 1 && isPathSeparator(firstPart.charCodeAt(1))) { + ++slashCount; + if (firstLen > 2) { + if (isPathSeparator(firstPart.charCodeAt(2))) { + ++slashCount; + } else { + // We matched a UNC path in the first part + needsReplace = false; + } + } + } + } + if (needsReplace) { + // Find any more consecutive slashes we need to replace + while (slashCount < joined.length && + isPathSeparator(joined.charCodeAt(slashCount))) { + slashCount++; + } + + // Replace the slashes if needed + if (slashCount >= 2) { + joined = `\\${joined.slice(slashCount)}`; + } + } + + return win32.normalize(joined); + }, + + + // It will solve the relative path from `from` to `to`, for instance: + // from = 'C:\\orandea\\test\\aaa' + // to = 'C:\\orandea\\impl\\bbb' + // The output of the function should be: '..\\..\\impl\\bbb' + relative(from: string, to: string): string { + validateString(from, 'from'); + validateString(to, 'to'); + + if (from === to) { + return ''; + } + + const fromOrig = win32.resolve(from); + const toOrig = win32.resolve(to); + + if (fromOrig === toOrig) { + return ''; + } + + from = fromOrig.toLowerCase(); + to = toOrig.toLowerCase(); + + if (from === to) { + return ''; + } + + // Trim any leading backslashes + let fromStart = 0; + while (fromStart < from.length && + from.charCodeAt(fromStart) === CHAR_BACKWARD_SLASH) { + fromStart++; + } + // Trim trailing backslashes (applicable to UNC paths only) + let fromEnd = from.length; + while (fromEnd - 1 > fromStart && + from.charCodeAt(fromEnd - 1) === CHAR_BACKWARD_SLASH) { + fromEnd--; + } + const fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + let toStart = 0; + while (toStart < to.length && + to.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + toStart++; + } + // Trim trailing backslashes (applicable to UNC paths only) + let toEnd = to.length; + while (toEnd - 1 > toStart && + to.charCodeAt(toEnd - 1) === CHAR_BACKWARD_SLASH) { + toEnd--; + } + const toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen; + let lastCommonSep = -1; + let i = 0; + for (; i < length; i++) { + const fromCode = from.charCodeAt(fromStart + i); + if (fromCode !== to.charCodeAt(toStart + i)) { + break; + } else if (fromCode === CHAR_BACKWARD_SLASH) { + lastCommonSep = i; + } + } + + // We found a mismatch before the first common path separator was seen, so + // return the original `to`. + if (i !== length) { + if (lastCommonSep === -1) { + return toOrig; + } + } else { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(toStart + i + 1); + } + if (i === 2) { + // We get here if `from` is the device root. + // For example: from='C:\\'; to='C:\\foo' + return toOrig.slice(toStart + i); + } + } + if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='C:\\foo\\bar'; to='C:\\foo' + lastCommonSep = i; + } else if (i === 2) { + // We get here if `to` is the device root. + // For example: from='C:\\foo\\bar'; to='C:\\' + lastCommonSep = 3; + } + } + if (lastCommonSep === -1) { + lastCommonSep = 0; + } + } + + let out = ''; + // Generate the relative path based on the path difference between `to` and + // `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { + out += out.length === 0 ? '..' : '\\..'; + } + } + + toStart += lastCommonSep; + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) { + return `${out}${toOrig.slice(toStart, toEnd)}`; + } + + if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + ++toStart; + } + + return toOrig.slice(toStart, toEnd); + }, + + toNamespacedPath(path: string): string { + // Note: this will *probably* throw somewhere. + if (typeof path !== 'string' || path.length === 0) { + return path; + } + + const resolvedPath = win32.resolve(path); + + if (resolvedPath.length <= 2) { + return path; + } + + if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { + // Possible UNC root + if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { + const code = resolvedPath.charCodeAt(2); + if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { + // Matched non-long UNC root, convert the path to a long UNC path + return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; + } + } + } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0)) && + resolvedPath.charCodeAt(1) === CHAR_COLON && + resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { + // Matched device root, convert the path to a long UNC path + return `\\\\?\\${resolvedPath}`; + } + + return path; + }, + + dirname(path: string): string { + validateString(path, 'path'); + const len = path.length; + if (len === 0) { + return '.'; + } + let rootEnd = -1; + let offset = 0; + const code = path.charCodeAt(0); + + if (len === 1) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work or a dot. + return isPathSeparator(code) ? path : '.'; + } + + // Try to match a root + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = offset = 1; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len) { + // We matched a UNC root only + return path; + } + if (j !== last) { + // We matched a UNC root with leftovers + + // Offset by 1 to include the separator after the UNC root to + // treat it as a "normal root" on top of a (UNC) root + rootEnd = offset = j + 1; + } + } + } + } + // Possible device root + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + rootEnd = len > 2 && isPathSeparator(path.charCodeAt(2)) ? 3 : 2; + offset = rootEnd; + } + + let end = -1; + let matchedSlash = true; + for (let i = len - 1; i >= offset; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) { + if (rootEnd === -1) { + return '.'; + } + + end = rootEnd; + } + return path.slice(0, end); + }, + + basename(path: string, suffix?: string): string { + if (suffix !== undefined) { + validateString(suffix, 'suffix'); + } + validateString(path, 'path'); + let start = 0; + let end = -1; + let matchedSlash = true; + let i; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + if (path.length >= 2 && + isWindowsDeviceRoot(path.charCodeAt(0)) && + path.charCodeAt(1) === CHAR_COLON) { + start = 2; + } + + if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) { + if (suffix === path) { + return ''; + } + let extIdx = suffix.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === suffix.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) { + end = firstNonSlashEnd; + } else if (end === -1) { + end = path.length; + } + return path.slice(start, end); + } + for (i = path.length - 1; i >= start; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); + }, + + extname(path: string): string { + validateString(path, 'path'); + let start = 0; + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + + if (path.length >= 2 && + path.charCodeAt(1) === CHAR_COLON && + isWindowsDeviceRoot(path.charCodeAt(0))) { + start = startPart = 2; + } + + for (let i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } + else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, + + format: _format.bind(null, '\\'), + + parse(path) { + validateString(path, 'path'); + + const ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) { + return ret; + } + + const len = path.length; + let rootEnd = 0; + let code = path.charCodeAt(0); + + if (len === 1) { + if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + ret.base = ret.name = path; + return ret; + } + // Try to match a root + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = 1; + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + while (j < len && isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + while (j < len && !isPathSeparator(path.charCodeAt(j))) { + j++; + } + if (j === len) { + // We matched a UNC root only + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + rootEnd = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code) && path.charCodeAt(1) === CHAR_COLON) { + // Possible device root + if (len <= 2) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 2; + if (isPathSeparator(path.charCodeAt(2))) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 3; + } + } + if (rootEnd > 0) { + ret.root = path.slice(0, rootEnd); + } + + let startDot = -1; + let startPart = rootEnd; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= rootEnd; --i) { + code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (end !== -1) { + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + ret.base = ret.name = path.slice(startPart, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + ret.ext = path.slice(startDot, end); + } + } + + // If the directory is the root, use the entire root as the `dir` including + // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the + // trailing slash (`C:\abc\def` -> `C:\abc`). + if (startPart > 0 && startPart !== rootEnd) { + ret.dir = path.slice(0, startPart - 1); + } else { + ret.dir = ret.root; + } + + return ret; + }, + + sep: '\\', + delimiter: ';', + win32: null, + posix: null +}; + +const posixCwd = (() => { + if (platformIsWin32) { + // Converts Windows' backslash path separators to POSIX forward slashes + // and truncates any drive indicator + const regexp = /\\/g; + return () => { + const cwd = process.cwd().replace(regexp, '/'); + return cwd.slice(cwd.indexOf('/')); + }; + } + + // We're already on POSIX, no need for any transformations + return () => process.cwd(); +})(); + +export const posix: IPath = { + // path.resolve([from ...], to) + resolve(...pathSegments: string[]): string { + let resolvedPath = ''; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + const path = i >= 0 ? pathSegments[i] : posixCwd(); + + validateString(path, `paths[${i}]`); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + resolvedPath = `${path}/${resolvedPath}`; + resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', + isPosixPathSeparator); + + if (resolvedAbsolute) { + return `/${resolvedPath}`; + } + return resolvedPath.length > 0 ? resolvedPath : '.'; + }, + + normalize(path: string): string { + validateString(path, 'path'); + + if (path.length === 0) { + return '.'; + } + + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + const trailingSeparator = + path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; + + // Normalize the path + path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator); + + if (path.length === 0) { + if (isAbsolute) { + return '/'; + } + return trailingSeparator ? './' : '.'; + } + if (trailingSeparator) { + path += '/'; + } + + return isAbsolute ? `/${path}` : path; + }, + + isAbsolute(path: string): boolean { + validateString(path, 'path'); + return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH; + }, + + join(...paths: string[]): string { + if (paths.length === 0) { + return '.'; + } + let joined; + for (let i = 0; i < paths.length; ++i) { + const arg = paths[i]; + validateString(arg, 'path'); + if (arg.length > 0) { + if (joined === undefined) { + joined = arg; + } else { + joined += `/${arg}`; + } + } + } + if (joined === undefined) { + return '.'; + } + return posix.normalize(joined); + }, + + relative(from: string, to: string): string { + validateString(from, 'from'); + validateString(to, 'to'); + + if (from === to) { + return ''; + } + + // Trim leading forward slashes. + from = posix.resolve(from); + to = posix.resolve(to); + + if (from === to) { + return ''; + } + + const fromStart = 1; + const fromEnd = from.length; + const fromLen = fromEnd - fromStart; + const toStart = 1; + const toLen = to.length - toStart; + + // Compare paths to find the longest common path from root + const length = (fromLen < toLen ? fromLen : toLen); + let lastCommonSep = -1; + let i = 0; + for (; i < length; i++) { + const fromCode = from.charCodeAt(fromStart + i); + if (fromCode !== to.charCodeAt(toStart + i)) { + break; + } else if (fromCode === CHAR_FORWARD_SLASH) { + lastCommonSep = i; + } + } + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } + if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo/bar'; to='/' + lastCommonSep = 0; + } + } + } + + let out = ''; + // Generate the relative path based on the path difference between `to` + // and `from`. + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { + out += out.length === 0 ? '..' : '/..'; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts. + return `${out}${to.slice(toStart + lastCommonSep)}`; + }, + + toNamespacedPath(path: string): string { + // Non-op on posix systems + return path; + }, + + dirname(path: string): string { + validateString(path, 'path'); + if (path.length === 0) { + return '.'; + } + const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let end = -1; + let matchedSlash = true; + for (let i = path.length - 1; i >= 1; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) { + return hasRoot ? '/' : '.'; + } + if (hasRoot && end === 1) { + return '//'; + } + return path.slice(0, end); + }, + + basename(path: string, suffix?: string): string { + if (suffix !== undefined) { + validateString(suffix, 'ext'); + } + validateString(path, 'path'); + + let start = 0; + let end = -1; + let matchedSlash = true; + let i; + + if (suffix !== undefined && suffix.length > 0 && suffix.length <= path.length) { + if (suffix === path) { + return ''; + } + let extIdx = suffix.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === suffix.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) { + end = firstNonSlashEnd; + } else if (end === -1) { + end = path.length; + } + return path.slice(start, end); + } + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); + }, + + extname(path: string): string { + validateString(path, 'path'); + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + for (let i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } + else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, + + format: _format.bind(null, '/'), + + parse(path: string): ParsedPath { + validateString(path, 'path'); + + const ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) { + return ret; + } + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let start; + if (isAbsolute) { + ret.root = '/'; + start = 1; + } else { + start = 0; + } + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (end !== -1) { + const start = startPart === 0 && isAbsolute ? 1 : startPart; + if (startDot === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + ret.base = ret.name = path.slice(start, end); + } else { + ret.name = path.slice(start, startDot); + ret.base = path.slice(start, end); + ret.ext = path.slice(startDot, end); + } + } + + if (startPart > 0) { + ret.dir = path.slice(0, startPart - 1); + } else if (isAbsolute) { + ret.dir = '/'; + } + + return ret; + }, + + sep: '/', + delimiter: ':', + win32: null, + posix: null +}; + +posix.win32 = win32.win32 = win32; +posix.posix = win32.posix = posix; + +export const normalize = (platformIsWin32 ? win32.normalize : posix.normalize); +export const isAbsolute = (platformIsWin32 ? win32.isAbsolute : posix.isAbsolute); +export const join = (platformIsWin32 ? win32.join : posix.join); +export const resolve = (platformIsWin32 ? win32.resolve : posix.resolve); +export const relative = (platformIsWin32 ? win32.relative : posix.relative); +export const dirname = (platformIsWin32 ? win32.dirname : posix.dirname); +export const basename = (platformIsWin32 ? win32.basename : posix.basename); +export const extname = (platformIsWin32 ? win32.extname : posix.extname); +export const format = (platformIsWin32 ? win32.format : posix.format); +export const parse = (platformIsWin32 ? win32.parse : posix.parse); +export const toNamespacedPath = (platformIsWin32 ? win32.toNamespacedPath : posix.toNamespacedPath); +export const sep = (platformIsWin32 ? win32.sep : posix.sep); +export const delimiter = (platformIsWin32 ? win32.delimiter : posix.delimiter); diff --git a/packages/core/src/platform.ts b/packages/core/src/platform.ts new file mode 100644 index 00000000..d516a2da --- /dev/null +++ b/packages/core/src/platform.ts @@ -0,0 +1,280 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from './nls.js'; + +export const LANGUAGE_DEFAULT = 'en'; + +let _isWindows = false; +let _isMacintosh = false; +let _isLinux = false; +let _isLinuxSnap = false; +let _isNative = false; +let _isWeb = false; +let _isElectron = false; +let _isIOS = false; +let _isCI = false; +let _isMobile = false; +let _locale: string | undefined = undefined; +let _language: string = LANGUAGE_DEFAULT; +let _platformLocale: string = LANGUAGE_DEFAULT; +let _translationsConfigFile: string | undefined = undefined; +let _userAgent: string | undefined = undefined; + +export interface IProcessEnvironment { + [key: string]: string | undefined; +} + +/** + * This interface is intentionally not identical to node.js + * process because it also works in sandboxed environments + * where the process object is implemented differently. We + * define the properties here that we need for `platform` + * to work and nothing else. + */ +export interface INodeProcess { + platform: string; + arch: string; + env: IProcessEnvironment; + versions?: { + node?: string; + electron?: string; + chrome?: string; + }; + type?: string; + cwd: () => string; +} + +declare const process: INodeProcess; + +const $globalThis: any = globalThis; + +let nodeProcess: INodeProcess | undefined = undefined; +if (typeof $globalThis.vscode !== 'undefined' && typeof $globalThis.vscode.process !== 'undefined') { + // Native environment (sandboxed) + nodeProcess = $globalThis.vscode.process; +} else if (typeof process !== 'undefined' && typeof process?.versions?.node === 'string') { + // Native environment (non-sandboxed) + nodeProcess = process; +} + +const isElectronProcess = typeof nodeProcess?.versions?.electron === 'string'; +const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer'; + +interface INavigator { + userAgent: string; + maxTouchPoints?: number; + language: string; +} +declare const navigator: INavigator; + +// Native environment +if (typeof nodeProcess === 'object') { + _isWindows = (nodeProcess.platform === 'win32'); + _isMacintosh = (nodeProcess.platform === 'darwin'); + _isLinux = (nodeProcess.platform === 'linux'); + _isLinuxSnap = _isLinux && !!nodeProcess.env['SNAP'] && !!nodeProcess.env['SNAP_REVISION']; + _isElectron = isElectronProcess; + _isCI = !!nodeProcess.env['CI'] || !!nodeProcess.env['BUILD_ARTIFACTSTAGINGDIRECTORY']; + _locale = LANGUAGE_DEFAULT; + _language = LANGUAGE_DEFAULT; + const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG']; + if (rawNlsConfig) { + try { + const nlsConfig: nls.INLSConfiguration = JSON.parse(rawNlsConfig); + _locale = nlsConfig.userLocale; + _platformLocale = nlsConfig.osLocale; + _language = nlsConfig.resolvedLanguage || LANGUAGE_DEFAULT; + _translationsConfigFile = nlsConfig.languagePack?.translationsConfigFile; + } catch (e) { + } + } + _isNative = true; +} + +// Web environment +else if (typeof navigator === 'object' && !isElectronRenderer) { + _userAgent = navigator.userAgent; + _isWindows = _userAgent.indexOf('Windows') >= 0; + _isMacintosh = _userAgent.indexOf('Macintosh') >= 0; + _isIOS = (_userAgent.indexOf('Macintosh') >= 0 || _userAgent.indexOf('iPad') >= 0 || _userAgent.indexOf('iPhone') >= 0) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0; + _isLinux = _userAgent.indexOf('Linux') >= 0; + _isMobile = _userAgent?.indexOf('Mobi') >= 0; + _isWeb = true; + _language = nls.getNLSLanguage() || LANGUAGE_DEFAULT; + _locale = navigator.language.toLowerCase(); + _platformLocale = _locale; +} + +// Unknown environment +else { + console.error('Unable to resolve platform.'); +} + +export const enum Platform { + Web, + Mac, + Linux, + Windows +} +export type PlatformName = 'Web' | 'Windows' | 'Mac' | 'Linux'; + +export function PlatformToString(platform: Platform): PlatformName { + switch (platform) { + case Platform.Web: return 'Web'; + case Platform.Mac: return 'Mac'; + case Platform.Linux: return 'Linux'; + case Platform.Windows: return 'Windows'; + } +} + +let _platform: Platform = Platform.Web; +if (_isMacintosh) { + _platform = Platform.Mac; +} else if (_isWindows) { + _platform = Platform.Windows; +} else if (_isLinux) { + _platform = Platform.Linux; +} + +export const isWindows = _isWindows; +export const isMacintosh = _isMacintosh; +export const isLinux = _isLinux; +export const isLinuxSnap = _isLinuxSnap; +export const isNative = _isNative; +export const isElectron = _isElectron; +export const isWeb = _isWeb; +export const isWebWorker = (_isWeb && typeof $globalThis.importScripts === 'function'); +export const webWorkerOrigin = isWebWorker ? $globalThis.origin : undefined; +export const isIOS = _isIOS; +export const isMobile = _isMobile; +/** + * Whether we run inside a CI environment, such as + * GH actions or Azure Pipelines. + */ +export const isCI = _isCI; +export const platform = _platform; +export const userAgent = _userAgent; + +/** + * The language used for the user interface. The format of + * the string is all lower case (e.g. zh-tw for Traditional + * Chinese or de for German) + */ +export const language = _language; + +export namespace Language { + + export function value(): string { + return language; + } + + export function isDefaultVariant(): boolean { + if (language.length === 2) { + return language === 'en'; + } else if (language.length >= 3) { + return language[0] === 'e' && language[1] === 'n' && language[2] === '-'; + } else { + return false; + } + } + + export function isDefault(): boolean { + return language === 'en'; + } +} + +/** + * Desktop: The OS locale or the locale specified by --locale or `argv.json`. + * Web: matches `platformLocale`. + * + * The UI is not necessarily shown in the provided locale. + */ +export const locale = _locale; + +/** + * This will always be set to the OS/browser's locale regardless of + * what was specified otherwise. The format of the string is all + * lower case (e.g. zh-tw for Traditional Chinese). The UI is not + * necessarily shown in the provided locale. + */ +export const platformLocale = _platformLocale; + +/** + * The translations that are available through language packs. + */ +export const translationsConfigFile = _translationsConfigFile; + +export const setTimeout0IsFaster = (typeof $globalThis.postMessage === 'function' && !$globalThis.importScripts); + +/** + * See https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#:~:text=than%204%2C%20then-,set%20timeout%20to%204,-. + * + * Works similarly to `setTimeout(0)` but doesn't suffer from the 4ms artificial delay + * that browsers set when the nesting level is > 5. + */ +export const setTimeout0 = (() => { + if (setTimeout0IsFaster) { + interface IQueueElement { + id: number; + callback: () => void; + } + const pending: IQueueElement[] = []; + + $globalThis.addEventListener('message', (e: any) => { + if (e.data && e.data.vscodeScheduleAsyncWork) { + for (let i = 0, len = pending.length; i < len; i++) { + const candidate = pending[i]; + if (candidate.id === e.data.vscodeScheduleAsyncWork) { + pending.splice(i, 1); + candidate.callback(); + return; + } + } + } + }); + let lastId = 0; + return (callback: () => void) => { + const myId = ++lastId; + pending.push({ + id: myId, + callback: callback + }); + $globalThis.postMessage({ vscodeScheduleAsyncWork: myId }, '*'); + }; + } + return (callback: () => void) => setTimeout(callback); +})(); + +export const enum OperatingSystem { + Windows = 1, + Macintosh = 2, + Linux = 3 +} +export const OS = (_isMacintosh || _isIOS ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux)); + +let _isLittleEndian = true; +let _isLittleEndianComputed = false; +export function isLittleEndian(): boolean { + if (!_isLittleEndianComputed) { + _isLittleEndianComputed = true; + const test = new Uint8Array(2); + test[0] = 1; + test[1] = 2; + const view = new Uint16Array(test.buffer); + _isLittleEndian = (view[0] === (2 << 8) + 1); + } + return _isLittleEndian; +} + +export const isChrome = !!(userAgent && userAgent.indexOf('Chrome') >= 0); +export const isFirefox = !!(userAgent && userAgent.indexOf('Firefox') >= 0); +export const isSafari = !!(!isChrome && (userAgent && userAgent.indexOf('Safari') >= 0)); +export const isEdge = !!(userAgent && userAgent.indexOf('Edg/') >= 0); +export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0); + +export function isBigSurOrNewer(osVersion: string): boolean { + return parseFloat(osVersion) >= 20; +} diff --git a/packages/core/src/primitives.ts b/packages/core/src/primitives.ts new file mode 100644 index 00000000..5aac9b58 --- /dev/null +++ b/packages/core/src/primitives.ts @@ -0,0 +1,200 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +const _typeof = { + number: 'number', + string: 'string', + undefined: 'undefined', + object: 'object', + function: 'function' +}; + +/** + * @returns whether the provided parameter is of type `Buffer` or Uint8Array dervived type + */ +export function isTypedArray(obj: unknown): obj is Object { + const TypedArray = Object.getPrototypeOf(Uint8Array); + return typeof obj === 'object' + && obj instanceof TypedArray; +} + +/** + * @returns whether the provided parameter is a JavaScript Array or not. + */ +export function isArray(array: any): array is any[] { + if (Array.isArray) { + return Array.isArray(array); + } + + if (array && typeof (array.length) === _typeof.number && array.constructor === Array) { + return true; + } + + return false; +} + +/** + * @returns whether the provided parameter is a JavaScript String or not. + */ +export function isString(str: any): str is string { + if (typeof (str) === _typeof.string || str instanceof String) { + return true; + } + + return false; +} + +/** + * @returns whether the provided parameter is a JavaScript Array and each element in the array is a string. + */ +export function isStringArray(value: any): value is string[] { + return isArray(value) && (value).every(elem => isString(elem)); +} + +/** + * + * @returns whether the provided parameter is of type `object` but **not** + * `null`, an `array`, a `regexp`, nor a `date`. + */ +export function isObject(obj: any): boolean { + // The method can't do a type cast since there are type (like strings) which + // are subclasses of any put not positvely matched by the function. Hence type + // narrowing results in wrong results. + return typeof obj === _typeof.object + && obj !== null + && !Array.isArray(obj) + && !(obj instanceof RegExp) + && !(obj instanceof Date); +} + +/** + * In **contrast** to just checking `typeof` this will return `false` for `NaN`. + * @returns whether the provided parameter is a JavaScript Number or not. + */ +export function isNumber(obj: any): obj is number { + if ((typeof (obj) === _typeof.number || obj instanceof Number) && !isNaN(obj)) { + return true; + } + + return false; +} + +/** + * @returns whether the provided parameter is a JavaScript Boolean or not. + */ +export function isBoolean(obj: any): obj is boolean { + return obj === true || obj === false; +} + +/** + * @returns whether the provided parameter is undefined. + */ +export function isUndefined(obj: any): boolean { + return typeof (obj) === _typeof.undefined; +} + +/** + * @returns whether the provided parameter is undefined or null. + */ +export function isUndefinedOrNull(obj: any): boolean { + return isUndefined(obj) || obj === null; +} + + +const hasOwnProperty = Object.prototype.hasOwnProperty; + +/** + * @returns whether the provided parameter is an empty JavaScript Object or not. + */ +export function isEmptyObject(obj: any): obj is any { + if (!isObject(obj)) { + return false; + } + + for (const key in obj) { + if (hasOwnProperty.call(obj, key)) { + return false; + } + } + + return true; +} + +/** + * @returns whether the provided parameter is a JavaScript Function or not. + */ +export function isFunction(obj: any): obj is Function { + return typeof obj === _typeof.function; +} + +/** + * @returns whether the provided parameters is are JavaScript Function or not. + */ +export function areFunctions(...objects: any[]): boolean { + return objects && objects.length > 0 && objects.every(isFunction); +} + +export type TypeConstraint = string | Function; + +export function validateConstraints(args: any[], constraints: TypeConstraint[]): void { + const len = Math.min(args.length, constraints.length); + for (let i = 0; i < len; i++) { + validateConstraint(args[i], constraints[i]); + } +} + +export function validateConstraint(arg: any, constraint: TypeConstraint): void { + + if (isString(constraint)) { + if (typeof arg !== constraint) { + throw new Error(`argument does not match constraint: typeof ${constraint}`); + } + } else if (isFunction(constraint)) { + if (arg instanceof constraint) { + return; + } + if (arg && arg.constructor === constraint) { + return; + } + if (constraint.length === 1 && constraint.call(undefined, arg) === true) { + return; + } + throw new Error(`argument does not match one of these constraints: arg instanceof constraint, arg.constructor === constraint, nor constraint(arg) === true`); + } +} + +/** + * Creates a new object of the provided class and will call the constructor with + * any additional argument supplied. + */ +export function create(ctor: Function, ...args: any[]): any { + const obj = Object.create(ctor.prototype); + ctor.apply(obj, args); + + return obj; +} + +export type IFunction0 = () => T; +export type IFunction1 = (a1: A1) => T; +export type IFunction2 = (a1: A1, a2: A2) => T; +export type IFunction3 = (a1: A1, a2: A2, a3: A3) => T; +export type IFunction4 = (a1: A1, a2: A2, a3: A3, a4: A4) => T; +export type IFunction5 = (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => T; +export type IFunction6 = (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6) => T; +export type IFunction7 = (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7) => T; +export type IFunction8 = (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8) => T; + +export interface IAction0 extends IFunction0 { } +export interface IAction1 extends IFunction1 { } +export interface IAction2 extends IFunction2 { } +export interface IAction3 extends IFunction3 { } +export interface IAction4 extends IFunction4 { } +export interface IAction5 extends IFunction5 { } +export interface IAction6 extends IFunction6 { } +export interface IAction7 extends IFunction7 { } +export interface IAction8 extends IFunction8 { } + +export type NumberCallback = (index: number) => void; diff --git a/packages/core/src/process.ts b/packages/core/src/process.ts new file mode 100644 index 00000000..4687b6ec --- /dev/null +++ b/packages/core/src/process.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isMacintosh, isWindows, type INodeProcess } from './platform.js'; + +let safeProcess: Omit & { arch: string | undefined }; +declare const process: INodeProcess; + +// Native sandbox environment +const vscodeGlobal = (globalThis as any).vscode; +if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') { + const sandboxProcess: INodeProcess = vscodeGlobal.process; + safeProcess = { + get platform() { return sandboxProcess.platform; }, + get arch() { return sandboxProcess.arch; }, + get env() { return sandboxProcess.env; }, + cwd() { return sandboxProcess.cwd(); } + }; +} + +// Native node.js environment +else if (typeof process !== 'undefined' && typeof process?.versions?.node === 'string') { + safeProcess = { + get platform() { return process.platform; }, + get arch() { return process.arch; }, + get env() { return process.env; }, + cwd() { return process.env['VSCODE_CWD'] || process.cwd(); } + }; +} + +// Web environment +else { + safeProcess = { + + // Supported + get platform() { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; }, + get arch() { return undefined; /* arch is undefined in web */ }, + + // Unsupported + get env() { return {}; }, + cwd() { return '/'; } + }; +} + +/** + * Provides safe access to the `cwd` property in node.js, sandboxed or web + * environments. + * + * Note: in web, this property is hardcoded to be `/`. + * + * @skipMangle + */ +export const cwd = safeProcess.cwd; + +/** + * Provides safe access to the `env` property in node.js, sandboxed or web + * environments. + * + * Note: in web, this property is hardcoded to be `{}`. + */ +export const env = safeProcess.env; + +/** + * Provides safe access to the `platform` property in node.js, sandboxed or web + * environments. + */ +export const platform = safeProcess.platform; + +/** + * Provides safe access to the `arch` method in node.js, sandboxed or web + * environments. + * Note: `arch` is `undefined` in web + */ +export const arch = safeProcess.arch; diff --git a/packages/core/src/resources.ts b/packages/core/src/resources.ts new file mode 100644 index 00000000..2944e1ba --- /dev/null +++ b/packages/core/src/resources.ts @@ -0,0 +1,444 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CharCode } from './charCode.js'; +import * as extpath from './extpath.js'; +import { Schemas } from './network.js'; +import * as paths from './path.js'; +import { isLinux, isWindows } from './platform.js'; +import { compare as strCompare, equalsIgnoreCase } from './strings.js'; +import { URI, uriToFsPath } from './uri.js'; + +export function originalFSPath(uri: URI): string { + return uriToFsPath(uri, true); +} + +//#region IExtUri + +export interface IExtUri { + + // --- identity + + /** + * Compares two uris. + * + * @param uri1 Uri + * @param uri2 Uri + * @param ignoreFragment Ignore the fragment (defaults to `false`) + */ + compare(uri1: URI, uri2: URI, ignoreFragment?: boolean): number; + + /** + * Tests whether two uris are equal + * + * @param uri1 Uri + * @param uri2 Uri + * @param ignoreFragment Ignore the fragment (defaults to `false`) + */ + isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment?: boolean): boolean; + + /** + * Tests whether a `candidate` URI is a parent or equal of a given `base` URI. + * + * @param base A uri which is "longer" or at least same length as `parentCandidate` + * @param parentCandidate A uri which is "shorter" or up to same length as `base` + * @param ignoreFragment Ignore the fragment (defaults to `false`) + */ + isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean; + + /** + * Creates a key from a resource URI to be used to resource comparison and for resource maps. + * @see {@link ResourceMap} + * @param uri Uri + * @param ignoreFragment Ignore the fragment (defaults to `false`) + */ + getComparisonKey(uri: URI, ignoreFragment?: boolean): string; + + /** + * Whether the casing of the path-component of the uri should be ignored. + */ + ignorePathCasing(uri: URI): boolean; + + // --- path math + + basenameOrAuthority(resource: URI): string; + + /** + * Returns the basename of the path component of an uri. + * @param resource + */ + basename(resource: URI): string; + + /** + * Returns the extension of the path component of an uri. + * @param resource + */ + extname(resource: URI): string; + /** + * Return a URI representing the directory of a URI path. + * + * @param resource The input URI. + * @returns The URI representing the directory of the input URI. + */ + dirname(resource: URI): URI; + /** + * Join a URI path with path fragments and normalizes the resulting path. + * + * @param resource The input URI. + * @param pathFragment The path fragment to add to the URI path. + * @returns The resulting URI. + */ + joinPath(resource: URI, ...pathFragment: string[]): URI; + /** + * Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names. + * + * @param resource The URI to normalize the path. + * @returns The URI with the normalized path. + */ + normalizePath(resource: URI): URI; + /** + * + * @param from + * @param to + */ + relativePath(from: URI, to: URI): string | undefined; + /** + * Resolves an absolute or relative path against a base URI. + * The path can be relative or absolute posix or a Windows path + */ + resolvePath(base: URI, path: string): URI; + + // --- misc + + /** + * Returns true if the URI path is absolute. + */ + isAbsolutePath(resource: URI): boolean; + /** + * Tests whether the two authorities are the same + */ + isEqualAuthority(a1: string, a2: string): boolean; + /** + * Returns true if the URI path has a trailing path separator + */ + hasTrailingPathSeparator(resource: URI, sep?: string): boolean; + /** + * Removes a trailing path separator, if there's one. + * Important: Doesn't remove the first slash, it would make the URI invalid + */ + removeTrailingPathSeparator(resource: URI, sep?: string): URI; + /** + * Adds a trailing path separator to the URI if there isn't one already. + * For example, c:\ would be unchanged, but c:\users would become c:\users\ + */ + addTrailingPathSeparator(resource: URI, sep?: string): URI; +} + +export class ExtUri implements IExtUri { + + constructor(private _ignorePathCasing: (uri: URI) => boolean) { } + + compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number { + if (uri1 === uri2) { + return 0; + } + return strCompare(this.getComparisonKey(uri1, ignoreFragment), this.getComparisonKey(uri2, ignoreFragment)); + } + + isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean { + if (uri1 === uri2) { + return true; + } + if (!uri1 || !uri2) { + return false; + } + return this.getComparisonKey(uri1, ignoreFragment) === this.getComparisonKey(uri2, ignoreFragment); + } + + getComparisonKey(uri: URI, ignoreFragment: boolean = false): string { + return uri.with({ + path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined, + fragment: ignoreFragment ? null : undefined + }).toString(); + } + + ignorePathCasing(uri: URI): boolean { + return this._ignorePathCasing(uri); + } + + isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean { + if (base.scheme === parentCandidate.scheme) { + if (base.scheme === Schemas.file) { + return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment); + } + if (isEqualAuthority(base.authority, parentCandidate.authority)) { + return extpath.isEqualOrParent(base.path, parentCandidate.path, this._ignorePathCasing(base), '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment); + } + } + return false; + } + + // --- path math + + joinPath(resource: URI, ...pathFragment: string[]): URI { + return URI.joinPath(resource, ...pathFragment); + } + + basenameOrAuthority(resource: URI): string { + return basename(resource) || resource.authority; + } + + basename(resource: URI): string { + return paths.posix.basename(resource.path); + } + + extname(resource: URI): string { + return paths.posix.extname(resource.path); + } + + dirname(resource: URI): URI { + if (resource.path.length === 0) { + return resource; + } + let dirname; + if (resource.scheme === Schemas.file) { + dirname = URI.file(paths.dirname(originalFSPath(resource))).path; + } else { + dirname = paths.posix.dirname(resource.path); + if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) { + console.error(`dirname("${resource.toString})) resulted in a relative path`); + dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character + } + } + return resource.with({ + path: dirname + }); + } + + normalizePath(resource: URI): URI { + if (!resource.path.length) { + return resource; + } + let normalizedPath: string; + if (resource.scheme === Schemas.file) { + normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path; + } else { + normalizedPath = paths.posix.normalize(resource.path); + } + return resource.with({ + path: normalizedPath + }); + } + + relativePath(from: URI, to: URI): string | undefined { + if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) { + return undefined; + } + if (from.scheme === Schemas.file) { + const relativePath = paths.relative(originalFSPath(from), originalFSPath(to)); + return isWindows ? extpath.toSlashes(relativePath) : relativePath; + } + let fromPath = from.path || '/'; + const toPath = to.path || '/'; + if (this._ignorePathCasing(from)) { + // make casing of fromPath match toPath + let i = 0; + for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) { + if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) { + if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) { + break; + } + } + } + fromPath = toPath.substr(0, i) + fromPath.substr(i); + } + return paths.posix.relative(fromPath, toPath); + } + + resolvePath(base: URI, path: string): URI { + if (base.scheme === Schemas.file) { + const newURI = URI.file(paths.resolve(originalFSPath(base), path)); + return base.with({ + authority: newURI.authority, + path: newURI.path + }); + } + path = extpath.toPosixPath(path); // we allow path to be a windows path + return base.with({ + path: paths.posix.resolve(base.path, path) + }); + } + + // --- misc + + isAbsolutePath(resource: URI): boolean { + return !!resource.path && resource.path[0] === '/'; + } + + isEqualAuthority(a1: string | undefined, a2: string | undefined) { + return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2)); + } + + hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean { + if (resource.scheme === Schemas.file) { + const fsp = originalFSPath(resource); + return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep; + } else { + const p = resource.path; + return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0 + } + } + + removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI { + // Make sure that the path isn't a drive letter. A trailing separator there is not removable. + if (hasTrailingPathSeparator(resource, sep)) { + return resource.with({ path: resource.path.substr(0, resource.path.length - 1) }); + } + return resource; + } + + addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI { + let isRootSep: boolean = false; + if (resource.scheme === Schemas.file) { + const fsp = originalFSPath(resource); + isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep)); + } else { + sep = '/'; + const p = resource.path; + isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash; + } + if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) { + return resource.with({ path: resource.path + '/' }); + } + return resource; + } +} + + +/** + * Unbiased utility that takes uris "as they are". This means it can be interchanged with + * uri#toString() usages. The following is true + * ``` + * assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri)) + * ``` + */ +export const extUri = new ExtUri(() => false); + +/** + * BIASED utility that _mostly_ ignored the case of urs paths. ONLY use this util if you + * understand what you are doing. + * + * This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged. + * + * When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient + * because those uris come from a "trustworthy source". When creating unknown uris it's always + * better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path + * casing matters. + */ +export const extUriBiasedIgnorePathCase = new ExtUri(uri => { + // A file scheme resource is in the same platform as code, so ignore case for non linux platforms + // Resource can be from another platform. Lowering the case as an hack. Should come from File system provider + return uri.scheme === Schemas.file ? !isLinux : true; +}); + + +/** + * BIASED utility that always ignores the casing of uris paths. ONLY use this util if you + * understand what you are doing. + * + * This utility is INCOMPATIBLE with `uri.toString()`-usages and both CANNOT be used interchanged. + * + * When dealing with uris from files or documents, `extUri` (the unbiased friend)is sufficient + * because those uris come from a "trustworthy source". When creating unknown uris it's always + * better to use `IUriIdentityService` which exposes an `IExtUri`-instance which knows when path + * casing matters. + */ +export const extUriIgnorePathCase = new ExtUri(_ => true); + +export const isEqual = extUri.isEqual.bind(extUri); +export const isEqualOrParent = extUri.isEqualOrParent.bind(extUri); +export const getComparisonKey = extUri.getComparisonKey.bind(extUri); +export const basenameOrAuthority = extUri.basenameOrAuthority.bind(extUri); +export const basename = extUri.basename.bind(extUri); +export const extname = extUri.extname.bind(extUri); +export const dirname = extUri.dirname.bind(extUri); +export const joinPath = extUri.joinPath.bind(extUri); +export const normalizePath = extUri.normalizePath.bind(extUri); +export const relativePath = extUri.relativePath.bind(extUri); +export const resolvePath = extUri.resolvePath.bind(extUri); +export const isAbsolutePath = extUri.isAbsolutePath.bind(extUri); +export const isEqualAuthority = extUri.isEqualAuthority.bind(extUri); +export const hasTrailingPathSeparator = extUri.hasTrailingPathSeparator.bind(extUri); +export const removeTrailingPathSeparator = extUri.removeTrailingPathSeparator.bind(extUri); +export const addTrailingPathSeparator = extUri.addTrailingPathSeparator.bind(extUri); + +//#endregion + +export function distinctParents(items: T[], resourceAccessor: (item: T) => URI): T[] { + const distinctParents: T[] = []; + for (let i = 0; i < items.length; i++) { + const candidateResource = resourceAccessor(items[i]); + if (items.some((otherItem, index) => { + if (index === i) { + return false; + } + + return isEqualOrParent(candidateResource, resourceAccessor(otherItem)); + })) { + continue; + } + + distinctParents.push(items[i]); + } + + return distinctParents; +} + +/** + * Data URI related helpers. + */ +export namespace DataUri { + + export const META_DATA_LABEL = 'label'; + export const META_DATA_DESCRIPTION = 'description'; + export const META_DATA_SIZE = 'size'; + export const META_DATA_MIME = 'mime'; + + export function parseMetaData(dataUri: URI): Map { + const metadata = new Map(); + + // Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5... + // the metadata is: size:2313;label:SomeLabel;description:SomeDescription + const meta = dataUri.path.substring(dataUri.path.indexOf(';') + 1, dataUri.path.lastIndexOf(';')); + meta.split(';').forEach(property => { + const [key, value] = property.split(':'); + if (key && value) { + metadata.set(key, value); + } + }); + + // Given a URI of: data:image/png;size:2313;label:SomeLabel;description:SomeDescription;base64,77+9UE5... + // the mime is: image/png + const mime = dataUri.path.substring(0, dataUri.path.indexOf(';')); + if (mime) { + metadata.set(META_DATA_MIME, mime); + } + + return metadata; + } +} + +export function toLocalResource(resource: URI, authority: string | undefined, localScheme: string): URI { + if (authority) { + let path = resource.path; + if (path && path[0] !== paths.posix.sep) { + path = paths.posix.sep + path; + } + + return resource.with({ scheme: localScheme, authority, path }); + } + + return resource.with({ scheme: localScheme }); +} diff --git a/packages/core/src/sequence.ts b/packages/core/src/sequence.ts new file mode 100644 index 00000000..9f42470e --- /dev/null +++ b/packages/core/src/sequence.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from './event.js'; + +export interface ISplice { + readonly start: number; + readonly deleteCount: number; + readonly toInsert: readonly T[]; +} + +export interface ISpliceable { + splice(start: number, deleteCount: number, toInsert: readonly T[]): void; +} + +export interface ISequence { + readonly elements: T[]; + readonly onDidSplice: Event>; +} + +export class Sequence implements ISequence, ISpliceable { + + readonly elements: T[] = []; + + private readonly _onDidSplice = new Emitter>(); + readonly onDidSplice: Event> = this._onDidSplice.event; + + splice(start: number, deleteCount: number, toInsert: readonly T[] = []): void { + this.elements.splice(start, deleteCount, ...toInsert); + this._onDidSplice.fire({ start, deleteCount, toInsert }); + } +} diff --git a/packages/core/src/set.ts b/packages/core/src/set.ts new file mode 100644 index 00000000..f325e9ef --- /dev/null +++ b/packages/core/src/set.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export class ArraySet { + + private _elements: T[]; + + constructor(elements: T[] = []) { + this._elements = elements.slice(); + } + + get size(): number { + return this._elements.length; + } + + set(element: T): void { + this.unset(element); + this._elements.push(element); + } + + contains(element: T): boolean { + return this._elements.includes(element); + } + + unset(element: T): void { + const index = this._elements.indexOf(element); + + if (index > -1) { + this._elements.splice(index, 1); + } + } + + get elements(): T[] { + return this._elements.slice(); + } +} \ No newline at end of file diff --git a/packages/core/src/stopwatch.ts b/packages/core/src/stopwatch.ts new file mode 100644 index 00000000..68f2315c --- /dev/null +++ b/packages/core/src/stopwatch.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// fake definition so that the valid layers check won't trip on this +declare const globalThis: { performance?: { now(): number } }; + +const hasPerformanceNow = (globalThis.performance && typeof globalThis.performance.now === 'function'); + +export class StopWatch { + + private _startTime: number; + private _stopTime: number; + + private readonly _now: () => number; + + public static create(highResolution?: boolean): StopWatch { + return new StopWatch(highResolution); + } + + constructor(highResolution?: boolean) { + this._now = hasPerformanceNow && highResolution === false ? Date.now : globalThis.performance!.now.bind(globalThis.performance); + this._startTime = this._now(); + this._stopTime = -1; + } + + public stop(): void { + this._stopTime = this._now(); + } + + public reset(): void { + this._startTime = this._now(); + this._stopTime = -1; + } + + public elapsed(): number { + if (this._stopTime !== -1) { + return this._stopTime - this._startTime; + } + return this._now() - this._startTime; + } +} diff --git a/packages/core/src/strings.ts b/packages/core/src/strings.ts new file mode 100644 index 00000000..491e564f --- /dev/null +++ b/packages/core/src/strings.ts @@ -0,0 +1,1334 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { CharCode } from './charCode.js' + +export const empty = '' + +import { REGEX_VAR, REGEX_VAR_ALT } from "./constants.js" + +export const substitute = (template, map) => { + const transform = (k) => k || ''; + return template.replace(REGEX_VAR, (match, key, format) => transform(map[key]).toString()) +} + +export const substituteAlt = (template, map) => { + const transform = (k) => k || ''; + return template.replace(REGEX_VAR_ALT, (match, key, format) => transform(map[key]).toString()) +} + +import { LRUCachedFunction } from './cache.js'; +import { Lazy } from './lazy.js'; +import { Constants } from './uint.js'; + +export function isFalsyOrWhitespace(str: string | undefined): boolean { + if (!str || typeof str !== 'string') { + return true; + } + return str.trim().length === 0; +} + +const _formatRegexp = /{(\d+)}/g; + +/** + * Helper to produce a string with a variable number of arguments. Insert variable segments + * into the string using the {n} notation where N is the index of the argument following the string. + * @param value string to which formatting is applied + * @param args replacements for {n}-entries + */ +export function format(value: string, ...args: any[]): string { + if (args.length === 0) { + return value; + } + return value.replace(_formatRegexp, function (match, group) { + const idx = parseInt(group, 10); + return isNaN(idx) || idx < 0 || idx >= args.length ? + match : + args[idx]; + }); +} + +const _format2Regexp = /{([^}]+)}/g; + +/** + * Helper to create a string from a template and a string record. + * Similar to `format` but with objects instead of positional arguments. + */ +export function format2(template: string, values: Record): string { + if (Object.keys(values).length === 0) { + return template; + } + return template.replace(_format2Regexp, (match, group) => (values[group] ?? match) as string); +} + +/** + * Encodes the given value so that it can be used as literal value in html attributes. + * + * In other words, computes `$val`, such that `attr` in `
` has the runtime value `value`. + * This prevents XSS injection. + */ +export function htmlAttributeEncodeValue(value: string): string { + return value.replace(/[<>"'&]/g, ch => { + switch (ch) { + case '<': return '<'; + case '>': return '>'; + case '"': return '"'; + case '\'': return '''; + case '&': return '&'; + } + return ch; + }); +} + +/** + * Converts HTML characters inside the string to use entities instead. Makes the string safe from + * being used e.g. in HTMLElement.innerHTML. + */ +export function escape(html: string): string { + return html.replace(/[<>&]/g, function (match) { + switch (match) { + case '<': return '<'; + case '>': return '>'; + case '&': return '&'; + default: return match; + } + }); +} + +/** + * Escapes regular expression characters in a given string + */ +export function escapeRegExpCharacters(value: string): string { + return value.replace(/[\\\{\}\*\+\?\|\^\$\.\[\]\(\)]/g, '\\$&'); +} + +/** + * Counts how often `substr` occurs inside `value`. + */ +export function count(value: string, substr: string): number { + let result = 0; + let index = value.indexOf(substr); + while (index !== -1) { + result++; + index = value.indexOf(substr, index + substr.length); + } + return result; +} + +export function truncate(value: string, maxLength: number, suffix = '…'): string { + if (value.length <= maxLength) { + return value; + } + + return `${value.substr(0, maxLength)}${suffix}`; +} + +export function truncateMiddle(value: string, maxLength: number, suffix = '…'): string { + if (value.length <= maxLength) { + return value; + } + + const prefixLength = Math.ceil(maxLength / 2) - suffix.length / 2; + const suffixLength = Math.floor(maxLength / 2) - suffix.length / 2; + + return `${value.substr(0, prefixLength)}${suffix}${value.substr(value.length - suffixLength)}`; +} + +/** + * Removes all occurrences of needle from the beginning and end of haystack. + * @param haystack string to trim + * @param needle the thing to trim (default is a blank) + */ +export function trim(haystack: string, needle: string = ' '): string { + const trimmed = ltrim(haystack, needle); + return rtrim(trimmed, needle); +} + +/** + * Removes all occurrences of needle from the beginning of haystack. + * @param haystack string to trim + * @param needle the thing to trim + */ +export function ltrim(haystack: string, needle: string): string { + if (!haystack || !needle) { + return haystack; + } + + const needleLen = needle.length; + if (needleLen === 0 || haystack.length === 0) { + return haystack; + } + + let offset = 0; + + while (haystack.indexOf(needle, offset) === offset) { + offset = offset + needleLen; + } + return haystack.substring(offset); +} + +/** + * Removes all occurrences of needle from the end of haystack. + * @param haystack string to trim + * @param needle the thing to trim + */ +export function rtrim(haystack: string, needle: string): string { + if (!haystack || !needle) { + return haystack; + } + + const needleLen = needle.length, + haystackLen = haystack.length; + + if (needleLen === 0 || haystackLen === 0) { + return haystack; + } + + let offset = haystackLen, + idx = -1; + + while (true) { + idx = haystack.lastIndexOf(needle, offset - 1); + if (idx === -1 || idx + needleLen !== offset) { + break; + } + if (idx === 0) { + return ''; + } + offset = idx; + } + + return haystack.substring(0, offset); +} + +export function convertSimple2RegExpPattern(pattern: string): string { + return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*'); +} + +export function stripWildcards(pattern: string): string { + return pattern.replace(/\*/g, ''); +} + +export interface RegExpOptions { + matchCase?: boolean; + wholeWord?: boolean; + multiline?: boolean; + global?: boolean; + unicode?: boolean; +} + +export function createRegExp(searchString: string, isRegex: boolean, options: RegExpOptions = {}): RegExp { + if (!searchString) { + throw new Error('Cannot create regex from empty string'); + } + if (!isRegex) { + searchString = escapeRegExpCharacters(searchString); + } + if (options.wholeWord) { + if (!/\B/.test(searchString.charAt(0))) { + searchString = '\\b' + searchString; + } + if (!/\B/.test(searchString.charAt(searchString.length - 1))) { + searchString = searchString + '\\b'; + } + } + let modifiers = ''; + if (options.global) { + modifiers += 'g'; + } + if (!options.matchCase) { + modifiers += 'i'; + } + if (options.multiline) { + modifiers += 'm'; + } + if (options.unicode) { + modifiers += 'u'; + } + + return new RegExp(searchString, modifiers); +} + +export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean { + // Exit early if it's one of these special cases which are meant to match + // against an empty string + if (regexp.source === '^' || regexp.source === '^$' || regexp.source === '$' || regexp.source === '^\\s*$') { + return false; + } + + // We check against an empty string. If the regular expression doesn't advance + // (e.g. ends in an endless loop) it will match an empty string. + const match = regexp.exec(''); + return !!(match && regexp.lastIndex === 0); +} + +export function joinStrings(items: (string | undefined | null | false)[], separator: string): string { + return items.filter(item => item !== undefined && item !== null && item !== false).join(separator); +} + +export function splitLines(str: string): string[] { + return str.split(/\r\n|\r|\n/); +} + +export function splitLinesIncludeSeparators(str: string): string[] { + const linesWithSeparators: string[] = []; + const splitLinesAndSeparators = str.split(/(\r\n|\r|\n)/); + for (let i = 0; i < Math.ceil(splitLinesAndSeparators.length / 2); i++) { + linesWithSeparators.push(splitLinesAndSeparators[2 * i] + (splitLinesAndSeparators[2 * i + 1] ?? '')); + } + return linesWithSeparators; +} + +/** + * Returns first index of the string that is not whitespace. + * If string is empty or contains only whitespaces, returns -1 + */ +export function firstNonWhitespaceIndex(str: string): number { + for (let i = 0, len = str.length; i < len; i++) { + const chCode = str.charCodeAt(i); + if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { + return i; + } + } + return -1; +} + +/** + * Returns the leading whitespace of the string. + * If the string contains only whitespaces, returns entire string + */ +export function getLeadingWhitespace(str: string, start: number = 0, end: number = str.length): string { + for (let i = start; i < end; i++) { + const chCode = str.charCodeAt(i); + if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { + return str.substring(start, i); + } + } + return str.substring(start, end); +} + +/** + * Returns last index of the string that is not whitespace. + * If string is empty or contains only whitespaces, returns -1 + */ +export function lastNonWhitespaceIndex(str: string, startIndex: number = str.length - 1): number { + for (let i = startIndex; i >= 0; i--) { + const chCode = str.charCodeAt(i); + if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { + return i; + } + } + return -1; +} + +export function getIndentationLength(str: string): number { + const idx = firstNonWhitespaceIndex(str); + if (idx === -1) { return str.length; } + return idx; +} + +/** + * Function that works identically to String.prototype.replace, except, the + * replace function is allowed to be async and return a Promise. + */ +export function replaceAsync(str: string, search: RegExp, replacer: (match: string, ...args: any[]) => Promise): Promise { + const parts: (string | Promise)[] = []; + + let last = 0; + for (const match of str.matchAll(search)) { + parts.push(str.slice(last, match.index)); + if (match.index === undefined) { + throw new Error('match.index should be defined'); + } + + last = match.index + match[0].length; + parts.push(replacer(match[0], ...match.slice(1), match.index, str, match.groups)); + } + + parts.push(str.slice(last)); + + return Promise.all(parts).then(p => p.join('')); +} + +export function compare(a: string, b: string): number { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } +} + +export function compareSubstring(a: string, b: string, aStart: number = 0, aEnd: number = a.length, bStart: number = 0, bEnd: number = b.length): number { + for (; aStart < aEnd && bStart < bEnd; aStart++, bStart++) { + const codeA = a.charCodeAt(aStart); + const codeB = b.charCodeAt(bStart); + if (codeA < codeB) { + return -1; + } else if (codeA > codeB) { + return 1; + } + } + const aLen = aEnd - aStart; + const bLen = bEnd - bStart; + if (aLen < bLen) { + return -1; + } else if (aLen > bLen) { + return 1; + } + return 0; +} + +export function compareIgnoreCase(a: string, b: string): number { + return compareSubstringIgnoreCase(a, b, 0, a.length, 0, b.length); +} + +export function compareSubstringIgnoreCase(a: string, b: string, aStart: number = 0, aEnd: number = a.length, bStart: number = 0, bEnd: number = b.length): number { + + for (; aStart < aEnd && bStart < bEnd; aStart++, bStart++) { + + let codeA = a.charCodeAt(aStart); + let codeB = b.charCodeAt(bStart); + + if (codeA === codeB) { + // equal + continue; + } + + if (codeA >= 128 || codeB >= 128) { + // not ASCII letters -> fallback to lower-casing strings + return compareSubstring(a.toLowerCase(), b.toLowerCase(), aStart, aEnd, bStart, bEnd); + } + + // mapper lower-case ascii letter onto upper-case varinats + // [97-122] (lower ascii) --> [65-90] (upper ascii) + if (isLowerAsciiLetter(codeA)) { + codeA -= 32; + } + if (isLowerAsciiLetter(codeB)) { + codeB -= 32; + } + + // compare both code points + const diff = codeA - codeB; + if (diff === 0) { + continue; + } + + return diff; + } + + const aLen = aEnd - aStart; + const bLen = bEnd - bStart; + + if (aLen < bLen) { + return -1; + } else if (aLen > bLen) { + return 1; + } + + return 0; +} + +export function isAsciiDigit(code: number): boolean { + return code >= CharCode.Digit0 && code <= CharCode.Digit9; +} + +export function isLowerAsciiLetter(code: number): boolean { + return code >= CharCode.a && code <= CharCode.z; +} + +export function isUpperAsciiLetter(code: number): boolean { + return code >= CharCode.A && code <= CharCode.Z; +} + +export function equalsIgnoreCase(a: string, b: string): boolean { + return a.length === b.length && compareSubstringIgnoreCase(a, b) === 0; +} + +export function startsWithIgnoreCase(str: string, candidate: string): boolean { + const candidateLength = candidate.length; + if (candidate.length > str.length) { + return false; + } + + return compareSubstringIgnoreCase(str, candidate, 0, candidateLength) === 0; +} + +/** + * @returns the length of the common prefix of the two strings. + */ +export function commonPrefixLength(a: string, b: string): number { + + const len = Math.min(a.length, b.length); + let i: number; + + for (i = 0; i < len; i++) { + if (a.charCodeAt(i) !== b.charCodeAt(i)) { + return i; + } + } + + return len; +} + +/** + * @returns the length of the common suffix of the two strings. + */ +export function commonSuffixLength(a: string, b: string): number { + + const len = Math.min(a.length, b.length); + let i: number; + + const aLastIndex = a.length - 1; + const bLastIndex = b.length - 1; + + for (i = 0; i < len; i++) { + if (a.charCodeAt(aLastIndex - i) !== b.charCodeAt(bLastIndex - i)) { + return i; + } + } + + return len; +} + +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ +export function isHighSurrogate(charCode: number): boolean { + return (0xD800 <= charCode && charCode <= 0xDBFF); +} + +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ +export function isLowSurrogate(charCode: number): boolean { + return (0xDC00 <= charCode && charCode <= 0xDFFF); +} + +/** + * See http://en.wikipedia.org/wiki/Surrogate_pair + */ +export function computeCodePoint(highSurrogate: number, lowSurrogate: number): number { + return ((highSurrogate - 0xD800) << 10) + (lowSurrogate - 0xDC00) + 0x10000; +} + +/** + * get the code point that begins at offset `offset` + */ +export function getNextCodePoint(str: string, len: number, offset: number): number { + const charCode = str.charCodeAt(offset); + if (isHighSurrogate(charCode) && offset + 1 < len) { + const nextCharCode = str.charCodeAt(offset + 1); + if (isLowSurrogate(nextCharCode)) { + return computeCodePoint(charCode, nextCharCode); + } + } + return charCode; +} + +/** + * get the code point that ends right before offset `offset` + */ +function getPrevCodePoint(str: string, offset: number): number { + const charCode = str.charCodeAt(offset - 1); + if (isLowSurrogate(charCode) && offset > 1) { + const prevCharCode = str.charCodeAt(offset - 2); + if (isHighSurrogate(prevCharCode)) { + return computeCodePoint(prevCharCode, charCode); + } + } + return charCode; +} + +export class CodePointIterator { + + private readonly _str: string; + private readonly _len: number; + private _offset: number; + + public get offset(): number { + return this._offset; + } + + constructor(str: string, offset: number = 0) { + this._str = str; + this._len = str.length; + this._offset = offset; + } + + public setOffset(offset: number): void { + this._offset = offset; + } + + public prevCodePoint(): number { + const codePoint = getPrevCodePoint(this._str, this._offset); + this._offset -= (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1); + return codePoint; + } + + public nextCodePoint(): number { + const codePoint = getNextCodePoint(this._str, this._len, this._offset); + this._offset += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1); + return codePoint; + } + + public eol(): boolean { + return (this._offset >= this._len); + } +} + +export class GraphemeIterator { + + private readonly _iterator: CodePointIterator; + + public get offset(): number { + return this._iterator.offset; + } + + constructor(str: string, offset: number = 0) { + this._iterator = new CodePointIterator(str, offset); + } + + public nextGraphemeLength(): number { + const graphemeBreakTree = GraphemeBreakTree.getInstance(); + const iterator = this._iterator; + const initialOffset = iterator.offset; + + let graphemeBreakType = graphemeBreakTree.getGraphemeBreakType(iterator.nextCodePoint()); + while (!iterator.eol()) { + const offset = iterator.offset; + const nextGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(iterator.nextCodePoint()); + if (breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) { + // move iterator back + iterator.setOffset(offset); + break; + } + graphemeBreakType = nextGraphemeBreakType; + } + return (iterator.offset - initialOffset); + } + + public prevGraphemeLength(): number { + const graphemeBreakTree = GraphemeBreakTree.getInstance(); + const iterator = this._iterator; + const initialOffset = iterator.offset; + + let graphemeBreakType = graphemeBreakTree.getGraphemeBreakType(iterator.prevCodePoint()); + while (iterator.offset > 0) { + const offset = iterator.offset; + const prevGraphemeBreakType = graphemeBreakTree.getGraphemeBreakType(iterator.prevCodePoint()); + if (breakBetweenGraphemeBreakType(prevGraphemeBreakType, graphemeBreakType)) { + // move iterator back + iterator.setOffset(offset); + break; + } + graphemeBreakType = prevGraphemeBreakType; + } + return (initialOffset - iterator.offset); + } + + public eol(): boolean { + return this._iterator.eol(); + } +} + +export function nextCharLength(str: string, initialOffset: number): number { + const iterator = new GraphemeIterator(str, initialOffset); + return iterator.nextGraphemeLength(); +} + +export function prevCharLength(str: string, initialOffset: number): number { + const iterator = new GraphemeIterator(str, initialOffset); + return iterator.prevGraphemeLength(); +} + +export function getCharContainingOffset(str: string, offset: number): [number, number] { + if (offset > 0 && isLowSurrogate(str.charCodeAt(offset))) { + offset--; + } + const endOffset = offset + nextCharLength(str, offset); + const startOffset = endOffset - prevCharLength(str, endOffset); + return [startOffset, endOffset]; +} + +export function charCount(str: string): number { + const iterator = new GraphemeIterator(str); + let length = 0; + while (!iterator.eol()) { + length++; + iterator.nextGraphemeLength(); + } + return length; +} + +let CONTAINS_RTL: RegExp | undefined = undefined; + +function makeContainsRtl() { + // Generated using https://github.com/alexdima/unicode-utils/blob/main/rtl-test.js + return /(?:[\u05BE\u05C0\u05C3\u05C6\u05D0-\u05F4\u0608\u060B\u060D\u061B-\u064A\u066D-\u066F\u0671-\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u0710\u0712-\u072F\u074D-\u07A5\u07B1-\u07EA\u07F4\u07F5\u07FA\u07FE-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u088E\u08A0-\u08C9\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDC7\uFDF0-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE35\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDD23\uDE80-\uDEA9\uDEAD-\uDF45\uDF51-\uDF81\uDF86-\uDFF6]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD4B-\uDFFF]|\uD83B[\uDC00-\uDEBB])/; +} + +/** + * Returns true if `str` contains any Unicode character that is classified as "R" or "AL". + */ +export function containsRTL(str: string): boolean { + if (!CONTAINS_RTL) { + CONTAINS_RTL = makeContainsRtl(); + } + + return CONTAINS_RTL.test(str); +} + +const IS_BASIC_ASCII = /^[\t\n\r\x20-\x7E]*$/; +/** + * Returns true if `str` contains only basic ASCII characters in the range 32 - 126 (including 32 and 126) or \n, \r, \t + */ +export function isBasicASCII(str: string): boolean { + return IS_BASIC_ASCII.test(str); +} + +export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029]/; // LINE SEPARATOR (LS) or PARAGRAPH SEPARATOR (PS) +/** + * Returns true if `str` contains unusual line terminators, like LS or PS + */ +export function containsUnusualLineTerminators(str: string): boolean { + return UNUSUAL_LINE_TERMINATORS.test(str); +} + +export function isFullWidthCharacter(charCode: number): boolean { + // Do a cheap trick to better support wrapping of wide characters, treat them as 2 columns + // http://jrgraphix.net/research/unicode_blocks.php + // 2E80 - 2EFF CJK Radicals Supplement + // 2F00 - 2FDF Kangxi Radicals + // 2FF0 - 2FFF Ideographic Description Characters + // 3000 - 303F CJK Symbols and Punctuation + // 3040 - 309F Hiragana + // 30A0 - 30FF Katakana + // 3100 - 312F Bopomofo + // 3130 - 318F Hangul Compatibility Jamo + // 3190 - 319F Kanbun + // 31A0 - 31BF Bopomofo Extended + // 31F0 - 31FF Katakana Phonetic Extensions + // 3200 - 32FF Enclosed CJK Letters and Months + // 3300 - 33FF CJK Compatibility + // 3400 - 4DBF CJK Unified Ideographs Extension A + // 4DC0 - 4DFF Yijing Hexagram Symbols + // 4E00 - 9FFF CJK Unified Ideographs + // A000 - A48F Yi Syllables + // A490 - A4CF Yi Radicals + // AC00 - D7AF Hangul Syllables + // [IGNORE] D800 - DB7F High Surrogates + // [IGNORE] DB80 - DBFF High Private Use Surrogates + // [IGNORE] DC00 - DFFF Low Surrogates + // [IGNORE] E000 - F8FF Private Use Area + // F900 - FAFF CJK Compatibility Ideographs + // [IGNORE] FB00 - FB4F Alphabetic Presentation Forms + // [IGNORE] FB50 - FDFF Arabic Presentation Forms-A + // [IGNORE] FE00 - FE0F Variation Selectors + // [IGNORE] FE20 - FE2F Combining Half Marks + // [IGNORE] FE30 - FE4F CJK Compatibility Forms + // [IGNORE] FE50 - FE6F Small Form Variants + // [IGNORE] FE70 - FEFF Arabic Presentation Forms-B + // FF00 - FFEF Halfwidth and Fullwidth Forms + // [https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms] + // of which FF01 - FF5E fullwidth ASCII of 21 to 7E + // [IGNORE] and FF65 - FFDC halfwidth of Katakana and Hangul + // [IGNORE] FFF0 - FFFF Specials + return ( + (charCode >= 0x2E80 && charCode <= 0xD7AF) + || (charCode >= 0xF900 && charCode <= 0xFAFF) + || (charCode >= 0xFF01 && charCode <= 0xFF5E) + ); +} + +/** + * A fast function (therefore imprecise) to check if code points are emojis. + * Generated using https://github.com/alexdima/unicode-utils/blob/main/emoji-test.js + */ +export function isEmojiImprecise(x: number): boolean { + return ( + (x >= 0x1F1E6 && x <= 0x1F1FF) || (x === 8986) || (x === 8987) || (x === 9200) + || (x === 9203) || (x >= 9728 && x <= 10175) || (x === 11088) || (x === 11093) + || (x >= 127744 && x <= 128591) || (x >= 128640 && x <= 128764) + || (x >= 128992 && x <= 129008) || (x >= 129280 && x <= 129535) + || (x >= 129648 && x <= 129782) + ); +} + +/** + * Given a string and a max length returns a shorted version. Shorting + * happens at favorable positions - such as whitespace or punctuation characters. + * The return value can be longer than the given value of `n`. Leading whitespace is always trimmed. + */ +export function lcut(text: string, n: number, prefix = '') { + const trimmed = text.trimStart(); + + if (trimmed.length < n) { + return trimmed; + } + + const re = /\b/g; + let i = 0; + while (re.test(trimmed)) { + if (trimmed.length - re.lastIndex < n) { + break; + } + + i = re.lastIndex; + re.lastIndex += 1; + } + + if (i === 0) { + return trimmed; + } + + return prefix + trimmed.substring(i).trimStart(); +} + +// Escape codes, compiled from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ +// Plus additional markers for custom `\x1b]...\x07` instructions. +const CSI_SEQUENCE = /(?:(?:\x1b\[|\x9B)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~])|(:?\x1b\].*?\x07)/g; + +/** Iterates over parts of a string with CSI sequences */ +export function* forAnsiStringParts(str: string) { + let last = 0; + for (const match of str.matchAll(CSI_SEQUENCE)) { + if (last !== match.index) { + yield { isCode: false, str: str.substring(last, match.index) }; + } + + yield { isCode: true, str: match[0] }; + last = match.index + match[0].length; + } + + if (last !== str.length) { + yield { isCode: false, str: str.substring(last) }; + } +} + +/** + * Strips ANSI escape sequences from a string. + * @param str The dastringa stringo strip the ANSI escape sequences from. + * + * @example + * removeAnsiEscapeCodes('\u001b[31mHello, World!\u001b[0m'); + * // 'Hello, World!' + */ +export function removeAnsiEscapeCodes(str: string): string { + if (str) { + str = str.replace(CSI_SEQUENCE, ''); + } + + return str; +} + +const PROMPT_NON_PRINTABLE = /\\\[.*?\\\]/g; + +/** + * Strips ANSI escape sequences from a UNIX-style prompt string (eg. `$PS1`). + * @param str The string to strip the ANSI escape sequences from. + * + * @example + * removeAnsiEscapeCodesFromPrompt('\n\\[\u001b[01;34m\\]\\w\\[\u001b[00m\\]\n\\[\u001b[1;32m\\]> \\[\u001b[0m\\]'); + * // '\n\\w\n> ' + */ +export function removeAnsiEscapeCodesFromPrompt(str: string): string { + return removeAnsiEscapeCodes(str).replace(PROMPT_NON_PRINTABLE, ''); +} + + +// -- UTF-8 BOM + +export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM); + +export function startsWithUTF8BOM(str: string): boolean { + return !!(str && str.length > 0 && str.charCodeAt(0) === CharCode.UTF8_BOM); +} + +export function stripUTF8BOM(str: string): string { + return startsWithUTF8BOM(str) ? str.substr(1) : str; +} + +/** + * Checks if the characters of the provided query string are included in the + * target string. The characters do not have to be contiguous within the string. + */ +export function fuzzyContains(target: string, query: string): boolean { + if (!target || !query) { + return false; // return early if target or query are undefined + } + + if (target.length < query.length) { + return false; // impossible for query to be contained in target + } + + const queryLen = query.length; + const targetLower = target.toLowerCase(); + + let index = 0; + let lastIndexOf = -1; + while (index < queryLen) { + const indexOf = targetLower.indexOf(query[index], lastIndexOf + 1); + if (indexOf < 0) { + return false; + } + + lastIndexOf = indexOf; + + index++; + } + + return true; +} + +export function containsUppercaseCharacter(target: string, ignoreEscapedChars = false): boolean { + if (!target) { + return false; + } + + if (ignoreEscapedChars) { + target = target.replace(/\\./g, ''); + } + + return target.toLowerCase() !== target; +} + +export function uppercaseFirstLetter(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +export function getNLines(str: string, n = 1): string { + if (n === 0) { + return ''; + } + + let idx = -1; + do { + idx = str.indexOf('\n', idx + 1); + n--; + } while (n > 0 && idx >= 0); + + if (idx === -1) { + return str; + } + + if (str[idx - 1] === '\r') { + idx--; + } + + return str.substr(0, idx); +} + +/** + * Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc. + */ +export function singleLetterHash(n: number): string { + const LETTERS_CNT = (CharCode.Z - CharCode.A + 1); + + n = n % (2 * LETTERS_CNT); + + if (n < LETTERS_CNT) { + return String.fromCharCode(CharCode.a + n); + } + + return String.fromCharCode(CharCode.A + n - LETTERS_CNT); +} + +//#region Unicode Grapheme Break + +export function getGraphemeBreakType(codePoint: number): GraphemeBreakType { + const graphemeBreakTree = GraphemeBreakTree.getInstance(); + return graphemeBreakTree.getGraphemeBreakType(codePoint); +} + +function breakBetweenGraphemeBreakType(breakTypeA: GraphemeBreakType, breakTypeB: GraphemeBreakType): boolean { + // http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules + + // !!! Let's make the common case a bit faster + if (breakTypeA === GraphemeBreakType.Other) { + // see https://www.unicode.org/Public/13.0.0/ucd/auxiliary/GraphemeBreakTest-13.0.0d10.html#table + return (breakTypeB !== GraphemeBreakType.Extend && breakTypeB !== GraphemeBreakType.SpacingMark); + } + + // Do not break between a CR and LF. Otherwise, break before and after controls. + // GB3 CR × LF + // GB4 (Control | CR | LF) ÷ + // GB5 ÷ (Control | CR | LF) + if (breakTypeA === GraphemeBreakType.CR) { + if (breakTypeB === GraphemeBreakType.LF) { + return false; // GB3 + } + } + if (breakTypeA === GraphemeBreakType.Control || breakTypeA === GraphemeBreakType.CR || breakTypeA === GraphemeBreakType.LF) { + return true; // GB4 + } + if (breakTypeB === GraphemeBreakType.Control || breakTypeB === GraphemeBreakType.CR || breakTypeB === GraphemeBreakType.LF) { + return true; // GB5 + } + + // Do not break Hangul syllable sequences. + // GB6 L × (L | V | LV | LVT) + // GB7 (LV | V) × (V | T) + // GB8 (LVT | T) × T + if (breakTypeA === GraphemeBreakType.L) { + if (breakTypeB === GraphemeBreakType.L || breakTypeB === GraphemeBreakType.V || breakTypeB === GraphemeBreakType.LV || breakTypeB === GraphemeBreakType.LVT) { + return false; // GB6 + } + } + if (breakTypeA === GraphemeBreakType.LV || breakTypeA === GraphemeBreakType.V) { + if (breakTypeB === GraphemeBreakType.V || breakTypeB === GraphemeBreakType.T) { + return false; // GB7 + } + } + if (breakTypeA === GraphemeBreakType.LVT || breakTypeA === GraphemeBreakType.T) { + if (breakTypeB === GraphemeBreakType.T) { + return false; // GB8 + } + } + + // Do not break before extending characters or ZWJ. + // GB9 × (Extend | ZWJ) + if (breakTypeB === GraphemeBreakType.Extend || breakTypeB === GraphemeBreakType.ZWJ) { + return false; // GB9 + } + + // The GB9a and GB9b rules only apply to extended grapheme clusters: + // Do not break before SpacingMarks, or after Prepend characters. + // GB9a × SpacingMark + // GB9b Prepend × + if (breakTypeB === GraphemeBreakType.SpacingMark) { + return false; // GB9a + } + if (breakTypeA === GraphemeBreakType.Prepend) { + return false; // GB9b + } + + // Do not break within emoji modifier sequences or emoji zwj sequences. + // GB11 \p{Extended_Pictographic} Extend* ZWJ × \p{Extended_Pictographic} + if (breakTypeA === GraphemeBreakType.ZWJ && breakTypeB === GraphemeBreakType.Extended_Pictographic) { + // Note: we are not implementing the rule entirely here to avoid introducing states + return false; // GB11 + } + + // GB12 sot (RI RI)* RI × RI + // GB13 [^RI] (RI RI)* RI × RI + if (breakTypeA === GraphemeBreakType.Regional_Indicator && breakTypeB === GraphemeBreakType.Regional_Indicator) { + // Note: we are not implementing the rule entirely here to avoid introducing states + return false; // GB12 & GB13 + } + + // GB999 Any ÷ Any + return true; +} + +export const enum GraphemeBreakType { + Other = 0, + Prepend = 1, + CR = 2, + LF = 3, + Control = 4, + Extend = 5, + Regional_Indicator = 6, + SpacingMark = 7, + L = 8, + V = 9, + T = 10, + LV = 11, + LVT = 12, + ZWJ = 13, + Extended_Pictographic = 14 +} + +class GraphemeBreakTree { + + private static _INSTANCE: GraphemeBreakTree | null = null; + public static getInstance(): GraphemeBreakTree { + if (!GraphemeBreakTree._INSTANCE) { + GraphemeBreakTree._INSTANCE = new GraphemeBreakTree(); + } + return GraphemeBreakTree._INSTANCE; + } + + private readonly _data: number[]; + + constructor() { + this._data = getGraphemeBreakRawData(); + } + + public getGraphemeBreakType(codePoint: number): GraphemeBreakType { + // !!! Let's make 7bit ASCII a bit faster: 0..31 + if (codePoint < 32) { + if (codePoint === CharCode.LineFeed) { + return GraphemeBreakType.LF; + } + if (codePoint === CharCode.CarriageReturn) { + return GraphemeBreakType.CR; + } + return GraphemeBreakType.Control; + } + // !!! Let's make 7bit ASCII a bit faster: 32..126 + if (codePoint < 127) { + return GraphemeBreakType.Other; + } + + const data = this._data; + const nodeCount = data.length / 3; + let nodeIndex = 1; + while (nodeIndex <= nodeCount) { + if (codePoint < data[3 * nodeIndex]) { + // go left + nodeIndex = 2 * nodeIndex; + } else if (codePoint > data[3 * nodeIndex + 1]) { + // go right + nodeIndex = 2 * nodeIndex + 1; + } else { + // hit + return data[3 * nodeIndex + 2]; + } + } + + return GraphemeBreakType.Other; + } +} + +function getGraphemeBreakRawData(): number[] { + // generated using https://github.com/alexdima/unicode-utils/blob/main/grapheme-break.js + return JSON.parse('[0,0,0,51229,51255,12,44061,44087,12,127462,127487,6,7083,7085,5,47645,47671,12,54813,54839,12,128678,128678,14,3270,3270,5,9919,9923,14,45853,45879,12,49437,49463,12,53021,53047,12,71216,71218,7,128398,128399,14,129360,129374,14,2519,2519,5,4448,4519,9,9742,9742,14,12336,12336,14,44957,44983,12,46749,46775,12,48541,48567,12,50333,50359,12,52125,52151,12,53917,53943,12,69888,69890,5,73018,73018,5,127990,127990,14,128558,128559,14,128759,128760,14,129653,129655,14,2027,2035,5,2891,2892,7,3761,3761,5,6683,6683,5,8293,8293,4,9825,9826,14,9999,9999,14,43452,43453,5,44509,44535,12,45405,45431,12,46301,46327,12,47197,47223,12,48093,48119,12,48989,49015,12,49885,49911,12,50781,50807,12,51677,51703,12,52573,52599,12,53469,53495,12,54365,54391,12,65279,65279,4,70471,70472,7,72145,72147,7,119173,119179,5,127799,127818,14,128240,128244,14,128512,128512,14,128652,128652,14,128721,128722,14,129292,129292,14,129445,129450,14,129734,129743,14,1476,1477,5,2366,2368,7,2750,2752,7,3076,3076,5,3415,3415,5,4141,4144,5,6109,6109,5,6964,6964,5,7394,7400,5,9197,9198,14,9770,9770,14,9877,9877,14,9968,9969,14,10084,10084,14,43052,43052,5,43713,43713,5,44285,44311,12,44733,44759,12,45181,45207,12,45629,45655,12,46077,46103,12,46525,46551,12,46973,46999,12,47421,47447,12,47869,47895,12,48317,48343,12,48765,48791,12,49213,49239,12,49661,49687,12,50109,50135,12,50557,50583,12,51005,51031,12,51453,51479,12,51901,51927,12,52349,52375,12,52797,52823,12,53245,53271,12,53693,53719,12,54141,54167,12,54589,54615,12,55037,55063,12,69506,69509,5,70191,70193,5,70841,70841,7,71463,71467,5,72330,72342,5,94031,94031,5,123628,123631,5,127763,127765,14,127941,127941,14,128043,128062,14,128302,128317,14,128465,128467,14,128539,128539,14,128640,128640,14,128662,128662,14,128703,128703,14,128745,128745,14,129004,129007,14,129329,129330,14,129402,129402,14,129483,129483,14,129686,129704,14,130048,131069,14,173,173,4,1757,1757,1,2200,2207,5,2434,2435,7,2631,2632,5,2817,2817,5,3008,3008,5,3201,3201,5,3387,3388,5,3542,3542,5,3902,3903,7,4190,4192,5,6002,6003,5,6439,6440,5,6765,6770,7,7019,7027,5,7154,7155,7,8205,8205,13,8505,8505,14,9654,9654,14,9757,9757,14,9792,9792,14,9852,9853,14,9890,9894,14,9937,9937,14,9981,9981,14,10035,10036,14,11035,11036,14,42654,42655,5,43346,43347,7,43587,43587,5,44006,44007,7,44173,44199,12,44397,44423,12,44621,44647,12,44845,44871,12,45069,45095,12,45293,45319,12,45517,45543,12,45741,45767,12,45965,45991,12,46189,46215,12,46413,46439,12,46637,46663,12,46861,46887,12,47085,47111,12,47309,47335,12,47533,47559,12,47757,47783,12,47981,48007,12,48205,48231,12,48429,48455,12,48653,48679,12,48877,48903,12,49101,49127,12,49325,49351,12,49549,49575,12,49773,49799,12,49997,50023,12,50221,50247,12,50445,50471,12,50669,50695,12,50893,50919,12,51117,51143,12,51341,51367,12,51565,51591,12,51789,51815,12,52013,52039,12,52237,52263,12,52461,52487,12,52685,52711,12,52909,52935,12,53133,53159,12,53357,53383,12,53581,53607,12,53805,53831,12,54029,54055,12,54253,54279,12,54477,54503,12,54701,54727,12,54925,54951,12,55149,55175,12,68101,68102,5,69762,69762,7,70067,70069,7,70371,70378,5,70720,70721,7,71087,71087,5,71341,71341,5,71995,71996,5,72249,72249,7,72850,72871,5,73109,73109,5,118576,118598,5,121505,121519,5,127245,127247,14,127568,127569,14,127777,127777,14,127872,127891,14,127956,127967,14,128015,128016,14,128110,128172,14,128259,128259,14,128367,128368,14,128424,128424,14,128488,128488,14,128530,128532,14,128550,128551,14,128566,128566,14,128647,128647,14,128656,128656,14,128667,128673,14,128691,128693,14,128715,128715,14,128728,128732,14,128752,128752,14,128765,128767,14,129096,129103,14,129311,129311,14,129344,129349,14,129394,129394,14,129413,129425,14,129466,129471,14,129511,129535,14,129664,129666,14,129719,129722,14,129760,129767,14,917536,917631,5,13,13,2,1160,1161,5,1564,1564,4,1807,1807,1,2085,2087,5,2307,2307,7,2382,2383,7,2497,2500,5,2563,2563,7,2677,2677,5,2763,2764,7,2879,2879,5,2914,2915,5,3021,3021,5,3142,3144,5,3263,3263,5,3285,3286,5,3398,3400,7,3530,3530,5,3633,3633,5,3864,3865,5,3974,3975,5,4155,4156,7,4229,4230,5,5909,5909,7,6078,6085,7,6277,6278,5,6451,6456,7,6744,6750,5,6846,6846,5,6972,6972,5,7074,7077,5,7146,7148,7,7222,7223,5,7416,7417,5,8234,8238,4,8417,8417,5,9000,9000,14,9203,9203,14,9730,9731,14,9748,9749,14,9762,9763,14,9776,9783,14,9800,9811,14,9831,9831,14,9872,9873,14,9882,9882,14,9900,9903,14,9929,9933,14,9941,9960,14,9974,9974,14,9989,9989,14,10006,10006,14,10062,10062,14,10160,10160,14,11647,11647,5,12953,12953,14,43019,43019,5,43232,43249,5,43443,43443,5,43567,43568,7,43696,43696,5,43765,43765,7,44013,44013,5,44117,44143,12,44229,44255,12,44341,44367,12,44453,44479,12,44565,44591,12,44677,44703,12,44789,44815,12,44901,44927,12,45013,45039,12,45125,45151,12,45237,45263,12,45349,45375,12,45461,45487,12,45573,45599,12,45685,45711,12,45797,45823,12,45909,45935,12,46021,46047,12,46133,46159,12,46245,46271,12,46357,46383,12,46469,46495,12,46581,46607,12,46693,46719,12,46805,46831,12,46917,46943,12,47029,47055,12,47141,47167,12,47253,47279,12,47365,47391,12,47477,47503,12,47589,47615,12,47701,47727,12,47813,47839,12,47925,47951,12,48037,48063,12,48149,48175,12,48261,48287,12,48373,48399,12,48485,48511,12,48597,48623,12,48709,48735,12,48821,48847,12,48933,48959,12,49045,49071,12,49157,49183,12,49269,49295,12,49381,49407,12,49493,49519,12,49605,49631,12,49717,49743,12,49829,49855,12,49941,49967,12,50053,50079,12,50165,50191,12,50277,50303,12,50389,50415,12,50501,50527,12,50613,50639,12,50725,50751,12,50837,50863,12,50949,50975,12,51061,51087,12,51173,51199,12,51285,51311,12,51397,51423,12,51509,51535,12,51621,51647,12,51733,51759,12,51845,51871,12,51957,51983,12,52069,52095,12,52181,52207,12,52293,52319,12,52405,52431,12,52517,52543,12,52629,52655,12,52741,52767,12,52853,52879,12,52965,52991,12,53077,53103,12,53189,53215,12,53301,53327,12,53413,53439,12,53525,53551,12,53637,53663,12,53749,53775,12,53861,53887,12,53973,53999,12,54085,54111,12,54197,54223,12,54309,54335,12,54421,54447,12,54533,54559,12,54645,54671,12,54757,54783,12,54869,54895,12,54981,55007,12,55093,55119,12,55243,55291,10,66045,66045,5,68325,68326,5,69688,69702,5,69817,69818,5,69957,69958,7,70089,70092,5,70198,70199,5,70462,70462,5,70502,70508,5,70750,70750,5,70846,70846,7,71100,71101,5,71230,71230,7,71351,71351,5,71737,71738,5,72000,72000,7,72160,72160,5,72273,72278,5,72752,72758,5,72882,72883,5,73031,73031,5,73461,73462,7,94192,94193,7,119149,119149,7,121403,121452,5,122915,122916,5,126980,126980,14,127358,127359,14,127535,127535,14,127759,127759,14,127771,127771,14,127792,127793,14,127825,127867,14,127897,127899,14,127945,127945,14,127985,127986,14,128000,128007,14,128021,128021,14,128066,128100,14,128184,128235,14,128249,128252,14,128266,128276,14,128335,128335,14,128379,128390,14,128407,128419,14,128444,128444,14,128481,128481,14,128499,128499,14,128526,128526,14,128536,128536,14,128543,128543,14,128556,128556,14,128564,128564,14,128577,128580,14,128643,128645,14,128649,128649,14,128654,128654,14,128660,128660,14,128664,128664,14,128675,128675,14,128686,128689,14,128695,128696,14,128705,128709,14,128717,128719,14,128725,128725,14,128736,128741,14,128747,128748,14,128755,128755,14,128762,128762,14,128981,128991,14,129009,129023,14,129160,129167,14,129296,129304,14,129320,129327,14,129340,129342,14,129356,129356,14,129388,129392,14,129399,129400,14,129404,129407,14,129432,129442,14,129454,129455,14,129473,129474,14,129485,129487,14,129648,129651,14,129659,129660,14,129671,129679,14,129709,129711,14,129728,129730,14,129751,129753,14,129776,129782,14,917505,917505,4,917760,917999,5,10,10,3,127,159,4,768,879,5,1471,1471,5,1536,1541,1,1648,1648,5,1767,1768,5,1840,1866,5,2070,2073,5,2137,2139,5,2274,2274,1,2363,2363,7,2377,2380,7,2402,2403,5,2494,2494,5,2507,2508,7,2558,2558,5,2622,2624,7,2641,2641,5,2691,2691,7,2759,2760,5,2786,2787,5,2876,2876,5,2881,2884,5,2901,2902,5,3006,3006,5,3014,3016,7,3072,3072,5,3134,3136,5,3157,3158,5,3260,3260,5,3266,3266,5,3274,3275,7,3328,3329,5,3391,3392,7,3405,3405,5,3457,3457,5,3536,3537,7,3551,3551,5,3636,3642,5,3764,3772,5,3895,3895,5,3967,3967,7,3993,4028,5,4146,4151,5,4182,4183,7,4226,4226,5,4253,4253,5,4957,4959,5,5940,5940,7,6070,6070,7,6087,6088,7,6158,6158,4,6432,6434,5,6448,6449,7,6679,6680,5,6742,6742,5,6754,6754,5,6783,6783,5,6912,6915,5,6966,6970,5,6978,6978,5,7042,7042,7,7080,7081,5,7143,7143,7,7150,7150,7,7212,7219,5,7380,7392,5,7412,7412,5,8203,8203,4,8232,8232,4,8265,8265,14,8400,8412,5,8421,8432,5,8617,8618,14,9167,9167,14,9200,9200,14,9410,9410,14,9723,9726,14,9733,9733,14,9745,9745,14,9752,9752,14,9760,9760,14,9766,9766,14,9774,9774,14,9786,9786,14,9794,9794,14,9823,9823,14,9828,9828,14,9833,9850,14,9855,9855,14,9875,9875,14,9880,9880,14,9885,9887,14,9896,9897,14,9906,9916,14,9926,9927,14,9935,9935,14,9939,9939,14,9962,9962,14,9972,9972,14,9978,9978,14,9986,9986,14,9997,9997,14,10002,10002,14,10017,10017,14,10055,10055,14,10071,10071,14,10133,10135,14,10548,10549,14,11093,11093,14,12330,12333,5,12441,12442,5,42608,42610,5,43010,43010,5,43045,43046,5,43188,43203,7,43302,43309,5,43392,43394,5,43446,43449,5,43493,43493,5,43571,43572,7,43597,43597,7,43703,43704,5,43756,43757,5,44003,44004,7,44009,44010,7,44033,44059,12,44089,44115,12,44145,44171,12,44201,44227,12,44257,44283,12,44313,44339,12,44369,44395,12,44425,44451,12,44481,44507,12,44537,44563,12,44593,44619,12,44649,44675,12,44705,44731,12,44761,44787,12,44817,44843,12,44873,44899,12,44929,44955,12,44985,45011,12,45041,45067,12,45097,45123,12,45153,45179,12,45209,45235,12,45265,45291,12,45321,45347,12,45377,45403,12,45433,45459,12,45489,45515,12,45545,45571,12,45601,45627,12,45657,45683,12,45713,45739,12,45769,45795,12,45825,45851,12,45881,45907,12,45937,45963,12,45993,46019,12,46049,46075,12,46105,46131,12,46161,46187,12,46217,46243,12,46273,46299,12,46329,46355,12,46385,46411,12,46441,46467,12,46497,46523,12,46553,46579,12,46609,46635,12,46665,46691,12,46721,46747,12,46777,46803,12,46833,46859,12,46889,46915,12,46945,46971,12,47001,47027,12,47057,47083,12,47113,47139,12,47169,47195,12,47225,47251,12,47281,47307,12,47337,47363,12,47393,47419,12,47449,47475,12,47505,47531,12,47561,47587,12,47617,47643,12,47673,47699,12,47729,47755,12,47785,47811,12,47841,47867,12,47897,47923,12,47953,47979,12,48009,48035,12,48065,48091,12,48121,48147,12,48177,48203,12,48233,48259,12,48289,48315,12,48345,48371,12,48401,48427,12,48457,48483,12,48513,48539,12,48569,48595,12,48625,48651,12,48681,48707,12,48737,48763,12,48793,48819,12,48849,48875,12,48905,48931,12,48961,48987,12,49017,49043,12,49073,49099,12,49129,49155,12,49185,49211,12,49241,49267,12,49297,49323,12,49353,49379,12,49409,49435,12,49465,49491,12,49521,49547,12,49577,49603,12,49633,49659,12,49689,49715,12,49745,49771,12,49801,49827,12,49857,49883,12,49913,49939,12,49969,49995,12,50025,50051,12,50081,50107,12,50137,50163,12,50193,50219,12,50249,50275,12,50305,50331,12,50361,50387,12,50417,50443,12,50473,50499,12,50529,50555,12,50585,50611,12,50641,50667,12,50697,50723,12,50753,50779,12,50809,50835,12,50865,50891,12,50921,50947,12,50977,51003,12,51033,51059,12,51089,51115,12,51145,51171,12,51201,51227,12,51257,51283,12,51313,51339,12,51369,51395,12,51425,51451,12,51481,51507,12,51537,51563,12,51593,51619,12,51649,51675,12,51705,51731,12,51761,51787,12,51817,51843,12,51873,51899,12,51929,51955,12,51985,52011,12,52041,52067,12,52097,52123,12,52153,52179,12,52209,52235,12,52265,52291,12,52321,52347,12,52377,52403,12,52433,52459,12,52489,52515,12,52545,52571,12,52601,52627,12,52657,52683,12,52713,52739,12,52769,52795,12,52825,52851,12,52881,52907,12,52937,52963,12,52993,53019,12,53049,53075,12,53105,53131,12,53161,53187,12,53217,53243,12,53273,53299,12,53329,53355,12,53385,53411,12,53441,53467,12,53497,53523,12,53553,53579,12,53609,53635,12,53665,53691,12,53721,53747,12,53777,53803,12,53833,53859,12,53889,53915,12,53945,53971,12,54001,54027,12,54057,54083,12,54113,54139,12,54169,54195,12,54225,54251,12,54281,54307,12,54337,54363,12,54393,54419,12,54449,54475,12,54505,54531,12,54561,54587,12,54617,54643,12,54673,54699,12,54729,54755,12,54785,54811,12,54841,54867,12,54897,54923,12,54953,54979,12,55009,55035,12,55065,55091,12,55121,55147,12,55177,55203,12,65024,65039,5,65520,65528,4,66422,66426,5,68152,68154,5,69291,69292,5,69633,69633,5,69747,69748,5,69811,69814,5,69826,69826,5,69932,69932,7,70016,70017,5,70079,70080,7,70095,70095,5,70196,70196,5,70367,70367,5,70402,70403,7,70464,70464,5,70487,70487,5,70709,70711,7,70725,70725,7,70833,70834,7,70843,70844,7,70849,70849,7,71090,71093,5,71103,71104,5,71227,71228,7,71339,71339,5,71344,71349,5,71458,71461,5,71727,71735,5,71985,71989,7,71998,71998,5,72002,72002,7,72154,72155,5,72193,72202,5,72251,72254,5,72281,72283,5,72344,72345,5,72766,72766,7,72874,72880,5,72885,72886,5,73023,73029,5,73104,73105,5,73111,73111,5,92912,92916,5,94095,94098,5,113824,113827,4,119142,119142,7,119155,119162,4,119362,119364,5,121476,121476,5,122888,122904,5,123184,123190,5,125252,125258,5,127183,127183,14,127340,127343,14,127377,127386,14,127491,127503,14,127548,127551,14,127744,127756,14,127761,127761,14,127769,127769,14,127773,127774,14,127780,127788,14,127796,127797,14,127820,127823,14,127869,127869,14,127894,127895,14,127902,127903,14,127943,127943,14,127947,127950,14,127972,127972,14,127988,127988,14,127992,127994,14,128009,128011,14,128019,128019,14,128023,128041,14,128064,128064,14,128102,128107,14,128174,128181,14,128238,128238,14,128246,128247,14,128254,128254,14,128264,128264,14,128278,128299,14,128329,128330,14,128348,128359,14,128371,128377,14,128392,128393,14,128401,128404,14,128421,128421,14,128433,128434,14,128450,128452,14,128476,128478,14,128483,128483,14,128495,128495,14,128506,128506,14,128519,128520,14,128528,128528,14,128534,128534,14,128538,128538,14,128540,128542,14,128544,128549,14,128552,128555,14,128557,128557,14,128560,128563,14,128565,128565,14,128567,128576,14,128581,128591,14,128641,128642,14,128646,128646,14,128648,128648,14,128650,128651,14,128653,128653,14,128655,128655,14,128657,128659,14,128661,128661,14,128663,128663,14,128665,128666,14,128674,128674,14,128676,128677,14,128679,128685,14,128690,128690,14,128694,128694,14,128697,128702,14,128704,128704,14,128710,128714,14,128716,128716,14,128720,128720,14,128723,128724,14,128726,128727,14,128733,128735,14,128742,128744,14,128746,128746,14,128749,128751,14,128753,128754,14,128756,128758,14,128761,128761,14,128763,128764,14,128884,128895,14,128992,129003,14,129008,129008,14,129036,129039,14,129114,129119,14,129198,129279,14,129293,129295,14,129305,129310,14,129312,129319,14,129328,129328,14,129331,129338,14,129343,129343,14,129351,129355,14,129357,129359,14,129375,129387,14,129393,129393,14,129395,129398,14,129401,129401,14,129403,129403,14,129408,129412,14,129426,129431,14,129443,129444,14,129451,129453,14,129456,129465,14,129472,129472,14,129475,129482,14,129484,129484,14,129488,129510,14,129536,129647,14,129652,129652,14,129656,129658,14,129661,129663,14,129667,129670,14,129680,129685,14,129705,129708,14,129712,129718,14,129723,129727,14,129731,129733,14,129744,129750,14,129754,129759,14,129768,129775,14,129783,129791,14,917504,917504,4,917506,917535,4,917632,917759,4,918000,921599,4,0,9,4,11,12,4,14,31,4,169,169,14,174,174,14,1155,1159,5,1425,1469,5,1473,1474,5,1479,1479,5,1552,1562,5,1611,1631,5,1750,1756,5,1759,1764,5,1770,1773,5,1809,1809,5,1958,1968,5,2045,2045,5,2075,2083,5,2089,2093,5,2192,2193,1,2250,2273,5,2275,2306,5,2362,2362,5,2364,2364,5,2369,2376,5,2381,2381,5,2385,2391,5,2433,2433,5,2492,2492,5,2495,2496,7,2503,2504,7,2509,2509,5,2530,2531,5,2561,2562,5,2620,2620,5,2625,2626,5,2635,2637,5,2672,2673,5,2689,2690,5,2748,2748,5,2753,2757,5,2761,2761,7,2765,2765,5,2810,2815,5,2818,2819,7,2878,2878,5,2880,2880,7,2887,2888,7,2893,2893,5,2903,2903,5,2946,2946,5,3007,3007,7,3009,3010,7,3018,3020,7,3031,3031,5,3073,3075,7,3132,3132,5,3137,3140,7,3146,3149,5,3170,3171,5,3202,3203,7,3262,3262,7,3264,3265,7,3267,3268,7,3271,3272,7,3276,3277,5,3298,3299,5,3330,3331,7,3390,3390,5,3393,3396,5,3402,3404,7,3406,3406,1,3426,3427,5,3458,3459,7,3535,3535,5,3538,3540,5,3544,3550,7,3570,3571,7,3635,3635,7,3655,3662,5,3763,3763,7,3784,3789,5,3893,3893,5,3897,3897,5,3953,3966,5,3968,3972,5,3981,3991,5,4038,4038,5,4145,4145,7,4153,4154,5,4157,4158,5,4184,4185,5,4209,4212,5,4228,4228,7,4237,4237,5,4352,4447,8,4520,4607,10,5906,5908,5,5938,5939,5,5970,5971,5,6068,6069,5,6071,6077,5,6086,6086,5,6089,6099,5,6155,6157,5,6159,6159,5,6313,6313,5,6435,6438,7,6441,6443,7,6450,6450,5,6457,6459,5,6681,6682,7,6741,6741,7,6743,6743,7,6752,6752,5,6757,6764,5,6771,6780,5,6832,6845,5,6847,6862,5,6916,6916,7,6965,6965,5,6971,6971,7,6973,6977,7,6979,6980,7,7040,7041,5,7073,7073,7,7078,7079,7,7082,7082,7,7142,7142,5,7144,7145,5,7149,7149,5,7151,7153,5,7204,7211,7,7220,7221,7,7376,7378,5,7393,7393,7,7405,7405,5,7415,7415,7,7616,7679,5,8204,8204,5,8206,8207,4,8233,8233,4,8252,8252,14,8288,8292,4,8294,8303,4,8413,8416,5,8418,8420,5,8482,8482,14,8596,8601,14,8986,8987,14,9096,9096,14,9193,9196,14,9199,9199,14,9201,9202,14,9208,9210,14,9642,9643,14,9664,9664,14,9728,9729,14,9732,9732,14,9735,9741,14,9743,9744,14,9746,9746,14,9750,9751,14,9753,9756,14,9758,9759,14,9761,9761,14,9764,9765,14,9767,9769,14,9771,9773,14,9775,9775,14,9784,9785,14,9787,9791,14,9793,9793,14,9795,9799,14,9812,9822,14,9824,9824,14,9827,9827,14,9829,9830,14,9832,9832,14,9851,9851,14,9854,9854,14,9856,9861,14,9874,9874,14,9876,9876,14,9878,9879,14,9881,9881,14,9883,9884,14,9888,9889,14,9895,9895,14,9898,9899,14,9904,9905,14,9917,9918,14,9924,9925,14,9928,9928,14,9934,9934,14,9936,9936,14,9938,9938,14,9940,9940,14,9961,9961,14,9963,9967,14,9970,9971,14,9973,9973,14,9975,9977,14,9979,9980,14,9982,9985,14,9987,9988,14,9992,9996,14,9998,9998,14,10000,10001,14,10004,10004,14,10013,10013,14,10024,10024,14,10052,10052,14,10060,10060,14,10067,10069,14,10083,10083,14,10085,10087,14,10145,10145,14,10175,10175,14,11013,11015,14,11088,11088,14,11503,11505,5,11744,11775,5,12334,12335,5,12349,12349,14,12951,12951,14,42607,42607,5,42612,42621,5,42736,42737,5,43014,43014,5,43043,43044,7,43047,43047,7,43136,43137,7,43204,43205,5,43263,43263,5,43335,43345,5,43360,43388,8,43395,43395,7,43444,43445,7,43450,43451,7,43454,43456,7,43561,43566,5,43569,43570,5,43573,43574,5,43596,43596,5,43644,43644,5,43698,43700,5,43710,43711,5,43755,43755,7,43758,43759,7,43766,43766,5,44005,44005,5,44008,44008,5,44012,44012,7,44032,44032,11,44060,44060,11,44088,44088,11,44116,44116,11,44144,44144,11,44172,44172,11,44200,44200,11,44228,44228,11,44256,44256,11,44284,44284,11,44312,44312,11,44340,44340,11,44368,44368,11,44396,44396,11,44424,44424,11,44452,44452,11,44480,44480,11,44508,44508,11,44536,44536,11,44564,44564,11,44592,44592,11,44620,44620,11,44648,44648,11,44676,44676,11,44704,44704,11,44732,44732,11,44760,44760,11,44788,44788,11,44816,44816,11,44844,44844,11,44872,44872,11,44900,44900,11,44928,44928,11,44956,44956,11,44984,44984,11,45012,45012,11,45040,45040,11,45068,45068,11,45096,45096,11,45124,45124,11,45152,45152,11,45180,45180,11,45208,45208,11,45236,45236,11,45264,45264,11,45292,45292,11,45320,45320,11,45348,45348,11,45376,45376,11,45404,45404,11,45432,45432,11,45460,45460,11,45488,45488,11,45516,45516,11,45544,45544,11,45572,45572,11,45600,45600,11,45628,45628,11,45656,45656,11,45684,45684,11,45712,45712,11,45740,45740,11,45768,45768,11,45796,45796,11,45824,45824,11,45852,45852,11,45880,45880,11,45908,45908,11,45936,45936,11,45964,45964,11,45992,45992,11,46020,46020,11,46048,46048,11,46076,46076,11,46104,46104,11,46132,46132,11,46160,46160,11,46188,46188,11,46216,46216,11,46244,46244,11,46272,46272,11,46300,46300,11,46328,46328,11,46356,46356,11,46384,46384,11,46412,46412,11,46440,46440,11,46468,46468,11,46496,46496,11,46524,46524,11,46552,46552,11,46580,46580,11,46608,46608,11,46636,46636,11,46664,46664,11,46692,46692,11,46720,46720,11,46748,46748,11,46776,46776,11,46804,46804,11,46832,46832,11,46860,46860,11,46888,46888,11,46916,46916,11,46944,46944,11,46972,46972,11,47000,47000,11,47028,47028,11,47056,47056,11,47084,47084,11,47112,47112,11,47140,47140,11,47168,47168,11,47196,47196,11,47224,47224,11,47252,47252,11,47280,47280,11,47308,47308,11,47336,47336,11,47364,47364,11,47392,47392,11,47420,47420,11,47448,47448,11,47476,47476,11,47504,47504,11,47532,47532,11,47560,47560,11,47588,47588,11,47616,47616,11,47644,47644,11,47672,47672,11,47700,47700,11,47728,47728,11,47756,47756,11,47784,47784,11,47812,47812,11,47840,47840,11,47868,47868,11,47896,47896,11,47924,47924,11,47952,47952,11,47980,47980,11,48008,48008,11,48036,48036,11,48064,48064,11,48092,48092,11,48120,48120,11,48148,48148,11,48176,48176,11,48204,48204,11,48232,48232,11,48260,48260,11,48288,48288,11,48316,48316,11,48344,48344,11,48372,48372,11,48400,48400,11,48428,48428,11,48456,48456,11,48484,48484,11,48512,48512,11,48540,48540,11,48568,48568,11,48596,48596,11,48624,48624,11,48652,48652,11,48680,48680,11,48708,48708,11,48736,48736,11,48764,48764,11,48792,48792,11,48820,48820,11,48848,48848,11,48876,48876,11,48904,48904,11,48932,48932,11,48960,48960,11,48988,48988,11,49016,49016,11,49044,49044,11,49072,49072,11,49100,49100,11,49128,49128,11,49156,49156,11,49184,49184,11,49212,49212,11,49240,49240,11,49268,49268,11,49296,49296,11,49324,49324,11,49352,49352,11,49380,49380,11,49408,49408,11,49436,49436,11,49464,49464,11,49492,49492,11,49520,49520,11,49548,49548,11,49576,49576,11,49604,49604,11,49632,49632,11,49660,49660,11,49688,49688,11,49716,49716,11,49744,49744,11,49772,49772,11,49800,49800,11,49828,49828,11,49856,49856,11,49884,49884,11,49912,49912,11,49940,49940,11,49968,49968,11,49996,49996,11,50024,50024,11,50052,50052,11,50080,50080,11,50108,50108,11,50136,50136,11,50164,50164,11,50192,50192,11,50220,50220,11,50248,50248,11,50276,50276,11,50304,50304,11,50332,50332,11,50360,50360,11,50388,50388,11,50416,50416,11,50444,50444,11,50472,50472,11,50500,50500,11,50528,50528,11,50556,50556,11,50584,50584,11,50612,50612,11,50640,50640,11,50668,50668,11,50696,50696,11,50724,50724,11,50752,50752,11,50780,50780,11,50808,50808,11,50836,50836,11,50864,50864,11,50892,50892,11,50920,50920,11,50948,50948,11,50976,50976,11,51004,51004,11,51032,51032,11,51060,51060,11,51088,51088,11,51116,51116,11,51144,51144,11,51172,51172,11,51200,51200,11,51228,51228,11,51256,51256,11,51284,51284,11,51312,51312,11,51340,51340,11,51368,51368,11,51396,51396,11,51424,51424,11,51452,51452,11,51480,51480,11,51508,51508,11,51536,51536,11,51564,51564,11,51592,51592,11,51620,51620,11,51648,51648,11,51676,51676,11,51704,51704,11,51732,51732,11,51760,51760,11,51788,51788,11,51816,51816,11,51844,51844,11,51872,51872,11,51900,51900,11,51928,51928,11,51956,51956,11,51984,51984,11,52012,52012,11,52040,52040,11,52068,52068,11,52096,52096,11,52124,52124,11,52152,52152,11,52180,52180,11,52208,52208,11,52236,52236,11,52264,52264,11,52292,52292,11,52320,52320,11,52348,52348,11,52376,52376,11,52404,52404,11,52432,52432,11,52460,52460,11,52488,52488,11,52516,52516,11,52544,52544,11,52572,52572,11,52600,52600,11,52628,52628,11,52656,52656,11,52684,52684,11,52712,52712,11,52740,52740,11,52768,52768,11,52796,52796,11,52824,52824,11,52852,52852,11,52880,52880,11,52908,52908,11,52936,52936,11,52964,52964,11,52992,52992,11,53020,53020,11,53048,53048,11,53076,53076,11,53104,53104,11,53132,53132,11,53160,53160,11,53188,53188,11,53216,53216,11,53244,53244,11,53272,53272,11,53300,53300,11,53328,53328,11,53356,53356,11,53384,53384,11,53412,53412,11,53440,53440,11,53468,53468,11,53496,53496,11,53524,53524,11,53552,53552,11,53580,53580,11,53608,53608,11,53636,53636,11,53664,53664,11,53692,53692,11,53720,53720,11,53748,53748,11,53776,53776,11,53804,53804,11,53832,53832,11,53860,53860,11,53888,53888,11,53916,53916,11,53944,53944,11,53972,53972,11,54000,54000,11,54028,54028,11,54056,54056,11,54084,54084,11,54112,54112,11,54140,54140,11,54168,54168,11,54196,54196,11,54224,54224,11,54252,54252,11,54280,54280,11,54308,54308,11,54336,54336,11,54364,54364,11,54392,54392,11,54420,54420,11,54448,54448,11,54476,54476,11,54504,54504,11,54532,54532,11,54560,54560,11,54588,54588,11,54616,54616,11,54644,54644,11,54672,54672,11,54700,54700,11,54728,54728,11,54756,54756,11,54784,54784,11,54812,54812,11,54840,54840,11,54868,54868,11,54896,54896,11,54924,54924,11,54952,54952,11,54980,54980,11,55008,55008,11,55036,55036,11,55064,55064,11,55092,55092,11,55120,55120,11,55148,55148,11,55176,55176,11,55216,55238,9,64286,64286,5,65056,65071,5,65438,65439,5,65529,65531,4,66272,66272,5,68097,68099,5,68108,68111,5,68159,68159,5,68900,68903,5,69446,69456,5,69632,69632,7,69634,69634,7,69744,69744,5,69759,69761,5,69808,69810,7,69815,69816,7,69821,69821,1,69837,69837,1,69927,69931,5,69933,69940,5,70003,70003,5,70018,70018,7,70070,70078,5,70082,70083,1,70094,70094,7,70188,70190,7,70194,70195,7,70197,70197,7,70206,70206,5,70368,70370,7,70400,70401,5,70459,70460,5,70463,70463,7,70465,70468,7,70475,70477,7,70498,70499,7,70512,70516,5,70712,70719,5,70722,70724,5,70726,70726,5,70832,70832,5,70835,70840,5,70842,70842,5,70845,70845,5,70847,70848,5,70850,70851,5,71088,71089,7,71096,71099,7,71102,71102,7,71132,71133,5,71219,71226,5,71229,71229,5,71231,71232,5,71340,71340,7,71342,71343,7,71350,71350,7,71453,71455,5,71462,71462,7,71724,71726,7,71736,71736,7,71984,71984,5,71991,71992,7,71997,71997,7,71999,71999,1,72001,72001,1,72003,72003,5,72148,72151,5,72156,72159,7,72164,72164,7,72243,72248,5,72250,72250,1,72263,72263,5,72279,72280,7,72324,72329,1,72343,72343,7,72751,72751,7,72760,72765,5,72767,72767,5,72873,72873,7,72881,72881,7,72884,72884,7,73009,73014,5,73020,73021,5,73030,73030,1,73098,73102,7,73107,73108,7,73110,73110,7,73459,73460,5,78896,78904,4,92976,92982,5,94033,94087,7,94180,94180,5,113821,113822,5,118528,118573,5,119141,119141,5,119143,119145,5,119150,119154,5,119163,119170,5,119210,119213,5,121344,121398,5,121461,121461,5,121499,121503,5,122880,122886,5,122907,122913,5,122918,122922,5,123566,123566,5,125136,125142,5,126976,126979,14,126981,127182,14,127184,127231,14,127279,127279,14,127344,127345,14,127374,127374,14,127405,127461,14,127489,127490,14,127514,127514,14,127538,127546,14,127561,127567,14,127570,127743,14,127757,127758,14,127760,127760,14,127762,127762,14,127766,127768,14,127770,127770,14,127772,127772,14,127775,127776,14,127778,127779,14,127789,127791,14,127794,127795,14,127798,127798,14,127819,127819,14,127824,127824,14,127868,127868,14,127870,127871,14,127892,127893,14,127896,127896,14,127900,127901,14,127904,127940,14,127942,127942,14,127944,127944,14,127946,127946,14,127951,127955,14,127968,127971,14,127973,127984,14,127987,127987,14,127989,127989,14,127991,127991,14,127995,127999,5,128008,128008,14,128012,128014,14,128017,128018,14,128020,128020,14,128022,128022,14,128042,128042,14,128063,128063,14,128065,128065,14,128101,128101,14,128108,128109,14,128173,128173,14,128182,128183,14,128236,128237,14,128239,128239,14,128245,128245,14,128248,128248,14,128253,128253,14,128255,128258,14,128260,128263,14,128265,128265,14,128277,128277,14,128300,128301,14,128326,128328,14,128331,128334,14,128336,128347,14,128360,128366,14,128369,128370,14,128378,128378,14,128391,128391,14,128394,128397,14,128400,128400,14,128405,128406,14,128420,128420,14,128422,128423,14,128425,128432,14,128435,128443,14,128445,128449,14,128453,128464,14,128468,128475,14,128479,128480,14,128482,128482,14,128484,128487,14,128489,128494,14,128496,128498,14,128500,128505,14,128507,128511,14,128513,128518,14,128521,128525,14,128527,128527,14,128529,128529,14,128533,128533,14,128535,128535,14,128537,128537,14]'); +} + +//#endregion + +/** + * Computes the offset after performing a left delete on the given string, + * while considering unicode grapheme/emoji rules. +*/ +export function getLeftDeleteOffset(offset: number, str: string): number { + if (offset === 0) { + return 0; + } + + // Try to delete emoji part. + const emojiOffset = getOffsetBeforeLastEmojiComponent(offset, str); + if (emojiOffset !== undefined) { + return emojiOffset; + } + + // Otherwise, just skip a single code point. + const iterator = new CodePointIterator(str, offset); + iterator.prevCodePoint(); + return iterator.offset; +} + +function getOffsetBeforeLastEmojiComponent(initialOffset: number, str: string): number | undefined { + // See https://www.unicode.org/reports/tr51/tr51-14.html#EBNF_and_Regex for the + // structure of emojis. + const iterator = new CodePointIterator(str, initialOffset); + let codePoint = iterator.prevCodePoint(); + + // Skip modifiers + while ((isEmojiModifier(codePoint) || codePoint === CodePoint.emojiVariantSelector || codePoint === CodePoint.enclosingKeyCap)) { + if (iterator.offset === 0) { + // Cannot skip modifier, no preceding emoji base. + return undefined; + } + codePoint = iterator.prevCodePoint(); + } + + // Expect base emoji + if (!isEmojiImprecise(codePoint)) { + // Unexpected code point, not a valid emoji. + return undefined; + } + + let resultOffset = iterator.offset; + + if (resultOffset > 0) { + // Skip optional ZWJ code points that combine multiple emojis. + // In theory, we should check if that ZWJ actually combines multiple emojis + // to prevent deleting ZWJs in situations we didn't account for. + const optionalZwjCodePoint = iterator.prevCodePoint(); + if (optionalZwjCodePoint === CodePoint.zwj) { + resultOffset = iterator.offset; + } + } + + return resultOffset; +} + +function isEmojiModifier(codePoint: number): boolean { + return 0x1F3FB <= codePoint && codePoint <= 0x1F3FF; +} + +const enum CodePoint { + zwj = 0x200D, + + /** + * Variation Selector-16 (VS16) + */ + emojiVariantSelector = 0xFE0F, + + /** + * Combining Enclosing Keycap + */ + enclosingKeyCap = 0x20E3, +} + +export const noBreakWhitespace = '\xa0'; + +export class AmbiguousCharacters { + private static readonly ambiguousCharacterData = new Lazy< + Record< + string | '_common' | '_default', + /* code point -> ascii code point */ number[] + > + >(() => { + // Generated using https://github.com/hediet/vscode-unicode-data + // Stored as key1, value1, key2, value2, ... + return JSON.parse( + '{\"_common\":[8232,32,8233,32,5760,32,8192,32,8193,32,8194,32,8195,32,8196,32,8197,32,8198,32,8200,32,8201,32,8202,32,8287,32,8199,32,8239,32,2042,95,65101,95,65102,95,65103,95,8208,45,8209,45,8210,45,65112,45,1748,45,8259,45,727,45,8722,45,10134,45,11450,45,1549,44,1643,44,8218,44,184,44,42233,44,894,59,2307,58,2691,58,1417,58,1795,58,1796,58,5868,58,65072,58,6147,58,6153,58,8282,58,1475,58,760,58,42889,58,8758,58,720,58,42237,58,451,33,11601,33,660,63,577,63,2429,63,5038,63,42731,63,119149,46,8228,46,1793,46,1794,46,42510,46,68176,46,1632,46,1776,46,42232,46,1373,96,65287,96,8219,96,8242,96,1370,96,1523,96,8175,96,65344,96,900,96,8189,96,8125,96,8127,96,8190,96,697,96,884,96,712,96,714,96,715,96,756,96,699,96,701,96,700,96,702,96,42892,96,1497,96,2036,96,2037,96,5194,96,5836,96,94033,96,94034,96,65339,91,10088,40,10098,40,12308,40,64830,40,65341,93,10089,41,10099,41,12309,41,64831,41,10100,123,119060,123,10101,125,65342,94,8270,42,1645,42,8727,42,66335,42,5941,47,8257,47,8725,47,8260,47,9585,47,10187,47,10744,47,119354,47,12755,47,12339,47,11462,47,20031,47,12035,47,65340,92,65128,92,8726,92,10189,92,10741,92,10745,92,119311,92,119355,92,12756,92,20022,92,12034,92,42872,38,708,94,710,94,5869,43,10133,43,66203,43,8249,60,10094,60,706,60,119350,60,5176,60,5810,60,5120,61,11840,61,12448,61,42239,61,8250,62,10095,62,707,62,119351,62,5171,62,94015,62,8275,126,732,126,8128,126,8764,126,65372,124,65293,45,120784,50,120794,50,120804,50,120814,50,120824,50,130034,50,42842,50,423,50,1000,50,42564,50,5311,50,42735,50,119302,51,120785,51,120795,51,120805,51,120815,51,120825,51,130035,51,42923,51,540,51,439,51,42858,51,11468,51,1248,51,94011,51,71882,51,120786,52,120796,52,120806,52,120816,52,120826,52,130036,52,5070,52,71855,52,120787,53,120797,53,120807,53,120817,53,120827,53,130037,53,444,53,71867,53,120788,54,120798,54,120808,54,120818,54,120828,54,130038,54,11474,54,5102,54,71893,54,119314,55,120789,55,120799,55,120809,55,120819,55,120829,55,130039,55,66770,55,71878,55,2819,56,2538,56,2666,56,125131,56,120790,56,120800,56,120810,56,120820,56,120830,56,130040,56,547,56,546,56,66330,56,2663,57,2920,57,2541,57,3437,57,120791,57,120801,57,120811,57,120821,57,120831,57,130041,57,42862,57,11466,57,71884,57,71852,57,71894,57,9082,97,65345,97,119834,97,119886,97,119938,97,119990,97,120042,97,120094,97,120146,97,120198,97,120250,97,120302,97,120354,97,120406,97,120458,97,593,97,945,97,120514,97,120572,97,120630,97,120688,97,120746,97,65313,65,119808,65,119860,65,119912,65,119964,65,120016,65,120068,65,120120,65,120172,65,120224,65,120276,65,120328,65,120380,65,120432,65,913,65,120488,65,120546,65,120604,65,120662,65,120720,65,5034,65,5573,65,42222,65,94016,65,66208,65,119835,98,119887,98,119939,98,119991,98,120043,98,120095,98,120147,98,120199,98,120251,98,120303,98,120355,98,120407,98,120459,98,388,98,5071,98,5234,98,5551,98,65314,66,8492,66,119809,66,119861,66,119913,66,120017,66,120069,66,120121,66,120173,66,120225,66,120277,66,120329,66,120381,66,120433,66,42932,66,914,66,120489,66,120547,66,120605,66,120663,66,120721,66,5108,66,5623,66,42192,66,66178,66,66209,66,66305,66,65347,99,8573,99,119836,99,119888,99,119940,99,119992,99,120044,99,120096,99,120148,99,120200,99,120252,99,120304,99,120356,99,120408,99,120460,99,7428,99,1010,99,11429,99,43951,99,66621,99,128844,67,71922,67,71913,67,65315,67,8557,67,8450,67,8493,67,119810,67,119862,67,119914,67,119966,67,120018,67,120174,67,120226,67,120278,67,120330,67,120382,67,120434,67,1017,67,11428,67,5087,67,42202,67,66210,67,66306,67,66581,67,66844,67,8574,100,8518,100,119837,100,119889,100,119941,100,119993,100,120045,100,120097,100,120149,100,120201,100,120253,100,120305,100,120357,100,120409,100,120461,100,1281,100,5095,100,5231,100,42194,100,8558,68,8517,68,119811,68,119863,68,119915,68,119967,68,120019,68,120071,68,120123,68,120175,68,120227,68,120279,68,120331,68,120383,68,120435,68,5024,68,5598,68,5610,68,42195,68,8494,101,65349,101,8495,101,8519,101,119838,101,119890,101,119942,101,120046,101,120098,101,120150,101,120202,101,120254,101,120306,101,120358,101,120410,101,120462,101,43826,101,1213,101,8959,69,65317,69,8496,69,119812,69,119864,69,119916,69,120020,69,120072,69,120124,69,120176,69,120228,69,120280,69,120332,69,120384,69,120436,69,917,69,120492,69,120550,69,120608,69,120666,69,120724,69,11577,69,5036,69,42224,69,71846,69,71854,69,66182,69,119839,102,119891,102,119943,102,119995,102,120047,102,120099,102,120151,102,120203,102,120255,102,120307,102,120359,102,120411,102,120463,102,43829,102,42905,102,383,102,7837,102,1412,102,119315,70,8497,70,119813,70,119865,70,119917,70,120021,70,120073,70,120125,70,120177,70,120229,70,120281,70,120333,70,120385,70,120437,70,42904,70,988,70,120778,70,5556,70,42205,70,71874,70,71842,70,66183,70,66213,70,66853,70,65351,103,8458,103,119840,103,119892,103,119944,103,120048,103,120100,103,120152,103,120204,103,120256,103,120308,103,120360,103,120412,103,120464,103,609,103,7555,103,397,103,1409,103,119814,71,119866,71,119918,71,119970,71,120022,71,120074,71,120126,71,120178,71,120230,71,120282,71,120334,71,120386,71,120438,71,1292,71,5056,71,5107,71,42198,71,65352,104,8462,104,119841,104,119945,104,119997,104,120049,104,120101,104,120153,104,120205,104,120257,104,120309,104,120361,104,120413,104,120465,104,1211,104,1392,104,5058,104,65320,72,8459,72,8460,72,8461,72,119815,72,119867,72,119919,72,120023,72,120179,72,120231,72,120283,72,120335,72,120387,72,120439,72,919,72,120494,72,120552,72,120610,72,120668,72,120726,72,11406,72,5051,72,5500,72,42215,72,66255,72,731,105,9075,105,65353,105,8560,105,8505,105,8520,105,119842,105,119894,105,119946,105,119998,105,120050,105,120102,105,120154,105,120206,105,120258,105,120310,105,120362,105,120414,105,120466,105,120484,105,618,105,617,105,953,105,8126,105,890,105,120522,105,120580,105,120638,105,120696,105,120754,105,1110,105,42567,105,1231,105,43893,105,5029,105,71875,105,65354,106,8521,106,119843,106,119895,106,119947,106,119999,106,120051,106,120103,106,120155,106,120207,106,120259,106,120311,106,120363,106,120415,106,120467,106,1011,106,1112,106,65322,74,119817,74,119869,74,119921,74,119973,74,120025,74,120077,74,120129,74,120181,74,120233,74,120285,74,120337,74,120389,74,120441,74,42930,74,895,74,1032,74,5035,74,5261,74,42201,74,119844,107,119896,107,119948,107,120000,107,120052,107,120104,107,120156,107,120208,107,120260,107,120312,107,120364,107,120416,107,120468,107,8490,75,65323,75,119818,75,119870,75,119922,75,119974,75,120026,75,120078,75,120130,75,120182,75,120234,75,120286,75,120338,75,120390,75,120442,75,922,75,120497,75,120555,75,120613,75,120671,75,120729,75,11412,75,5094,75,5845,75,42199,75,66840,75,1472,108,8739,73,9213,73,65512,73,1633,108,1777,73,66336,108,125127,108,120783,73,120793,73,120803,73,120813,73,120823,73,130033,73,65321,73,8544,73,8464,73,8465,73,119816,73,119868,73,119920,73,120024,73,120128,73,120180,73,120232,73,120284,73,120336,73,120388,73,120440,73,65356,108,8572,73,8467,108,119845,108,119897,108,119949,108,120001,108,120053,108,120105,73,120157,73,120209,73,120261,73,120313,73,120365,73,120417,73,120469,73,448,73,120496,73,120554,73,120612,73,120670,73,120728,73,11410,73,1030,73,1216,73,1493,108,1503,108,1575,108,126464,108,126592,108,65166,108,65165,108,1994,108,11599,73,5825,73,42226,73,93992,73,66186,124,66313,124,119338,76,8556,76,8466,76,119819,76,119871,76,119923,76,120027,76,120079,76,120131,76,120183,76,120235,76,120287,76,120339,76,120391,76,120443,76,11472,76,5086,76,5290,76,42209,76,93974,76,71843,76,71858,76,66587,76,66854,76,65325,77,8559,77,8499,77,119820,77,119872,77,119924,77,120028,77,120080,77,120132,77,120184,77,120236,77,120288,77,120340,77,120392,77,120444,77,924,77,120499,77,120557,77,120615,77,120673,77,120731,77,1018,77,11416,77,5047,77,5616,77,5846,77,42207,77,66224,77,66321,77,119847,110,119899,110,119951,110,120003,110,120055,110,120107,110,120159,110,120211,110,120263,110,120315,110,120367,110,120419,110,120471,110,1400,110,1404,110,65326,78,8469,78,119821,78,119873,78,119925,78,119977,78,120029,78,120081,78,120185,78,120237,78,120289,78,120341,78,120393,78,120445,78,925,78,120500,78,120558,78,120616,78,120674,78,120732,78,11418,78,42208,78,66835,78,3074,111,3202,111,3330,111,3458,111,2406,111,2662,111,2790,111,3046,111,3174,111,3302,111,3430,111,3664,111,3792,111,4160,111,1637,111,1781,111,65359,111,8500,111,119848,111,119900,111,119952,111,120056,111,120108,111,120160,111,120212,111,120264,111,120316,111,120368,111,120420,111,120472,111,7439,111,7441,111,43837,111,959,111,120528,111,120586,111,120644,111,120702,111,120760,111,963,111,120532,111,120590,111,120648,111,120706,111,120764,111,11423,111,4351,111,1413,111,1505,111,1607,111,126500,111,126564,111,126596,111,65259,111,65260,111,65258,111,65257,111,1726,111,64428,111,64429,111,64427,111,64426,111,1729,111,64424,111,64425,111,64423,111,64422,111,1749,111,3360,111,4125,111,66794,111,71880,111,71895,111,66604,111,1984,79,2534,79,2918,79,12295,79,70864,79,71904,79,120782,79,120792,79,120802,79,120812,79,120822,79,130032,79,65327,79,119822,79,119874,79,119926,79,119978,79,120030,79,120082,79,120134,79,120186,79,120238,79,120290,79,120342,79,120394,79,120446,79,927,79,120502,79,120560,79,120618,79,120676,79,120734,79,11422,79,1365,79,11604,79,4816,79,2848,79,66754,79,42227,79,71861,79,66194,79,66219,79,66564,79,66838,79,9076,112,65360,112,119849,112,119901,112,119953,112,120005,112,120057,112,120109,112,120161,112,120213,112,120265,112,120317,112,120369,112,120421,112,120473,112,961,112,120530,112,120544,112,120588,112,120602,112,120646,112,120660,112,120704,112,120718,112,120762,112,120776,112,11427,112,65328,80,8473,80,119823,80,119875,80,119927,80,119979,80,120031,80,120083,80,120187,80,120239,80,120291,80,120343,80,120395,80,120447,80,929,80,120504,80,120562,80,120620,80,120678,80,120736,80,11426,80,5090,80,5229,80,42193,80,66197,80,119850,113,119902,113,119954,113,120006,113,120058,113,120110,113,120162,113,120214,113,120266,113,120318,113,120370,113,120422,113,120474,113,1307,113,1379,113,1382,113,8474,81,119824,81,119876,81,119928,81,119980,81,120032,81,120084,81,120188,81,120240,81,120292,81,120344,81,120396,81,120448,81,11605,81,119851,114,119903,114,119955,114,120007,114,120059,114,120111,114,120163,114,120215,114,120267,114,120319,114,120371,114,120423,114,120475,114,43847,114,43848,114,7462,114,11397,114,43905,114,119318,82,8475,82,8476,82,8477,82,119825,82,119877,82,119929,82,120033,82,120189,82,120241,82,120293,82,120345,82,120397,82,120449,82,422,82,5025,82,5074,82,66740,82,5511,82,42211,82,94005,82,65363,115,119852,115,119904,115,119956,115,120008,115,120060,115,120112,115,120164,115,120216,115,120268,115,120320,115,120372,115,120424,115,120476,115,42801,115,445,115,1109,115,43946,115,71873,115,66632,115,65331,83,119826,83,119878,83,119930,83,119982,83,120034,83,120086,83,120138,83,120190,83,120242,83,120294,83,120346,83,120398,83,120450,83,1029,83,1359,83,5077,83,5082,83,42210,83,94010,83,66198,83,66592,83,119853,116,119905,116,119957,116,120009,116,120061,116,120113,116,120165,116,120217,116,120269,116,120321,116,120373,116,120425,116,120477,116,8868,84,10201,84,128872,84,65332,84,119827,84,119879,84,119931,84,119983,84,120035,84,120087,84,120139,84,120191,84,120243,84,120295,84,120347,84,120399,84,120451,84,932,84,120507,84,120565,84,120623,84,120681,84,120739,84,11430,84,5026,84,42196,84,93962,84,71868,84,66199,84,66225,84,66325,84,119854,117,119906,117,119958,117,120010,117,120062,117,120114,117,120166,117,120218,117,120270,117,120322,117,120374,117,120426,117,120478,117,42911,117,7452,117,43854,117,43858,117,651,117,965,117,120534,117,120592,117,120650,117,120708,117,120766,117,1405,117,66806,117,71896,117,8746,85,8899,85,119828,85,119880,85,119932,85,119984,85,120036,85,120088,85,120140,85,120192,85,120244,85,120296,85,120348,85,120400,85,120452,85,1357,85,4608,85,66766,85,5196,85,42228,85,94018,85,71864,85,8744,118,8897,118,65366,118,8564,118,119855,118,119907,118,119959,118,120011,118,120063,118,120115,118,120167,118,120219,118,120271,118,120323,118,120375,118,120427,118,120479,118,7456,118,957,118,120526,118,120584,118,120642,118,120700,118,120758,118,1141,118,1496,118,71430,118,43945,118,71872,118,119309,86,1639,86,1783,86,8548,86,119829,86,119881,86,119933,86,119985,86,120037,86,120089,86,120141,86,120193,86,120245,86,120297,86,120349,86,120401,86,120453,86,1140,86,11576,86,5081,86,5167,86,42719,86,42214,86,93960,86,71840,86,66845,86,623,119,119856,119,119908,119,119960,119,120012,119,120064,119,120116,119,120168,119,120220,119,120272,119,120324,119,120376,119,120428,119,120480,119,7457,119,1121,119,1309,119,1377,119,71434,119,71438,119,71439,119,43907,119,71919,87,71910,87,119830,87,119882,87,119934,87,119986,87,120038,87,120090,87,120142,87,120194,87,120246,87,120298,87,120350,87,120402,87,120454,87,1308,87,5043,87,5076,87,42218,87,5742,120,10539,120,10540,120,10799,120,65368,120,8569,120,119857,120,119909,120,119961,120,120013,120,120065,120,120117,120,120169,120,120221,120,120273,120,120325,120,120377,120,120429,120,120481,120,5441,120,5501,120,5741,88,9587,88,66338,88,71916,88,65336,88,8553,88,119831,88,119883,88,119935,88,119987,88,120039,88,120091,88,120143,88,120195,88,120247,88,120299,88,120351,88,120403,88,120455,88,42931,88,935,88,120510,88,120568,88,120626,88,120684,88,120742,88,11436,88,11613,88,5815,88,42219,88,66192,88,66228,88,66327,88,66855,88,611,121,7564,121,65369,121,119858,121,119910,121,119962,121,120014,121,120066,121,120118,121,120170,121,120222,121,120274,121,120326,121,120378,121,120430,121,120482,121,655,121,7935,121,43866,121,947,121,8509,121,120516,121,120574,121,120632,121,120690,121,120748,121,1199,121,4327,121,71900,121,65337,89,119832,89,119884,89,119936,89,119988,89,120040,89,120092,89,120144,89,120196,89,120248,89,120300,89,120352,89,120404,89,120456,89,933,89,978,89,120508,89,120566,89,120624,89,120682,89,120740,89,11432,89,1198,89,5033,89,5053,89,42220,89,94019,89,71844,89,66226,89,119859,122,119911,122,119963,122,120015,122,120067,122,120119,122,120171,122,120223,122,120275,122,120327,122,120379,122,120431,122,120483,122,7458,122,43923,122,71876,122,66293,90,71909,90,65338,90,8484,90,8488,90,119833,90,119885,90,119937,90,119989,90,120041,90,120197,90,120249,90,120301,90,120353,90,120405,90,120457,90,918,90,120493,90,120551,90,120609,90,120667,90,120725,90,5059,90,42204,90,71849,90,65282,34,65284,36,65285,37,65286,38,65290,42,65291,43,65294,46,65295,47,65296,48,65297,49,65298,50,65299,51,65300,52,65301,53,65302,54,65303,55,65304,56,65305,57,65308,60,65309,61,65310,62,65312,64,65316,68,65318,70,65319,71,65324,76,65329,81,65330,82,65333,85,65334,86,65335,87,65343,95,65346,98,65348,100,65350,102,65355,107,65357,109,65358,110,65361,113,65362,114,65364,116,65365,117,65367,119,65370,122,65371,123,65373,125,119846,109],\"_default\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"cs\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"de\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"es\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"fr\":[65374,126,65306,58,65281,33,8216,96,8245,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"it\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ja\":[8211,45,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65292,44,65307,59],\"ko\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pl\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pt-BR\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"qps-ploc\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ru\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,305,105,921,73,1009,112,215,120,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"tr\":[160,32,8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"zh-hans\":[65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41],\"zh-hant\":[8211,45,65374,126,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65307,59]}' + ); + }); + + private static readonly cache = new LRUCachedFunction< + string[], + AmbiguousCharacters + >({ getCacheKey: JSON.stringify }, (locales) => { + function arrayToMap(arr: number[]): Map { + const result = new Map(); + for (let i = 0; i < arr.length; i += 2) { + result.set(arr[i], arr[i + 1]); + } + return result; + } + + function mergeMaps( + map1: Map, + map2: Map + ): Map { + const result = new Map(map1); + for (const [key, value] of map2) { + result.set(key, value); + } + return result; + } + + function intersectMaps( + map1: Map | undefined, + map2: Map + ) { + if (!map1) { + return map2; + } + const result = new Map(); + for (const [key, value] of map1) { + if (map2.has(key)) { + result.set(key, value); + } + } + return result; + } + + const data = this.ambiguousCharacterData.value; + + let filteredLocales = locales.filter( + (l) => !l.startsWith('_') && l in data + ); + if (filteredLocales.length === 0) { + filteredLocales = ['_default']; + } + + let languageSpecificMap: Map | undefined = undefined; + for (const locale of filteredLocales) { + const map = arrayToMap(data[locale]); + languageSpecificMap = intersectMaps(languageSpecificMap, map); + } + + const commonMap = arrayToMap(data['_common']); + const map = mergeMaps(commonMap, languageSpecificMap!); + + return new AmbiguousCharacters(map); + }); + + public static getInstance(locales: Set): AmbiguousCharacters { + return AmbiguousCharacters.cache.get(Array.from(locales)); + } + + private static _locales = new Lazy(() => + Object.keys(AmbiguousCharacters.ambiguousCharacterData.value).filter( + (k) => !k.startsWith('_') + ) + ); + public static getLocales(): string[] { + return AmbiguousCharacters._locales.value; + } + + private constructor( + private readonly confusableDictionary: Map + ) { } + + public isAmbiguous(codePoint: number): boolean { + return this.confusableDictionary.has(codePoint); + } + + public containsAmbiguousCharacter(str: string): boolean { + for (let i = 0; i < str.length; i++) { + const codePoint = str.codePointAt(i); + if (typeof codePoint === 'number' && this.isAmbiguous(codePoint)) { + return true; + } + } + return false; + } + + /** + * Returns the non basic ASCII code point that the given code point can be confused, + * or undefined if such code point does note exist. + */ + public getPrimaryConfusable(codePoint: number): number | undefined { + return this.confusableDictionary.get(codePoint); + } + + public getConfusableCodePoints(): ReadonlySet { + return new Set(this.confusableDictionary.keys()); + } +} + +export class InvisibleCharacters { + private static getRawData(): number[] { + // Generated using https://github.com/hediet/vscode-unicode-data + return JSON.parse('[9,10,11,12,13,32,127,160,173,847,1564,4447,4448,6068,6069,6155,6156,6157,6158,7355,7356,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8203,8204,8205,8206,8207,8234,8235,8236,8237,8238,8239,8287,8288,8289,8290,8291,8292,8293,8294,8295,8296,8297,8298,8299,8300,8301,8302,8303,10240,12288,12644,65024,65025,65026,65027,65028,65029,65030,65031,65032,65033,65034,65035,65036,65037,65038,65039,65279,65440,65520,65521,65522,65523,65524,65525,65526,65527,65528,65532,78844,119155,119156,119157,119158,119159,119160,119161,119162,917504,917505,917506,917507,917508,917509,917510,917511,917512,917513,917514,917515,917516,917517,917518,917519,917520,917521,917522,917523,917524,917525,917526,917527,917528,917529,917530,917531,917532,917533,917534,917535,917536,917537,917538,917539,917540,917541,917542,917543,917544,917545,917546,917547,917548,917549,917550,917551,917552,917553,917554,917555,917556,917557,917558,917559,917560,917561,917562,917563,917564,917565,917566,917567,917568,917569,917570,917571,917572,917573,917574,917575,917576,917577,917578,917579,917580,917581,917582,917583,917584,917585,917586,917587,917588,917589,917590,917591,917592,917593,917594,917595,917596,917597,917598,917599,917600,917601,917602,917603,917604,917605,917606,917607,917608,917609,917610,917611,917612,917613,917614,917615,917616,917617,917618,917619,917620,917621,917622,917623,917624,917625,917626,917627,917628,917629,917630,917631,917760,917761,917762,917763,917764,917765,917766,917767,917768,917769,917770,917771,917772,917773,917774,917775,917776,917777,917778,917779,917780,917781,917782,917783,917784,917785,917786,917787,917788,917789,917790,917791,917792,917793,917794,917795,917796,917797,917798,917799,917800,917801,917802,917803,917804,917805,917806,917807,917808,917809,917810,917811,917812,917813,917814,917815,917816,917817,917818,917819,917820,917821,917822,917823,917824,917825,917826,917827,917828,917829,917830,917831,917832,917833,917834,917835,917836,917837,917838,917839,917840,917841,917842,917843,917844,917845,917846,917847,917848,917849,917850,917851,917852,917853,917854,917855,917856,917857,917858,917859,917860,917861,917862,917863,917864,917865,917866,917867,917868,917869,917870,917871,917872,917873,917874,917875,917876,917877,917878,917879,917880,917881,917882,917883,917884,917885,917886,917887,917888,917889,917890,917891,917892,917893,917894,917895,917896,917897,917898,917899,917900,917901,917902,917903,917904,917905,917906,917907,917908,917909,917910,917911,917912,917913,917914,917915,917916,917917,917918,917919,917920,917921,917922,917923,917924,917925,917926,917927,917928,917929,917930,917931,917932,917933,917934,917935,917936,917937,917938,917939,917940,917941,917942,917943,917944,917945,917946,917947,917948,917949,917950,917951,917952,917953,917954,917955,917956,917957,917958,917959,917960,917961,917962,917963,917964,917965,917966,917967,917968,917969,917970,917971,917972,917973,917974,917975,917976,917977,917978,917979,917980,917981,917982,917983,917984,917985,917986,917987,917988,917989,917990,917991,917992,917993,917994,917995,917996,917997,917998,917999]'); + } + + private static _data: Set | undefined = undefined; + + private static getData() { + if (!this._data) { + this._data = new Set(InvisibleCharacters.getRawData()); + } + return this._data; + } + + public static isInvisibleCharacter(codePoint: number): boolean { + return InvisibleCharacters.getData().has(codePoint); + } + + public static containsInvisibleCharacter(str: string): boolean { + for (let i = 0; i < str.length; i++) { + const codePoint = str.codePointAt(i); + if (typeof codePoint === 'number' && InvisibleCharacters.isInvisibleCharacter(codePoint)) { + return true; + } + } + return false; + + } + + public static get codePoints(): ReadonlySet { + return InvisibleCharacters.getData(); + } +} diff --git a/packages/core/src/symbols.ts b/packages/core/src/symbols.ts new file mode 100644 index 00000000..5d2a641a --- /dev/null +++ b/packages/core/src/symbols.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Can be passed into the Delayed to defer using a microtask + * */ +export const MicrotaskDelay = Symbol('MicrotaskDelay'); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts new file mode 100644 index 00000000..68227c3a --- /dev/null +++ b/packages/core/src/types.ts @@ -0,0 +1,299 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { assert } from './assert.js'; + +/** + * @returns whether the provided parameter is a JavaScript String or not. + */ +export function isString(str: unknown): str is string { + return (typeof str === 'string'); +} + +/** + * @returns whether the provided parameter is a JavaScript Array and each element in the array is a string. + */ +export function isStringArray(value: unknown): value is string[] { + return Array.isArray(value) && (value).every(elem => isString(elem)); +} + +/** + * @returns whether the provided parameter is of type `object` but **not** + * `null`, an `array`, a `regexp`, nor a `date`. + */ +export function isObject(obj: unknown): obj is Object { + // The method can't do a type cast since there are type (like strings) which + // are subclasses of any put not positvely matched by the function. Hence type + // narrowing results in wrong results. + return typeof obj === 'object' + && obj !== null + && !Array.isArray(obj) + && !(obj instanceof RegExp) + && !(obj instanceof Date); +} + +/** + * @returns whether the provided parameter is of type `Buffer` or Uint8Array dervived type + */ +export function isTypedArray(obj: unknown): obj is Object { + const TypedArray = Object.getPrototypeOf(Uint8Array); + return typeof obj === 'object' + && obj instanceof TypedArray; +} + +/** + * In **contrast** to just checking `typeof` this will return `false` for `NaN`. + * @returns whether the provided parameter is a JavaScript Number or not. + */ +export function isNumber(obj: unknown): obj is number { + return (typeof obj === 'number' && !isNaN(obj)); +} + +/** + * @returns whether the provided parameter is an Iterable, casting to the given generic + */ +export function isIterable(obj: unknown): obj is Iterable { + return !!obj && typeof (obj as any)[Symbol.iterator] === 'function'; +} + +/** + * @returns whether the provided parameter is a JavaScript Boolean or not. + */ +export function isBoolean(obj: unknown): obj is boolean { + return (obj === true || obj === false); +} + +/** + * @returns whether the provided parameter is undefined. + */ +export function isUndefined(obj: unknown): obj is undefined { + return (typeof obj === 'undefined'); +} + +/** + * @returns whether the provided parameter is defined. + */ +export function isDefined(arg: T | null | undefined): arg is T { + return !isUndefinedOrNull(arg); +} + +/** + * @returns whether the provided parameter is undefined or null. + */ +export function isUndefinedOrNull(obj: unknown): obj is undefined | null { + return (isUndefined(obj) || obj === null); +} + + +export function assertType(condition: unknown, type?: string): asserts condition { + if (!condition) { + throw new Error(type ? `Unexpected type, expected '${type}'` : 'Unexpected type'); + } +} + +/** + * Asserts that the argument passed in is neither undefined nor null. + * + * @see {@link assertDefined} for a similar utility that leverages TS assertion functions to narrow down the type of `arg` to be non-nullable. + */ +export function assertIsDefined(arg: T | null | undefined): NonNullable { + assert( + arg !== null && arg !== undefined, + 'Argument is `undefined` or `null`.', + ); + + return arg; +} + +/** + * Asserts that a provided `value` is `defined` - not `null` or `undefined`, + * throwing an error with the provided error or error message, while also + * narrowing down the type of the `value` to be `NonNullable` using TS + * assertion functions. + * + * @throws if the provided `value` is `null` or `undefined`. + * + * ## Examples + * + * ```typescript + * // an assert with an error message + * assertDefined('some value', 'String constant is not defined o_O.'); + * + * // `throws!` the provided error + * assertDefined(null, new Error('Should throw this error.')); + * + * // narrows down the type of `someValue` to be non-nullable + * const someValue: string | undefined | null = blackbox(); + * assertDefined(someValue, 'Some value must be defined.'); + * console.log(someValue.length); // now type of `someValue` is `string` + * ``` + * + * @see {@link assertIsDefined} for a similar utility but without assertion. + * @see {@link https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions typescript-3-7.html#assertion-functions} + */ +export function assertDefined(value: T, error: string | NonNullable): asserts value is NonNullable { + if (value === null || value === undefined) { + const errorToThrow = typeof error === 'string' ? new Error(error) : error; + + throw errorToThrow; + } +} + +/** + * Asserts that each argument passed in is neither undefined nor null. + */ +export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined): [T1, T2]; +export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined): [T1, T2, T3]; +export function assertAllDefined(t1: T1 | null | undefined, t2: T2 | null | undefined, t3: T3 | null | undefined, t4: T4 | null | undefined): [T1, T2, T3, T4]; +export function assertAllDefined(...args: (unknown | null | undefined)[]): unknown[] { + const result:any = []; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (isUndefinedOrNull(arg)) { + throw new Error(`Assertion Failed: argument at index ${i} is undefined or null`); + } + + result.push(arg); + } + + return result; +} + +const hasOwnProperty = Object.prototype.hasOwnProperty; + +/** + * @returns whether the provided parameter is an empty JavaScript Object or not. + */ +export function isEmptyObject(obj: unknown): obj is object { + if (!isObject(obj)) { + return false; + } + + for (const key in obj) { + if (hasOwnProperty.call(obj, key)) { + return false; + } + } + + return true; +} + +/** + * @returns whether the provided parameter is a JavaScript Function or not. + */ +export function isFunction(obj: unknown): obj is Function { + return (typeof obj === 'function'); +} + +/** + * @returns whether the provided parameters is are JavaScript Function or not. + */ +export function areFunctions(...objects: unknown[]): boolean { + return objects.length > 0 && objects.every(isFunction); +} + +export type TypeConstraint = string | Function; + +export function validateConstraints(args: unknown[], constraints: Array): void { + const len = Math.min(args.length, constraints.length); + for (let i = 0; i < len; i++) { + validateConstraint(args[i], constraints[i]); + } +} + +export function validateConstraint(arg: unknown, constraint: TypeConstraint | undefined): void { + + if (isString(constraint)) { + if (typeof arg !== constraint) { + throw new Error(`argument does not match constraint: typeof ${constraint}`); + } + } else if (isFunction(constraint)) { + try { + if (arg instanceof constraint) { + return; + } + } catch { + // ignore + } + if (!isUndefinedOrNull(arg) && (arg as any).constructor === constraint) { + return; + } + if (constraint.length === 1 && constraint.call(undefined, arg) === true) { + return; + } + throw new Error(`argument does not match one of these constraints: arg instanceof constraint, arg.constructor === constraint, nor constraint(arg) === true`); + } +} + +/** + * Helper type assertion that safely upcasts a type to a supertype. + * + * This can be used to make sure the argument correctly conforms to the subtype while still being able to pass it + * to contexts that expects the supertype. + */ +export function upcast(x: Sub): Base { + return x; +} + +type AddFirstParameterToFunction = T extends (...args: any[]) => TargetFunctionsReturnType ? + // Function: add param to function + (firstArg: FirstParameter, ...args: Parameters) => ReturnType : + + // Else: just leave as is + T; + +/** + * Allows to add a first parameter to functions of a type. + */ +export type AddFirstParameterToFunctions = { + // For every property + [K in keyof Target]: AddFirstParameterToFunction; +}; + +/** + * Given an object with all optional properties, requires at least one to be defined. + * i.e. AtLeastOne; + */ +export type AtLeastOne }> = Partial & U[keyof U]; + +/** + * Only picks the non-optional properties of a type. + */ +export type OmitOptional = { [K in keyof T as T[K] extends Required[K] ? K : never]: T[K] }; + +/** + * A type that removed readonly-less from all properties of `T` + */ +export type Mutable = { + -readonly [P in keyof T]: T[P] +}; + +/** + * A single object or an array of the objects. + */ +export type SingleOrMany = T | T[]; + + +/** + * A type that recursively makes all properties of `T` required + */ +export type DeepRequiredNonNullable = { + [P in keyof T]-?: T[P] extends object ? DeepRequiredNonNullable : Required>; +}; + + +/** + * Represents a type that is a partial version of a given type `T`, where all properties are optional and can be deeply nested. + */ +export type DeepPartial = { + [P in keyof T]?: T[P] extends object ? DeepPartial : Partial; +}; + +/** + * Represents a type that is a partial version of a given type `T`, except a subset. + */ +export type PartialExcept = Partial> & Pick; diff --git a/packages/core/src/uint.ts b/packages/core/src/uint.ts new file mode 100644 index 00000000..e47c6a04 --- /dev/null +++ b/packages/core/src/uint.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const enum Constants { + /** + * MAX SMI (SMall Integer) as defined in v8. + * one bit is lost for boxing/unboxing flag. + * one bit is lost for sign flag. + * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values + */ + MAX_SAFE_SMALL_INTEGER = 1 << 30, + + /** + * MIN SMI (SMall Integer) as defined in v8. + * one bit is lost for boxing/unboxing flag. + * one bit is lost for sign flag. + * See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values + */ + MIN_SAFE_SMALL_INTEGER = -(1 << 30), + + /** + * Max unsigned integer that fits on 8 bits. + */ + MAX_UINT_8 = 255, // 2^8 - 1 + + /** + * Max unsigned integer that fits on 16 bits. + */ + MAX_UINT_16 = 65535, // 2^16 - 1 + + /** + * Max unsigned integer that fits on 32 bits. + */ + MAX_UINT_32 = 4294967295, // 2^32 - 1 + + UNICODE_SUPPLEMENTARY_PLANE_BEGIN = 0x010000 +} + +export function toUint8(v: number): number { + if (v < 0) { + return 0; + } + if (v > Constants.MAX_UINT_8) { + return Constants.MAX_UINT_8; + } + return v | 0; +} + +export function toUint32(v: number): number { + if (v < 0) { + return 0; + } + if (v > Constants.MAX_UINT_32) { + return Constants.MAX_UINT_32; + } + return v | 0; +} diff --git a/packages/core/src/uri.ts b/packages/core/src/uri.ts new file mode 100644 index 00000000..ef08ef65 --- /dev/null +++ b/packages/core/src/uri.ts @@ -0,0 +1,750 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CharCode } from './charCode.js'; +import { MarshalledId } from './marshallingIds.js'; +import * as paths from './path.js'; +import { isWindows } from './platform.js'; + +const _schemePattern = /^\w[\w\d+.-]*$/; +const _singleSlashStart = /^\//; +const _doubleSlashStart = /^\/\//; + +function _validateUri(ret: URI, _strict?: boolean): void { + + // scheme, must be set + if (!ret.scheme && _strict) { + throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${ret.authority}", path: "${ret.path}", query: "${ret.query}", fragment: "${ret.fragment}"}`); + } + + // scheme, https://tools.ietf.org/html/rfc3986#section-3.1 + // ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + if (ret.scheme && !_schemePattern.test(ret.scheme)) { + throw new Error('[UriError]: Scheme contains illegal characters.'); + } + + // path, http://tools.ietf.org/html/rfc3986#section-3.3 + // If a URI contains an authority component, then the path component + // must either be empty or begin with a slash ("/") character. If a URI + // does not contain an authority component, then the path cannot begin + // with two slash characters ("//"). + if (ret.path) { + if (ret.authority) { + if (!_singleSlashStart.test(ret.path)) { + throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character'); + } + } else { + if (_doubleSlashStart.test(ret.path)) { + throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")'); + } + } + } +} + +// for a while we allowed uris *without* schemes and this is the migration +// for them, e.g. an uri without scheme and without strict-mode warns and falls +// back to the file-scheme. that should cause the least carnage and still be a +// clear warning +function _schemeFix(scheme: string, _strict: boolean): string { + if (!scheme && !_strict) { + return 'file'; + } + return scheme; +} + +// implements a bit of https://tools.ietf.org/html/rfc3986#section-5 +function _referenceResolution(scheme: string, path: string): string { + + // the slash-character is our 'default base' as we don't + // support constructing URIs relative to other URIs. This + // also means that we alter and potentially break paths. + // see https://tools.ietf.org/html/rfc3986#section-5.1.4 + switch (scheme) { + case 'https': + case 'http': + case 'file': + if (!path) { + path = _slash; + } else if (path[0] !== _slash) { + path = _slash + path; + } + break; + } + return path; +} + +const _empty = ''; +const _slash = '/'; +const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; + +/** + * Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986. + * This class is a simple parser which creates the basic component parts + * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation + * and encoding. + * + * ```txt + * foo://example.com:8042/over/there?name=ferret#nose + * \_/ \______________/\_________/ \_________/ \__/ + * | | | | | + * scheme authority path query fragment + * | _____________________|__ + * / \ / \ + * urn:example:animal:ferret:nose + * ``` + */ +export class URI implements UriComponents { + + static isUri(thing: any): thing is URI { + if (thing instanceof URI) { + return true; + } + if (!thing) { + return false; + } + return typeof (thing).authority === 'string' + && typeof (thing).fragment === 'string' + && typeof (thing).path === 'string' + && typeof (thing).query === 'string' + && typeof (thing).scheme === 'string' + && typeof (thing).fsPath === 'string' + && typeof (thing).with === 'function' + && typeof (thing).toString === 'function'; + } + + /** + * scheme is the 'http' part of 'http://www.example.com/some/path?query#fragment'. + * The part before the first colon. + */ + readonly scheme: string; + + /** + * authority is the 'www.example.com' part of 'http://www.example.com/some/path?query#fragment'. + * The part between the first double slashes and the next slash. + */ + readonly authority: string; + + /** + * path is the '/some/path' part of 'http://www.example.com/some/path?query#fragment'. + */ + readonly path: string; + + /** + * query is the 'query' part of 'http://www.example.com/some/path?query#fragment'. + */ + readonly query: string; + + /** + * fragment is the 'fragment' part of 'http://www.example.com/some/path?query#fragment'. + */ + readonly fragment: string; + + /** + * @internal + */ + protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean); + + /** + * @internal + */ + protected constructor(components: UriComponents); + + /** + * @internal + */ + protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) { + + if (typeof schemeOrData === 'object') { + this.scheme = schemeOrData.scheme || _empty; + this.authority = schemeOrData.authority || _empty; + this.path = schemeOrData.path || _empty; + this.query = schemeOrData.query || _empty; + this.fragment = schemeOrData.fragment || _empty; + // no validation because it's this URI + // that creates uri components. + // _validateUri(this); + } else { + this.scheme = _schemeFix(schemeOrData, _strict); + this.authority = authority || _empty; + this.path = _referenceResolution(this.scheme, path || _empty); + this.query = query || _empty; + this.fragment = fragment || _empty; + + _validateUri(this, _strict); + } + } + + // ---- filesystem path ----------------------- + + /** + * Returns a string representing the corresponding file system path of this URI. + * Will handle UNC paths, normalizes windows drive letters to lower-case, and uses the + * platform specific path separator. + * + * * Will *not* validate the path for invalid characters and semantics. + * * Will *not* look at the scheme of this URI. + * * The result shall *not* be used for display purposes but for accessing a file on disk. + * + * + * The *difference* to `URI#path` is the use of the platform specific separator and the handling + * of UNC paths. See the below sample of a file-uri with an authority (UNC path). + * + * ```ts + const u = URI.parse('file://server/c$/folder/file.txt') + u.authority === 'server' + u.path === '/shares/c$/file.txt' + u.fsPath === '\\server\c$\folder\file.txt' + ``` + * + * Using `URI#path` to read a file (using fs-apis) would not be enough because parts of the path, + * namely the server name, would be missing. Therefore `URI#fsPath` exists - it's sugar to ease working + * with URIs that represent files on disk (`file` scheme). + */ + get fsPath(): string { + // if (this.scheme !== 'file') { + // console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`); + // } + return uriToFsPath(this, false); + } + + // ---- modify to new ------------------------- + + with(change: { scheme?: string; authority?: string | null; path?: string | null; query?: string | null; fragment?: string | null }): URI { + + if (!change) { + return this; + } + + let { scheme, authority, path, query, fragment } = change; + if (scheme === undefined) { + scheme = this.scheme; + } else if (scheme === null) { + scheme = _empty; + } + if (authority === undefined) { + authority = this.authority; + } else if (authority === null) { + authority = _empty; + } + if (path === undefined) { + path = this.path; + } else if (path === null) { + path = _empty; + } + if (query === undefined) { + query = this.query; + } else if (query === null) { + query = _empty; + } + if (fragment === undefined) { + fragment = this.fragment; + } else if (fragment === null) { + fragment = _empty; + } + + if (scheme === this.scheme + && authority === this.authority + && path === this.path + && query === this.query + && fragment === this.fragment) { + + return this; + } + + return new Uri(scheme, authority, path, query, fragment); + } + + // ---- parse & validate ------------------------ + + /** + * Creates a new URI from a string, e.g. `http://www.example.com/some/path`, + * `file:///usr/home`, or `scheme:with/path`. + * + * @param value A string which represents an URI (see `URI#toString`). + */ + static parse(value: string, _strict: boolean = false): URI { + const match = _regexp.exec(value); + if (!match) { + return new Uri(_empty, _empty, _empty, _empty, _empty); + } + return new Uri( + match[2] || _empty, + percentDecode(match[4] || _empty), + percentDecode(match[5] || _empty), + percentDecode(match[7] || _empty), + percentDecode(match[9] || _empty), + _strict + ); + } + + /** + * Creates a new URI from a file system path, e.g. `c:\my\files`, + * `/usr/home`, or `\\server\share\some\path`. + * + * The *difference* between `URI#parse` and `URI#file` is that the latter treats the argument + * as path, not as stringified-uri. E.g. `URI.file(path)` is **not the same as** + * `URI.parse('file://' + path)` because the path might contain characters that are + * interpreted (# and ?). See the following sample: + * ```ts + const good = URI.file('/coding/c#/project1'); + good.scheme === 'file'; + good.path === '/coding/c#/project1'; + good.fragment === ''; + const bad = URI.parse('file://' + '/coding/c#/project1'); + bad.scheme === 'file'; + bad.path === '/coding/c'; // path is now broken + bad.fragment === '/project1'; + ``` + * + * @param path A file system path (see `URI#fsPath`) + */ + static file(path: string): URI { + + let authority = _empty; + + // normalize to fwd-slashes on windows, + // on other systems bwd-slashes are valid + // filename character, eg /f\oo/ba\r.txt + if (isWindows) { + path = path.replace(/\\/g, _slash); + } + + // check for authority as used in UNC shares + // or use the path as given + if (path[0] === _slash && path[1] === _slash) { + const idx = path.indexOf(_slash, 2); + if (idx === -1) { + authority = path.substring(2); + path = _slash; + } else { + authority = path.substring(2, idx); + path = path.substring(idx) || _slash; + } + } + + return new Uri('file', authority, path, _empty, _empty); + } + + /** + * Creates new URI from uri components. + * + * Unless `strict` is `true` the scheme is defaults to be `file`. This function performs + * validation and should be used for untrusted uri components retrieved from storage, + * user input, command arguments etc + */ + static from(components: UriComponents, strict?: boolean): URI { + const result = new Uri( + components.scheme, + components.authority, + components.path, + components.query, + components.fragment, + strict + ); + return result; + } + + /** + * Join a URI path with path fragments and normalizes the resulting path. + * + * @param uri The input URI. + * @param pathFragment The path fragment to add to the URI path. + * @returns The resulting URI. + */ + static joinPath(uri: URI, ...pathFragment: string[]): URI { + if (!uri.path) { + throw new Error(`[UriError]: cannot call joinPath on URI without path`); + } + let newPath: string; + if (isWindows && uri.scheme === 'file') { + newPath = URI.file(paths.win32.join(uriToFsPath(uri, true), ...pathFragment)).path; + } else { + newPath = paths.posix.join(uri.path, ...pathFragment); + } + return uri.with({ path: newPath }); + } + + // ---- printing/externalize --------------------------- + + /** + * Creates a string representation for this URI. It's guaranteed that calling + * `URI.parse` with the result of this function creates an URI which is equal + * to this URI. + * + * * The result shall *not* be used for display purposes but for externalization or transport. + * * The result will be encoded using the percentage encoding and encoding happens mostly + * ignore the scheme-specific encoding rules. + * + * @param skipEncoding Do not encode the result, default is `false` + */ + toString(skipEncoding: boolean = false): string { + return _asFormatted(this, skipEncoding); + } + + toJSON(): UriComponents { + return this; + } + + /** + * A helper function to revive URIs. + * + * **Note** that this function should only be used when receiving URI#toJSON generated data + * and that it doesn't do any validation. Use {@link URI.from} when received "untrusted" + * uri components such as command arguments or data from storage. + * + * @param data The URI components or URI to revive. + * @returns The revived URI or undefined or null. + */ + static revive(data: UriComponents | URI): URI; + static revive(data: UriComponents | URI | undefined): URI | undefined; + static revive(data: UriComponents | URI | null): URI | null; + static revive(data: UriComponents | URI | undefined | null): URI | undefined | null; + static revive(data: UriComponents | URI | undefined | null): URI | undefined | null { + if (!data) { + return data as undefined | null; + } else if (data instanceof URI) { + return data; + } else { + const result = new Uri(data); + result._formatted = (data).external ?? null; + result._fsPath = (data)._sep === _pathSepMarker ? (data).fsPath ?? null : null; + return result; + } + } + + [Symbol.for('debug.description')]() { + return `URI(${this.toString()})`; + } +} + +export interface UriComponents { + scheme: string; + authority?: string; + path?: string; + query?: string; + fragment?: string; +} + +export function isUriComponents(thing: any): thing is UriComponents { + if (!thing || typeof thing !== 'object') { + return false; + } + return typeof (thing).scheme === 'string' + && (typeof (thing).authority === 'string' || typeof (thing).authority === 'undefined') + && (typeof (thing).path === 'string' || typeof (thing).path === 'undefined') + && (typeof (thing).query === 'string' || typeof (thing).query === 'undefined') + && (typeof (thing).fragment === 'string' || typeof (thing).fragment === 'undefined'); +} + +interface UriState extends UriComponents { + $mid: MarshalledId.Uri; + external?: string; + fsPath?: string; + _sep?: 1; +} + +const _pathSepMarker = isWindows ? 1 : undefined; + +// This class exists so that URI is compatible with vscode.Uri (API). +class Uri extends URI { + + _formatted: string | null = null; + _fsPath: string | null = null; + + override get fsPath(): string { + if (!this._fsPath) { + this._fsPath = uriToFsPath(this, false); + } + return this._fsPath; + } + + override toString(skipEncoding: boolean = false): string { + if (!skipEncoding) { + if (!this._formatted) { + this._formatted = _asFormatted(this, false); + } + return this._formatted; + } else { + // we don't cache that + return _asFormatted(this, true); + } + } + + override toJSON(): UriComponents { + // eslint-disable-next-line local/code-no-dangerous-type-assertions + const res = { + $mid: MarshalledId.Uri + }; + // cached state + if (this._fsPath) { + res.fsPath = this._fsPath; + res._sep = _pathSepMarker; + } + if (this._formatted) { + res.external = this._formatted; + } + //--- uri components + if (this.path) { + res.path = this.path; + } + // TODO + // this isn't correct and can violate the UriComponents contract but + // this is part of the vscode.Uri API and we shouldn't change how that + // works anymore + if (this.scheme) { + res.scheme = this.scheme; + } + if (this.authority) { + res.authority = this.authority; + } + if (this.query) { + res.query = this.query; + } + if (this.fragment) { + res.fragment = this.fragment; + } + return res; + } +} + +// reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2 +const encodeTable: { [ch: number]: string } = { + [CharCode.Colon]: '%3A', // gen-delims + [CharCode.Slash]: '%2F', + [CharCode.QuestionMark]: '%3F', + [CharCode.Hash]: '%23', + [CharCode.OpenSquareBracket]: '%5B', + [CharCode.CloseSquareBracket]: '%5D', + [CharCode.AtSign]: '%40', + + [CharCode.ExclamationMark]: '%21', // sub-delims + [CharCode.DollarSign]: '%24', + [CharCode.Ampersand]: '%26', + [CharCode.SingleQuote]: '%27', + [CharCode.OpenParen]: '%28', + [CharCode.CloseParen]: '%29', + [CharCode.Asterisk]: '%2A', + [CharCode.Plus]: '%2B', + [CharCode.Comma]: '%2C', + [CharCode.Semicolon]: '%3B', + [CharCode.Equals]: '%3D', + + [CharCode.Space]: '%20', +}; + +function encodeURIComponentFast(uriComponent: string, isPath: boolean, isAuthority: boolean): string { + let res: string | undefined = undefined; + let nativeEncodePos = -1; + + for (let pos = 0; pos < uriComponent.length; pos++) { + const code = uriComponent.charCodeAt(pos); + + // unreserved characters: https://tools.ietf.org/html/rfc3986#section-2.3 + if ( + (code >= CharCode.a && code <= CharCode.z) + || (code >= CharCode.A && code <= CharCode.Z) + || (code >= CharCode.Digit0 && code <= CharCode.Digit9) + || code === CharCode.Dash + || code === CharCode.Period + || code === CharCode.Underline + || code === CharCode.Tilde + || (isPath && code === CharCode.Slash) + || (isAuthority && code === CharCode.OpenSquareBracket) + || (isAuthority && code === CharCode.CloseSquareBracket) + || (isAuthority && code === CharCode.Colon) + ) { + // check if we are delaying native encode + if (nativeEncodePos !== -1) { + res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos)); + nativeEncodePos = -1; + } + // check if we write into a new string (by default we try to return the param) + if (res !== undefined) { + res += uriComponent.charAt(pos); + } + + } else { + // encoding needed, we need to allocate a new string + if (res === undefined) { + res = uriComponent.substr(0, pos); + } + + // check with default table first + const escaped = encodeTable[code]; + if (escaped !== undefined) { + + // check if we are delaying native encode + if (nativeEncodePos !== -1) { + res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos)); + nativeEncodePos = -1; + } + + // append escaped variant to result + res += escaped; + + } else if (nativeEncodePos === -1) { + // use native encode only when needed + nativeEncodePos = pos; + } + } + } + + if (nativeEncodePos !== -1) { + res += encodeURIComponent(uriComponent.substring(nativeEncodePos)); + } + + return res !== undefined ? res : uriComponent; +} + +function encodeURIComponentMinimal(path: string): string { + let res: string | undefined = undefined; + for (let pos = 0; pos < path.length; pos++) { + const code = path.charCodeAt(pos); + if (code === CharCode.Hash || code === CharCode.QuestionMark) { + if (res === undefined) { + res = path.substr(0, pos); + } + res += encodeTable[code]; + } else { + if (res !== undefined) { + res += path[pos]; + } + } + } + return res !== undefined ? res : path; +} + +/** + * Compute `fsPath` for the given uri + */ +export function uriToFsPath(uri: URI, keepDriveLetterCasing: boolean): string { + + let value: string; + if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') { + // unc path: file://shares/c$/far/boo + value = `//${uri.authority}${uri.path}`; + } else if ( + uri.path.charCodeAt(0) === CharCode.Slash + && (uri.path.charCodeAt(1) >= CharCode.A && uri.path.charCodeAt(1) <= CharCode.Z || uri.path.charCodeAt(1) >= CharCode.a && uri.path.charCodeAt(1) <= CharCode.z) + && uri.path.charCodeAt(2) === CharCode.Colon + ) { + if (!keepDriveLetterCasing) { + // windows drive letter: file:///c:/far/boo + value = uri.path[1].toLowerCase() + uri.path.substr(2); + } else { + value = uri.path.substr(1); + } + } else { + // other path + value = uri.path; + } + if (isWindows) { + value = value.replace(/\//g, '\\'); + } + return value; +} + +/** + * Create the external version of a uri + */ +function _asFormatted(uri: URI, skipEncoding: boolean): string { + + const encoder = !skipEncoding + ? encodeURIComponentFast + : encodeURIComponentMinimal; + + let res = ''; + let { scheme, authority, path, query, fragment } = uri; + if (scheme) { + res += scheme; + res += ':'; + } + if (authority || scheme === 'file') { + res += _slash; + res += _slash; + } + if (authority) { + let idx = authority.indexOf('@'); + if (idx !== -1) { + // @ + const userinfo = authority.substr(0, idx); + authority = authority.substr(idx + 1); + idx = userinfo.lastIndexOf(':'); + if (idx === -1) { + res += encoder(userinfo, false, false); + } else { + // :@ + res += encoder(userinfo.substr(0, idx), false, false); + res += ':'; + res += encoder(userinfo.substr(idx + 1), false, true); + } + res += '@'; + } + authority = authority.toLowerCase(); + idx = authority.lastIndexOf(':'); + if (idx === -1) { + res += encoder(authority, false, true); + } else { + // : + res += encoder(authority.substr(0, idx), false, true); + res += authority.substr(idx); + } + } + if (path) { + // lower-case windows drive letters in /C:/fff or C:/fff + if (path.length >= 3 && path.charCodeAt(0) === CharCode.Slash && path.charCodeAt(2) === CharCode.Colon) { + const code = path.charCodeAt(1); + if (code >= CharCode.A && code <= CharCode.Z) { + path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3 + } + } else if (path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) { + const code = path.charCodeAt(0); + if (code >= CharCode.A && code <= CharCode.Z) { + path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3 + } + } + // encode the rest of the path + res += encoder(path, true, false); + } + if (query) { + res += '?'; + res += encoder(query, false, false); + } + if (fragment) { + res += '#'; + res += !skipEncoding ? encodeURIComponentFast(fragment, false, false) : fragment; + } + return res; +} + +// --- decode + +function decodeURIComponentGraceful(str: string): string { + try { + return decodeURIComponent(str); + } catch { + if (str.length > 3) { + return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3)); + } else { + return str; + } + } +} + +const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g; + +function percentDecode(str: string): string { + if (!str.match(_rEncodedAsHex)) { + return str; + } + return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match)); +} + +/** + * Mapped-type that replaces all occurrences of URI with UriComponents + */ +export type UriDto = { [K in keyof T]: T[K] extends URI + ? UriComponents + : UriDto }; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts new file mode 100644 index 00000000..a7029a1e --- /dev/null +++ b/packages/core/src/utils.ts @@ -0,0 +1,443 @@ +export function StingEnum(o: Array): { [K in T]: K } { + return o.reduce((res, key) => { + res[key] = key; + return res; + }, Object.create(null)); +} + +import { isString } from './primitives.js'; + +const escapeRegExpPattern = /[[\]{}()|/\\^$.*+?]/g; +const escapeXmlPattern = /[&<]/g; +const escapeXmlForPattern = /[&<>'"]/g; +const escapeXmlMap: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''' +}; +export const DefaultDelimiter = { + begin: '<%', + end: '%>' +}; +export const hasFlag = (field, enumValue) => { + //noinspection JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage + return ((1 << enumValue) & field) ? true : false; +}; +export const hasFlagHex = (field, enumValue) => { + //noinspection JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage,JSBitwiseOperatorUsage + return enumValue & field ? true : false; +}; +export const disableFlag = (enumValue, field) => { + enumValue &= ~(1 << field); + return enumValue; +}; +/** + * The minimum location of high surrogates + */ +export const HIGH_SURROGATE_MIN = 0xD800; +/** + * The maximum location of high surrogates + */ +export const HIGH_SURROGATE_MAX = 0xDBFF; +/** + * The minimum location of low surrogates + */ +export const LOW_SURROGATE_MIN = 0xDC00; +/** + * The maximum location of low surrogates + */ +export const LOW_SURROGATE_MAX = 0xDFFF; + +const BASE64_KEYSTR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + +export const capitalize = (word) => { + return word.substring(0, 1).toUpperCase() + word.substring(1); +}; + +export const getJson = (inData, validOnly, ommit) => { + try { + return isString(inData) ? JSON.parse(inData) : validOnly === true ? null : inData; + } catch (e) { + ommit !== false && console.error('error parsing json data ' + inData + ' error = ' + e); + } + return null; +}; + +/** + * Escapes a string so that it can safely be passed to the RegExp constructor. + * @param text The string to be escaped + * @return The escaped string + */ +export function escapeRegExpEx(text: string): string { + return !text ? text : text.replace(escapeRegExpPattern, '\\$&'); +} + +/** + * Sanitizes a string to protect against tag injection. + * @param xml The string to be escaped + * @param forAttribute Whether to also escape ', ", and > in addition to < and & + * @return The escaped string + */ +export function escapeXml(xml: string, forAttribute = true): string { + if (!xml) { + return xml; + } + + const pattern = forAttribute ? escapeXmlForPattern : escapeXmlPattern; + + return xml.replace(pattern, function (character: string): string { + return escapeXmlMap[character]; + }); +} + +export function createUUID(): string { + const S4 = function () { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + }; + return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4()); +} + +export function escapeRegExp(str: string): string { + const special = ['[', ']', '(', ')', '{', '}', '*', '+', '.', '|', '||']; + for (let n = 0; n < special.length; n++) { + str = str.replace(special[n], '\\' + special[n]); + } + return str; +}; + + + +export function replaceAll(find: string, replace: string, str: string): string { + return str ? str.split(find).join(replace) : ''; +}; + +export interface IDelimiter { + begin: string; + end: string; +} + +export const substitute = (template, map) => { + const transform = (k) => k || ''; + return template.replace(/\$\{([^\s:}]+)(?::([^\s:}]+))?\}/g, + (match, key, format) => transform(map[key]).toString()); +} + +function decodeUtf8EncodedCodePoint(codePoint: number, validationRange: number[] = [0, Infinity], checkSurrogate?: boolean): string { + if (codePoint < validationRange[0] || codePoint > validationRange[1]) { + throw Error('Invalid continuation byte'); + } + + if (checkSurrogate && codePoint >= HIGH_SURROGATE_MIN && codePoint <= LOW_SURROGATE_MAX) { + throw Error('Surrogate is not a scalar value'); + } + + let encoded = ''; + + if (codePoint > 0xFFFF) { + codePoint -= 0x010000; + encoded += String.fromCharCode(codePoint >>> 0x10 & 0x03FF | HIGH_SURROGATE_MIN); + codePoint = LOW_SURROGATE_MIN | codePoint & 0x03FF; + } + + encoded += String.fromCharCode(codePoint); + + return encoded; +} + +function validateUtf8EncodedCodePoint(codePoint: number): void { + if ((codePoint & 0xC0) !== 0x80) { + throw Error('Invalid continuation byte'); + } +} + +export type ByteBuffer = Uint16Array | Uint8Array | Buffer | number[]; + +export interface Codec { + encode(data: string): number[]; + decode(data: ByteBuffer): string; +} + +/** + * Provides facilities for encoding a string into an ASCII-encoded byte buffer and + * decoding an ASCII-encoded byte buffer into a string. + */ +export const ascii: Codec = { + /** + * Encodes a string into an ASCII-encoded byte buffer. + * + * @param data The text string to encode + */ + encode(data: string): number[] { + if (data == null) { + return []; + } + + const buffer: number[] = []; + + for (let i = 0, length = data.length; i < length; i++) { + buffer[i] = data.charCodeAt(i); + } + + return buffer; + }, + /** + * Decodes an ASCII-encoded byte buffer into a string. + * + * @param data The byte buffer to decode + */ + decode(data: ByteBuffer): string { + if (data == null) { + return ''; + } + + let decoded = ''; + + for (let i = 0, length = data.length; i < length; i++) { + decoded += String.fromCharCode(data[i]); + } + + return decoded; + } +}; + +/** + * Provides facilities for encoding a string into a Base64-encoded byte buffer and + * decoding a Base64-encoded byte buffer into a string. + */ +export const base64: Codec = { + /** + * Encodes a Base64-encoded string into a Base64 byte buffer. + * + * @param data The Base64-encoded string to encode + */ + encode(data: string): number[] { + if (data == null) { + return []; + } + + const buffer: number[] = []; + + let i = 0; + let length = data.length; + + while (data[--length] === '=') { } + while (i < length) { + let encoded = BASE64_KEYSTR.indexOf(data[i++]) << 18; + if (i <= length) { + encoded |= BASE64_KEYSTR.indexOf(data[i++]) << 12; + } + if (i <= length) { + encoded |= BASE64_KEYSTR.indexOf(data[i++]) << 6; + } + if (i <= length) { + encoded |= BASE64_KEYSTR.indexOf(data[i++]); + } + + buffer.push((encoded >>> 16) & 0xff); + buffer.push((encoded >>> 8) & 0xff); + buffer.push(encoded & 0xff); + } + + while (buffer[buffer.length - 1] === 0) { + buffer.pop(); + } + + return buffer; + }, + /** + * Decodes a Base64-encoded byte buffer into a Base64-encoded string. + * + * @param data The byte buffer to decode + */ + decode(data: ByteBuffer): string { + if (data == null) { + return ''; + } + + let decoded = ''; + let i = 0; + + for (let length = data.length - (data.length % 3); i < length;) { + const encoded = data[i++] << 16 | data[i++] << 8 | data[i++]; + + decoded += BASE64_KEYSTR.charAt((encoded >>> 18) & 0x3F); + decoded += BASE64_KEYSTR.charAt((encoded >>> 12) & 0x3F); + decoded += BASE64_KEYSTR.charAt((encoded >>> 6) & 0x3F); + decoded += BASE64_KEYSTR.charAt(encoded & 0x3F); + } + + if (data.length % 3 === 1) { + const encoded = data[i++] << 16; + decoded += BASE64_KEYSTR.charAt((encoded >>> 18) & 0x3f); + decoded += BASE64_KEYSTR.charAt((encoded >>> 12) & 0x3f); + decoded += '=='; + } else if (data.length % 3 === 2) { + const encoded = data[i++] << 16 | data[i++] << 8; + decoded += BASE64_KEYSTR.charAt((encoded >>> 18) & 0x3f); + decoded += BASE64_KEYSTR.charAt((encoded >>> 12) & 0x3f); + decoded += BASE64_KEYSTR.charAt((encoded >>> 6) & 0x3f); + decoded += '='; + } + + return decoded; + } +}; + +/** + * Provides facilities for encoding a string into a hex-encoded byte buffer and + * decoding a hex-encoded byte buffer into a string. + */ +export const hex: Codec = { + /** + * Encodes a string into a hex-encoded byte buffer. + * + * @param data The hex-encoded string to encode + */ + encode(data: string): number[] { + if (data == null) { + return []; + } + + const buffer: number[] = []; + + for (let i = 0, length = data.length; i < length; i += 2) { + const encodedChar = parseInt(data.substr(i, 2), 16); + + buffer.push(encodedChar); + } + + return buffer; + }, + /** + * Decodes a hex-encoded byte buffer into a hex-encoded string. + * + * @param data The byte buffer to decode + */ + decode(data: ByteBuffer): string { + if (data == null) { + return ''; + } + + let decoded = ''; + + for (let i = 0, length = data.length; i < length; i++) { + decoded += data[i].toString(16).toUpperCase(); + } + + return decoded; + } +}; + +/** + * Provides facilities for encoding a string into a UTF-8-encoded byte buffer and + * decoding a UTF-8-encoded byte buffer into a string. + * Inspired by the work of: https://github.com/mathiasbynens/utf8.js + */ +export const utf8: Codec = { + /** + * Encodes a string into a UTF-8-encoded byte buffer. + * + * @param data The text string to encode + */ + encode(data: string): number[] { + if (data == null) { + return []; + } + + const buffer: number[] = []; + + for (let i = 0, length = data.length; i < length; i++) { + let encodedChar = data.charCodeAt(i); + /** + * Surrogates + * http://en.wikipedia.org/wiki/Universal_Character_Set_characters + */ + if (encodedChar >= HIGH_SURROGATE_MIN && encodedChar <= HIGH_SURROGATE_MAX) { + const lowSurrogate = data.charCodeAt(i + 1); + if (lowSurrogate >= LOW_SURROGATE_MIN && lowSurrogate <= LOW_SURROGATE_MAX) { + encodedChar = 0x010000 + (encodedChar - HIGH_SURROGATE_MIN) * 0x0400 + (lowSurrogate - LOW_SURROGATE_MIN); + i++; + } + } + + if (encodedChar < 0x80) { + buffer.push(encodedChar); + } else { + if (encodedChar < 0x800) { + buffer.push(((encodedChar >> 0x06) & 0x1F) | 0xC0); + } else if (encodedChar < 0x010000) { + if (encodedChar >= HIGH_SURROGATE_MIN && encodedChar <= LOW_SURROGATE_MAX) { + throw Error('Surrogate is not a scalar value'); + } + + buffer.push(((encodedChar >> 0x0C) & 0x0F) | 0xE0); + buffer.push(((encodedChar >> 0x06) & 0x3F) | 0x80); + } else if (encodedChar < 0x200000) { + buffer.push(((encodedChar >> 0x12) & 0x07) | 0xF0); + buffer.push(((encodedChar >> 0x0C) & 0x3F) | 0x80); + buffer.push(((encodedChar >> 0x06) & 0x3F) | 0x80); + } + buffer.push((encodedChar & 0x3F) | 0x80); + } + } + + return buffer; + }, + /** + * Decodes a UTF-8-encoded byte buffer into a string. + * + * @param data The byte buffer to decode + */ + decode(data: ByteBuffer): string { + if (data == null) { + return ''; + } + + let decoded = ''; + + for (let i = 0, length = data.length; i < length; i++) { + const byte1 = data[i] & 0xFF; + + if ((byte1 & 0x80) === 0) { + decoded += decodeUtf8EncodedCodePoint(byte1); + } else if ((byte1 & 0xE0) === 0xC0) { + let byte2 = data[++i] & 0xFF; + validateUtf8EncodedCodePoint(byte2); + byte2 = byte2 & 0x3F; + const encodedByte = ((byte1 & 0x1F) << 0x06) | byte2; + decoded += decodeUtf8EncodedCodePoint(encodedByte, [0x80, Infinity]); + } else if ((byte1 & 0xF0) === 0xE0) { + let byte2 = data[++i] & 0xFF; + validateUtf8EncodedCodePoint(byte2); + byte2 = byte2 & 0x3F; + + let byte3 = data[++i] & 0xFF; + validateUtf8EncodedCodePoint(byte3); + byte3 = byte3 & 0x3F; + + const encodedByte = ((byte1 & 0x1F) << 0x0C) | (byte2 << 0x06) | byte3; + decoded += decodeUtf8EncodedCodePoint(encodedByte, [0x0800, Infinity], true); + } else if ((byte1 & 0xF8) === 0xF0) { + let byte2 = data[++i] & 0xFF; + validateUtf8EncodedCodePoint(byte2); + byte2 = byte2 & 0x3F; + + let byte3 = data[++i] & 0xFF; + validateUtf8EncodedCodePoint(byte3); + byte3 = byte3 & 0x3F; + + let byte4 = data[++i] & 0xFF; + validateUtf8EncodedCodePoint(byte4); + byte4 = byte4 & 0x3F; + + const encodedByte = ((byte1 & 0x1F) << 0x0C) | (byte2 << 0x0C) | (byte3 << 0x06) | byte4; + decoded += decodeUtf8EncodedCodePoint(encodedByte, [0x010000, 0x10FFFF]); + } else { + validateUtf8EncodedCodePoint(byte1); + } + } + return decoded; + } +}; diff --git a/packages/core/src/uuid.ts b/packages/core/src/uuid.ts new file mode 100644 index 00000000..f09ee1fc --- /dev/null +++ b/packages/core/src/uuid.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +/** + * Represents a UUID as defined by rfc4122. + */ +export interface UUID { + + /** + * @returns the canonical representation in sets of hexadecimal numbers separated by dashes. + */ + asHex(): string; + + equals(other: UUID): boolean; +} + +class ValueUUID implements UUID { + + constructor(public _value: string) { + // empty + } + + public asHex(): string { + return this._value; + } + + public equals(other: UUID): boolean { + return this.asHex() === other.asHex(); + } +} + +class V4UUID extends ValueUUID { + + private static _chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + + private static _timeHighBits = ['8', '9', 'a', 'b']; + + private static _oneOf(array: string[]): string { + return array[Math.floor(array.length * Math.random())]; + } + + private static _randomHex(): string { + return V4UUID._oneOf(V4UUID._chars); + } + + constructor() { + super([ + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + '-', + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + '-', + '4', + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + '-', + V4UUID._oneOf(V4UUID._timeHighBits), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + '-', + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + V4UUID._randomHex(), + ].join('')); + } +} + +/** + * An empty UUID that contains only zeros. + */ +export const empty: UUID = new ValueUUID('00000000-0000-0000-0000-000000000000'); + +export function v4(): UUID { + return new V4UUID(); +} + +const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +export function isUUID(value: string): boolean { + return _UUIDPattern.test(value); +} + +/** + * Parses a UUID that is of the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. + * @param value A uuid string. + */ +export function parse(value: string): UUID { + if (!isUUID(value)) { + throw new Error('invalid uuid'); + } + + return new ValueUUID(value); +} + +export function generateUuid(): string { + return v4().asHex(); +} \ No newline at end of file diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 00000000..fdf8e607 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../typescript-config/base.json", + "include": ["src/**/*.ts"], + "files": ["src/index.ts"], + "compilerOptions": { + "allowJs": true, + "declarationDir": "./dist", + "outDir": "./dist", + "sourceMap": true, + + "preserveConstEnums": true + }, +} diff --git a/packages/core/tsup.config.ts b/packages/core/tsup.config.ts new file mode 100644 index 00000000..03b4a81f --- /dev/null +++ b/packages/core/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "tsup"; + +export default defineConfig((options) => ({ + entryPoints: [ + "src/*.ts" + ], + format: ["cjs", "esm"], + dts: true, + sourcemap: true, + ...options, + bundle: false +})); diff --git a/packages/eslint-config/README.md b/packages/eslint-config/README.md new file mode 100644 index 00000000..8b42d901 --- /dev/null +++ b/packages/eslint-config/README.md @@ -0,0 +1,3 @@ +# `@turbo/eslint-config` + +Collection of internal eslint configurations. diff --git a/packages/eslint-config/library.js b/packages/eslint-config/library.js new file mode 100644 index 00000000..18d716c5 --- /dev/null +++ b/packages/eslint-config/library.js @@ -0,0 +1,35 @@ +const { resolve } = require("node:path"); + +const project = resolve(process.cwd(), "tsconfig.json"); + +/* + * This is a custom ESLint configuration for use with + * typescript packages. + * + * This config extends the Vercel Engineering Style Guide. + * For more information, see https://github.com/vercel/style-guide + * + */ + +module.exports = { + extends: [ + "@vercel/style-guide/eslint/node", + "@vercel/style-guide/eslint/typescript", + ].map(require.resolve), + parserOptions: { + project, + }, + plugins: ["only-warn"], + globals: { + React: true, + JSX: true, + }, + settings: { + "import/resolver": { + typescript: { + project, + }, + }, + }, + ignorePatterns: ["node_modules/", "dist/"], +}; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json new file mode 100644 index 00000000..e7a9fd6a --- /dev/null +++ b/packages/eslint-config/package.json @@ -0,0 +1,17 @@ +{ + "name": "@repo/eslint-config", + "version": "0.0.0", + "private": true, + "files": [ + "library.js", + "react.js", + "storybook.js" + ], + "devDependencies": { + "@vercel/style-guide": "^5.2.0", + "eslint-config-turbo": "^2.0.0", + "eslint-plugin-mdx": "^3.1.5", + "eslint-plugin-only-warn": "^1.1.0", + "eslint-plugin-storybook": "^0.8.0" + } +} diff --git a/packages/eslint-config/react.js b/packages/eslint-config/react.js new file mode 100644 index 00000000..4b2f37e9 --- /dev/null +++ b/packages/eslint-config/react.js @@ -0,0 +1,39 @@ +const { resolve } = require("node:path"); + +const project = resolve(process.cwd(), "tsconfig.json"); + +/* + * This is a custom ESLint configuration for use a library + * that utilizes React. + * + * This config extends the Vercel Engineering Style Guide. + * For more information, see https://github.com/vercel/style-guide + * + */ + +module.exports = { + extends: [ + "@vercel/style-guide/eslint/browser", + "@vercel/style-guide/eslint/typescript", + "@vercel/style-guide/eslint/react", + ].map(require.resolve), + parserOptions: { + project, + }, + plugins: ["only-warn"], + globals: { + JSX: true, + }, + settings: { + "import/resolver": { + typescript: { + project, + }, + }, + }, + ignorePatterns: ["node_modules/", "dist/", ".eslintrc.js", "**/*.css"], + // add rules configurations here + rules: { + "import/no-default-export": "off", + }, +}; diff --git a/packages/eslint-config/storybook.js b/packages/eslint-config/storybook.js new file mode 100644 index 00000000..147db865 --- /dev/null +++ b/packages/eslint-config/storybook.js @@ -0,0 +1,45 @@ +const { resolve } = require("node:path"); + +const project = resolve(process.cwd(), "tsconfig.json"); + +/* + * This is a custom ESLint configuration for use with + * typescript packages. + * + * This config extends the Vercel Engineering Style Guide. + * For more information, see https://github.com/vercel/style-guide + * + */ + +module.exports = { + extends: [ + "plugin:storybook/recommended", + "plugin:mdx/recommended", + ...[ + "@vercel/style-guide/eslint/node", + "@vercel/style-guide/eslint/typescript", + "@vercel/style-guide/eslint/browser", + "@vercel/style-guide/eslint/react", + ].map(require.resolve), + ], + parserOptions: { + project, + }, + plugins: ["only-warn"], + globals: { + React: true, + JSX: true, + }, + settings: { + "import/resolver": { + typescript: { + project, + }, + }, + }, + ignorePatterns: ["node_modules/", "dist/"], + // add rules configurations here + rules: { + "import/no-default-export": "off", + }, +}; diff --git a/packages/fs/.editorconfig b/packages/fs/.editorconfig new file mode 100644 index 00000000..10ab7cd4 --- /dev/null +++ b/packages/fs/.editorconfig @@ -0,0 +1,12 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = tab +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/fs/.gitignore b/packages/fs/.gitignore new file mode 100644 index 00000000..cab85ca2 --- /dev/null +++ b/packages/fs/.gitignore @@ -0,0 +1,4 @@ +/node_modules +/coverage +*.log +.DS_Store diff --git a/.npmignore b/packages/fs/.npmignore similarity index 58% rename from .npmignore rename to packages/fs/.npmignore index 4c9addac..c99f7770 100644 --- a/.npmignore +++ b/packages/fs/.npmignore @@ -1,4 +1,5 @@ ./docs ./scripts ./tests -./incoming \ No newline at end of file +./spec +./benchmark \ No newline at end of file diff --git a/packages/fs/.travis.yml b/packages/fs/.travis.yml new file mode 100644 index 00000000..90343195 --- /dev/null +++ b/packages/fs/.travis.yml @@ -0,0 +1,10 @@ +language: node_js + +node_js: + - "9" + +script: +- npm run test + +after_success: +- ./node_modules/.bin/codecov diff --git a/packages/fs/.vscode/launch.json b/packages/fs/.vscode/launch.json new file mode 100644 index 00000000..f9498634 --- /dev/null +++ b/packages/fs/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Find", + "skipFiles": [ + "/**" + ], + "args": [ + "find" + ], + "program": "${workspaceFolder}\\main.js", + "preLaunchTask": "tsc: build - tsconfig.json", + "outFiles": [ + "${workspaceFolder}/./**/*.js" + ] + } + ] +} diff --git a/packages/fs/.vscode/settings.json b/packages/fs/.vscode/settings.json new file mode 100644 index 00000000..2c9004b2 --- /dev/null +++ b/packages/fs/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "throttel" + ] +} diff --git a/packages/fs/CHANGELOG.md b/packages/fs/CHANGELOG.md new file mode 100644 index 00000000..78aa7f02 --- /dev/null +++ b/packages/fs/CHANGELOG.md @@ -0,0 +1,103 @@ +# 0.12.1 (2017-02-24) +- Typescript port complete +- all tests passing + +# 0.11.0 (2017-02-09) +- Added input validation for the whole API +- **(breaking change)** Removed already deprecated option `buf` for `read()` method + +# 0.10.5 (2016-12-07) +- Fixed `find()` bug when `directories` is set to `true` and only negation glob is used. + +# 0.10.4 (2016-12-06) +- Fixed matcher edge cases, improved matcher tests (affects `find()` and `copy()` methods). + +# 0.10.3 (2016-11-23) +- Fixed directory tree traversal bug which was causing problems for `findAsync()` and `copyAsync()`. + +# 0.10.2 (2016-11-08) +- Fixed `console.log(jetpack)` for node v6.6.0 or newer. + +# 0.10.1 (2016-11-01) +- Bugfixed case when `copyAsync()` was leaving open read stream if write stream errored. +- Tests ported from jasmine to mocha. + +# 0.10.0 (2016-10-17) +- `copyAsync()` uses only streams (much more memory efficient). +- `find()` supports `recursive` option. + +# 0.9.2 (2016-06-27) +- Updated third party dependencies to quell minimatch intallation warnings. + +# 0.9.1 (2016-05-21) +- Bug-fixed `jetpack.read('nonexistent_file', 'json')`. + +# 0.9.0 (2016-05-10) +- **(breaking change)** `read()`, `list()`, `inspect()` and `inspectTree()` returns `undefined` instead of `null` if path doesn't exist. +- More sane edge cases for `dir()`, `file()` and `list()`. + +# 0.8.0 (2016-04-09) +- **(breaking change)** `find()` now distinguishes between files and directories and by default searches only for files (previously searched for both). +- **(breaking change)** `find()` no longer can be configured with `returnAs` parameter and returns always relative paths (previously returned absolute). +- **(breaking change)** `list()` no longer accepts `useInspect` as a parameter. To achieve old behaviour use `jetpack.list()` with `Array.map()`. +- **(deprecation)** Don't do `jetpack.read('sth', 'buf')`, do `jetpack.read('sth', 'buffer')` instead. +- `remove()`, `list()` and `find()` now can be called without provided `path`, and defaults to CWD in that case. + +# 0.7.3 (2016-03-21) +- Bugfixed `copy()` with symlink overwrite + +# 0.7.2 (2016-03-09) +- Fixed .dotfiles copying + +# 0.7.1 (2015-12-17) +- Updated third party dependencies. + +# 0.7.0 (2015-07-20) +- **(breaking change)** `matching` option in `copy()` and `find()` resolves glob patterns to the folder you want copy or find stuff in (previously CWD was used). + +# 0.6.5 (2015-06-19) +- `exists()` can handle ENOTDIR error. + +# 0.6.3 and 0.6.4 (2015-04-18) +- Added support for symbolic links. + +# 0.6.2 (2015-04-07) +- Option `matching` in `copy()` and `find()` now accepts patterns anchored to CWD. + +# 0.6.1 (2015-04-03) +- Option `matching` in `copy()` and `find()` now accepts negation patterns (e.g. `!some/file.txt`). + +# 0.6.0 (2015-03-30) +- Lots of code refactoring +- **(breaking change)** `dir()` no longer has `exists` option. +- **(breaking change)** `file()` no longer has `exists` and `empty` options. +- **(breaking change)** `safe` option for `write()` renamed to `atomic` (and uses new algorithm under the hood). +- **(breaking change)** `safe` option for `read()` dropped (`atomic` while writing is enough). +- **(breaking change)** In `copy()` options `only` and `allBut` have been replaced by option `matching`. +- **(breaking change)** In `remove()` options `only` and `allBut` have been dropped (to do the same use `find()`, and then remove). +- **(breaking change)** Default jsonIndent changed form 0 to 2. +- `find()` method added. +- More telling errors when `read()` failed while parsing JSON. + +# 0.5.3 (2015-01-06) +- `inspect()` can return file access/modify/change time and mode. + +# 0.5.2 (2014-09-21) +- `inspect()` checksum of empty file is now `null`. + +# 0.5.1 (2014-09-21) +- `cwd()` accepts many arguments as path parts. + +# 0.5.0 (2014-08-31) +- **(breaking change)** Method `tree()` renamed to `inspectTree()`. +- **(breaking change)** Parameters passed to `list()` has changed. +- Methods `inspect()` and `inspectTree()` can calculate md5 and sha1 checksums. +- Added aliases to `fs.createReadStream()` and `fs.createWriteStream()`. + +# 0.4.1 (2014-07-16) +- `copy()` now copies also file permissions on unix systems. +- `append()` can specify file mode if file doesn't exist. +- Can indent saved JSON data. + +# 0.4.0 (2014-07-14) +- Changelog starts here. diff --git a/packages/fs/EXAMPLES.md b/packages/fs/EXAMPLES.md new file mode 100644 index 00000000..a0f1750b --- /dev/null +++ b/packages/fs/EXAMPLES.md @@ -0,0 +1,82 @@ +# Cool things about fs-jetpack in examples +**Note:** All examples here are synchronous for simplicity. You can easily make them asynchronous just by adding 'Async' to method names and expecting promise to be returned instead of ready value. + +## Every jetpack instance has its internal CWD +You can create many jetpack objects with different internal working directories (which are independent of `process.cwd()`) and work on directories in a little more object-oriented manner. +```js +var src = jetpack.cwd('path/to/source'); +var dest = jetpack.cwd('path/to/destination'); +src.copy('foo.txt', dest.path('bar.txt')); +``` + +## JSON is a first class citizen +You can write JavaScript object directly to disk and it will be transformed to JSON automatically. +```js +var obj = { greet: "Hello World!" }; +jetpack.write('file.json', obj); +``` +Then you can get your object back just by telling read method that it's a JSON. +```js +var obj = jetpack.read('file.json', 'json'); +``` + +## Jetpack throws errors at you as the last resort +Everyone who did something with files for sure seen (and probably hates) *"ENOENT, no such file or directory"* error. Jetpack tries to recover from that error if possible. +1. For write/creation operations, if any of parent directories doesn't exist jetpack will just create lacking directories. +2. For read/inspect operations, if file or directory doesn't exist `undefined` is returned instead of throwing. + +## Jetpack is great for build scripts +```js +var src = jetpack.cwd('path/to/source'); +var dest = jetpack.dir('path/to/destination', { empty: true }); + +src.copy('.', dest.path(), { + matching: ['./vendor/**', '*.html', '*.png', '*.jpg'] +}); + +var config = src.read('config.json', 'json'); +config.env = 'production'; +dest.write('config.json', config); +``` + +## All methods play nicely with each other +Let's say you want to create folder structure: +``` +. +|- greets + |- greet.txt + |- greet.json +|- greets-i18n + |- polish.txt +``` +Peace of cake with jetpack! +```js +jetpack +.dir('greets') + .file('greet.txt', { content: 'Hello world!' }) + .file('greet.json', { content: { greet: 'Hello world!' } }) + .cwd('..') +.dir('greets-i18n') + .file('polish.txt', { content: 'Witaj świecie!' }); +``` + +## Find and delete all `.tmp` files inside `my-dir` +```js +jetpack.find('my-dir', { + matching: '*.tmp' +}) +.forEach(jetpack.remove); +``` + +## Check if two files have the same content +```js +var file1 = jetpack.inspect('file1', { checksum: 'md5' }); +var file2 = jetpack.inspect('file2', { checksum: 'md5' }); +var areTheSame = (file1.md5 === file2.md5); +``` + +## More secure writes to disk +For essential data you might consider "atomic write" feature. To read more about "why" and "how" please see: [Transactionally writing files in Node.js](http://stackoverflow.com/questions/17047994/transactionally-writing-files-in-node-js) Jetpack implements this simple trick and makes it available as an option. +```js +jetpack.write('important_config.json', { atomic: true }); +``` diff --git a/packages/fs/LICENSE b/packages/fs/LICENSE new file mode 100644 index 00000000..ae8a7719 --- /dev/null +++ b/packages/fs/LICENSE @@ -0,0 +1,28 @@ +The "New" BSD License +********************* + +Copyright (c) 2015 - 2016, xblox +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the xblox nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/fs/README.md b/packages/fs/README.md new file mode 100644 index 00000000..ee78f8ee --- /dev/null +++ b/packages/fs/README.md @@ -0,0 +1,20 @@ +# Advanced filesystem implementation in Typescript + +- [ ] Correct totals +- [ ] support streams +- [ ] Switch to fast glob +- [x] Remap nodes for cp and mv +- [x] zero conf +- [ ] update unit tests +- [-] common CLI commands for global: each, concat, cat (with plugins) +- [-] rimraf rename plugin, part of `each ` +- [ ] node type incorrect for invalid file/folder names +- [-] update to latest fs-jetpack +- [ ] WSAENAMETOOLONG +- [ ] diff + - [ ] images +- [ ] defaults : path limit, ARS, busy + +## References + +- [https://github.com/alessioalex/get-folder-size](https://github.com/alessioalex/get-folder-size) diff --git a/packages/fs/eslint.config.js b/packages/fs/eslint.config.js new file mode 100644 index 00000000..542e9d9c --- /dev/null +++ b/packages/fs/eslint.config.js @@ -0,0 +1,138 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import tseslint from 'typescript-eslint'; + +// plugins +import regexpEslint from 'eslint-plugin-regexp'; +const typescriptEslint = tseslint.plugin; + +// parsers +const typescriptParser = tseslint.parser; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default [ + // If ignores is used without any other keys in the configuration object, then the patterns act as global ignores. + // ref: https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores + { + ignores: [ + '**/.*', + '**/*.d.ts', + 'packages/**/*.min.js', + 'packages/**/dist/', + 'packages/**/fixtures/', + 'packages/astro/vendor/vite/', + 'benchmark/**/dist/', + 'examples/', + 'scripts/', + '.github/', + '.changeset/', + ], + }, + + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + regexpEslint.configs['flat/recommended'], + { + languageOptions: { + parser: typescriptParser, + parserOptions: { + project: [ + './packages/*/tsconfig.json', + '../../tsconfig.eslint.json' + ], + tsconfigRootDir: __dirname, + }, + }, + plugins: { + '@typescript-eslint': typescriptEslint, + regexp: regexpEslint, + }, + rules: { + // These off/configured-differently-by-default rules fit well for us + '@typescript-eslint/switch-exhaustiveness-check': 'error', + '@typescript-eslint/no-shadow': 'error', + 'no-console': 'off', + + // Todo: do we want these? + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/class-literal-property-style': 'off', + '@typescript-eslint/consistent-indexed-object-style': 'off', + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/dot-notation': 'off', + '@typescript-eslint/no-base-to-string': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-redundant-type-constituents': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/only-throw-error': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'off', + '@typescript-eslint/prefer-optional-chain': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-string-starts-ends-with': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/sort-type-constituents': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/no-explicit-any': 'off', + + // Used by Biome + '@typescript-eslint/consistent-type-imports': 'off', + // These rules enabled by the preset configs don't work well for us + '@typescript-eslint/await-thenable': 'off', + 'prefer-const': 'off', + + // In some cases, using explicit letter-casing is more performant than the `i` flag + 'regexp/use-ignore-case': 'off', + 'regexp/prefer-regexp-exec': 'warn', + 'regexp/prefer-regexp-test': 'warn', + }, + }, + { + files: ['packages/astro/src/runtime/client/**/*.ts'], + languageOptions: { + globals: { + browser: true, + }, + }, + }, + { + files: ['packages/astro/src/core/errors/errors-data.ts'], + rules: { + // This file is used for docs generation, as such the code need to be in a certain format, we can somewhat ensure this with these rules + 'object-shorthand': ['error', 'methods', { avoidExplicitReturnArrows: true }], + 'arrow-body-style': ['error', 'never'], + }, + }, + + { + files: ['packages/db/src/runtime/**/*.ts'], + rules: { + 'no-restricted-imports': 'off', + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['../core/*'], + allowTypeImports: true, + }, + ], + }, + ], + }, + }, +]; diff --git a/packages/fs/package.json b/packages/fs/package.json new file mode 100644 index 00000000..836d032a --- /dev/null +++ b/packages/fs/package.json @@ -0,0 +1,78 @@ +{ + "name": "@polymech/fs", + "description": "Simplified file system API in Typescript", + "version": "0.13.41", + "type": "module", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@polymech/core": "workspace:*", + "denodeify": "^1.2.1", + "errno": "^0.1.4", + "eslint-plugin-regexp": "^2.7.0", + "glob": "^10.4.1", + "mime": "^2.0.3", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "progress-stream": "^1.2.0", + "q": "^1.4.1", + "rimraf": "^6.0.1", + "write-file-atomic": "^1.3.1", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@eslint/js": "^9.18.0", + "@repo/eslint-config": "workspace:*", + "@repo/typescript-config": "workspace:*", + "@types/denodeify": "^1.2.31", + "@types/glob": "^8.1.0", + "@types/mime": "^2.0.0", + "@types/minimatch": "^3.0.3", + "@types/mkdirp": "^0.5.1", + "@types/node": "^22.10.2", + "chai": "^3.5.0", + "codecov": "^3.8.3", + "eslint": "^8.57.1", + "eslint-plugin-import": "^2.31.0", + "fs-extra": "^4.0.2", + "globals": "^15.14.0", + "istanbul": "^0.4.5", + "mocha": "^11.0.1", + "mocha-typescript": "^1.0.12", + "ts-node": "^10.9.1", + "tslint": "^5.7.0", + "tslint-presets": "^2.0.0", + "typescript": "^5.7.2", + "typescript-eslint": "^8.20.0" + }, + "scripts": { + "test": "tsc; mocha \"spec/**/*.spec.js\"", + "testcp": "tsc;mocha \"spec/copy.spec.js\"", + "testdir": "tsc;mocha \"spec/dir.spec.js\"", + "testrename": "tsc;mocha \"spec/rename.spec.js\"", + "testmove": "tsc;mocha \"spec/move.spec.js\"", + "testremove": "tsc;mocha \"spec/remove.spec.js\"", + "test-with-coverage": "istanbul cover node_modules/.bin/_mocha -- 'spec/**/*.spec.js'", + "lint": "npx eslint ./src/**/*.ts --fix", + "build": "tsc -p .", + "typings": "tsc --declaration", + "dev": "tsc -p . --declaration -w", + "dev-test-watch": "mocha-typescript-watch" + }, + "homepage": "https://git.polymech.io/polymech/fs", + "repository": { + "type": "git", + "url": "https://git.polymech.io/polymech/fs.git" + }, + "engines": { + "node": ">= 8.0.0" + }, + "license": "BSD-3-Clause", + "keywords": [ + "fs", + "file system", + "typescript" + ], + "pre-commit": [] +} diff --git a/packages/fs/scripts/docs.sh b/packages/fs/scripts/docs.sh new file mode 100644 index 00000000..036584ca --- /dev/null +++ b/packages/fs/scripts/docs.sh @@ -0,0 +1,4 @@ +rm -rf docs +typedoc --out ./docs/ src/**/*.ts +git add -A ./docs/* +#gitc "doc update" diff --git a/packages/fs/scripts/test.sh b/packages/fs/scripts/test.sh new file mode 100644 index 00000000..031e42ec --- /dev/null +++ b/packages/fs/scripts/test.sh @@ -0,0 +1,4 @@ +npm run build +npm run test + + diff --git a/packages/fs/scripts/update.sh b/packages/fs/scripts/update.sh new file mode 100644 index 00000000..685780e2 --- /dev/null +++ b/packages/fs/scripts/update.sh @@ -0,0 +1,6 @@ +npm run build +npm run test +sh scripts/docs.sh +node scripts/gen_typings.js +git push --mirror https://github.com/xblox/fs-jetpack.git +gitc $1 diff --git a/packages/fs/src/append.ts b/packages/fs/src/append.ts new file mode 100644 index 00000000..bf4ae18f --- /dev/null +++ b/packages/fs/src/append.ts @@ -0,0 +1,54 @@ +import * as fs from 'fs' +const Q = require('q') + +import { sync as writeSync, async as writeASync } from './write.js' +import { validateArgument, validateOptions } from './utils/validate.js' +export interface Options { + mode: string; + encoding?: string; + flag?: string; +} +export const validateInput = (methodName: string, path: string, data: any, options?: Options) => { + const methodSignature = methodName + '(path, data, [options])'; + validateArgument(methodSignature, 'path', path, ['string']); + validateArgument(methodSignature, 'data', data, ['string', 'buffer']); + validateOptions(methodSignature, 'options', options, { + mode: ['string', 'number'] + }); +}; +// --------------------------------------------------------- +// SYNC +// --------------------------------------------------------- +export const sync = (path: string, data: any, options: Options): void => { + try { + fs.appendFileSync(path, data, options ? { encoding: options.encoding as BufferEncoding, mode: options.mode as string } : {}); + } catch (err) { + if (err.code === 'ENOENT') { + // Parent directory doesn't exist, so just pass the task to `write`, + // which will create the folder and file. + writeSync(path, data, options); + } else { + throw err; + } + } +}; + +// --------------------------------------------------------- +// ASYNC +// --------------------------------------------------------- +const promisedAppendFile = Q.denodeify(fs.appendFile); +export const async = (path: string, data: string | Buffer | object, options?: Options): Promise => { + return new Promise((resolve, reject) => { + promisedAppendFile(path, data, options) + .then(resolve) + .catch((err: any) => { + if (err.code === 'ENOENT') { + // Parent directory doesn't exist, so just pass the task to `write`, + // which will create the folder and file. + writeASync(path, data, options).then(resolve, reject); + } else { + reject(err); + } + }); + }); +}; diff --git a/packages/fs/src/copy.ts b/packages/fs/src/copy.ts new file mode 100644 index 00000000..2ef862a3 --- /dev/null +++ b/packages/fs/src/copy.ts @@ -0,0 +1,673 @@ +import * as pathUtil from 'path'; +import * as fs from 'fs'; +import { symlinkSync, readFileSync, createReadStream, createWriteStream } from 'fs'; +import { sync as mkdirp } from 'mkdirp'; + +import { sync as existsSync, async as existsASync } from './exists.js'; +import { create as matcher } from './utils/matcher.js'; +import { normalizeFileMode as fileMode } from './utils/mode.js'; +import { sync as treeWalkerSync } from './utils/tree_walker.js'; +import { validateArgument, validateOptions } from './utils/validate.js'; +import { sync as writeSync } from './write.js'; +import { ErrDestinationExists, ErrDoesntExists } from './errors.js'; + +import type { + INode, + IWriteOptions, + IProcessingNode, + ICopyOptions, + IConflictSettings, + TCopyResult, + INodeReport } from './interfaces.js'; + + import { + ErrnoException, + ENodeType, + ECopyFlags, + ENodeOperationStatus, + EError, + EInspectFlags, + EResolveMode, + EResolve + } from './interfaces.js'; + +import { createItem } from './inspect.js'; +import { sync as rmSync } from './remove.js'; +import { promisify } from './promisify.js'; +import { async as iteratorAsync } from './iterator.js'; +//import { ArrayIterator } from '@polymech/core/iterator'; + +const promisedSymlink = promisify(fs.symlink); +const promisedReadlink = promisify(fs.readlink); +const promisedUnlink = promisify(fs.unlink); +const promisedMkdirp = promisify(mkdirp); + +const progress = require('progress-stream'); + +const CPROGRESS_THRESHOLD = 1048576 * 5; // minimum file size threshold to use write progress = 5MB + +export function validateInput(methodName: string, from: string, to: string, options?: ICopyOptions): void { + const methodSignature = methodName + '(from, to, [options])'; + validateArgument(methodSignature, 'from', from, ['string']); + validateArgument(methodSignature, 'to', to, ['string']); + validateOptions(methodSignature, 'options', options, { + overwrite: ['boolean'], + matching: ['string', 'array of string'], + progress: ['function'], + content: ['function'], + writeProgress: ['function'], + conflictCallback: ['function'], + conflictSettings: ['object'], + throttel: ['number'], + debug: ['boolean'], + flags: ['number'] + }); +} + +const parseOptions = (options: any | null, from: string): ICopyOptions => { + const opts: ICopyOptions = options || {} as ICopyOptions; + const parsedOptions: ICopyOptions = {}; + parsedOptions.overwrite = opts.overwrite; + parsedOptions.progress = opts.progress; + parsedOptions.writeProgress = opts.writeProgress; + parsedOptions.content = opts.content; + parsedOptions.conflictCallback = opts.conflictCallback; + parsedOptions.conflictSettings = opts.conflictSettings; + parsedOptions.debug = opts.debug; + parsedOptions.throttel = opts.throttel; + parsedOptions.renameCallback = opts.renameCallback; + parsedOptions.flags = opts.flags || 0; + if (!opts.filter) { + if (opts.matching) { + parsedOptions.filter = matcher(from, opts.matching); + } else { + parsedOptions.filter = () => { + return true; + }; + } + } + return parsedOptions; +}; +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- +const checksBeforeCopyingSync = (from: string, to: string, options: ICopyOptions = {}) => { + if (!existsSync(from)) { + throw ErrDoesntExists(from); + } + + if (existsSync(to) && !options.overwrite) { + throw ErrDestinationExists(to); + } +}; + +async function copyFileSyncWithProgress(from: string, to: string, options: ICopyOptions = {}) { + return new Promise((resolve, reject) => { + const started = Date.now(); + let cbCalled = false; + let elapsed = Date.now(); + let speed = 0; + const done = (err?: any) => { + if (!cbCalled) { + cbCalled = true; + resolve(1); + } + }; + const rd = createReadStream(from). + on('error', (err: Error) => done(err)); + + const str = progress({ + length: fs.statSync(from).size, + time: 100 + }).on('progress', (e: any) => { + elapsed = (Date.now() - started) / 1000; + speed = e.transferred / elapsed; + if (options.writeProgress) { + options.writeProgress(from, e.transferred, e.length); + } + }); + + const wr = createWriteStream(to); + wr.on('error', (err: Error) => done(err)); + wr.on('close', done); + + rd.pipe(str).pipe(wr); + }); +} + +async function copyFileSync(from: string, to: string, mode: string, options: ICopyOptions) { + let data = readFileSync(from); + const writeOptions: IWriteOptions = { + mode: mode + }; + + if (options.renameCallback) { + const rename = options.renameCallback(from, to); + if (rename) { + to = rename; + } + } + + if (options.content) { + data = options.content(from, data, createItem(from)); + } + + if (options && options.writeProgress) { + await copyFileSyncWithProgress(from, to, options); + } else { + writeSync(to, data, writeOptions); + } +} +const copySymlinkSync = (from: string, to: string) => { + const symlinkPointsAt = fs.readlinkSync(from); + try { + symlinkSync(symlinkPointsAt, to); + } catch (err) { + // There is already file/symlink with this name on destination location. + // Must erase it manually, otherwise system won't allow us to place symlink there. + if (err.code === 'EEXIST') { + fs.unlinkSync(to); + // Retry... + fs.symlinkSync(symlinkPointsAt, to); + } else { + throw err; + } + } +}; + +async function copyItemSync(from: string, inspectData: INode, to: string, options: ICopyOptions) { + const mode: string = fileMode(inspectData.mode as number); + if (inspectData.type === ENodeType.DIR) { + if (options.renameCallback) { + const rename = options.renameCallback(from, to); + if (rename) { + to = rename + } + } + mkdirp(to, { mode: parseInt(mode, 8), fs: null }); + } else if (inspectData.type === ENodeType.FILE) { + await copyFileSync(from, to, mode, options) + } else if (inspectData.type === ENodeType.SYMLINK) { + if (options.renameCallback) { + const rename = options.renameCallback(from, to) + if (rename) { + to = rename + } + } + copySymlinkSync(from, to); + } +} +export function sync(from: string, to: string, options?: ICopyOptions): void { + const opts = parseOptions(options, from) + checksBeforeCopyingSync(from, to, opts) + const nodes: IProcessingNode[] = [] + let sizeTotal = 0 + if (options && options.flags & ECopyFlags.EMPTY) { + const dstStat = fs.statSync(to) + if (dstStat.isDirectory()) { + rmSync(to) + } + } + + const visitor = (path: string, inspectData: INode) => { + if (opts.filter(path)) { + nodes.push({ + path: path, + item: inspectData, + dst: pathUtil.resolve(to, pathUtil.relative(from, path)) + }); + sizeTotal += inspectData.size; + } + }; + + treeWalkerSync(from, { + inspectOptions: { + mode: true, + symlinks: true + } + }, visitor); + + nodes.map((item, current) => { + copyItemSync(item.path, item.item, item.dst, options) + if (opts.progress) { + opts.progress(item.path, current, nodes.length, item.item, item.dst) + } + }) +} + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- + +/** + * + * + * @param {string} from + * @param {string} to + * @param {ICopyOptions} opts + * @returns {(Promise)} + */ +const checkAsync = (from: string, to: string, opts: ICopyOptions): Promise => { + return existsASync(from) + .then(srcPathExists => { + if (!srcPathExists) { + throw ErrDoesntExists(from); + } else { + return existsASync(to); + } + }) + .then(destPathExists => { + if (destPathExists) { + if (opts.conflictSettings) { + return Promise.resolve(opts.conflictSettings); + } + if (opts.conflictCallback) { + const promise = opts.conflictCallback(to, createItem(to), EError.EXISTS); + promise.then((settings: IConflictSettings) => { + settings.error = EError.EXISTS; + }); + return promise; + } + if (!opts.overwrite) { + throw ErrDestinationExists(to); + } + } + }); +}; + +const copyFileAsync = (from: string, to: string, mode: any, options?: ICopyOptions, retriedAttempt?: boolean) => { + return new Promise((resolve, reject) => { + const readStream = fs.createReadStream(from); + const writeStream = fs.createWriteStream(to, { mode: mode }); + readStream.on('error', reject); + writeStream.on('error', (err: ErrnoException) => { + const toDirPath = pathUtil.dirname(to); + // Force read stream to close, since write stream errored + // read stream serves us no purpose. + readStream.resume(); + if (err.code === EError.NOEXISTS && retriedAttempt === undefined) { + // Some parent directory doesn't exits. Create it and retry. + promisedMkdirp(toDirPath, null).then(() => { + // Make retry attempt only once to prevent vicious infinite loop + // (when for some obscure reason I/O will keep returning ENOENT error). + // Passing retriedAttempt = true. + copyFileAsync(from, to, mode, null, true) + .then(resolve) + .catch(reject); + }); + } else { + reject(err); + } + }); + + writeStream.on('finish', () => { + // feature: preserve times + if (options && options.flags & ECopyFlags.PRESERVE_TIMES) { + const sourceStat = fs.statSync(from); + fs.open(to, 'w', (err: ErrnoException, fd: number) => { + if (err) { + throw err; + } + fs.futimes(fd, sourceStat.atime, sourceStat.mtime, (err2) => { + if (err2) { + throw err2; + } + fs.close(fd, null); + resolve(1); + }); + }); + } else { + resolve(1); + } + }); + + const size = fs.statSync(from).size; + let progressStream = null; + if (options && options.writeProgress && size > CPROGRESS_THRESHOLD) { + progressStream = progress({ + length: fs.statSync(from).size, + time: 100 // call progress each 100 ms + }); + let elapsed = Date.now(); + let speed = 0; + const started = Date.now(); + progressStream.on('progress', (e: any) => { + elapsed = (Date.now() - started) / 1000; + speed = e.transferred / elapsed; + options.writeProgress(from, e.transferred, e.length); + if (options.debug) { + console.log('write ' + from + ' (' + e.transferred + ' of ' + e.length); + } + }); + readStream.pipe(progressStream).pipe(writeStream); + } else { + if (options && options.debug) { + console.log('write ' + from + ' to ' + to); + } + readStream.pipe(writeStream); + } + }); +}; +export function copySymlinkAsync(from: string, to: string) { + return promisedReadlink(from) + .then((symlinkPointsAt: string) => { + return new Promise((resolve, reject) => { + promisedSymlink(symlinkPointsAt, to, null) + .then(resolve) + .catch((err: ErrnoException) => { + if (err.code === EError.EXISTS) { + // There is already file/symlink with this name on destination location. + // Must erase it manually, otherwise system won't allow us to place symlink there. + promisedUnlink(to, null) + // Retry... + .then(() => { + return promisedSymlink(symlinkPointsAt, to, null); + }) + .then(resolve, reject); + } else { + reject(err); + } + }); + }); + }); +} + +const copyItemAsync = (from: string, inspectData: INode, to: string, options: ICopyOptions): Promise => { + const mode = fileMode(inspectData.mode) + if (inspectData.type === ENodeType.DIR) { + return promisedMkdirp(to, { mode: mode }) + } else if (inspectData.type === ENodeType.FILE) { + return copyFileAsync(from, to, mode, options) + } else if (inspectData.type === ENodeType.SYMLINK) { + return copySymlinkAsync(from, to) + } + // EInspectItemType.OTHER + return Promise.resolve() +} +// handle user side setting "THROW" and non enum values (null) +const onConflict = (from: string, to: string, options: ICopyOptions, settings: IConflictSettings): EResolveMode | undefined => { + switch (settings.overwrite) { + case EResolveMode.THROW: { + throw ErrDestinationExists(to); + } + case EResolveMode.OVERWRITE: + case EResolveMode.APPEND: + case EResolveMode.IF_NEWER: + case EResolveMode.ABORT: + case EResolveMode.IF_SIZE_DIFFERS: + case EResolveMode.SKIP: { + return settings.overwrite; + } + default: { + return undefined; + } + } +}; + +export function resolveConflict(from: string, to: string, options: ICopyOptions, resolveMode: EResolveMode): boolean { + if (resolveMode === undefined) { + return true; + } + const src = createItem(from); + const dst = createItem(to); + if (resolveMode === EResolveMode.SKIP) { + return false; + } else if (resolveMode === EResolveMode.IF_NEWER) { + if (src.type === ENodeType.DIR && dst.type === ENodeType.DIR) { + return true; + } + if (dst.modifyTime.getTime() > src.modifyTime.getTime()) { + return false; + } + } else if (resolveMode === EResolveMode.IF_SIZE_DIFFERS) { + // @TODO : not implemented: copy EInspectItemType.DIR with ECopyResolveMode.IF_SIZE_DIFFERS + if (src.type === ENodeType.DIR && dst.type === ENodeType.DIR) { + return true; + } else if (src.type === ENodeType.FILE && dst.type === ENodeType.FILE) { + if (src.size === dst.size) { + return false; + } + } + } else if (resolveMode === EResolveMode.OVERWRITE) { + return true; + } else if (resolveMode === EResolveMode.ABORT) { + return false; + } +} + +function isDone(nodes: IProcessingNode[]) { + let done = true; + nodes.forEach((element: IProcessingNode) => { + if (element.status !== ENodeOperationStatus.DONE) { + done = false; + } + }); + return done; +} +/** + * A callback for treeWalkerStream. This is called when a node has been found. + * + * @param {string} from + * @param {string} to + * @param {*} vars + * @param {{ path: string, item: INode }} item + * @returns {Promise} + */ +async function visitor(from: string, to: string, vars: IVisitorArgs, item: IProcessingNode): Promise { + const options = vars.options; + let rel: string; + let destPath: string; + if (!item) { + return; + } + rel = pathUtil.relative(from, item.path); + destPath = pathUtil.resolve(to, rel); + + item.status = ENodeOperationStatus.PROCESSING; + const done = () => { + item.status = ENodeOperationStatus.DONE; + if (isDone(vars.nodes)) { + return vars.resolve(vars.result); + } + }; + if (isDone(vars.nodes)) { + return vars.resolve(vars.result); + } + vars.filesInProgress += 1; + // our main function after sanity checks + const checked = (subResolveSettings: IConflictSettings) => { + item.status = ENodeOperationStatus.CHECKED; + // feature : report + if (subResolveSettings && options && options.flags && options.flags & ECopyFlags.REPORT) { + (vars.result as INodeReport[]).push({ + error: subResolveSettings.error, + node: item, + resolved: subResolveSettings + } as INodeReport); + } + if (subResolveSettings) { + // if the first resolve callback returned an individual resolve settings "THIS", + // ask the user again with the same item + const always = subResolveSettings.mode === EResolve.ALWAYS; + if (always) { + options.conflictSettings = subResolveSettings; + } + let overwriteMode = subResolveSettings.overwrite; + overwriteMode = onConflict(item.path, destPath, options, subResolveSettings) as EResolveMode + + if (overwriteMode === EResolveMode.ABORT) { + vars.abort = true; + } + if (vars.abort) { + return; + } + + if (!resolveConflict(item.path, destPath, options, overwriteMode)) { + done(); + return; + } + + } + item.status = ENodeOperationStatus.PROCESS; + copyItemAsync(item.path, item.item, destPath, options).then(() => { + vars.filesInProgress -= 1; + if (options.progress) { + if (options.progress(item.path, vars.filesInProgress, vars.filesInProgress, item.item) === false) { + vars.abort = true; + return vars.resolve(); + } + } + done(); + }).catch((err: ErrnoException) => { + if (options && options.conflictCallback) { + if (err.code === EError.PERMISSION || err.code === EError.NOEXISTS) { + options.conflictCallback(item.path, createItem(destPath), err.code).then((errorResolveSettings: IConflictSettings) => { + // the user has set the conflict resolver to always, so we use the last one + if (vars.onCopyErrorResolveSettings) { + errorResolveSettings = vars.onCopyErrorResolveSettings; + } + // user said use this settings always, we track and use this last setting from now on + if (errorResolveSettings.mode === EResolve.ALWAYS && !vars.onCopyErrorResolveSettings) { + vars.onCopyErrorResolveSettings = errorResolveSettings; + } + + if (errorResolveSettings.overwrite === EResolveMode.ABORT) { + vars.abort = true; + return vars.resolve(); + } + if (errorResolveSettings.overwrite === EResolveMode.THROW) { + vars.abort = true; + return vars.reject(err); + } + if (errorResolveSettings.overwrite === EResolveMode.SKIP) { + vars.filesInProgress -= 1; + } + + // user error, should never happen, unintended + if (errorResolveSettings.overwrite === EResolveMode.IF_NEWER || + errorResolveSettings.overwrite === EResolveMode.IF_SIZE_DIFFERS || + errorResolveSettings.overwrite === EResolveMode.OVERWRITE) { + vars.reject(new ErrnoException('settings make no sense : errorResolveSettings.overwrite = ' + errorResolveSettings.overwrite)); + } + }); + } + } + vars.reject(err); + }); + }; + return checkAsync(item.path, destPath, options).then(checked); +} + +function next(nodes: IProcessingNode[]): IProcessingNode { + for (let i = 0; i < nodes.length; i++) { + if (nodes[i].status === ENodeOperationStatus.COLLECTED) { + return nodes[i]; + } + } + return null; +} + +interface IVisitorArgs { + resolve: Function; + reject: Function; + abort: boolean; + filesInProgress: number; + resolveSettings: IConflictSettings; + options: ICopyOptions; + result: TCopyResult; + nodes: IProcessingNode[]; + onCopyErrorResolveSettings: IConflictSettings | null; +} + +/** + * Final async copy function. + * @export + * @param {string} from + * @param {string} to + * @param {ICopyOptions} [options] + * @returns + */ +export function async(from: string, to: string, options?: ICopyOptions): Promise { + options = parseOptions(options, from); + return new Promise((resolve, reject) => { + checkAsync(from, to, options).then((resolver: IConflictSettings) => { + if (!resolver) { + resolver = options.conflictSettings || { + mode: EResolve.THIS, + overwrite: EResolveMode.OVERWRITE + }; + } else { + if (resolver.mode === EResolve.ALWAYS) { + options.conflictSettings = resolver; + } + } + let overwriteMode = resolver.overwrite; + let result: TCopyResult = void 0; + + if (options && options.flags && options.flags & ECopyFlags.REPORT) { + result = []; + } + + // call onConflict to eventually throw an error + overwriteMode = onConflict(from, to, options, resolver) as EResolveMode; + + // now evaluate the copy conflict settings and eventually abort + if (options && options.conflictSettings && !resolveConflict(from, to, options, overwriteMode)) { + return resolve(); + } + // feature: clean before + if (options && options.flags as ECopyFlags & ECopyFlags.EMPTY) { + const dstStat = fs.statSync(to); + if (dstStat.isDirectory()) { + rmSync(to); + } + } + // walker variables + const visitorArgs: IVisitorArgs = { + resolve: resolve, + reject: reject, + abort: false, + filesInProgress: 0, + resolveSettings: resolver, + options: options, + result: result, + nodes: [], + onCopyErrorResolveSettings: null + }; + const nodes = visitorArgs.nodes; + // a function called when the treeWalkerStream or visitor has been finished + const process = function () { + visitorArgs.nodes = nodes; + if (isDone(nodes)) { + return resolve(result); + } + if (nodes.length) { + const item = next(nodes); + if (item) { + visitor(item.path, item.dst as string, visitorArgs, item).then(process); + } + } + }; + + let flags: EInspectFlags = EInspectFlags.MODE; + if (options && options.flags && options.flags & ECopyFlags.FOLLOW_SYMLINKS) { + flags |= EInspectFlags.SYMLINKS; + } + iteratorAsync(from, { + filter: options.filter, + flags: flags + }).then((it: any) => { + let node: IProcessingNode; + while (node = it.next() as any) { + nodes.push({ + path: node.path, + item: node.item, + dst: pathUtil.resolve(to, pathUtil.relative(from, node.path)), + status: ENodeOperationStatus.COLLECTED + }); + } + process(); + }); + }).catch(reject); + }); +} diff --git a/packages/fs/src/dir.ts b/packages/fs/src/dir.ts new file mode 100644 index 00000000..15ff6433 --- /dev/null +++ b/packages/fs/src/dir.ts @@ -0,0 +1,198 @@ +import * as pathUtil from 'path'; +import { Stats, stat, statSync, readdirSync, readdir } from 'fs'; +import { promisify } from 'util'; +import * as fs from 'fs'; +import { sync as removeSync, async as removeAsync } from './remove.js'; +import { normalizeFileMode as modeUtil } from './utils/mode.js'; +import { validateArgument, validateOptions } from './utils/validate.js'; +import { ErrNoDirectory } from './errors.js'; +import { EError } from './interfaces.js'; +import * as mkdirp from 'mkdirp'; + +export interface IOptions { + empty?: boolean; + mode?: number | string; +} + +export const validateInput = (methodName: string, path: string, options?: IOptions) => { + const methodSignature = methodName + '(path, [criteria])'; + validateArgument(methodSignature, 'path', path, ['string']); + validateOptions(methodSignature, 'criteria', options, { + empty: ['boolean'], + mode: ['string', 'number'] + }); +}; + +const defaults = (options?: IOptions): IOptions => { + const result = options || {}; + if (typeof result.empty !== 'boolean') { + result.empty = false; + } + if (result.mode !== undefined) { + result.mode = modeUtil(result.mode); + } + return result; +}; + +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- +const dirStatsSync = (path: string): Stats => { + let _stat: Stats; + try { + _stat = statSync(path); + } catch (err) { + // Detection if path already exists + if (err.code !== EError.NOEXISTS) { + throw err; + } + } + + if (_stat && !_stat.isDirectory()) { + throw ErrNoDirectory(path); + } + + return _stat; +}; + +function mkdirSync(path: string, criteria: IOptions) { + mkdirp.sync(path, { mode: criteria.mode as number, fs: null }); +} + +function checkDirSync(path: string, _stat: Stats, options: IOptions) { + const check = function () { + if (options.mode !== undefined) { + fs.chmodSync(path, options.mode); + } + }; + const checkEmptiness = function () { + let list: string[]; + if (options.empty) { + // Delete everything inside this directory + list = readdirSync(path); + list.forEach(function (filename) { + removeSync(pathUtil.resolve(path, filename)); + }); + } + }; + check(); + checkEmptiness(); +} + +export const sync = (path: string, options?: IOptions) => { + const criteria = defaults(options); + const _stat = dirStatsSync(path); + if (_stat) { + checkDirSync(path, _stat, criteria); + } else { + mkdirSync(path, criteria); + } +}; + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- +const promisedStat = promisify(stat); +const promisedReaddir = promisify(readdir); +const dirStatAsync = (path: string): Promise => { + return new Promise((resolve, reject) => { + promisedStat(path) + .then((_stat: any) => { + if (_stat.isDirectory()) { + resolve(_stat); + } else { + reject(ErrNoDirectory(path)); + } + }) + .catch((err: any) => (err.code === EError.NOEXISTS ? resolve(undefined) : reject(err))); + + }); +}; + +// Delete all files and directores inside given directory +const emptyAsync = (path: string) => { + return new Promise((resolve, reject) => { + promisedReaddir(path) + .then(function (list: any[]) { + const doOne = function (index: number) { + let subPath: string; + if (index === list.length) { + resolve(1); + } else { + subPath = pathUtil.resolve(path, list[index]); + removeAsync(subPath).then(() => doOne(index + 1)); + } + }; + doOne(0); + }) + .catch(reject); + }); +}; + +const checkMode = function (criteria: IOptions, _stat: Stats, path: string): Promise { + if (criteria.mode !== undefined) { + return promisify(fs.chmod)(path, criteria.mode); + } + return Promise.resolve(null); +}; + +const checkDirAsync = (path: string, _stat: Stats, options: IOptions) => { + return new Promise((resolve, reject) => { + const checkEmptiness = function (): Promise { + if (options.empty) { + return emptyAsync(path); + } + return Promise.resolve(); + }; + checkMode(options, _stat, path) + .then(checkEmptiness) + .then(resolve, reject); + }); +}; + +const mkdirAsync = (path: string, criteria: IOptions): Promise => { + const options = criteria || {}; + return new Promise((resolve, reject) => { + promisify(fs.mkdir)(path, options.mode) + .then(resolve) + .catch((err) => { + if (err.code === 'ENOENT') { + // Parent directory doesn't exist. Need to create it first. + mkdirAsync(pathUtil.dirname(path), options) + .then(() => { + // Now retry creating this directory. + return promisify(fs.mkdir)(path, options.mode); + }) + .then(resolve) + .catch((err2) => { + if (err2.code === 'EEXIST') { + // Hmm, something other have already created the directory? + // No problem for us. + resolve(1); + } else { + reject(err2); + } + }); + } else if (err.code === 'EEXIST') { + // The path already exists. We're fine. + resolve(1); + } else { + reject(err); + } + }); + }); +}; + +export const async = (path: string, passedCriteria?: IOptions) => { + const criteria = defaults(passedCriteria); + return new Promise((resolve, reject) => { + dirStatAsync(path) + .then((_stat: Stats) => { + if (_stat !== undefined) { + return checkDirAsync(path, _stat, criteria); + } + return mkdirAsync(path, criteria); + }) + .then(resolve, reject); + }); +}; diff --git a/packages/fs/src/errors.ts b/packages/fs/src/errors.ts new file mode 100644 index 00000000..482d7056 --- /dev/null +++ b/packages/fs/src/errors.ts @@ -0,0 +1,45 @@ +import { ErrnoException } from './interfaces.js'; +const errno = require('errno'); + +Object.keys(errno.code).forEach(function (code) { + const e = errno.code[code]; + exports[code] = (path: string) => { + const err = new Error(code + ', ' + e.description + (path ? ' \'' + path + '\'' : '')) as ErrnoException; + err.errno = e.errno; + err.code = code; + err.path = path; + return err; + }; +}); +export const ErrNoFileOrDir = (path: string): Error => { + return new Error('Can\'t remove ' + path + ' The path is not file nor directory'); +}; +export const ErrCantDelete = (path: string): Error => { + return new Error('Can\'t remove ' + path); +}; +export const ErrNotFile = (path: string): Error => { + return new Error('Path ' + path + ' exists but is not a file.' + + ' Halting jetpack.file() call for safety reasons.'); +}; +export const ErrNoDirectory = (path: string): Error => { + return new Error('Path ' + path + ' exists but is not a directory.' + + ' Halting jetpack.dir() call for safety reasons.'); +}; + +export const ErrDoesntExists = (path: string): Error => { + const err: any = new Error('Path to copy doesn\'t exist ' + path); + err.code = 'ENOENT'; + return err; +}; + +export const ErrDestinationExists = (path: string): Error => { + const err: any = new Error('Destination path already exists ' + path); + err.code = 'EEXIST'; + return err; +}; + +export const ErrIsNotDirectory = (path: string): Error => { + const err = new ErrnoException('Path you want to find stuff in must be a directory ' + path); + err.code = 'ENOTDIR'; + return err; +}; diff --git a/packages/fs/src/exists.ts b/packages/fs/src/exists.ts new file mode 100644 index 00000000..026b6f53 --- /dev/null +++ b/packages/fs/src/exists.ts @@ -0,0 +1,52 @@ +import { Stats, statSync, lstat } from 'fs'; +import { validateArgument } from './utils/validate.js'; +import { ENodeType, ErrnoException } from './interfaces.js'; + +export function validateInput(methodName: string, path: string) { + const methodSignature = methodName + '(path)'; + validateArgument(methodSignature, 'path', path, ['string']); +} + +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- +export function sync(path: string): boolean | string { + let stat: Stats; + try { + stat = statSync(path); + if (stat.isDirectory()) { + return 'dir'; + } else if (stat.isFile()) { + return 'file'; + } + return 'other'; + } catch (err) { + if (err.code !== 'ENOENT' && err.code !== 'ENOTDIR') { + throw err; + } + } + return false; +} + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- +export function async(path: string): Promise { + return new Promise ((resolve, reject) => { + lstat(path, (err: ErrnoException, stat: Stats) => { + if (err) { + if (err.code === 'ENOENT' || err.code === 'ENOTDIR') { + resolve(false); + } else { + reject(err); + } + } else if (stat.isDirectory()) { + resolve(ENodeType.DIR); + } else if (stat.isFile()) { + resolve(ENodeType.FILE); + } else { + resolve(ENodeType.OTHER); + } + }); + }); +} diff --git a/packages/fs/src/file.ts b/packages/fs/src/file.ts new file mode 100644 index 00000000..99bafd6c --- /dev/null +++ b/packages/fs/src/file.ts @@ -0,0 +1,167 @@ +import * as fs from 'fs'; +import { Stats } from 'fs'; +const Q = require('q'); +import { normalizeFileMode } from './utils/mode.js'; +import { validateArgument, validateOptions } from './utils/validate.js'; +import { sync as writeSync, async as writeASync } from './write.js'; +import { ErrNotFile } from './errors.js'; +import { EError } from './interfaces.js'; + +const promisedStat = Q.denodeify(fs.stat); +const promisedChmod = Q.denodeify(fs.chmod); + +export interface IOptions { + content: string | Buffer | object | Array; + jsonIndent: number; + mode: string; +} + +export function validateInput(methodName: string, path: string, options?: IOptions) { + const methodSignature = methodName + '(path, [criteria])'; + validateArgument(methodSignature, 'path', path, ['string']); + validateOptions(methodSignature, 'criteria', options, { + content: ['string', 'buffer', 'object', 'array'], + jsonIndent: ['number'], + mode: ['string', 'number'] + }); +} + +export function defaults(passedCriteria: IOptions | null): IOptions { + const criteria: any = passedCriteria || {}; + if (criteria.mode !== undefined) { + criteria.mode = normalizeFileMode(criteria.mode); + } + return criteria; +} + +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- + +const isFile = (path: string): Stats => { + let stat: Stats; + try { + stat = fs.statSync(path); + } catch (err) { + // Detection if path exists + if (err.code !== EError.NOEXISTS) { + throw err; + } + } + + if (stat && !stat.isFile()) { + throw ErrNotFile(path); + } + + return stat; +}; + +const checkContent = function (path: string, mode: string, options: IOptions): boolean { + if (options.content !== undefined) { + writeSync(path, options.content, { + mode: mode, + jsonIndent: options.jsonIndent + }); + return true; + } + return false; +}; + +const checkMode = function (path: string, mode: string, options: IOptions) { + if (options.mode !== undefined && options.mode !== mode) { + fs.chmodSync(path, options.mode); + } +}; + +const accept = (path: string, stat: Stats, options?: IOptions): void => { + const mode = normalizeFileMode(stat.mode); + if (!checkContent(path, mode, options)) { + checkMode(path, mode, options); + } +}; + +const touch = (path: string, options: IOptions): void => { + const content: string | Buffer | object | Array = options.content !== undefined ? options.content : ''; + writeSync(path, content, { + mode: options.mode, + jsonIndent: options.jsonIndent + }); +}; + +export function sync(path: string, options: IOptions) { + options = defaults(options); + const stat: Stats = isFile(path); + if (stat !== undefined) { + accept(path, stat, options); + } else { + touch(path, options); + } +} + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- + +function isFileAsync(path: string): Promise { + return new Promise((resolve, reject) => { + promisedStat(path) + .then((stat: Stats) => { + if ((stat).isFile()) { + resolve(stat); + } else { + reject(ErrNotFile(path)); + } + }) + .catch((err: any) => (err.code === EError.NOEXISTS ? resolve(undefined) : reject(err))); + }); +} +const checkModeAsync = (path: string, mode: string, options: IOptions) => { + if (options.mode !== undefined && options.mode !== mode) { + return promisedChmod(path, options.mode); + } + return undefined; +}; +const checkContentAsync = (path: string, mode: string, options: IOptions) => { + return new Promise((resolve, reject) => { + if (options.content !== undefined) { + writeASync(path, options.content, { + mode: mode, + jsonIndent: options.jsonIndent + }).then(() => resolve(true)) + .catch(reject); + } else { + resolve(false); + } + }); +}; +async function writeAsync(path: string, stat: Stats, options: IOptions): Promise { + const mode = normalizeFileMode(stat.mode); + return checkContentAsync(path, mode, options) + .then(contentReplaced => { + if (!contentReplaced) { + return checkModeAsync(path, mode, options); + } + return undefined; + }); +} + +const touchAsync = (path: string, options: IOptions) => { + return writeASync(path, options.content !== undefined ? options.content : '', { + mode: options.mode, + jsonIndent: options.jsonIndent + }); +}; + +export async function async(path: string, options: IOptions) { + return new Promise((resolve, reject) => { + options = defaults(options); + isFileAsync(path) + .then((stat: Stats) => { + if (stat !== undefined) { + return writeAsync(path, stat, options); + } + return touchAsync(path, options); + }) + .then(resolve, reject); + }); +} diff --git a/packages/fs/src/find.ts b/packages/fs/src/find.ts new file mode 100644 index 00000000..676a6745 --- /dev/null +++ b/packages/fs/src/find.ts @@ -0,0 +1,132 @@ +import * as pathUtil from 'path' +import { sync as treeWalkerSync, stream as treeWalkerStream } from './utils/tree_walker.js' +import { sync as inspectSync, async as inspectASync } from './inspect.js' +import { create as matcher } from './utils/matcher.js' +import { validateArgument, validateOptions } from './utils/validate.js' +import { INode, ENodeType, IInspectOptions } from './interfaces.js' +import { ErrDoesntExists, ErrIsNotDirectory } from './errors.js' + +export interface IOptions { + matching?: string[] + files?: boolean + directories?: boolean + recursive?: boolean + cwd?: string + inspectOptions?: IInspectOptions +} +export function validateInput(methodName: string, path: string, options?: IOptions): void { + const methodSignature = methodName + '([path], options)'; + validateArgument(methodSignature, 'path', path, ['string']); + validateOptions(methodSignature, 'options', options, { + matching: ['string', 'array of string'], + files: ['boolean'], + directories: ['boolean'], + recursive: ['boolean'] + }) +} + +const defaults = (options?: IOptions): IOptions => { + const opts = options || {} as IOptions; + // defaults: + if (opts.files === undefined) { + opts.files = true + } + if (opts.directories === undefined) { + opts.directories = false + } + if (opts.recursive === undefined) { + opts.recursive = true + } + return opts +} + +const processFoundObjects = (foundObjects: any, cwd: string): string[] => + foundObjects.map((inspectObj: INode) => pathUtil.relative(cwd, inspectObj.absolutePath)) + +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- +export const findSync = (path: string, options: IOptions): string[] => + processFoundObjects(findSyncEx(path, options), options.cwd) + + +export const findSyncEx = (path: string, options: IOptions): INode[] => { + const foundInspectObjects: INode[] = []; + const matchesAnyOfGlobs = matcher(path, options.matching); + treeWalkerSync(path, { + maxLevelsDeep: options.recursive ? Infinity : 1, + inspectOptions: { + absolutePath: true, + ...options.inspectOptions || {} + } + }, (itemPath, item) => { + if (itemPath !== path && matchesAnyOfGlobs(itemPath)) { + if ((item.type === ENodeType.FILE && options.files === true) + || (item.type === ENodeType.DIR && options.directories === true)) { + foundInspectObjects.push(item) + } + } + }) + return foundInspectObjects +} + +export function sync(path: string, options: IOptions): string[] { + const entryPointInspect = inspectSync(path) + if (entryPointInspect === undefined) { + throw ErrDoesntExists(path) + } else if (entryPointInspect.type !== 'dir') { + throw ErrIsNotDirectory(path) + } + return findSync(path, defaults(options)) +} + +export const syncEx = (path: string, options: IOptions): INode[] => { + const entryPointInspect = inspectSync(path) + if (entryPointInspect === undefined) { + throw ErrDoesntExists(path) + } else if (entryPointInspect.type !== 'dir') { + throw ErrIsNotDirectory(path) + } + return findSyncEx(path, defaults(options)) +} + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- + +const findAsync = (path: string, options: IOptions): Promise => { + return new Promise((resolve, reject) => { + const foundInspectObjects: INode[] = []; + const matchesAnyOfGlobs = matcher(path, options.matching); + const walker = treeWalkerStream(path, { + maxLevelsDeep: options.recursive ? Infinity : 1, + inspectOptions: { + absolutePath: true + } + }).on('readable', () => { + const data = walker.read() + let item: INode + if (data && data.path !== path && matchesAnyOfGlobs(data.path)) { + item = data.item + if ((item.type === ENodeType.FILE && options.files === true) + || (item.type === ENodeType.DIR && options.directories === true)) { + foundInspectObjects.push(item) + } + } + }).on('error', reject) + .on('end', () => { + resolve(processFoundObjects(foundInspectObjects, options.cwd)) + }) + }); +} + +export function async(path: string, options: IOptions): Promise { + return inspectASync(path).then(entryPointInspect => { + if (entryPointInspect === undefined) { + throw ErrDoesntExists(path) + } else if ((entryPointInspect as any).type !== ENodeType.DIR) { + throw ErrIsNotDirectory(path) + } + return findAsync(path, defaults(options)) + }) +} diff --git a/packages/fs/src/imports.ts b/packages/fs/src/imports.ts new file mode 100644 index 00000000..e231058c --- /dev/null +++ b/packages/fs/src/imports.ts @@ -0,0 +1,8 @@ +const writefs = require('write-file-atomic'); +export const file = { + write_atomic: writefs +}; +export const json = { + parse: JSON.parse, + serialize: JSON.stringify +}; diff --git a/packages/fs/src/index.ts b/packages/fs/src/index.ts new file mode 100644 index 00000000..eb5a9581 --- /dev/null +++ b/packages/fs/src/index.ts @@ -0,0 +1,328 @@ +import * as util from 'util' +import * as pathUtil from 'path' + +import * as append from './append.js' +import { Options as AppendOptions } from './append.js' +import * as dir from './dir.js' +import { IOptions as DirOptions } from './dir.js' +import * as file from './file.js' +import { IOptions as FileOptions } from './file.js' +import * as find from './find.js' +import { IOptions as FindOptions } from './find.js' +import * as inspect from './inspect.js' +import * as inspectTree from './inspect_tree.js' +import { Options as InspectTreeOptions } from './inspect_tree.js' +import * as copy from './copy.js' +import * as exists from './exists.js' +import * as list from './list.js' +import * as move from './move.js' +import * as remove from './remove.js' +import * as rename from './rename.js' +import * as symlink from './symlink.js' +import * as streams from './streams.js' +import { IWriteOptions } from './interfaces.js' +import * as write from './write.js' + +import * as read from './read.js' +import * as stats from './stats.js' + +import { ICopyOptions, INode, IInspectOptions } from './interfaces.js' +import { ReadWriteDataType, TCopyResult, ENodeType, TDeleteResult } from './interfaces.js' + + +const Q = require('q') +export interface IJetpack { + cwd(w?: any): IJetpack | string; + path(): string; + append(path: string, data: string | Buffer | object, options?: AppendOptions): void; + appendAsync(path: string, data: string | Buffer | object, options?: AppendOptions): Promise; + copy(from: string, to: string, options?: ICopyOptions): void; + copyAsync(from: string, to: string, options?: ICopyOptions): Promise; + createWriteStream(path: string, options?: { + flags?: string; + encoding?: string; + fd?: number; + mode?: number; + autoClose?: boolean; + start?: number; + }): any; + createReadStream(path: string, options?: { + flags?: string; + encoding?: string; + fd?: number; + mode?: number; + autoClose?: boolean; + start?: number; + end?: number; + }): any; + dir(path: string, criteria?: DirOptions): IJetpack; + dirAsync(path: string, criteria?: DirOptions): Promise; + exists(path: string): boolean | string; + existsAsync(path: string): Promise; + file(path: string, criteria?: FileOptions): void; + fileAsync(path: string, criteria?: FileOptions): Promise; + find(startPath: string, options: FindOptions): string[]; + findAsync(startPath: string, options: FindOptions): Promise; + inspect(path: string, fieldsToInclude: IInspectOptions): INode; + inspectAsync(path: string, fieldsToInclude: IInspectOptions): Promise; + inspectTree(path: string, options?: InspectTreeOptions): INode; + inspectTreeAsync(path: string, options?: InspectTreeOptions): Promise; + list(path: string): string[]; + listAsync(path: string): Promise; + move(from: string, to: string): void; + moveAsync(from: string, to: string): Promise; + read(path: string, returnAs?: string): ReadWriteDataType; + readAsync(path: string, returnAs?: string): Promise; + remove(path: string): void; + removeAsync(path: string): Promise; + rename(path: string, newName: string): void; + renameAsync(path: string, newName: string): Promise; + symlink(symlinkValue: string, path: string): void; + symlinkAsync(symlinkValue: string, path: string): Promise; + write(path: string, data: string | Buffer | object, options?: IWriteOptions): void; + writeAsync(path: string, data: string | Buffer | object, options?: IWriteOptions): Promise; +} +// The Jetpack Context object. +// It provides the public API, and resolves all paths regarding to +// passed cwdPath, or default process.cwd() if cwdPath was not specified. +export const fs = (cwdPath?: string): IJetpack => { + const getCwdPath = function () { + return cwdPath || process.cwd(); + }; + + const cwd = function (w?: any): IJetpack | string { + let args; + let pathParts; + + // return current CWD if no arguments specified... + if (arguments.length === 0) { + return getCwdPath(); + } + + // ...create new CWD context otherwise + args = Array.prototype.slice.call(arguments); + pathParts = [getCwdPath()].concat(args); + const res = fs(pathUtil.resolve.apply(null, pathParts)); + return res; + }; + + // resolves path to inner CWD path of this jetpack instance + const resolvePath = function (path: string): string { + return pathUtil.resolve(getCwdPath(), path); + }; + + const getPath = function (): string { + // add CWD base path as first element of arguments array + Array.prototype.unshift.call(arguments, getCwdPath()); + return pathUtil.resolve.apply(null, arguments); + }; + + const normalizeOptions = function (options: { cwd?: string }): any { + return options || { cwd: getCwdPath() }; + }; + + // API + const api: IJetpack = { + cwd: cwd, + path: getPath, + append: function (path: string, data: string | Buffer | object, options?: AppendOptions): void { + append.validateInput('append', path, data, options); + append.sync(resolvePath(path), data, options); + }, + appendAsync: function (path: string, data: string | Buffer | object, options?: AppendOptions): Promise { + append.validateInput('appendAsync', path, data, options); + return append.async(resolvePath(path), data, options); + }, + + copy: function (from: string, to: string, options?: ICopyOptions) { + copy.validateInput('copy', from, to, options); + copy.sync(resolvePath(from), resolvePath(to), options); + }, + copyAsync: function (from: string, to: string, options?: ICopyOptions) { + copy.validateInput('copyAsync', from, to, options); + return copy.async(resolvePath(from), resolvePath(to), options); + }, + createWriteStream: function (path: string, options?: { + flags?: string; + encoding?: string; + fd?: number; + mode?: number; + autoClose?: boolean; + start?: number; + }) { + return streams.createWriteStream(resolvePath(path), options as any); + }, + createReadStream: function (path: string, options?: { + flags?: string; + encoding?: string; + fd?: number; + mode?: number; + autoClose?: boolean; + start?: number; + end?: number; + }) { + return streams.createReadStream(resolvePath(path), options as any); + }, + + dir: function (path: string, criteria?: DirOptions): IJetpack { + let normalizedPath; + dir.validateInput('dir', path, criteria); + normalizedPath = resolvePath(path); + dir.sync(normalizedPath, criteria); + return cwd(normalizedPath) as IJetpack; + }, + dirAsync: function (path: string, criteria?: DirOptions): Promise { + const deferred = Q.defer(); + let normalizedPath: string; + dir.validateInput('dirAsync', path, criteria); + normalizedPath = resolvePath(path); + dir.async(normalizedPath, criteria) + .then(function () { + deferred.resolve(cwd(normalizedPath)); + }, deferred.reject); + return deferred.promise; + }, + + exists: function (path: string): boolean | string { + exists.validateInput('exists', path); + return exists.sync(resolvePath(path)); + }, + + existsAsync: function (path: string): Promise { + exists.validateInput('existsAsync', path); + return exists.async(resolvePath(path)); + }, + + file: function (path: string, criteria?: FileOptions) { + file.validateInput('file', path, criteria); + file.sync(resolvePath(path), criteria); + return this; + }, + + fileAsync: function (path: string, criteria?: FileOptions) { + const deferred = Q.defer(); + const that = this; + file.validateInput('fileAsync', path, criteria); + file.async(resolvePath(path), criteria) + .then(function () { + deferred.resolve(that); + }, deferred.reject); + return deferred.promise; + }, + + find: function (startPath: string, options: FindOptions): string[] { + // startPath is optional parameter, if not specified move rest of params + // to proper places and default startPath to CWD. + if (typeof options === 'undefined' && typeof startPath === 'object') { + options = startPath; + startPath = '.'; + } + find.validateInput('find', startPath, options); + return find.sync(resolvePath(startPath), normalizeOptions(options)); + }, + findAsync: function (startPath: string, options: FindOptions): Promise { + // startPath is optional parameter, if not specified move rest of params + // to proper places and default startPath to CWD. + if (typeof options === 'undefined' && typeof startPath === 'object') { + options = startPath; + startPath = '.'; + } + find.validateInput('findAsync', startPath, options); + return find.async(resolvePath(startPath), normalizeOptions(options)); + }, + + inspect: function (path: string, fieldsToInclude: IInspectOptions) { + inspect.validateInput('inspect', path, fieldsToInclude); + return inspect.sync(resolvePath(path), fieldsToInclude); + }, + inspectAsync: function (path: string, fieldsToInclude: IInspectOptions) { + inspect.validateInput('inspectAsync', path, fieldsToInclude); + return inspect.async(resolvePath(path), fieldsToInclude); + }, + inspectTree: function (path: string, options?: InspectTreeOptions) { + inspectTree.validateInput('inspectTree', path, options); + return inspectTree.sync(resolvePath(path), options); + }, + inspectTreeAsync: function (path: string, options?: InspectTreeOptions) { + inspectTree.validateInput('inspectTreeAsync', path, options); + return inspectTree.async(resolvePath(path), options); + }, + + list: function (path: string): string[] { + list.validateInput('list', path); + return list.sync(resolvePath(path || '.')); + }, + listAsync: function (path: string): Promise { + list.validateInput('listAsync', path); + return list.async(resolvePath(path || '.')); + }, + + move: function (from: string, to: string): void { + move.validateInput('move', from, to); + move.sync(resolvePath(from), resolvePath(to)); + }, + moveAsync: function (from: string, to: string): Promise { + move.validateInput('moveAsync', from, to); + return move.async(resolvePath(from), resolvePath(to)); + }, + + read: function (path: string, returnAs?: string) { + read.validateInput('read', path, returnAs); + return read.sync(resolvePath(path), returnAs); + }, + readAsync: function (path: string, returnAs?: string): Promise { + read.validateInput('readAsync', path, returnAs); + return read.async(resolvePath(path), returnAs); + }, + + remove: function (path: string): void { + remove.validateInput('remove', path); + // If path not specified defaults to CWD + remove.sync(resolvePath(path || '.')); + }, + removeAsync: function (path: string): Promise { + remove.validateInput('removeAsync', path); + // If path not specified defaults to CWD + return remove.async(resolvePath(path || '.')); + }, + + rename: function (path: string, newName: string): void { + rename.validateInput('rename', path, newName); + rename.sync(resolvePath(path), newName); + }, + renameAsync: function (path: string, newName: string): Promise { + rename.validateInput('renameAsync', path, newName); + return rename.async(resolvePath(path), newName); + }, + + symlink: function (symlinkValue: string, path: string): void { + symlink.validateInput('symlink', symlinkValue, path); + symlink.sync(symlinkValue, resolvePath(path)); + }, + symlinkAsync: function (symlinkValue: string, path: string): Promise { + symlink.validateInput('symlinkAsync', symlinkValue, path); + return symlink.async(symlinkValue, resolvePath(path)); + }, + + write: function (path: string, data: string | Buffer | object, options?: IWriteOptions): void { + write.validateInput('write', path, data, options); + write.sync(resolvePath(path), data, options); + }, + writeAsync: function (path: string, data: string | Buffer | object, options?: IWriteOptions) { + write.validateInput('writeAsync', path, data, options); + return write.async(resolvePath(path), data, options); + } + }; + if ((util.inspect as any)['custom'] !== undefined) { + // Without this console.log(jetpack) throws obscure error. Details: + // https://github.com/szwacz/fs-jetpack/issues/29 + // https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects + (api as any)[(util.inspect as any)['custom']] = function () { + return getCwdPath(); + }; + } + + return api; +}; + +export default fs; diff --git a/packages/fs/src/inspect.ts b/packages/fs/src/inspect.ts new file mode 100644 index 00000000..9004bb2d --- /dev/null +++ b/packages/fs/src/inspect.ts @@ -0,0 +1,137 @@ +import * as fs from 'node:fs' +import { Stats, readlinkSync, statSync, lstatSync, readFileSync } from 'node:fs' +import { getType } from 'mime' +import * as pathUtil from 'node:path' +import { createHash } from 'node:crypto' +import { validateArgument, validateOptions } from './utils/validate.js' +import { ENodeType, INode, IInspectOptions } from './interfaces.js' + +export const supportedChecksumAlgorithms: string[] = ['md5', 'sha1', 'sha256', 'sha512'] + +export function DefaultInspectOptions(): IInspectOptions { + return { + times: true, + mode: true + }; +} +export function validateInput(methodName: string, path: string, options?: IInspectOptions): void { + const methodSignature: string = methodName + '(path, [options])'; + validateArgument(methodSignature, 'path', path, ['string']); + validateOptions(methodSignature, 'options', options, { + checksum: ['string'], + mode: ['boolean'], + times: ['boolean'], + absolutePath: ['boolean'], + symlinks: ['boolean'], + size: 'number', + mime: 'string' + }); + + if (options && options.checksum !== undefined + && !supportedChecksumAlgorithms.includes(options.checksum)) { + throw new Error('Argument "options.checksum" passed to ' + methodSignature + + ' must have one of values: ' + supportedChecksumAlgorithms.join(', ')); + } +} + +const createInspectObj = (path: string, options: IInspectOptions, stat: fs.Stats): INode => { + const obj: INode = {} as INode + obj.name = pathUtil.basename(path) + if (stat.isFile()) { + obj.type = ENodeType.FILE; + obj.size = stat.size; + } else if (stat.isDirectory()) { + obj.type = ENodeType.DIR; + } else if (stat.isSymbolicLink()) { + obj.type = ENodeType.SYMLINK; + } else { + obj.type = ENodeType.OTHER; + } + if (options.mode) { + obj.mode = stat.mode; + } + if (options.mime) { + if (stat.isDirectory()) { + obj.mime = 'inode/directory'; + } else if (stat.isBlockDevice()) { + obj.mime = 'inode/blockdevice'; + } else if (stat.isCharacterDevice()) { + obj.mime = 'inode/chardevice'; + } else if (stat.isSymbolicLink()) { + obj.mime = 'inode/symlink'; + } else if (stat.isFIFO()) { + obj.mime = 'inode/fifo'; + } else if (stat.isSocket()) { + obj.mime = 'inode/socket'; + } else { + obj.mime = getType(path); + } + } + + if (options.times) { + obj.accessTime = stat.atime + obj.modifyTime = stat.mtime + obj.changeTime = stat.ctime + obj.birthTime = stat.birthtime + } + + if (options.absolutePath) { + obj.absolutePath = path; + } + return obj; +}; +export function createItem(path: string, options?: IInspectOptions): INode { + options = options || DefaultInspectOptions(); + const stat = (options.symlinks ? lstatSync : statSync)(path); + return createInspectObj(path, options, stat); +} +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- +const fileChecksum = (path: string, algo: string): string => { + const hash = createHash(algo) + const data = readFileSync(path) + hash.update(data) + return hash.digest('hex') +}; + +const addExtraFieldsSync = (path: string, inspectObj: any, options: IInspectOptions): INode => { + if (inspectObj.type === ENodeType.FILE && options.checksum) { + inspectObj[options.checksum] = fileChecksum(path, options.checksum); + } else if (inspectObj.type === ENodeType.SYMLINK) { + inspectObj.pointsAt = readlinkSync(path); + } + return inspectObj; +}; + +export function sync(path: string, options?: IInspectOptions): INode { + let statOperation = fs.lstatSync + let stat + const opts = options || {} + + if (opts.symlinks === "follow") { + statOperation = fs.statSync + } + + try { + stat = statOperation(path) + } catch (err) { + // Detection if path exists + if (err.code === "ENOENT") { + // Doesn't exist. Return undefined instead of throwing. + return undefined; + } + throw err; + } + + const inspectObj = createInspectObj(path, opts, stat) + addExtraFieldsSync(path, inspectObj, opts) + + return inspectObj +} + +export const async = async (path: string, options?: IInspectOptions): Promise => { + options = options || {} as IInspectOptions; + const stat = await (options.symlinks ? fs.promises.lstat : fs.promises.statfs)(path) + return addExtraFieldsSync(path, createInspectObj(path, options, stat as Stats), options) +} diff --git a/packages/fs/src/inspect_tree.ts b/packages/fs/src/inspect_tree.ts new file mode 100644 index 00000000..b3e06681 --- /dev/null +++ b/packages/fs/src/inspect_tree.ts @@ -0,0 +1,143 @@ +import { createHash } from 'node:crypto' +import * as pathUtil from 'node:path' +import { sync as inspectSync, async as inspectASync, supportedChecksumAlgorithms } from './inspect.js' + +import { ENodeType } from './interfaces.js' +import type { INode } from './interfaces.js' + +import { sync as listSync, async as listASync } from './list.js' +import { validateArgument, validateOptions } from './utils/validate.js' + +export interface Options { + checksum: string; + relativePath: boolean; + symlinks: boolean; +} + +export function validateInput(methodName: string, path: string, options: Options): void { + const methodSignature = methodName + '(path, options)'; + validateArgument(methodSignature, 'path', path, ['string']); + validateOptions(methodSignature, 'options', options, { + checksum: ['string'], + relativePath: ['boolean'] + }); + + if (options && options.checksum !== undefined + && supportedChecksumAlgorithms.indexOf(options.checksum) === -1) { + throw new Error('Argument "options.checksum" passed to ' + methodSignature + + ' must have one of values: ' + supportedChecksumAlgorithms.join(', ')); + } +} + +function generateTreeNodeRelativePath(parent: any, path: string): string { + if (!parent) { + return '.'; + } + return parent.relativePath + '/' + pathUtil.basename(path); +} + +// Creates checksum of a directory by using +// checksums and names of all its children inside. +const checksumOfDir = (inspectList: any[], algo: string): string => { + const hash = createHash(algo); + inspectList.forEach(function (inspectObj) { + hash.update(inspectObj.name + inspectObj[algo]); + }); + return hash.digest('hex'); +}; + +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- +function inspectTreeNodeSync(path: string, options: Options, parent: any): INode { + const treeBranch = inspectSync(path, { checksum: options.checksum, symlinks: options.symlinks }); + if (treeBranch) { + if (options.relativePath) { + treeBranch.relativePath = generateTreeNodeRelativePath(parent, path); + } + if (treeBranch.type === ENodeType.DIR /*|| (options.symlinks && treeBranch.type === 'symlink')*/) { + treeBranch.size = 0; + treeBranch.children = (listSync(path) || []).map(function (filename) { + const subBranchPath = pathUtil.join(path, filename); + const treeSubBranch = inspectTreeNodeSync(subBranchPath, options, treeBranch); + // Add together all childrens' size to get directory combined size. + treeBranch.size += treeSubBranch.size || 0; + // treeBranch.total += treeSubBranch.total; + return treeSubBranch; + }); + if (options.checksum) { + (treeBranch as any)[options.checksum] = checksumOfDir(treeBranch.children, options.checksum); + } + } + } + return treeBranch; +} + +export function sync(path: string, options?: any): any | undefined { + options = options || { + }; + options.symlinks = true; + return inspectTreeNodeSync(path, options, undefined); +} + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- +function inspectTreeNodeAsync(path: string, options: Options, parent?: any): Promise { + return new Promise((resolve, reject) => { + const inspectAllChildren = (treeBranch: INode) => { + return new Promise((resolve, reject) => { + listASync(path).then((children: any) => { + const doNext = (index: number) => { + let subPath: string; + if (index === children.length) { + if (options.checksum) { + // We are done, but still have to calculate checksum of whole directory. + [options.checksum] = checksumOfDir(treeBranch.children, options.checksum); + } + resolve(1); + } else { + subPath = pathUtil.join(path, children[index]); + inspectTreeNodeAsync(subPath, options, treeBranch) + .then((treeSubBranch: INode) => { + children[index] = treeSubBranch; + treeBranch.size += treeSubBranch.size || 0; + doNext(index + 1); + }) + .catch(reject); + } + }; + treeBranch.children = children; + treeBranch.size = 0; + doNext(0); + }); + }); + }; + + inspectASync(path, options) + .then((treeBranch: INode) => { + if (!treeBranch) { + // Given path doesn't exist. We are done. + resolve(treeBranch); + } else { + if (options.relativePath) { + treeBranch.relativePath = generateTreeNodeRelativePath(parent, path); + } + if (treeBranch.type !== ENodeType.DIR) { + resolve(treeBranch); + } else { + inspectAllChildren(treeBranch) + .then(() => resolve(treeBranch)) + .catch(reject); + } + } + }) + .catch(reject); + }); +} + +export function async(path: string, options?: Options): Promise { + options = options || {} as Options; + options.symlinks = true; + return inspectTreeNodeAsync(path, options); +} diff --git a/packages/fs/src/interfaces.ts b/packages/fs/src/interfaces.ts new file mode 100644 index 00000000..8898eac9 --- /dev/null +++ b/packages/fs/src/interfaces.ts @@ -0,0 +1,527 @@ +///////////////////////////////////////////////////////// +// +// Enums +// +export enum ENodeType { + FILE = 'file', + DIR = 'dir', + SYMLINK = 'symlink', + OTHER = 'other', + BLOCK = 'block' +} + +/** + * Native errors. + * @todo : replace with errno. + */ +export let EError: any = { + NONE: 'None', + EXISTS: 'EEXIST', + PERMISSION: 'EACCES', + NOEXISTS: 'ENOENT', + CROSS_DEVICE: 'EXDEV' +}; + +///////////////////////////////////////////////////////// +// +// Data structures +// +export interface INode { + name: string + type: ENodeType | string + size: number + accessTime?: Date + modifyTime?: Date + changeTime?: Date + birthTime?: Date + absolutePath?: string + mode?: number + pointsAt?: string + relativePath?: string + children: INode[] + total?: number + checksum?: string + mime?: string +} +/** + * The options for "inspect". + * + * @export + * @interface IInspectOptions + */ +export interface IInspectOptions { + checksum?: string + mode?: boolean + times?: boolean + absolutePath?: boolean + symlinks?: boolean | string + size?: boolean + mime?: boolean +} + +export interface INodeReport { + node: IProcessingNode + error: string + resolved: IConflictSettings +} + +/** + * The accepted types for write and read as union. + */ +export type ReadWriteDataType = string | Buffer | object; + +/** + * An extended version of Error to make typescript happy. This has been copied from + * the official Node typings. + * + * @export + * @class ErrnoException + * @extends {Error} + */ +export class ErrnoException extends Error { + public errno?: number + public code?: string + public path?: string + public syscall?: string + public stack?: string +} + +/** + * Structure for file operations. + */ +export interface IProcessingNode { + path: string + item: INode + status?: ENodeOperationStatus + dst?: string +} + +/** + * Basic flags during a file operation. + * + * @export + * @enum {number} + */ +export enum EBaseFlags { + /** + * When copying, don't copy symlinks but resolve them instead. + */ + FOLLOW_SYMLINKS = 8 +} + +/** + * Flags to determine certain properties during inspection. + * + * @export + * @enum {number} + */ +export enum EInspectFlags { + MODE = 2, + TIMES = 4, + SYMLINKS = 8, + FILE_SIZE = 16, + DIRECTORY_SIZE = 32, + CHECKSUM = 64, + MIME = 128 +} +/** + * Basic options for file operations: used by cp, mv, rename and rm. + * + * @export + * @interface IBaseOptions + */ +export interface IBaseOptions { + /** + * Array of glob minimatch patterns + * + * @type {string[]} + * @memberOf IBaseOptions + */ + matching?: string[] + /** + * A function called to reject or accept nodes. This is used only when matching + * has been left empty. + * @memberOf IBaseOptions + */ + filter?: (from: string) => boolean + /** + * Flags to determine properties per node + * + * @type {EInspectFlags} + * @memberOf IBaseOptions + */ + flags?: EInspectFlags +} +///////////////////////////////////////////////////////// +// +// File operations : copy +// +/** + * Callback prototype signature when an item has been copied. + * This is used to abort the copy process when returning false. + * + * @param {string} path The path of the item + * @param {number} current The current index of the item + * @param {number} total The total of all items + * @param {INode} [item] The node data for the item + * @param {string} [item] The destination path + * @returns {boolean} + */ +export type ItemProgressCallback = (path: string, current: number, total: number, item?: INode, dst?:string) => boolean + +/** + * Callback prototype signature when an item conflict occurs. + * It's async since the conflict might be resolved in an client application and hence + * we have to wait til the user decided. + * + * This is not being called if: + * - a previous callback returned with IConflictSettings#mode == ALWAYS + * - the options object already contains pre-defined conflict settings. + * + * @param {string} path The path of the item. + * @param {INode} item The node data. + * @param {string} err The native error code of the conflict (EEXIST,...) + * @returns {Promise} + */ +export type ResolveConflictCallback = (path: string, item: INode, err: string) => Promise + +/** + * Callback prototype signature when an item is written during a copy. +**/ +export type ContentCallback = (path: string, data: Buffer, item: INode) => Buffer + + +/** + * Callback prototype signature to rename an item when copied + * + * @param {string} from The source path. + * @param {string} from The target path. + * @returns {string|null} + */ +export type RenameCallback = (from: string, to: string) => string | null + +/** + * Callback prototype signature when a file with at least 5MB size is being copied. + * + * @param {string} path The path of the item. + * @param {number} current The current copied bytes. + * @param {number} total The total size in bytes. + * @returns {Promise} + */ +export type WriteProgressCallback = (path: string, current: number, total: number) => void + +/** + * Status of a node operation. + * + * @export + * @enum {number} + */ +export enum ENodeOperationStatus { + // Node has been collected + COLLECTED, + // Node has been checked for existence + CHECKED, + // Node is in progress, before copy + PROCESSING, + // Node is in process + PROCESS, + // Node is in conflict, and user is being asked what to do + ASKING, + // Node conflict has been resolved by user + ANSWERED, + // Node has been copied + DONE +} + +/** + * The possible modes to resolve a conflict during copy and move. + * + * @export + * @enum {number} + */ +export enum EResolveMode { + SKIP = 0, + OVERWRITE, + IF_NEWER, + IF_SIZE_DIFFERS, + APPEND, + THROW, + RETRY, + ABORT +} + +/** + * Additional flags for copy + * + * @export + * @enum {number} + */ +export enum ECopyFlags { + /** + * Transfer atime and mtime of source to target + */ + NONE = 0, + /** + * Transfer atime and mtime of source to target + */ + PRESERVE_TIMES = 2, + /** + * Empty the target folder + */ + EMPTY = 4, + /** + * When copying, don't copy symlinks but resolve them instead. + */ + FOLLOW_SYMLINKS = 8, + /** + * Collect errors & success + */ + REPORT = 16 +} + +export interface IStatOptions { + /** + * Array of glob minimatch patterns + * + * @type {string[]} + * @memberOf ICopyOptions + */ + matching?: string[] + /** + * A function called to reject or accept nodes to be copied. This is used only when matching + * has been left empty. + * @memberOf ICopyOptions + */ + filter?: (from: string) => boolean + /** + * A progress callback for any copied item. Only executed in async. + */ + progress?: ItemProgressCallback +} + +/** + * Copy options + * + * @export + * @interface ICopyOptions + */ +export interface ICopyOptions { + /** + * @type {boolean} + * @memberOf ICopyOptions + */ + overwrite?: boolean + + /** + * Array of glob minimatch patterns + * + * @type {string[]} + * @memberOf ICopyOptions + */ + matching?: string[] + + /** + * A function called to reject or accept nodes to be copied. This is used only when matching + * has been left empty. + * @memberOf ICopyOptions + */ + filter?: (from: string) => boolean + + /** + * A progress callback for any copied item. Only executed in async. + */ + progress?: ItemProgressCallback + + /** + * A progress function called for async and larger files only. + * + * @type {WriteProgressCallback} + * @memberOf ICopyOptions + */ + writeProgress?: WriteProgressCallback + + /** + * Callback function when writing source content + */ + content?: ContentCallback + + /** + * A callback when a conflict or error occurs. This is being called only if the user + * didn't provide conflictSettings. + * + * @type {ResolveConflictCallback} + * @memberOf ICopyOptions + */ + conflictCallback?: ResolveConflictCallback + + /** + * A callback to rename nodes during copy + * @type {ResolveConflictCallback} + * @memberOf ICopyOptions + */ + renameCallback?: RenameCallback + + /** + * Ability to set conflict resolver settings in advance, so that no callback will be called. + * + * @type {IConflictSettings} + * @memberOf ICopyOptions + */ + conflictSettings?: IConflictSettings + + /** + * Throttel copy for larger files. This will be only used when writeProgress is set and the file is at least 5MB. + * + * @type {number} + * @memberOf ICopyOptions + */ + throttel?: number + + /** + * Print some debug messages. + * + * @type {boolean} + * @memberOf ICopyOptions + */ + debug?: boolean + + /** + * The copy flags. + * + * @type {ECopyFlags} + * @memberOf ICopyOptions + */ + flags?: ECopyFlags +} + +/** + * An enumeration to narrow a conflict resolve to a single item or for all following conflicts. + * + * @export + * @enum {number} + */ +export enum EResolve { + /** + * Always will use the chose conflict settings for all following conflicts. + */ + ALWAYS, + /** + * 'This' will use the conflict settings for a single conflict so the conflict callback will be triggered again for the next conflict. + */ + THIS +} + +/** + * A composite conflict settings and it's scope. This is the result type + * for the conflict callback. + * + * @export + * @interface IConflictSettings + */ +export interface IConflictSettings { + /** + * How to resolve this conflict/error. + * + * @type {EResolveMode} + * @memberOf IConflictSettings + */ + overwrite: EResolveMode; + /** + * The scope of this conflict resolver: always or this. + * + * @type {EResolve} + * @memberOf IConflictSettings + */ + mode: EResolve; + /** + * Track the origin error type for this settings. + * + * @type {string} + * @memberOf IConflictSettings + */ + error?: string; +} + +export type TCopyResult = void | INodeReport[]; + +///////////////////////////////////////////////////////// +// +// File operations : write +// + +/** + * fs/write options. + * + * @export + * @interface IWriteOptions + */ +export interface IWriteOptions { + atomic?: boolean; + jsonIndent?: number; + mode?: string; +} +///////////////////////////////////////////////////////// +// +// File operations : remove +// +export type TDeleteResult = void | INodeReport[]; +/** + * Additional flags for delete + * + * @export + * @enum {number} + */ +export enum EDeleteFlags { + REPORT = 16 +} +/** + * Delete options + * + * @export + * @interface IDeleteOptions + */ +export interface IDeleteOptions { + /** + * Array of glob minimatch patterns + * + * @type {string[]} + * @memberOf IDeleteOptions + */ + matching?: string[]; + /** + * A callback when a conflict or error occurs. This is being called only if the user + * didn't provide conflictSettings. + * + * @type {ResolveConflictCallback} + * @memberOf IDeleteOptions + */ + conflictCallback?: ResolveConflictCallback; + /** + * Ability to set conflict resolver settings in advance, so that no callback will be called. + * + * @type {IConflictSettings} + * @memberOf IDeleteOptions + */ + conflictSettings?: IConflictSettings; + /** + * + * A progress callback for any deleted item. Only excecuted in async. + * @type {ItemProgressCallback} + * @memberOf IDeleteOptions + */ + progress?: ItemProgressCallback; + /** + * Print some messages. + * + * @type {boolean} + * @memberOf IDeleteOptions + */ + debug?: boolean; + /** + * A function called to reject or accept nodes to be copied. This is used only when matching + * has been left empty. + * @memberOf IDeleteOptions + */ + filter?: (from: string) => boolean; + flags?: EDeleteFlags; +} diff --git a/packages/fs/src/iterator.ts b/packages/fs/src/iterator.ts new file mode 100644 index 00000000..a12a6bd3 --- /dev/null +++ b/packages/fs/src/iterator.ts @@ -0,0 +1,77 @@ +import { sync as treeWalkerSync } from './utils/tree_walker.js'; +import { INode, ENodeOperationStatus, IProcessingNode, IBaseOptions, EInspectFlags } from './interfaces.js'; +import { create as matcher } from './utils/matcher.js'; + +import { ArrayIterator } from '@polymech/core/iterator'; + +export async function async(from: string, options: IBaseOptions): Promise> { + if (options && !options.filter) { + if (options.matching) { + options.filter = matcher(from, options.matching); + } else { + options.filter = () => true; + } + } + const collectorSync = function (path: string, item: INode) { + if (!item) { + return; + } + if (options.filter(path)) { + nodes.push({ + path: path, + item: item, + status: ENodeOperationStatus.COLLECTED + }); + } + }; + const nodes: IProcessingNode[] = []; + return new Promise>((resolve, reject) => { + treeWalkerSync(from, { + inspectOptions: { + mode: options ? options.flags & EInspectFlags.MODE ? true : false : false, + times: options ? options.flags & EInspectFlags.TIMES ? true : false : false, + checksum: options ? options.flags & EInspectFlags.CHECKSUM ? 'md5' : null : null, + symlinks: options ? options.flags & EInspectFlags.SYMLINKS ? false : true : true, + mime: options ? options.flags & EInspectFlags.MIME ? true : false : false + } + }, collectorSync); + resolve(new ArrayIterator(nodes)); + }); +} + + +export function sync(from: string, options: IBaseOptions): ArrayIterator { + if (options && !options.filter) { + if (options.matching) { + options.filter = matcher(from, options.matching); + } else { + options.filter = () => true; + } + } + const nodes: IProcessingNode[] = []; + + const collectorSync = function (path: string, item: INode) { + if (!item) { + return; + } + if (options.filter(path)) { + nodes.push({ + path: path, + item: item, + status: ENodeOperationStatus.COLLECTED + }); + } + }; + + treeWalkerSync(from, { + inspectOptions: { + mode: options ? options.flags & EInspectFlags.MODE ? true : false : false, + times: options ? options.flags & EInspectFlags.TIMES ? true : false : false, + checksum: options ? options.flags & EInspectFlags.CHECKSUM ? 'md5' : null : null, + symlinks: options ? options.flags & EInspectFlags.SYMLINKS ? false : true : true, + mime: options ? options.flags & EInspectFlags.MIME ? true : false : false + } + }, collectorSync); + + return new ArrayIterator(nodes); +} diff --git a/packages/fs/src/list.ts b/packages/fs/src/list.ts new file mode 100644 index 00000000..dba1631b --- /dev/null +++ b/packages/fs/src/list.ts @@ -0,0 +1,66 @@ +import { readdirSync, readdir } from 'fs'; +import { validateArgument } from './utils/validate.js'; +import { isMacintosh } from './utils/platform.js'; +import { normalizeNFC } from './utils/strings.js'; + +export function validateInput(methodName: string, path: string) { + const methodSignature = methodName + '(path)'; + validateArgument(methodSignature, 'path', path, ['string', 'undefined']); +} + +export function _readdirSync(path: string): string[] { + // Mac: uses NFD unicode form on disk, but we want NFC + // See also https://github.com/nodejs/node/issues/2165 + if (isMacintosh) { + return readdirSync(path).map(c => normalizeNFC(c)); + } + + return readdirSync(path); +} +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- +export function sync(path: string): string[] { + try { + return _readdirSync(path); + } catch (err) { + if (err.code === 'ENOENT') { + // Doesn't exist. Return undefined instead of throwing. + return undefined; + } + throw err; + } +} + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- +function readdirASync(path: string): Promise { + // export function readdir(path: string | Buffer, callback?: (err: NodeJS.ErrnoException, files: string[]) => void): void; + // Mac: uses NFD unicode form on disk, but we want NFC + // See also https://github.com/nodejs/node/issues/2165 + + return new Promise((resolve, reject) => { + if (isMacintosh) { + readdir(path, (err: NodeJS.ErrnoException, files: string[]) => { + if (err) { + reject(err); + } + resolve(files); + }); + } + readdir(path, (err: NodeJS.ErrnoException, files: string[]) => { + if (err) { + reject(err); + } + resolve(files); + }); + }); +} +export function async(path: string): Promise { + return new Promise((resolve, reject) => { + readdirASync(path) + .then((list) => resolve(list)) + .catch(err => (err.code === 'ENOENT' ? resolve(undefined) : reject(err))); + }); +} diff --git a/packages/fs/src/main.ts b/packages/fs/src/main.ts new file mode 100644 index 00000000..9c1d75d4 --- /dev/null +++ b/packages/fs/src/main.ts @@ -0,0 +1,11 @@ +#!/usr/bin/env node +import * as cli from 'yargs' + +const argv: any = cli.argv + +if (argv.h || argv.help) { + cli.showHelp(); + process.exit(); +} else if (argv.v || argv.version) { + process.exit(); +} diff --git a/packages/fs/src/move.ts b/packages/fs/src/move.ts new file mode 100644 index 00000000..75534f49 --- /dev/null +++ b/packages/fs/src/move.ts @@ -0,0 +1,104 @@ +import * as pathUtil from 'path'; +import { rename, renameSync } from 'fs'; +import { async as existsAsync, sync as existsSync } from './exists.js'; +import { validateArgument } from './utils/validate.js'; +import { ErrDoesntExists } from './errors.js'; +import { EError } from './interfaces.js'; +import { sync as copySync } from './copy.js'; +import { sync as removeSync } from './remove.js'; +import { promisify } from 'util'; +import { async as mkdirAsync, sync as dirSync } from './dir.js'; + +export const validateInput = (methodName: string, from: string, to: string) => { + const methodSignature: string = methodName + '(from, to)'; + validateArgument(methodSignature, 'from', from, ['string']); + validateArgument(methodSignature, 'to', to, ['string']); +}; +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- + +export const sync = (from: string, to: string): void => { + try { + renameSync(from, to); + } catch (err) { + // not the same device, rename doesnt work here + if (err.code === EError.CROSS_DEVICE) { + try { + copySync(from, to); + } catch (e) { + throw e; + } + try { + removeSync(from); + } catch (e) { + throw e; + } + return; + } + if (err.code !== EError.NOEXISTS) { + // We can't make sense of this error. Rethrow it. + throw err; + } else { + // Ok, source or destination path doesn't exist. + // Must do more investigation. + if (!existsSync(from)) { + throw ErrDoesntExists(from); + } + if (!existsSync(to)) { + // Some parent directory doesn't exist. Create it. + dirSync(pathUtil.dirname(to)); + // Retry the attempt + renameSync(from, to); + } + } + } +}; + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- + +const ensureDestinationPathExistsAsync = (to: string): Promise => { + return new Promise((resolve, reject) => { + const destDir: string = pathUtil.dirname(to); + existsAsync(destDir) + .then(dstExists => { + if (!dstExists) { + mkdirAsync(destDir).then(resolve, reject); + } else { + // Hah, no idea. + reject(); + } + }) + .catch(reject); + }); +}; + +export const async = (from: string, to: string): Promise => { + return new Promise((resolve, reject) => { + promisify(rename)(from, to) + .then(resolve) + .catch(err => { + if (err.code !== EError.NOEXISTS) { + // Something unknown. Rethrow original error. + reject(err); + } else { + // Ok, source or destination path doesn't exist. + // Must do more investigation. + existsAsync(from) + .then(srcExists => { + if (!srcExists) { + reject(ErrDoesntExists(from)); + } else { + ensureDestinationPathExistsAsync(to) + // Retry the attempt + .then(() => promisify(rename)(from, to)) + .then(resolve, reject); + } + }) + .catch(reject); + } + }); + }); +}; diff --git a/packages/fs/src/promisify.ts b/packages/fs/src/promisify.ts new file mode 100644 index 00000000..81970f3a --- /dev/null +++ b/packages/fs/src/promisify.ts @@ -0,0 +1,44 @@ +export function promisify(f: (cb: (err: any, res: T) => void) => void, thisContext?: any): () => Promise; +export function promisify(f: (arg: A, cb: (err: any, res: T) => void) => void, thisContext?: any): (arg: A) => Promise; +export function promisify(f: (arg: A, arg2: A2, cb: (err: any, res: T) => void) => void, thisContext?: any): (arg: A, arg2: A2) => Promise; +export function promisify(f: (arg: A, arg2: A2, arg3: A3, cb: (err: any, res: T) => void) => void, thisContext?: any): (arg: A, arg2: A2, arg3: A3) => Promise; +export function promisify(f: (arg: A, arg2: A2, arg3: A3, arg4: A4, cb: (err: any, res: T) => void) => void, thisContext?: any): (arg: A, arg2: A2, arg3: A3, arg4: A4) => Promise; +export function promisify(f: (arg: A, arg2: A2, arg3: A3, arg4: A4, arg5: A5, cb: (err: any, res: T) => void) => void, thisContext?: any): (arg: A, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Promise; + +export function promisify(f: any, thisContext?: any) { + return function () { + const args = Array.prototype.slice.call(arguments); + return new Promise((resolve, reject) => { + args.push((err: any, result: any) => err !== null ? reject(err) : resolve(result)); + f.apply(thisContext, args); + }); + }; +} + +export function map(elts: PromiseLike[]>, f: (t: T) => U | PromiseLike): Promise; +export function map(elts: PromiseLike, f: (t: T) => U | PromiseLike): Promise; +export function map(elts: PromiseLike[], f: (t: T) => U | PromiseLike): Promise; +export function map(elts: T[], f: (t: T) => U | PromiseLike): Promise; + +export function map(elts: any, f: any) { + const apply = (appElts: any) => Promise.all(appElts.map((elt: any) => typeof elt.then === 'function' ? elt.then(f) : f(elt))); + return typeof elts.then === 'function' ? elts.then(apply) : apply(elts); +} + +export function _try(f: () => T): Promise; +export function _try(f: (arg: any) => T, arg: any): Promise; +export function _try(f: (arg: any, arg2: any) => T, arg: any, arg2: any): Promise; +export function _try(f: (arg: any, arg2: any, arg3: any) => T, arg: any, arg2: any, arg3: any): Promise; +export function _try(f: (arg: any, arg2: any, arg3: any, arg4: any) => T, arg: any, arg2: any, arg3: any, arg4: any): Promise; + +export function _try(f: any, thisContext?: any) { + const args = Array.prototype.slice.call(arguments); + return new Promise((res, rej) => { + try { + args.shift(); + res(f.apply(thisContext, args)); + } catch (err) { + rej(err); + } + }); +} diff --git a/packages/fs/src/read.ts b/packages/fs/src/read.ts new file mode 100644 index 00000000..c7fd03c8 --- /dev/null +++ b/packages/fs/src/read.ts @@ -0,0 +1,91 @@ +import { readFile, readFileSync } from 'fs'; +import { json } from './imports.js'; +import { validateArgument } from './utils/validate.js'; +import type { ReadWriteDataType } from './interfaces.js'; +const Q = require('q'); +const supportedReturnAs = ['utf8', 'buffer', 'json', 'jsonWithDates']; +const promisedReadFile = Q.denodeify(readFile); + +export function validateInput(methodName: string, path: string, returnAs: string) { + const methodSignature = methodName + '(path, returnAs)'; + validateArgument(methodSignature, 'path', path, ['string']); + validateArgument(methodSignature, 'returnAs', returnAs, ['string', 'undefined']); + if (returnAs && !supportedReturnAs.includes(returnAs)) { + throw new Error('Argument "returnAs" passed to ' + methodSignature + + ' must have one of values: ' + supportedReturnAs.join(', ')); + } +} + +// Matches strings generated by Date.toJSON() +// which is called to serialize date to JSON. +const jsonDateParser = (key: string, value: string | Date): Date => { + const reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}\.\d*)(?:Z|(\+|-)([\d|:]*))?$/; + if (typeof value === 'string') { + if (reISO.test(value)) { + return new Date(value); + } + } + return value as Date; +}; + +const ErrJson = (path: string, err: Error): Error => { + const nicerError: any = new Error('JSON parsing failed while reading ' + + path + ' [' + err + ']'); + nicerError.originalError = err; + return nicerError; +}; + +// --------------------------------------------------------- +// SYNC +// --------------------------------------------------------- +export function sync(path: string, returnAs?: string): ReadWriteDataType | undefined { + const retAs = returnAs || 'utf8'; + let data; + try { + data = readFileSync(path, { encoding: retAs === 'buffer' ? null : 'utf8' }); + } catch (err) { + if (err.code === 'ENOENT') { + // If file doesn't exist return undefined instead of throwing. + return undefined; + } + // Otherwise rethrow the error + throw err; + } + + try { + if (retAs === 'json') { + data = json.parse(data); + } else if (retAs === 'jsonWithDates') { + data = json.parse(data, jsonDateParser); + } + } catch (err) { + throw ErrJson(path, err); + } + + return data; +} + +// --------------------------------------------------------- +// ASYNC +// --------------------------------------------------------- +export function async(path: string, returnAs?: string): Promise { + return new Promise((resolve, reject) => { + const retAs = returnAs || 'utf8'; + promisedReadFile(path, { encoding: retAs === 'buffer' ? null : 'utf8' }) + .then((data: ReadWriteDataType) => { + // Make final parsing of the data before returning. + try { + if (retAs === 'json') { + resolve(json.parse(data as any)); + } else if (retAs === 'jsonWithDates') { + resolve(json.parse(data as any, jsonDateParser)); + } else { + resolve(data); + } + } catch (err) { + reject(ErrJson(path, err)); + } + }) + .catch((err: any) => (err.code === 'ENOENT' ? resolve(null) : reject(err))); + }); +} diff --git a/packages/fs/src/remove.ts b/packages/fs/src/remove.ts new file mode 100644 index 00000000..7a64037b --- /dev/null +++ b/packages/fs/src/remove.ts @@ -0,0 +1,339 @@ + +import { validateArgument } from './utils/validate.js' +import { sync as inspectSync } from './inspect.js' +import { async as listAsync, sync as listSync } from './list.js' +import * as pathUtil from 'path' +import { rmdir, unlink, rmdirSync, unlinkSync } from 'fs' +import { ErrNoFileOrDir } from './errors.js' +import { IDeleteOptions, IProcessingNode, ErrnoException } from './interfaces.js' +import { create as matcher } from './utils/matcher.js' +import { IConflictSettings, ENodeOperationStatus, TDeleteResult, INodeReport, EDeleteFlags, EResolve, EResolveMode } from './interfaces.js' +import { createItem } from './inspect.js' +import { async as iteratorAsync } from './iterator.js' +import { ErrCantDelete } from './errors.js' + +export function validateInput(methodName: string, path: string) { + const methodSignature = methodName + '([path])'; + validateArgument(methodSignature, 'path', path, ['string', 'undefined']); +} + +const parseOptions = (options: any | null, path: string): IDeleteOptions => { + const opts: IDeleteOptions = options || {} as IDeleteOptions; + const parsedOptions: IDeleteOptions = {}; + parsedOptions.progress = opts.progress; + parsedOptions.conflictCallback = opts.conflictCallback; + parsedOptions.conflictSettings = opts.conflictSettings; + parsedOptions.debug = opts.debug; + parsedOptions.matching = opts.matching; + if (!opts.filter) { + if (opts.matching) { + parsedOptions.filter = matcher(path, opts.matching); + } else { + parsedOptions.filter = () => { + return true; + }; + } + } + return parsedOptions; +}; + +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- +export function sync(path: string, options?: IDeleteOptions): void { + const inspectedFile = inspectSync(path, { symlinks: true }); + if (inspectedFile === undefined) { + // The path already doesn't exits. Nothing to do here. + } else if (inspectedFile.type === 'dir') { + listSync(path).forEach((filename) => { + sync(pathUtil.join(path, filename)); + }); + rmdirSync(path); + } else if (inspectedFile.type === 'file' || inspectedFile.type === 'symlink') { + unlinkSync(path); + } else { + throw ErrNoFileOrDir(path); + } +} + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- +const rmASync = (path: string, options: IDeleteOptions): any => { + return new Promise((resolve, reject) => { + unlink(path, (err: ErrnoException) => { + if (!err) { + resolve(); + } else { + reject(err); + } + }); + + }); +}; +interface IVisitorArgs { + resolve: Function; + reject: Function; + abort: boolean; + filesInProgress: number; + resolveSettings: IConflictSettings; + options: IDeleteOptions; + result: TDeleteResult; + nodes: IProcessingNode[]; +} +const isDone = (nodes: IProcessingNode[]) => { + let done = true; + nodes.forEach((element: IProcessingNode) => { + if (element.status !== ENodeOperationStatus.DONE) { + done = false; + } + }); + return done; +}; +const next = (nodes: IProcessingNode[]): IProcessingNode => { + for (let i = 0; i < nodes.length; i++) { + if (nodes[i].status === ENodeOperationStatus.COLLECTED) { + return nodes[i]; + } + } + return null; +}; +// handle user side setting "THROW" and non enum values (null) +const onConflict = (from: string, options: IDeleteOptions, settings: IConflictSettings): EResolveMode | undefined => { + switch (settings.overwrite) { + case EResolveMode.THROW: { + throw ErrCantDelete(from); + } + case EResolveMode.OVERWRITE: + case EResolveMode.APPEND: + case EResolveMode.IF_NEWER: + case EResolveMode.ABORT: + case EResolveMode.IF_SIZE_DIFFERS: + case EResolveMode.SKIP: { + return settings.overwrite; + } + default: { + return undefined; + } + } +}; +export function resolveConflict(path: string, resolveMode: EResolveMode): boolean { + if (resolveMode === undefined) { + return true; + } + if (resolveMode === EResolveMode.SKIP) { + return false; + } else if (resolveMode === EResolveMode.ABORT) { + return false; + } else if (resolveMode === EResolveMode.RETRY) { + return true; + } + return false; +} +const visitor = (path: string, vars: IVisitorArgs, item: IProcessingNode): Promise => { + const options = vars.options; + + if (!item) { + return; + } + item.status = ENodeOperationStatus.PROCESSING; + const done = () => { + item.status = ENodeOperationStatus.DONE; + if (isDone(vars.nodes)) { + return vars.resolve(vars.result); + } else { + if (vars.nodes.length) { + const itemInner = next(vars.nodes); + if (itemInner) { + visitor(itemInner.path, vars, itemInner); + } else { + vars.resolve(vars.result); + } + } + + } + }; + if (isDone(vars.nodes)) { + return vars.resolve(vars.result); + } + vars.filesInProgress += 1; + rmASync(path, options) + .then((res: any) => { + done(); + }) + .catch((err: ErrnoException) => { + if (err.code === 'EACCES' || err.code === 'EPERM' || err.code === 'EISDIR' || err.code === 'ENOTEMPTY') { + + const resolved = (settings: IConflictSettings) => { + settings.error = err.code; + // feature : report + if (settings && options && options.flags && options.flags & EDeleteFlags.REPORT) { + (vars.result as INodeReport[]).push({ + error: settings.error, + node: item, + resolved: settings + } as INodeReport); + } + if (settings) { + // if the first resolve callback returned an individual resolve settings "THIS", + // ask the user again with the same item + const always = settings.mode === EResolve.ALWAYS; + if (always) { + options.conflictSettings = settings; + } + + let how = settings.overwrite; + how = onConflict(item.path, options, settings); + if (how === EResolveMode.ABORT) { + vars.abort = true; + } + if (vars.abort) { + done(); + return; + } + if (!resolveConflict(item.path, how)) { + done(); + return; + } + item.status = ENodeOperationStatus.PROCESS; + if (settings.overwrite === EResolveMode.RETRY) { + item.status = ENodeOperationStatus.COLLECTED; + visitor(path, vars, item); + } + } + }; + + if (!options.conflictSettings) { + const promise = options.conflictCallback(path, createItem(path), err.code); + promise.then(resolved); + } else { + resolved(options.conflictSettings); + } + } + }); +}; + +async function collect(path: string, options?: IDeleteOptions): Promise { + return new Promise((resolve, reject) => { + const all: IProcessingNode[] = []; + iteratorAsync(path, { + filter: options.filter + }).then((it:any) => { + let node: IProcessingNode; + while (node = it.next() as any) { + all.push({ + path: node.path, + item: node.item, + status: ENodeOperationStatus.COLLECTED + }); + } + resolve(all); + }).catch((err: Error) => { + console.error('read error', err); + }); + }); +} +export async function async(path: string, options?: IDeleteOptions): Promise { + options = parseOptions(options, path); + const onError = (err: ErrnoException, resolve: any, reject: any, nodes?: IProcessingNode[]) => { + if (err.code === 'EPERM' || err.code === 'EISDIR' || err.code === 'ENOTEMPTY') { + const proceed = () => { + // It's not a file, it's a directory. + // Must delete everything inside first. + listAsync(path).then((filenamesInsideDir: string[]) => { + const promises = filenamesInsideDir.map((filename: string) => { + return async(pathUtil.join(path, filename), options); + }); + return Promise.all(promises); + }).then(() => { + // Everything inside directory has been removed, + // it's safe now to go for the directory itself. + return rmdir(path, (err2: ErrnoException) => { + if (err2) { + reject(err2); + } + }); + }) + .then(resolve, reject); + }; + // we have a user conflict callback, + // collect nodes and start asking + if (options.conflictCallback) { + const result: TDeleteResult = void 0; + // walker variables + const visitorArgs: IVisitorArgs = { + resolve: resolve, + reject: reject, + abort: false, + filesInProgress: 0, + resolveSettings: null, + options: options, + result: result, + nodes: nodes || [] + }; + + const process = () => { + visitorArgs.nodes = nodes; + if (isDone(nodes)) { + return resolve(result); + } + if (nodes.length) { + const item = next(nodes); + if (item) { + visitor(item.path, visitorArgs, item); + } + } + }; + if (!nodes) { + const _nodes = visitorArgs.nodes; + iteratorAsync(path, { + filter: options.filter + }).then((it: any) => { + let node: IProcessingNode; + while (node = it.next() as any) { + _nodes.push({ + path: node.path, + item: node.item, + status: ENodeOperationStatus.COLLECTED + }); + } + process(); + }).catch((err2: Error) => { + console.error('read error', err2); + }); + } else { + process(); + } + } else { + proceed(); + } + } else if (err.code === 'ENOENT') { + // File already doesn't exist. We're done. + resolve(); + } else { + // Something unexpected happened. Rethrow original error. + reject(err); + } + }; + + // if matching is set, its like rm somePath/*.ext + // in this case, we collect the inner matching nodes and proceed as it + // would be an error + if (options.matching) { + const nodes = await collect(path, options); + const err = new ErrnoException('dummy'); + err.code = 'ENOTEMPTY'; + return new Promise((resolve, reject) => { + onError(err, resolve, reject, nodes); + }); + } else { + return new Promise((resolve, reject) => { + // Assume the path is a file or directory and just try to remove it. + rmASync(path, options) + .then((res: any) => resolve()) + .catch((err: ErrnoException) => { + onError(err, resolve, reject); + }); + }); + } +} diff --git a/packages/fs/src/rename.ts b/packages/fs/src/rename.ts new file mode 100644 index 00000000..d09aca6c --- /dev/null +++ b/packages/fs/src/rename.ts @@ -0,0 +1,23 @@ +import * as pathUtil from 'path'; +import { sync as moveSync, async as moveASync } from './move.js'; +import { validateArgument } from './utils/validate.js'; + +export function validateInput(methodName: string, path: string, newName: string): void { + const methodSignature = methodName + '(path, newName)'; + validateArgument(methodSignature, 'path', path, ['string']); + validateArgument(methodSignature, 'newName', newName, ['string']); +} + +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- +export function sync(path: string, newName: string) { + moveSync(path, pathUtil.join(pathUtil.dirname(path), newName)); +} + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- +export function async(path: string, newName: string): Promise { + return moveASync(path, pathUtil.join(pathUtil.dirname(path), newName)); +} diff --git a/packages/fs/src/stats.ts b/packages/fs/src/stats.ts new file mode 100644 index 00000000..7915ddeb --- /dev/null +++ b/packages/fs/src/stats.ts @@ -0,0 +1,18 @@ +import { + INode, + ENodeOperationStatus, + EError, + ErrnoException, + IConflictSettings, + EResolve, + INodeReport, + IStatOptions +} from './interfaces.js' + +import { } from './utils/stats.js' + +export const sync = (path: string, options: IStatOptions ) => { +} + +export const async = async (path: string, options: IStatOptions ) => { +} diff --git a/packages/fs/src/streams.ts b/packages/fs/src/streams.ts new file mode 100644 index 00000000..5021dc61 --- /dev/null +++ b/packages/fs/src/streams.ts @@ -0,0 +1,2 @@ +export { createWriteStream } from 'fs'; +export { createReadStream } from 'fs'; diff --git a/packages/fs/src/symlink.ts b/packages/fs/src/symlink.ts new file mode 100644 index 00000000..feb2beab --- /dev/null +++ b/packages/fs/src/symlink.ts @@ -0,0 +1,50 @@ +const Q = require('q'); +import * as fs from 'fs'; +import * as mkdirp from 'mkdirp'; +import * as pathUtil from "path"; +import { validateArgument } from './utils/validate.js'; +const promisedSymlink = Q.denodeify(fs.symlink); +const promisedMkdirp = Q.denodeify(mkdirp); + +export function validateInput(methodName: string, symlinkValue: string, path: string) { + const methodSignature = methodName + '(symlinkValue, path)'; + validateArgument(methodSignature, 'symlinkValue', symlinkValue, ['string']); + validateArgument(methodSignature, 'path', path, ['string']); +}; +// --------------------------------------------------------- +// Sync +// --------------------------------------------------------- + +export function sync(symlinkValue: string, path: string): void { + try { + fs.symlinkSync(symlinkValue, path); + } catch (err) { + if (err.code === 'ENOENT') { + // Parent directories don't exist. Just create them and rety. + mkdirp.sync(pathUtil.dirname(path)); + fs.symlinkSync(symlinkValue, path); + } else { + throw err; + } + } +} + +// --------------------------------------------------------- +// Async +// --------------------------------------------------------- +export function async(symlinkValue: string, path: string):Promise { + return new Promise((resolve, reject) => { + promisedSymlink(symlinkValue, path) + .then(resolve) + .catch((err: any) => { + if (err.code === 'ENOENT') { + // Parent directories don't exist. Just create them and rety. + promisedMkdirp(pathUtil.dirname(path)) + .then(() => { return promisedSymlink(symlinkValue, path); }) + .then(resolve, reject); + } else { + reject(err); + } + }); + }); +} diff --git a/packages/fs/src/util.ts b/packages/fs/src/util.ts new file mode 100644 index 00000000..8438fe0e --- /dev/null +++ b/packages/fs/src/util.ts @@ -0,0 +1,2 @@ +import { substitute as _substitute, substituteAlt } from "@polymech/core/strings.js" +export const substitute = (alt:boolean, template:string, vars:Record) => alt ? substituteAlt(template,vars) : _substitute(template, vars) diff --git a/packages/fs/src/utils/dot.ts b/packages/fs/src/utils/dot.ts new file mode 100644 index 00000000..cf6ac8d5 --- /dev/null +++ b/packages/fs/src/utils/dot.ts @@ -0,0 +1,10 @@ +export const DOT_DIR_REGEX = /(?:^|[\\/])(\.\w+)[\\/]/; +export const isDotDir = (str: string) => DOT_DIR_REGEX.test(str); +export function isDotFile(str: string) { + if (str.charCodeAt(0) === 46 /* . */ && !str.includes('/', 1)) { + return true; + } + + const last = str.lastIndexOf('/'); + return last !== -1 ? str.charCodeAt(last + 1) === 46 /* . */ : false; +} diff --git a/packages/fs/src/utils/fs.ts b/packages/fs/src/utils/fs.ts new file mode 100644 index 00000000..9b35e63a --- /dev/null +++ b/packages/fs/src/utils/fs.ts @@ -0,0 +1,43 @@ +// Adater module exposing all `fs` methods with promises instead of callbacks. +import * as fs from 'fs'; +const promisify = require('./promisify'); + +const isCallbackMethod = (key) => { + return [ + typeof fs[key] === 'function', + !key.match(/Sync$/), + !key.match(/^[A-Z]/), + !key.match(/^create/), + !key.match(/^(un)?watch/), + ].every(Boolean); +}; + +const adaptMethod = (name) => { + const original = fs[name]; + return promisify(original); +}; + +const adaptAllMethods = () => { + const adapted = {}; + + Object.keys(fs).forEach((key) => { + if (isCallbackMethod(key)) { + if (key === 'exists') { + // fs.exists() does not follow standard + // Node callback conventions, and has + // no error object in the callback + adapted['exists'] = () => { + throw new Error('fs.exists() is deprecated'); + }; + } else { + adapted[key] = adaptMethod(key); + } + } else { + adapted[key] = fs[key]; + } + }); + + return adapted; +}; + +module.exports = adaptAllMethods(); diff --git a/packages/fs/src/utils/matcher.ts b/packages/fs/src/utils/matcher.ts new file mode 100644 index 00000000..46d3e163 --- /dev/null +++ b/packages/fs/src/utils/matcher.ts @@ -0,0 +1,79 @@ +import { Minimatch } from 'minimatch'; +export interface IOptions { + matchBase: boolean; + nocomment: boolean; + dot: boolean; +} +const patternToAbsolutePath = (basePath: string, pattern: string): string => { + // All patterns without slash are left as they are, if pattern contain + // any slash we need to turn it into absolute path. + const hasSlash: boolean = (pattern.includes('/')); + const isAbsolute: boolean = /^!?\//.test(pattern); + const isNegated: boolean = /^!/.test(pattern); + let separator; + + if (!isAbsolute && hasSlash) { + // Throw out meaningful characters from the beginning ("!", "./"). + pattern = pattern.replace(/^!/, '').replace(/^\.\//, ''); + + if (/\/$/.test(basePath)) { + separator = ''; + } else { + separator = '/'; + } + + if (isNegated) { + return '!' + basePath + separator + pattern; + } + return basePath + separator + pattern; + } + + return pattern; +}; + +export function create(basePath: string, patterns: string[], options?: IOptions) { + let matchers: any[]; + if (typeof patterns === 'string') { + patterns = [patterns]; + } + matchers = patterns.map(pattern => { + return patternToAbsolutePath(basePath, pattern); + }).map(pattern => { + return new Minimatch(pattern, options || { + matchBase: true, + nocomment: true, + dot: true + }); + }); + + return function performMatch(absolutePath: string): boolean { + let mode = 'matching'; + let weHaveMatch = false; + let currentMatcher; + let i; + + for (i = 0; i < matchers.length; i += 1) { + currentMatcher = matchers[i]; + if (currentMatcher.negate) { + mode = 'negation'; + if (i === 0) { + // There are only negated patterns in the set, + // so make everything matching by default and + // start to reject stuff. + weHaveMatch = true; + } + } + + if (mode === 'negation' && weHaveMatch && !currentMatcher.match(absolutePath)) { + // One negation match is enought to know we can reject this one. + return false; + } + + if (mode === 'matching' && !weHaveMatch) { + weHaveMatch = currentMatcher.match(absolutePath); + } + } + + return weHaveMatch; + }; +} diff --git a/packages/fs/src/utils/mime_match.ts b/packages/fs/src/utils/mime_match.ts new file mode 100644 index 00000000..a86892d2 --- /dev/null +++ b/packages/fs/src/utils/mime_match.ts @@ -0,0 +1,19 @@ +import { default as wildcard } from './wildcard.js'; +const reMimePartSplit = /[/+.]/; +/** + * A simple function to checker whether a target mime type matches a mime-type + * pattern (e.g. image/jpeg matches image/jpeg OR image/*). + * + * @export + * @param {string} target + * @param {string} pattern + * @returns + */ +export default function (target: string, pattern: string) { + const test = (_pattern) => { + const result = wildcard(_pattern, target, reMimePartSplit); + // ensure that we have a valid mime type (should have two parts) + return result && result.length >= 2; + }; + return pattern ? test(pattern.split(';')[0]) : test; +} diff --git a/packages/fs/src/utils/mode.ts b/packages/fs/src/utils/mode.ts new file mode 100644 index 00000000..8d73732b --- /dev/null +++ b/packages/fs/src/utils/mode.ts @@ -0,0 +1,10 @@ +// Converts mode to string 3 characters long. +export const normalizeFileMode = (mode: string | number): string => { + let modeAsString: string; + if (typeof mode === 'number') { + modeAsString = mode.toString(8); + } else { + modeAsString = mode; + } + return modeAsString.substring(modeAsString.length - 3); +}; diff --git a/packages/fs/src/utils/paths.ts b/packages/fs/src/utils/paths.ts new file mode 100644 index 00000000..5e8f116c --- /dev/null +++ b/packages/fs/src/utils/paths.ts @@ -0,0 +1,47 @@ +import { substitute as _substitute, substituteAlt as _substituteAlt } from "@polymech/core/strings.js"; + +export const substitute = (alt: boolean, template: string, vars: Record) => + alt ? _substituteAlt(template, vars) : _substitute(template, vars) + +export const resolve = (_path: string, alt = false, vars: Record = {}) => + substitute(alt, _path, { + ...vars + }) + +export const sep = '/'; + +/** + * The native path separator depending on the OS. + */ +/* +export const nativeSep = isWindows ? '\\' : '/'; + +export function relative(from: string, to: string): string { + // ignore trailing slashes + const originalNormalizedFrom = rtrim(normalize(from), sep); + const originalNormalizedTo = rtrim(normalize(to), sep); + + // we're assuming here that any non=linux OS is case insensitive + // so we must compare each part in its lowercase form + const normalizedFrom = isLinux ? originalNormalizedFrom : originalNormalizedFrom.toLowerCase(); + const normalizedTo = isLinux ? originalNormalizedTo : originalNormalizedTo.toLowerCase(); + + const fromParts = normalizedFrom.split(sep); + const toParts = normalizedTo.split(sep); + + let i = 0, max = Math.min(fromParts.length, toParts.length); + + for (; i < max; i++) { + if (fromParts[i] !== toParts[i]) { + break; + } + } + + const result = [ + ...fill(fromParts.length - i, () => '..'), + ...originalNormalizedTo.split(sep).slice(i) + ]; + + return result.join(sep); +} +*/ diff --git a/packages/fs/src/utils/platform.ts b/packages/fs/src/utils/platform.ts new file mode 100644 index 00000000..10de8367 --- /dev/null +++ b/packages/fs/src/utils/platform.ts @@ -0,0 +1,40 @@ +export enum Platform { + Web, + Mac, + Linux, + Windows +} +let _isWindows = false; +let _isMacintosh = false; +let _isLinux = false; +let _isRootUser = false; +let _isNative = false; +let _isWeb = false; +let _isQunit = false; +export let _platform: Platform = Platform.Web; +// OS detection +if (typeof process === 'object') { + _isWindows = (process.platform === 'win32'); + _isMacintosh = (process.platform === 'darwin'); + _isLinux = (process.platform === 'linux'); + _isRootUser = !_isWindows && (process.getuid() === 0); + _isNative = true; +} +if (_isNative) { + if (_isMacintosh) { + _platform = Platform.Mac; + } else if (_isWindows) { + _platform = Platform.Windows; + } else if (_isLinux) { + _platform = Platform.Linux; + } +} + +export const isWindows = _isWindows; +export const isMacintosh = _isMacintosh; +export const isLinux = _isLinux; +export const isRootUser = _isRootUser; +export const isNative = _isNative; +export const isWeb = _isWeb; +export const isQunit = _isQunit; +export const platform = _platform; diff --git a/packages/fs/src/utils/stats.ts b/packages/fs/src/utils/stats.ts new file mode 100644 index 00000000..b3b7ea32 --- /dev/null +++ b/packages/fs/src/utils/stats.ts @@ -0,0 +1,98 @@ +import { join as joinPaths } from 'node:path' +/** + * Returns an object containing the size of the folder and a list of errors encountered while traversing the folder. + * + * If any errors are returned, the returned folder size is likely smaller than the real folder size. + * + * @param {string} itemPath - Path of the folder. + * @param {object} [options] - Options. + * @param {boolean} [options.bigint] - Should the folder size be returned as a BigInt instead of a Number. + * @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted. + * @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default. + * + * @returns {Promise<{size: number | bigint, errors: Array | null}>} - An object containing the size of the folder in bytes and a list of encountered errors. + */ +export const getFolderSize = async (itemPath, options) => await core(itemPath, options, {errors: true}) + +/** + * Returns the size of the folder. If any errors are encountered while traversing the folder, they are silently ignored. + * + * The returned folder size might be smaller than the real folder size. It is impossible to know for sure, since errors are ignored. + * + * @param {string} itemPath - Path of the folder. + * @param {object} [options] - Options. + * @param {boolean} [options.bigint] - Should the folder size be returned as a BigInt instead of a Number. + * @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted. + * @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default. + * + * @returns {Promise} - The size of the folder in bytes. + */ +getFolderSize.loose = async (itemPath, options) => await core(itemPath, options); + +/** + * Returns the size of the folder. If any errors are encountered while traversing the folder, this method will throw an error. + * + * Because errors will otherwise make this method fail, the returned folder size will always be accurate. + * + * @param {string} itemPath - Path of the folder. + * @param {object} [options] - Options. + * @param {boolean} [options.bigint] - Should the folder size be returned as a BigInt instead of a Number. + * @param {object} [options.ignore] - If a file's path matches this regex object, its size is not counted. + * @param {object} [options.fs] - The filesystem that should be used. Uses node fs by default. + * + * @returns {Promise} - The size of the folder in bytes. + */ +getFolderSize.strict = async (itemPath, options) => await core(itemPath, options, {strict: true}); + + + +async function core (rootItemPath, options:any = {}, returnType:any = {}) { + const fs = options.fs || await import('fs/promises'); + + const fileSizes = new Map(); + const errors = []; + + await processItem(rootItemPath); + + async function processItem(itemPath) { + if(options.ignore?.test(itemPath)) return; + + const stats = returnType.strict ? await fs.lstat(itemPath, {bigint: true}) : await fs.lstat(itemPath, {bigint: true}).catch(error => errors.push(error)); + if(typeof stats !== 'object') return; + fileSizes.set(stats.ino, stats.size); + + if(stats.isDirectory()) { + const directoryItems = returnType.strict ? await fs.readdir(itemPath) : await fs.readdir(itemPath).catch(error => errors.push(error)); + if(typeof directoryItems !== 'object') return; + await Promise.all( + directoryItems.map(directoryItem => + processItem(joinPaths(itemPath, directoryItem)) + ) + ); + } + } + + let folderSize = Array.from(fileSizes.values()).reduce((total, fileSize) => total + fileSize, 0n); + + if(!options.bigint) { + if(folderSize > BigInt(Number.MAX_SAFE_INTEGER)){ + const error = new RangeError('The folder size is too large to return as a Number. You can instruct this package to return a BigInt instead.'); + if(returnType.strict){ + throw error; + }else{ + errors.push(error); + } + } + folderSize = Number(folderSize); + } + + if (returnType.errors) { + return { + size: folderSize, + errors: errors.length > 0 ? errors : null, + }; + } else { + return folderSize; + } + +} diff --git a/packages/fs/src/utils/strings.ts b/packages/fs/src/utils/strings.ts new file mode 100644 index 00000000..f73de9d4 --- /dev/null +++ b/packages/fs/src/utils/strings.ts @@ -0,0 +1,15 @@ +export let canNormalize = typeof (('' as any).normalize) === 'function'; +const nonAsciiCharactersPattern = /[^\u0000-\u0080]/; +export const normalizeNFC = (str: string): string => { + if (!canNormalize || !str) { + return str; + } + + let res: string; + if (nonAsciiCharactersPattern.test(str)) { + res = (str as any).normalize('NFC'); + } else { + res = str; + } + return res; +}; diff --git a/packages/fs/src/utils/tree_walker.ts b/packages/fs/src/utils/tree_walker.ts new file mode 100644 index 00000000..27c61c50 --- /dev/null +++ b/packages/fs/src/utils/tree_walker.ts @@ -0,0 +1,120 @@ +import { Readable } from 'node:stream'; +import * as pathUtil from 'node:path'; +import { sync as inspectSync, async as inspectASync } from '../inspect.js'; +import { ENodeType, IInspectOptions, INode } from '../interfaces.js'; +import { sync as listSync, async as listASync } from '../list.js'; + +export interface IOptions { + inspectOptions: IInspectOptions; + maxLevelsDeep?: number; + user?: any; +} +// --------------------------------------------------------- +// SYNC +// --------------------------------------------------------- +export function sync(path: string, options: IOptions, callback: (path: string, item: INode) => void, currentLevel?: number): void { + const item = inspectSync(path, options.inspectOptions); + if (options.maxLevelsDeep === undefined) { + options.maxLevelsDeep = Infinity; + } + if (currentLevel === undefined) { + currentLevel = 0; + } + + let children: string[] = []; + const hasChildren: boolean = item && item.type === ENodeType.DIR && currentLevel < options.maxLevelsDeep; + if (hasChildren) { + children = listSync(path); + } + callback(path, item); + if (hasChildren) { + children.forEach(child => sync(path + pathUtil.sep + child, options, callback, currentLevel + 1)); + } +} + +// --------------------------------------------------------- +// STREAM +// --------------------------------------------------------- +interface IPrivateNode { + path: string; + level: number; + parent?: IPrivateNode; + nextSibling?: IPrivateNode; + inspected?: INode; + item?: INode; +} +export function stream(path: string, options: IOptions) { + const rs = new Readable({ objectMode: true }); + let nextTreeNode: IPrivateNode = { + path: path, + parent: undefined, + level: 0 + }; + let running = false; + let readSome: any; + const error = (err: Error) => { + rs.emit('error', err); + }; + const findNextUnprocessedNode = (node: IPrivateNode): IPrivateNode => { + if (node.nextSibling) { + return node.nextSibling; + } else if (node.parent) { + return findNextUnprocessedNode(node.parent); + } + return undefined; + }; + + const pushAndContinueMaybe = (data: { path: string, item: INode }) => { + const theyWantMore = rs.push(data); + running = false; + if (!nextTreeNode) { + // Previous was the last node. The job is done. + rs.push(null); + } else if (theyWantMore) { + readSome(); + } + }; + + if (options.maxLevelsDeep === undefined) { + options.maxLevelsDeep = Infinity; + } + + readSome = (): void => { + const theNode: IPrivateNode = nextTreeNode; + running = true; + inspectASync(theNode.path, options.inspectOptions) + .then((inspected: INode) => { + theNode.inspected = inspected; + if (inspected && inspected.type === ENodeType.DIR && theNode.level < options.maxLevelsDeep) { + listASync(theNode.path) + .then((childrenNames: string[]) => { + const children = childrenNames.map((name) => { + return { + name: name, + path: theNode.path + pathUtil.sep + name, + parent: theNode, + level: theNode.level + 1 + }; + }); + children.forEach((child: IPrivateNode, index: number) => { + child.nextSibling = children[index + 1]; + }); + + nextTreeNode = children[0] || findNextUnprocessedNode(theNode); + pushAndContinueMaybe({ path: theNode.path, item: inspected }); + }) + .catch(error); + } else { + nextTreeNode = findNextUnprocessedNode(theNode); + pushAndContinueMaybe({ path: theNode.path, item: inspected }); + } + }) + .catch(error); + }; + rs['_read'] = () => { + if (!running) { + readSome(); + } + }; + return rs; +} diff --git a/packages/fs/src/utils/validate.ts b/packages/fs/src/utils/validate.ts new file mode 100644 index 00000000..07f4e3ef --- /dev/null +++ b/packages/fs/src/utils/validate.ts @@ -0,0 +1,123 @@ +const prettyPrintTypes = (types) => { + const addArticle = (str) => { + const vowels = ["a", "e", "i", "o", "u"] + if (vowels.includes(str[0])) { + return `an ${str}` + } + return `a ${str}` + }; + + return types.map(addArticle).join(" or ") +} + +const isArrayOfNotation = function (typeDefinition: string) { + return typeDefinition.includes('array of '); +}; + +const extractTypeFromArrayOfNotation = function (typeDefinition: string) { + // The notation is e.g. 'array of string' + return typeDefinition.split(' of ')[1]; +}; + +const isValidTypeDefinition = (typeStr) => { + if (isArrayOfNotation(typeStr)) { + return isValidTypeDefinition(extractTypeFromArrayOfNotation(typeStr)); + } + return [ + "string", + "number", + "boolean", + "array", + "object", + "buffer", + "null", + "undefined", + "function", + ].some((validType) => validType === typeStr) +} + +const detectType = function (value: any | null): string { + if (value === null) { + return 'null'; + } + if (Array.isArray(value)) { + return 'array'; + } + if (Buffer.isBuffer(value)) { + return 'buffer'; + } + return typeof value; +}; + +const onlyUniqueValuesInArrayFilter = function (value: string, index: number, self: any) { + return self.indexOf(value) === index; +} + +const detectTypeDeep = (value) => { + let type = detectType(value); + let typesInArray; + + if (type === "array") { + typesInArray = value + .map((element) => { + return detectType(element); + }) + .filter(onlyUniqueValuesInArrayFilter); + type += ` of ${typesInArray.join(", ")}`; + } + + return type; +} +const validateArray = (argumentValue, typeToCheck) => { + const allowedTypeInArray = extractTypeFromArrayOfNotation(typeToCheck); + + if (detectType(argumentValue) !== "array") { + return false; + } + + return argumentValue.every((element) => + detectType(element) === allowedTypeInArray) +} + +export const validateArgument = ( + methodName, + argumentName, + argumentValue, + argumentMustBe +) => { + const isOneOfAllowedTypes = argumentMustBe.some((type) => { + if (!isValidTypeDefinition(type)) { + throw new Error(`Unknown type "${type}"`) + } + + if (isArrayOfNotation(type)) { + return validateArray(argumentValue, type) + } + + return type === detectType(argumentValue) + }) + + if (!isOneOfAllowedTypes) { + throw new Error( + `Argument "${argumentName}" passed to ${methodName} must be ${prettyPrintTypes( + argumentMustBe + )}. Received ${detectTypeDeep(argumentValue)}` + ) + } +} + +export const validateOptions = (methodName, optionsObjName, obj, allowedOptions) => { + if (obj !== undefined) { + validateArgument(methodName, optionsObjName, obj, ["object"]) + Object.keys(obj).forEach((key) => { + const argName = `${optionsObjName}.${key}` + if (allowedOptions[key] !== undefined) { + validateArgument(methodName, argName, obj[key], allowedOptions[key]) + } else { + throw new Error( + `Unknown argument "${argName}" passed to ${methodName}` + ) + } + }) + } +} diff --git a/packages/fs/src/utils/wildcard.ts b/packages/fs/src/utils/wildcard.ts new file mode 100644 index 00000000..83297f0b --- /dev/null +++ b/packages/fs/src/utils/wildcard.ts @@ -0,0 +1,62 @@ +function WildcardMatcher(text, separator) { + this.text = text = text || ''; + this.hasWild = ~text.indexOf('*'); + this.separator = separator; + this.parts = text.split(separator); +} + +WildcardMatcher.prototype.match = (input) => { + let matches: boolean | object | Array = true; + var thiz: any = this; + const parts = thiz.parts || []; + let ii; + const partsCount = parts.length; + let testParts; + + if (typeof input === 'string' || input instanceof String) { + if (!thiz.hasWild && thiz.text !== input) { + matches = false; + } else { + testParts = (input || '').split(thiz.separator); + for (ii = 0; matches && ii < partsCount; ii++) { + if (parts[ii] === '*') { + continue; + } else if (ii < testParts.length) { + matches = parts[ii] === testParts[ii]; + } else { + matches = false; + } + } + + // If matches, then return the component parts + matches = matches && testParts; + } + }else if (typeof input.splice === 'function') { + matches = []; + + for (ii = input.length; ii--;) { + if (thiz.match(input[ii])) { + matches[(matches as string[]).length] = input[ii]; + } + } + }else if (typeof input === 'object') { + matches = {}; + + for (const key in input) { + if (thiz.match(key)) { + matches[key] = input[key]; + } + } + } + + return matches; +}; + +export default function (text: string, test: string, separator?: string | RegExp) { + const matcher = new WildcardMatcher(text, separator || /[/.]/); + if (typeof test !== 'undefined') { + return matcher.match(test); + } + + return matcher; +} diff --git a/packages/fs/src/write.ts b/packages/fs/src/write.ts new file mode 100644 index 00000000..de2b79c1 --- /dev/null +++ b/packages/fs/src/write.ts @@ -0,0 +1,91 @@ +import * as pathUtil from 'path'; +import * as fs from 'fs'; +import { writeFileSync } from 'fs'; +const Q = require('q'); +import * as mkdirp from 'mkdirp'; +import { json, file } from './imports.js'; +import { IWriteOptions, ReadWriteDataType } from './interfaces.js'; +import { validateArgument, validateOptions } from './utils/validate.js'; + +// Temporary file extensions used for atomic file overwriting. +const newExt = '.__new__'; + +export function validateInput(methodName: string, path: string, data: ReadWriteDataType, options: IWriteOptions): void { + const methodSignature = methodName + '(path, data, [options])'; + validateArgument(methodSignature, 'path', path, ['string']); + validateArgument(methodSignature, 'data', data, ['string', 'buffer', 'object', 'array']); + validateOptions(methodSignature, 'options', options, { + atomic: ['boolean'], + jsonIndent: ['number'], + progress: ['function'] + }); +} + +const toJson = (data: ReadWriteDataType, jsonIndent: number): string => { + if (typeof data === 'object' + && !Buffer.isBuffer(data) + && data !== null) { + return json.serialize(data, null, typeof jsonIndent !== 'number' ? 2 : jsonIndent); + } + return data as string; +}; + +// --------------------------------------------------------- +// SYNC +// --------------------------------------------------------- +const _writeFileSync = (path: string, data: any | string, options?: IWriteOptions): void => { + try { + writeFileSync(path, data, options); + } catch (err) { + if (err.code === 'ENOENT') { + // Means parent directory doesn't exist, so create it and try again. + mkdirp.sync(pathUtil.dirname(path)); + fs.writeFileSync(path, data, options); + } else { + throw err; + } + } +}; + +const writeAtomicSync = (path: string, data: string, options?: IWriteOptions): void => { + return file.write_atomic(path + newExt, data, options, function () { }); +}; + +export function sync(path: string, data: ReadWriteDataType, options?: IWriteOptions): void { + const opts: any = options || {}; + const processedData = toJson(data, opts.jsonIndent); + const writeStrategy = opts.atomic ? writeAtomicSync : _writeFileSync; + writeStrategy(path, processedData, { mode: opts.mode }); +} + +// --------------------------------------------------------- +// ASYNC +// --------------------------------------------------------- +const promisedWriteFile = Q.denodeify(fs.writeFile); +const promisedMkdirp = Q.denodeify(mkdirp); +const promisedAtomic = Q.denodeify(writeAtomicSync); + +function writeFileAsync(path: string, data: string, options?: IWriteOptions): Promise { + return new Promise((resolve, reject) => { + promisedWriteFile(path, data, options) + .then(resolve) + .catch((err: any) => { + // First attempt to write a file ended with error. + // Check if this is not due to nonexistent parent directory. + if (err.code === 'ENOENT') { + // Parent directory doesn't exist, so create it and try again. + promisedMkdirp(pathUtil.dirname(path)) + .then(() => promisedWriteFile(path, data, options)) + .then(resolve, reject); + } else { + // Nope, some other error, throw it. + reject(err); + } + }); + }); +} +export function async(path: string, data: ReadWriteDataType, options?: IWriteOptions): Promise { + const opts: any = options || {}; + const processedData: string = toJson(data, opts.jsonIndent); + return (opts.atomic ? promisedAtomic : writeFileAsync)(path, processedData, { mode: opts.mode }); +} diff --git a/packages/fs/tsconfig.json b/packages/fs/tsconfig.json new file mode 100644 index 00000000..ddfeac45 --- /dev/null +++ b/packages/fs/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../typescript-config/base.json", + "include": ["src"], + "compilerOptions": { + "allowJs": true, + "declarationDir": "./dist", + "outDir": "./dist", + "strictNullChecks": false, + "paths": { + + } + } +} diff --git a/packages/fs/tslint.json b/packages/fs/tslint.json new file mode 100644 index 00000000..5658cba9 --- /dev/null +++ b/packages/fs/tslint.json @@ -0,0 +1,21 @@ +{ + "extends": [ + "tslint-presets" + ], + "rules": { + "no-unused-expression": true, + "no-duplicate-variable": true, + "curly": true, + "class-name": true, + "no-var-keyword": true, + "triple-equals": true, + "semicolon":true, + "indent":false, + "no-require-imports":false, + "no-conditional-assignment":false, + "no-bitwise":false, + "no-var-requires":false, + "only-arrow-functions": false, + "no-console": false + } +} diff --git a/packages/typescript-config/base.json b/packages/typescript-config/base.json new file mode 100644 index 00000000..18a2ce19 --- /dev/null +++ b/packages/typescript-config/base.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "declaration": true, + "strict": false, + "moduleResolution": "Node16", + "target": "ES2022", + "module": "Node16", + "esModuleInterop": true, + "skipLibCheck": true, + "stripInternal": true + } +} diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json new file mode 100644 index 00000000..27c0e604 --- /dev/null +++ b/packages/typescript-config/package.json @@ -0,0 +1,9 @@ +{ + "name": "@repo/typescript-config", + "version": "0.0.0", + "private": true, + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/typescript-config/react-app.json b/packages/typescript-config/react-app.json new file mode 100644 index 00000000..cb17cf06 --- /dev/null +++ b/packages/typescript-config/react-app.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "allowJs": true, + "declaration": false, + "declarationMap": false, + "incremental": true, + "jsx": "preserve", + "lib": ["dom", "dom.iterable", "esnext"], + "module": "esnext", + "noEmit": true, + "resolveJsonModule": true, + "target": "es5" + } +} diff --git a/packages/typescript-config/react-library.json b/packages/typescript-config/react-library.json new file mode 100644 index 00000000..5958a35c --- /dev/null +++ b/packages/typescript-config/react-library.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["dom", "ES2015"], + "module": "ESNext", + "target": "es6" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..bf947950 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,8660 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@changesets/cli': + specifier: ^2.27.1 + version: 2.27.2 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + turbo: + specifier: ^2.3.3 + version: 2.3.3 + + packages/commons: + dependencies: + '@polymech/core': + specifier: workspace:* + version: link:../core + '@polymech/fs': + specifier: workspace:* + version: link:../fs + tslog: + specifier: ^3.3.3 + version: 3.3.4 + tsup: + specifier: ^8.3.5 + version: 8.3.5(typescript@4.9.5)(yaml@2.4.2) + zod: + specifier: ^3.24.1 + version: 3.24.1 + devDependencies: + '@eslint/js': + specifier: ^9.18.0 + version: 9.18.0 + '@repo/eslint-config': + specifier: workspace:* + version: link:../eslint-config + '@repo/typescript-config': + specifier: workspace:* + version: link:../typescript-config + '@types/node': + specifier: ^8.10.66 + version: 8.10.66 + eslint: + specifier: ^8.57.1 + version: 8.57.1 + eslint-plugin-import: + specifier: ^2.31.0 + version: 2.31.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1) + eslint-plugin-regexp: + specifier: ^2.7.0 + version: 2.7.0(eslint@8.57.1) + globals: + specifier: ^15.14.0 + version: 15.14.0 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@8.10.66)(typescript@4.9.5) + typescript: + specifier: ^4.9.5 + version: 4.9.5 + typescript-eslint: + specifier: ^8.20.0 + version: 8.20.0(eslint@8.57.1)(typescript@4.9.5) + + packages/core: + dependencies: + tslog: + specifier: ^3.3.3 + version: 3.3.4 + tsup: + specifier: ^8.3.5 + version: 8.3.5(typescript@4.9.5)(yaml@2.4.2) + zod: + specifier: ^3.24.1 + version: 3.24.1 + devDependencies: + '@eslint/js': + specifier: ^9.18.0 + version: 9.18.0 + '@repo/eslint-config': + specifier: workspace:* + version: link:../eslint-config + '@repo/typescript-config': + specifier: workspace:* + version: link:../typescript-config + '@types/node': + specifier: ^8.10.66 + version: 8.10.66 + eslint: + specifier: ^8.57.1 + version: 8.57.1 + eslint-plugin-import: + specifier: ^2.31.0 + version: 2.31.0(eslint@8.57.1) + eslint-plugin-regexp: + specifier: ^2.7.0 + version: 2.7.0(eslint@8.57.1) + globals: + specifier: ^15.14.0 + version: 15.14.0 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@8.10.66)(typescript@4.9.5) + typescript: + specifier: ^4.9.5 + version: 4.9.5 + typescript-eslint: + specifier: ^8.20.0 + version: 8.20.0(eslint@8.57.1)(typescript@4.9.5) + + packages/eslint-config: + devDependencies: + '@vercel/style-guide': + specifier: ^5.2.0 + version: 5.2.0(eslint@8.57.1)(prettier@3.2.5)(typescript@5.7.3) + eslint-config-turbo: + specifier: ^2.0.0 + version: 2.0.0(eslint@8.57.1) + eslint-plugin-mdx: + specifier: ^3.1.5 + version: 3.1.5(eslint@8.57.1) + eslint-plugin-only-warn: + specifier: ^1.1.0 + version: 1.1.0 + eslint-plugin-storybook: + specifier: ^0.8.0 + version: 0.8.0(eslint@8.57.1)(typescript@5.7.3) + + packages/fs: + dependencies: + '@polymech/core': + specifier: workspace:* + version: link:../core + denodeify: + specifier: ^1.2.1 + version: 1.2.1 + errno: + specifier: ^0.1.4 + version: 0.1.8 + eslint-plugin-regexp: + specifier: ^2.7.0 + version: 2.7.0(eslint@8.57.1) + glob: + specifier: ^10.4.1 + version: 10.4.5 + mime: + specifier: ^2.0.3 + version: 2.6.0 + minimatch: + specifier: ^3.0.4 + version: 3.1.2 + mkdirp: + specifier: ^0.5.1 + version: 0.5.6 + progress-stream: + specifier: ^1.2.0 + version: 1.2.0 + q: + specifier: ^1.4.1 + version: 1.5.1 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + write-file-atomic: + specifier: ^1.3.1 + version: 1.3.4 + yargs: + specifier: ^17.7.2 + version: 17.7.2 + devDependencies: + '@eslint/js': + specifier: ^9.18.0 + version: 9.18.0 + '@repo/eslint-config': + specifier: workspace:* + version: link:../eslint-config + '@repo/typescript-config': + specifier: workspace:* + version: link:../typescript-config + '@types/denodeify': + specifier: ^1.2.31 + version: 1.2.35 + '@types/glob': + specifier: ^8.1.0 + version: 8.1.0 + '@types/mime': + specifier: ^2.0.0 + version: 2.0.3 + '@types/minimatch': + specifier: ^3.0.3 + version: 3.0.5 + '@types/mkdirp': + specifier: ^0.5.1 + version: 0.5.2 + '@types/node': + specifier: ^22.10.2 + version: 22.10.6 + chai: + specifier: ^3.5.0 + version: 3.5.0 + codecov: + specifier: ^3.8.3 + version: 3.8.3 + eslint: + specifier: ^8.57.1 + version: 8.57.1 + eslint-plugin-import: + specifier: ^2.31.0 + version: 2.31.0(eslint@8.57.1) + fs-extra: + specifier: ^4.0.2 + version: 4.0.3 + globals: + specifier: ^15.14.0 + version: 15.14.0 + istanbul: + specifier: ^0.4.5 + version: 0.4.5 + mocha: + specifier: ^11.0.1 + version: 11.0.1 + mocha-typescript: + specifier: ^1.0.12 + version: 1.1.17 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@22.10.6)(typescript@5.7.3) + tslint: + specifier: ^5.7.0 + version: 5.20.1(typescript@5.7.3) + tslint-presets: + specifier: ^2.0.0 + version: 2.0.0(typescript@5.7.3) + typescript: + specifier: ^5.7.2 + version: 5.7.3 + typescript-eslint: + specifier: ^8.20.0 + version: 8.20.0(eslint@8.57.1)(typescript@5.7.3) + + packages/typescript-config: {} + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.24.2': + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.24.4': + resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.24.5': + resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} + engines: {node: '>=6.9.0'} + + '@babel/eslint-parser@7.24.5': + resolution: {integrity: sha512-gsUcqS/fPlgAw1kOtpss7uhY6E9SFFANQ6EFX5GTvzUwaV0+sGaZWk6xq22MOdeT9wfxyokW3ceCUvOiRtZciQ==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + + '@babel/generator@7.24.5': + resolution: {integrity: sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.23.6': + resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-environment-visitor@7.22.20': + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-function-name@7.23.0': + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.22.5': + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.3': + resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.24.5': + resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-simple-access@7.24.5': + resolution: {integrity: sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-split-export-declaration@7.24.5': + resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.1': + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.5': + resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.23.5': + resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.24.5': + resolution: {integrity: sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.5': + resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.24.5': + resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.24.5': + resolution: {integrity: sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.24.0': + resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.24.5': + resolution: {integrity: sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.24.5': + resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==} + engines: {node: '>=6.9.0'} + + '@changesets/apply-release-plan@7.0.1': + resolution: {integrity: sha512-aPdSq/R++HOyfEeBGjEe6LNG8gs0KMSyRETD/J2092OkNq8mOioAxyKjMbvVUdzgr/HTawzMOz7lfw339KnsCA==} + + '@changesets/assemble-release-plan@6.0.0': + resolution: {integrity: sha512-4QG7NuisAjisbW4hkLCmGW2lRYdPrKzro+fCtZaILX+3zdUELSvYjpL4GTv0E4aM9Mef3PuIQp89VmHJ4y2bfw==} + + '@changesets/changelog-git@0.2.0': + resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} + + '@changesets/cli@2.27.2': + resolution: {integrity: sha512-6/kADjKMOrlLwNr/Y5HAq7T9oGOA2Lq5A59AGtwQCCiXuSGp4EgszzdJFeBiF8pdz7Wn1HaLzSUBhAaNToEJqg==} + hasBin: true + + '@changesets/config@3.0.0': + resolution: {integrity: sha512-o/rwLNnAo/+j9Yvw9mkBQOZySDYyOr/q+wptRLcAVGlU6djOeP9v1nlalbL9MFsobuBVQbZCTp+dIzdq+CLQUA==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.0.0': + resolution: {integrity: sha512-cafUXponivK4vBgZ3yLu944mTvam06XEn2IZGjjKc0antpenkYANXiiE6GExV/yKdsCnE8dXVZ25yGqLYZmScA==} + + '@changesets/get-release-plan@4.0.0': + resolution: {integrity: sha512-9L9xCUeD/Tb6L/oKmpm8nyzsOzhdNBBbt/ZNcjynbHC07WW4E1eX8NMGC5g5SbM5z/V+MOrYsJ4lRW41GCbg3w==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.0': + resolution: {integrity: sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==} + + '@changesets/logger@0.1.0': + resolution: {integrity: sha512-pBrJm4CQm9VqFVwWnSqKEfsS2ESnwqwH+xR7jETxIErZcfd1u2zBSqrHbRHR7xjhSgep9x2PSKFKY//FAshA3g==} + + '@changesets/parse@0.4.0': + resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} + + '@changesets/pre@2.0.0': + resolution: {integrity: sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==} + + '@changesets/read@0.6.0': + resolution: {integrity: sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.0.0': + resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} + + '@changesets/write@0.3.1': + resolution: {integrity: sha512-SyGtMXzH3qFqlHKcvFY2eX+6b0NGiFcNav8AFsYwy5l8hejOeoeTDemu5Yjmke2V5jpzY+pBvM0vCCQ3gdZpfw==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.10.0': + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@9.18.0': + resolution: {integrity: sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + + '@microsoft/tsdoc-config@0.16.2': + resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==} + + '@microsoft/tsdoc@0.14.2': + resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@npmcli/config@8.3.2': + resolution: {integrity: sha512-IMzf+fhRXibqh9mBwXK/QFIr97SAlZjfwsWPEz/2pST1cE9k9LcwznO7aDNXJoMrDjxPHZmb2bAAKASsa6EedA==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@npmcli/map-workspaces@3.0.6': + resolution: {integrity: sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@npmcli/name-from-folder@2.0.0': + resolution: {integrity: sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@rollup/rollup-android-arm-eabi@4.30.1': + resolution: {integrity: sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.30.1': + resolution: {integrity: sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.30.1': + resolution: {integrity: sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.30.1': + resolution: {integrity: sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.30.1': + resolution: {integrity: sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.30.1': + resolution: {integrity: sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + resolution: {integrity: sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.30.1': + resolution: {integrity: sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.30.1': + resolution: {integrity: sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.30.1': + resolution: {integrity: sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + resolution: {integrity: sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + resolution: {integrity: sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.30.1': + resolution: {integrity: sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-s390x-gnu@4.30.1': + resolution: {integrity: sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.30.1': + resolution: {integrity: sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.30.1': + resolution: {integrity: sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.30.1': + resolution: {integrity: sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.30.1': + resolution: {integrity: sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.30.1': + resolution: {integrity: sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==} + cpu: [x64] + os: [win32] + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@rushstack/eslint-patch@1.10.3': + resolution: {integrity: sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==} + + '@storybook/csf@0.0.1': + resolution: {integrity: sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==} + + '@tootallnate/once@1.1.2': + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/acorn@4.0.6': + resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + + '@types/concat-stream@2.0.3': + resolution: {integrity: sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/denodeify@1.2.35': + resolution: {integrity: sha512-5ixm6RMZKJUMlFKPZj378M/NOCGdodVVzvkYKlYbGp6mQ/r1C7C5KVroSTsZTI75qqmczZeOhhGywcWVURPMMw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/glob@8.1.0': + resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/is-empty@1.2.3': + resolution: {integrity: sha512-4J1l5d79hoIvsrKh5VUKVRA1aIdsOb10Hu5j3J2VfP/msDnfTdGPmNp2E1Wg+vs97Bktzo+MZePFFXSGoykYJw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/mdast@3.0.15': + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mime@2.0.3': + resolution: {integrity: sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==} + + '@types/minimatch@3.0.5': + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + + '@types/minimatch@5.1.2': + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + + '@types/mkdirp@0.5.2': + resolution: {integrity: sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==} + + '@types/mocha@5.2.7': + resolution: {integrity: sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==} + + '@types/ms@0.7.34': + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + '@types/node@20.12.12': + resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} + + '@types/node@22.10.6': + resolution: {integrity: sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==} + + '@types/node@8.10.66': + resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/supports-color@8.1.3': + resolution: {integrity: sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==} + + '@types/unist@2.0.10': + resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + + '@types/unist@3.0.2': + resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/eslint-plugin@8.20.0': + resolution: {integrity: sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@8.20.0': + resolution: {integrity: sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/scope-manager@8.20.0': + resolution: {integrity: sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/type-utils@8.20.0': + resolution: {integrity: sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/types@8.20.0': + resolution: {integrity: sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@8.20.0': + resolution: {integrity: sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/utils@8.20.0': + resolution: {integrity: sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/visitor-keys@8.20.0': + resolution: {integrity: sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + '@vercel/style-guide@5.2.0': + resolution: {integrity: sha512-fNSKEaZvSkiBoF6XEefs8CcgAV9K9e+MbcsDZjUsktHycKdA0jvjAzQi1W/FzLS+Nr5zZ6oejCwq/97dHUKe0g==} + engines: {node: '>=16'} + peerDependencies: + '@next/eslint-plugin-next': '>=12.3.0 <15' + eslint: '>=8.48.0 <9' + prettier: '>=3.0.0 <4' + typescript: '>=4.8.0 <6' + peerDependenciesMeta: + '@next/eslint-plugin-next': + optional: true + eslint: + optional: true + prettier: + optional: true + typescript: + optional: true + + abbrev@1.0.9: + resolution: {integrity: sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==} + + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + amdefine@1.0.1: + resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==} + engines: {node: '>=0.4.2'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + + ansi-regex@3.0.1: + resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} + engines: {node: '>=4'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + argv@0.0.2: + resolution: {integrity: sha512-dEamhpPEwRUBpLNHeuCm/v+g0anFByHahxodVO/BbAarHVBBg2MccCwf9K+o1Pof+2btdnkJelYVUWjW/VrATw==} + engines: {node: '>=0.6.10'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + array.prototype.toreversed@1.1.2: + resolution: {integrity: sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==} + + array.prototype.tosorted@1.1.3: + resolution: {integrity: sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + async@1.5.2: + resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.7.0: + resolution: {integrity: sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==} + engines: {node: '>=4'} + + axobject-query@3.2.1: + resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + + breakword@1.0.6: + resolution: {integrity: sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + builtin-modules@1.1.1: + resolution: {integrity: sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==} + engines: {node: '>=0.10.0'} + + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + + camelcase@4.1.0: + resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==} + engines: {node: '>=4'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001620: + resolution: {integrity: sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@3.5.0: + resolution: {integrity: sha512-eRYY0vPS2a9zt5w5Z0aCeWbrXTEyvk7u/Xf71EzNObrjSCPgMm1Nku/D/u2tiqHBX5j40wWhj54YJLtgn8g55A==} + engines: {node: '>= 0.4.0'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + ci-info@4.0.0: + resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} + engines: {node: '>=8'} + + clean-regexp@1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + + cliui@4.1.0: + resolution: {integrity: sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + code-point-at@1.1.0: + resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} + engines: {node: '>=0.10.0'} + + codecov@3.8.3: + resolution: {integrity: sha512-Y8Hw+V3HgR7V71xWH2vQ9lyS358CbGCldWlJFR0JirqoGtOoas3R3/OclRTvgUYFK29mmJICDPauVKmpqbwhOA==} + engines: {node: '>=4.0'} + deprecated: https://about.codecov.io/blog/codecov-uploader-deprecation-plan/ + hasBin: true + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + consola@3.4.0: + resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + engines: {node: ^14.18.0 || >=16.10.0} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + csv-generate@3.4.3: + resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} + + csv-parse@4.16.3: + resolution: {integrity: sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==} + + csv-stringify@5.6.5: + resolution: {integrity: sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==} + + csv@5.5.3: + resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} + engines: {node: '>= 0.1.90'} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + + deep-eql@0.1.3: + resolution: {integrity: sha512-6sEotTRGBFiNcqVoeHwnfopbSpi5NbH1VWJmYCVkmxMmaVTT0bUTrNaGyBwhgP4MZL012W/mkzIn3Da+iDYweg==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + denodeify@1.2.1: + resolution: {integrity: sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + detect-indent@7.0.1: + resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==} + engines: {node: '>=12.20'} + + detect-newline@4.0.1: + resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@0.7.2: + resolution: {integrity: sha512-qiB/Rir6Un6Ad/TIgTRzsremsTGWzs8j7woXvp14jgq00676uBiBT5eUOi+FgRywZFVy5Us/c04ISRpZhRbS6w==} + engines: {node: '>=0.10.0'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.4.774: + resolution: {integrity: sha512-132O1XCd7zcTkzS3FgkAzKmnBuNJjK8WjcTtNuoylj7MYbqw5eXehjQ5OK91g0zm7OTKIPeaAG4CPoRfD9M1Mg==} + + emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + enhanced-resolve@5.16.1: + resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==} + engines: {node: '>=10.13.0'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.0.19: + resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@1.8.1: + resolution: {integrity: sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==} + engines: {node: '>=0.12.0'} + hasBin: true + + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-config-turbo@2.0.0: + resolution: {integrity: sha512-EtdL8t3iuj6JFHq8nESXwnu0U7K/ug7dkxTsYNctuR6udOudjLMZz3A0P131Bz5ZFmPoFmkdHjlRYwocGgLbOw==} + peerDependencies: + eslint: '>6.6.0' + + eslint-import-resolver-alias@1.1.2: + resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} + engines: {node: '>= 4'} + peerDependencies: + eslint-plugin-import: '>=1.4.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.6.1: + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + + eslint-mdx@3.1.5: + resolution: {integrity: sha512-ynztX0k7CQ3iDL7fDEIeg3g0O/d6QPv7IBI9fdYLhXp5fAp0fi8X22xF/D3+Pk0f90R27uwqa1clHpay6t0l8Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + eslint: '>=8.0.0' + + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-module-utils@2.8.1: + resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-eslint-comments@3.2.0: + resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} + engines: {node: '>=6.5.0'} + peerDependencies: + eslint: '>=4.19.1' + + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jest@27.9.0: + resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + + eslint-plugin-jsx-a11y@6.8.0: + resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-plugin-markdown@3.0.1: + resolution: {integrity: sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + eslint-plugin-mdx@3.1.5: + resolution: {integrity: sha512-lUE7tP7IrIRHU3gTtASDe5u4YM2SvQveYVJfuo82yn3MLh/B/v05FNySURCK4aIxIYF1QYo3IRemQG/lyQzpAg==} + engines: {node: '>=18.0.0'} + peerDependencies: + eslint: '>=8.0.0' + + eslint-plugin-only-warn@1.1.0: + resolution: {integrity: sha512-2tktqUAT+Q3hCAU0iSf4xAN1k9zOpjK5WO8104mB0rT/dGhOa09582HN5HlbxNbPRZ0THV7nLGvzugcNOSjzfA==} + engines: {node: '>=6'} + + eslint-plugin-playwright@0.16.0: + resolution: {integrity: sha512-DcHpF0SLbNeh9MT4pMzUGuUSnJ7q5MWbP8sSEFIMS6j7Ggnduq8ghNlfhURgty4c1YFny7Ge9xYTO1FSAoV2Vw==} + peerDependencies: + eslint: '>=7' + eslint-plugin-jest: '>=25' + peerDependenciesMeta: + eslint-plugin-jest: + optional: true + + eslint-plugin-react-hooks@4.6.2: + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + + eslint-plugin-react@7.34.1: + resolution: {integrity: sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-plugin-regexp@2.7.0: + resolution: {integrity: sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA==} + engines: {node: ^18 || >=20} + peerDependencies: + eslint: '>=8.44.0' + + eslint-plugin-storybook@0.8.0: + resolution: {integrity: sha512-CZeVO5EzmPY7qghO2t64oaFM+8FTaD4uzOEjHKp516exyTKo+skKAL9GI3QALS2BXhyALJjNtwbmr1XinGE8bA==} + engines: {node: '>= 18'} + peerDependencies: + eslint: '>=6' + + eslint-plugin-testing-library@6.2.2: + resolution: {integrity: sha512-1E94YOTUDnOjSLyvOwmbVDzQi/WkKm3WVrMXu6SmBr6DN95xTGZmI6HJ/eOkSXh/DlheRsxaPsJvZByDBhWLVQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} + peerDependencies: + eslint: ^7.5.0 || ^8.0.0 + + eslint-plugin-tsdoc@0.2.17: + resolution: {integrity: sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==} + + eslint-plugin-turbo@2.0.0: + resolution: {integrity: sha512-31tZqfGbjBn6BzXVsmW50c2m8NDra6mOS2us/qHxUwN4YrHI/uYSpyItAw4qdVrxk7RmilvmnJ5WXFwtnfuLqw==} + peerDependencies: + eslint: '>6.6.0' + + eslint-plugin-unicorn@48.0.1: + resolution: {integrity: sha512-FW+4r20myG/DqFcCSzoumaddKBicIPeFnTrifon2mWIzlfyvzwyqZjqVP7m4Cqr/ZYisS2aiLghkUWaPg6vtCw==} + engines: {node: '>=16'} + peerDependencies: + eslint: '>=8.44.0' + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@2.7.3: + resolution: {integrity: sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==} + engines: {node: '>=0.10.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@1.9.3: + resolution: {integrity: sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==} + engines: {node: '>=0.10.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + esutils@1.1.6: + resolution: {integrity: sha512-RG1ZkUT7iFJG9LSHr7KDuuMSlujfeTtMNIcInURxKAxhMtwQhI3NrQhz26gZQYlsYZQKzsnwtpKrFKj9K9Qu1A==} + engines: {node: '>=0.10.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@1.0.0: + resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} + engines: {node: '>=6'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-url-parser@1.1.3: + resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fdir@6.4.2: + resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + + find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + find-yarn-workspace-root2@1.2.16: + resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + + fs-extra@4.0.3: + resolution: {integrity: sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@1.0.3: + resolution: {integrity: sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-stdin@9.0.0: + resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} + engines: {node: '>=12'} + + get-stream@4.1.0: + resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} + engines: {node: '>=6'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.7.5: + resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} + + git-hooks-list@3.1.0: + resolution: {integrity: sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@11.0.1: + resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} + engines: {node: 20 || >=22} + hasBin: true + + glob@5.0.15: + resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globals@15.14.0: + resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@1.0.0: + resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==} + engines: {node: '>=0.10.0'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + http-proxy-agent@4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + human-id@1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ignore-walk@3.0.4: + resolution: {integrity: sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==} + + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@4.1.2: + resolution: {integrity: sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + + invert-kv@2.0.0: + resolution: {integrity: sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==} + engines: {node: '>=4'} + + is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-empty@1.2.0: + resolution: {integrity: sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + + is-fullwidth-code-point@1.0.0: + resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul@0.4.5: + resolution: {integrity: sha512-nMtdn4hvK0HjUlzr1DrKSUY8ychprt8dzHOgY2KXsIhHu5PuQQEOTM27gV9Xblyon7aUH/TSFIjRHEODF/FRPg==} + deprecated: |- + This module is no longer maintained, try this instead: + npm i nyc + Visit https://istanbul.js.org/integrations for other alternatives. + hasBin: true + + iterator.prototype@1.1.2: + resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jackspeak@4.0.2: + resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==} + engines: {node: 20 || >=22} + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} + engines: {node: '>=12.0.0'} + + jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-parse-even-better-errors@3.0.2: + resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + language-subtag-registry@0.3.22: + resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + lcid@2.0.0: + resolution: {integrity: sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==} + engines: {node: '>=6'} + + levn@0.3.0: + resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} + engines: {node: '>= 0.8.0'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lines-and-columns@2.0.4: + resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + load-plugin@6.0.3: + resolution: {integrity: sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + load-yaml-file@0.2.0: + resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} + engines: {node: '>=6'} + + locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + + lru-cache@11.0.2: + resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} + engines: {node: 20 || >=22} + + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + map-age-cleaner@0.1.3: + resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==} + engines: {node: '>=6'} + + map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + + mdast-util-from-markdown@0.8.5: + resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + + mdast-util-from-markdown@2.0.0: + resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==} + + mdast-util-mdx-expression@2.0.0: + resolution: {integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==} + + mdast-util-mdx-jsx@3.1.2: + resolution: {integrity: sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.0: + resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + + mdast-util-to-string@2.0.0: + resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mem@4.3.0: + resolution: {integrity: sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==} + engines: {node: '>=6'} + + meow@6.1.1: + resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} + engines: {node: '>=8'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-core-commonmark@2.0.1: + resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} + + micromark-extension-mdx-expression@3.0.0: + resolution: {integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==} + + micromark-extension-mdx-jsx@3.0.0: + resolution: {integrity: sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + + micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + + micromark-factory-mdx-expression@2.0.1: + resolution: {integrity: sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==} + + micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + + micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + + micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + + micromark-util-character@2.1.0: + resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + + micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + + micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + + micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + + micromark-util-decode-numeric-character-reference@2.0.1: + resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + + micromark-util-decode-string@2.0.0: + resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + + micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + + micromark-util-events-to-acorn@2.0.2: + resolution: {integrity: sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==} + + micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + + micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + + micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + + micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + + micromark-util-subtokenize@2.0.1: + resolution: {integrity: sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==} + + micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + + micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + + micromark@2.11.4: + resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + + micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + + micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.0.1: + resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mixme@0.5.10: + resolution: {integrity: sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q==} + engines: {node: '>= 8.0.0'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mocha-typescript@1.1.17: + resolution: {integrity: sha512-Ge6pCQkZumkkhxVNdAf3JxunskShgaynCb30HYD7TT1Yhog/7NW2+6w5RcRHI+nuQrCMTX6z1+qf2pD8qwCoQA==} + deprecated: mocha-typescript has been deprecated, use @testdeck/mocha instead + hasBin: true + + mocha@11.0.1: + resolution: {integrity: sha512-+3GkODfsDG71KSCQhc4IekSW+ItCK/kiez1Z28ksWvYhKXV/syxMlerR/sC7whDp7IyreZ4YxceMLdTs5hQE8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + nopt@3.0.6: + resolution: {integrity: sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==} + hasBin: true + + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-normalize-package-bin@3.0.1: + resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + + number-is-nan@1.0.1: + resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + + object-keys@0.4.0: + resolution: {integrity: sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.hasown@1.1.4: + resolution: {integrity: sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==} + engines: {node: '>= 0.4'} + + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-locale@3.1.0: + resolution: {integrity: sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==} + engines: {node: '>=6'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + p-defer@1.0.0: + resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} + engines: {node: '>=4'} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-is-promise@2.1.0: + resolution: {integrity: sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==} + engines: {node: '>=6'} + + p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + + parse-entities@4.0.1: + resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-json@7.1.1: + resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} + engines: {node: '>=16'} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + preferred-pm@3.1.3: + resolution: {integrity: sha512-MkXsENfftWSRpzCzImcp4FRsCc3y1opwB73CfCNWyzMqArju2CrlMHlqB7VexKiPEOjGMbttv1r9fSCn5S610w==} + engines: {node: '>=10'} + + prelude-ls@1.1.2: + resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} + engines: {node: '>= 0.8.0'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-packagejson@2.5.0: + resolution: {integrity: sha512-6XkH3rpin5QEQodBSVNg+rBo4r91g/1mCaRwS1YGdQJZ6jwqrg2UchBsIG9tpS1yK1kNBvOt84OILsX8uHzBGg==} + peerDependencies: + prettier: '>= 1.16.0' + peerDependenciesMeta: + prettier: + optional: true + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + progress-stream@1.2.0: + resolution: {integrity: sha512-MIBPjZz6oGNSw5rn2mSp+nP9FGoaVo6QsPyPVEaD4puilz5hZNa3kfnrlqRNYFsugslbU3An4mnkLLtZOaWvrA==} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + + pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + + punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + q@1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + read-package-json-fast@3.0.2: + resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + + readable-stream@1.1.14: + resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.1: + resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} + engines: {node: '>= 14.18.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + reflect.getprototypeof@1.0.6: + resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} + engines: {node: '>= 0.4'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + + regjsparser@0.10.0: + resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} + hasBin: true + + remark-mdx@3.0.1: + resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-main-filename@1.0.1: + resolution: {integrity: sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + requireindex@1.2.0: + resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} + engines: {node: '>=0.10.5'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.1.7: + resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} + + resolve@1.19.0: + resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@6.0.1: + resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} + engines: {node: 20 || >=22} + hasBin: true + + rollup@4.30.1: + resolution: {integrity: sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + engines: {node: '>=10'} + hasBin: true + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + + slide@1.1.6: + resolution: {integrity: sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==} + + smartwrap@2.0.2: + resolution: {integrity: sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==} + engines: {node: '>=6'} + hasBin: true + + sort-object-keys@1.1.3: + resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} + + sort-package-json@2.10.0: + resolution: {integrity: sha512-MYecfvObMwJjjJskhxYfuOADkXp1ZMMnCFC8yhp+9HDsk7HhR336hd7eiBs96lTXfiqmUNI+WQCeCMRBhl251g==} + hasBin: true + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.2.0: + resolution: {integrity: sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==} + engines: {node: '>=0.8.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + + spawndamnit@2.0.0: + resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.17: + resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} + + speedometer@0.1.4: + resolution: {integrity: sha512-phdEoDlA6EUIVtzwq1UiNMXDUogczp204aYF/yfOhjNePWFfIpBJ1k5wLMuXQhEOOMjuTJEcc4vdZa+vuP+n/Q==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stream-events@1.0.5: + resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==} + + stream-transform@2.1.3: + resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==} + + string-width@1.0.2: + resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==} + engines: {node: '>=0.10.0'} + + string-width@2.1.1: + resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} + engines: {node: '>=4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@6.1.0: + resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} + engines: {node: '>=16'} + + string.prototype.matchall@4.0.11: + resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@3.0.1: + resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} + engines: {node: '>=0.10.0'} + + strip-ansi@4.0.0: + resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} + engines: {node: '>=4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + stubs@3.0.0: + resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@3.2.3: + resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==} + engines: {node: '>=0.8.0'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-color@9.4.0: + resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} + engines: {node: '>=12'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + synckit@0.9.0: + resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==} + engines: {node: ^14.18.0 || >=16.0.0} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + teeny-request@7.1.1: + resolution: {integrity: sha512-iwY6rkW5DDGq8hE2YgNQlKbptYpY5Nn2xecjQiNjOXWbKzPGUfmeUBCSQbbr306d7Z7U2N0TPl+/SwYRfua1Dg==} + engines: {node: '>=10'} + + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through2@0.2.3: + resolution: {integrity: sha512-mLa8Bn2mZurjyomGKWRu3Bo2mvoQojFks9NvOK8H+k4kDJNkdEqG522KFZsEFBEl6rKkxTgFbE5+OPcgfvPEHA==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.10: + resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} + engines: {node: '>=12.0.0'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-api-utils@2.0.0: + resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + tslint-eslint-rules@4.1.1: + resolution: {integrity: sha512-QS9o6vNZ2XwWxW+DE5uXde1dhQ2ebNuvebjfF/P4b9uACPdzxQCkaHjNU5GO+0UqPuOmZNR7mwsBaSlWQfCgVg==} + peerDependencies: + tslint: ^5.0.0 + + tslint-presets@2.0.0: + resolution: {integrity: sha512-S6vPoh52/34JbvzKOxgDVb51/uMyKGIGa2P/ghpgtcpFnkh9gB3MlIVL2cgqdgM7X6WfsFqbxPCgv+2ifejG6w==} + + tslint@5.20.1: + resolution: {integrity: sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==} + engines: {node: '>=4.8.0'} + hasBin: true + peerDependencies: + typescript: '>=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev' + + tslog@3.3.4: + resolution: {integrity: sha512-N0HHuHE0e/o75ALfkioFObknHR5dVchUad4F0XyFf3gXJYB++DewEzwGI/uIOM216E5a43ovnRNEeQIq9qgm4Q==} + engines: {node: '>=10'} + + tsup@8.3.5: + resolution: {integrity: sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + tsutils@1.9.1: + resolution: {integrity: sha512-Z4MMpdLvxER0Wz+l9TM71URBKGoHKBzArEraOFmTp44jxzdqiG8oTCtpjiZ9YtFXNwWQfMv+g8VAxTlBEVS6yw==} + peerDependencies: + typescript: '>=2.0.0 || >=2.0.0-dev || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >= 2.4.0-dev' + + tsutils@2.29.0: + resolution: {integrity: sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==} + peerDependencies: + typescript: '>=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev' + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + 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' + + tty-table@4.2.3: + resolution: {integrity: sha512-Fs15mu0vGzCrj8fmJNP7Ynxt5J7praPXqFN0leZeZBXJwkMxv9cb2D454k1ltrtUSJbZ4yH4e0CynsHLxmUfFA==} + engines: {node: '>=8.0.0'} + hasBin: true + + turbo-darwin-64@2.3.3: + resolution: {integrity: sha512-bxX82xe6du/3rPmm4aCC5RdEilIN99VUld4HkFQuw+mvFg6darNBuQxyWSHZTtc25XgYjQrjsV05888w1grpaA==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.3.3: + resolution: {integrity: sha512-DYbQwa3NsAuWkCUYVzfOUBbSUBVQzH5HWUFy2Kgi3fGjIWVZOFk86ss+xsWu//rlEAfYwEmopigsPYSmW4X15A==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.3.3: + resolution: {integrity: sha512-eHj9OIB0dFaP6BxB88jSuaCLsOQSYWBgmhy2ErCu6D2GG6xW3b6e2UWHl/1Ho9FsTg4uVgo4DB9wGsKa5erjUA==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.3.3: + resolution: {integrity: sha512-NmDE/NjZoDj1UWBhMtOPmqFLEBKhzGS61KObfrDEbXvU3lekwHeoPvAMfcovzswzch+kN2DrtbNIlz+/rp8OCg==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.3.3: + resolution: {integrity: sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.3.3: + resolution: {integrity: sha512-dW4ZK1r6XLPNYLIKjC4o87HxYidtRRcBeo/hZ9Wng2XM/MqqYkAyzJXJGgRMsc0MMEN9z4+ZIfnSNBrA0b08ag==} + cpu: [arm64] + os: [win32] + + turbo@2.3.3: + resolution: {integrity: sha512-DUHWQAcC8BTiUZDRzAYGvpSpGLiaOQPfYXlCieQbwUvmml/LRGIe3raKdrOPOoiX0DYlzxs2nH6BoWJoZrj8hA==} + hasBin: true + + type-check@0.3.2: + resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} + engines: {node: '>= 0.8.0'} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@0.1.1: + resolution: {integrity: sha512-5rqszGVwYgBoDkIm2oUtvkfZMQ0vk29iDMU0W2qCa3rG0vPDNczCMT4hV/bLBgLg8k8ri6+u3Zbt+S/14eMzlA==} + + type-detect@1.0.0: + resolution: {integrity: sha512-f9Uv6ezcpvCQjJU0Zqbg+65qdcszv3qUQsZfjdRbWiZ7AMenrX1u0lNk9EoWWX6e1F+NULyg27mtdeZ5WhpljA==} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typescript-eslint@8.20.0: + resolution: {integrity: sha512-Kxz2QRFsgbWj6Xcftlw3Dd154b3cEPFqQC+qMZrMypSijPd4UanKKvoKDrJ4o8AIfZFKAF+7sMaEIR8mTElozA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + unified-engine@11.2.1: + resolution: {integrity: sha512-xBAdZ8UY2X4R9Hm6X6kMne4Nz0PlpOc1oE6DPeqJnewr5Imkb8uT5Eyvy1h7xNekPL3PSWh3ZJyNrMW6jnNQBg==} + + unified@11.0.4: + resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} + + unist-util-inspect@8.0.0: + resolution: {integrity: sha512-/3Wn/wU6/H6UEo4FoYUeo8KUePN8ERiZpQYFWYoihOsr1DoDuv80PeB0hobVZyYSvALa2e556bG1A1/AbwU4yg==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@2.0.3: + resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + update-browserslist-db@1.0.16: + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + urlgrey@1.0.0: + resolution: {integrity: sha512-hJfIzMPJmI9IlLkby8QrsCykQ+SXDeO2W5Q9QTW3QpqZVTx4a/K7p8/5q+/isD8vsbVaFgql/gvAoQCRQ2Cb5w==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile-reporter@8.1.1: + resolution: {integrity: sha512-qxRZcnFSQt6pWKn3PAk81yLK2rO2i7CDXpy8v8ZquiEOMLSnPw6BMSi9Y1sUCwGGl7a9b3CJT1CKpnRF7pp66g==} + + vfile-sort@4.0.0: + resolution: {integrity: sha512-lffPI1JrbHDTToJwcq0rl6rBmkjQmMuXkAxsZPRS9DXbaJQvc642eCg6EGxcX2i1L+esbuhq+2l9tBll5v8AeQ==} + + vfile-statistics@3.0.0: + resolution: {integrity: sha512-/qlwqwWBWFOmpXujL/20P+Iuydil0rZZNglR+VNm6J0gpLHwuVM5s7g2TfVoswbXjZ4HuIhLMySEyIw5i7/D8w==} + + vfile@6.0.1: + resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + + walk-up-path@3.0.1: + resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-builtin-type@1.1.3: + resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-pm@2.0.0: + resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} + engines: {node: '>=8.15'} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + + wrap-ansi@2.1.0: + resolution: {integrity: sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@1.3.4: + resolution: {integrity: sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==} + + xtend@2.1.2: + resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==} + engines: {node: '>=0.4'} + + y18n@3.2.2: + resolution: {integrity: sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.4.2: + resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-parser@9.0.2: + resolution: {integrity: sha512-CswCfdOgCr4MMsT1GzbEJ7Z2uYudWyrGX8Bgh/0eyCzj/DXWdKq6a/ADufkzI1WAOIW6jYaXJvRyLhDO0kfqBw==} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@11.1.1: + resolution: {integrity: sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.24.2': + dependencies: + '@babel/highlight': 7.24.5 + picocolors: 1.0.1 + + '@babel/compat-data@7.24.4': {} + + '@babel/core@7.24.5': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.5 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) + '@babel/helpers': 7.24.5 + '@babel/parser': 7.24.5 + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.5 + '@babel/types': 7.24.5 + convert-source-map: 2.0.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/eslint-parser@7.24.5(@babel/core@7.24.5)(eslint@8.57.1)': + dependencies: + '@babel/core': 7.24.5 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 8.57.1 + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + + '@babel/generator@7.24.5': + dependencies: + '@babel/types': 7.24.5 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + + '@babel/helper-compilation-targets@7.23.6': + dependencies: + '@babel/compat-data': 7.24.4 + '@babel/helper-validator-option': 7.23.5 + browserslist: 4.23.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-environment-visitor@7.22.20': {} + + '@babel/helper-function-name@7.23.0': + dependencies: + '@babel/template': 7.24.0 + '@babel/types': 7.24.5 + + '@babel/helper-hoist-variables@7.22.5': + dependencies: + '@babel/types': 7.24.5 + + '@babel/helper-module-imports@7.24.3': + dependencies: + '@babel/types': 7.24.5 + + '@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-simple-access': 7.24.5 + '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-validator-identifier': 7.24.5 + + '@babel/helper-simple-access@7.24.5': + dependencies: + '@babel/types': 7.24.5 + + '@babel/helper-split-export-declaration@7.24.5': + dependencies: + '@babel/types': 7.24.5 + + '@babel/helper-string-parser@7.24.1': {} + + '@babel/helper-validator-identifier@7.24.5': {} + + '@babel/helper-validator-option@7.23.5': {} + + '@babel/helpers@7.24.5': + dependencies: + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.5 + '@babel/types': 7.24.5 + transitivePeerDependencies: + - supports-color + + '@babel/highlight@7.24.5': + dependencies: + '@babel/helper-validator-identifier': 7.24.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/parser@7.24.5': + dependencies: + '@babel/types': 7.24.5 + + '@babel/runtime@7.24.5': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.24.0': + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/parser': 7.24.5 + '@babel/types': 7.24.5 + + '@babel/traverse@7.24.5': + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.24.5 + '@babel/parser': 7.24.5 + '@babel/types': 7.24.5 + debug: 4.4.0(supports-color@8.1.1) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.24.5': + dependencies: + '@babel/helper-string-parser': 7.24.1 + '@babel/helper-validator-identifier': 7.24.5 + to-fast-properties: 2.0.0 + + '@changesets/apply-release-plan@7.0.1': + dependencies: + '@babel/runtime': 7.24.5 + '@changesets/config': 3.0.0 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.6.2 + + '@changesets/assemble-release-plan@6.0.0': + dependencies: + '@babel/runtime': 7.24.5 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.0.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.6.2 + + '@changesets/changelog-git@0.2.0': + dependencies: + '@changesets/types': 6.0.0 + + '@changesets/cli@2.27.2': + dependencies: + '@babel/runtime': 7.24.5 + '@changesets/apply-release-plan': 7.0.1 + '@changesets/assemble-release-plan': 6.0.0 + '@changesets/changelog-git': 0.2.0 + '@changesets/config': 3.0.0 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.0.0 + '@changesets/get-release-plan': 4.0.0 + '@changesets/git': 3.0.0 + '@changesets/logger': 0.1.0 + '@changesets/pre': 2.0.0 + '@changesets/read': 0.6.0 + '@changesets/types': 6.0.0 + '@changesets/write': 0.3.1 + '@manypkg/get-packages': 1.1.3 + '@types/semver': 7.5.8 + ansi-colors: 4.1.3 + chalk: 2.4.2 + ci-info: 3.9.0 + enquirer: 2.4.1 + external-editor: 3.1.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + meow: 6.1.1 + outdent: 0.5.0 + p-limit: 2.3.0 + preferred-pm: 3.1.3 + resolve-from: 5.0.0 + semver: 7.6.2 + spawndamnit: 2.0.0 + term-size: 2.2.1 + tty-table: 4.2.3 + + '@changesets/config@3.0.0': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.0.0 + '@changesets/logger': 0.1.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.5 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.0.0': + dependencies: + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + chalk: 2.4.2 + fs-extra: 7.0.1 + semver: 7.6.2 + + '@changesets/get-release-plan@4.0.0': + dependencies: + '@babel/runtime': 7.24.5 + '@changesets/assemble-release-plan': 6.0.0 + '@changesets/config': 3.0.0 + '@changesets/pre': 2.0.0 + '@changesets/read': 0.6.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.0': + dependencies: + '@babel/runtime': 7.24.5 + '@changesets/errors': 0.2.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.5 + spawndamnit: 2.0.0 + + '@changesets/logger@0.1.0': + dependencies: + chalk: 2.4.2 + + '@changesets/parse@0.4.0': + dependencies: + '@changesets/types': 6.0.0 + js-yaml: 3.14.1 + + '@changesets/pre@2.0.0': + dependencies: + '@babel/runtime': 7.24.5 + '@changesets/errors': 0.2.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.0': + dependencies: + '@babel/runtime': 7.24.5 + '@changesets/git': 3.0.0 + '@changesets/logger': 0.1.0 + '@changesets/parse': 0.4.0 + '@changesets/types': 6.0.0 + chalk: 2.4.2 + fs-extra: 7.0.1 + p-filter: 2.1.0 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.0.0': {} + + '@changesets/write@0.3.1': + dependencies: + '@babel/runtime': 7.24.5 + '@changesets/types': 6.0.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + prettier: 2.8.8 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.24.2': + optional: true + + '@esbuild/android-arm@0.24.2': + optional: true + + '@esbuild/android-x64@0.24.2': + optional: true + + '@esbuild/darwin-arm64@0.24.2': + optional: true + + '@esbuild/darwin-x64@0.24.2': + optional: true + + '@esbuild/freebsd-arm64@0.24.2': + optional: true + + '@esbuild/freebsd-x64@0.24.2': + optional: true + + '@esbuild/linux-arm64@0.24.2': + optional: true + + '@esbuild/linux-arm@0.24.2': + optional: true + + '@esbuild/linux-ia32@0.24.2': + optional: true + + '@esbuild/linux-loong64@0.24.2': + optional: true + + '@esbuild/linux-mips64el@0.24.2': + optional: true + + '@esbuild/linux-ppc64@0.24.2': + optional: true + + '@esbuild/linux-riscv64@0.24.2': + optional: true + + '@esbuild/linux-s390x@0.24.2': + optional: true + + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + + '@esbuild/openbsd-x64@0.24.2': + optional: true + + '@esbuild/sunos-x64@0.24.2': + optional: true + + '@esbuild/win32-arm64@0.24.2': + optional: true + + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.10.0': {} + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@eslint/js@9.18.0': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.0(supports-color@8.1.1) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.24.5 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.24.5 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@microsoft/tsdoc-config@0.16.2': + dependencies: + '@microsoft/tsdoc': 0.14.2 + ajv: 6.12.6 + jju: 1.4.0 + resolve: 1.19.0 + + '@microsoft/tsdoc@0.14.2': {} + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + dependencies: + eslint-scope: 5.1.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@npmcli/config@8.3.2': + dependencies: + '@npmcli/map-workspaces': 3.0.6 + ci-info: 4.0.0 + ini: 4.1.2 + nopt: 7.2.1 + proc-log: 4.2.0 + read-package-json-fast: 3.0.2 + semver: 7.6.2 + walk-up-path: 3.0.1 + + '@npmcli/map-workspaces@3.0.6': + dependencies: + '@npmcli/name-from-folder': 2.0.0 + glob: 10.4.5 + minimatch: 9.0.4 + read-package-json-fast: 3.0.2 + + '@npmcli/name-from-folder@2.0.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.1.1': {} + + '@rollup/rollup-android-arm-eabi@4.30.1': + optional: true + + '@rollup/rollup-android-arm64@4.30.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.30.1': + optional: true + + '@rollup/rollup-darwin-x64@4.30.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.30.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.30.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.30.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.30.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.30.1': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.30.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.30.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.30.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.30.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.30.1': + optional: true + + '@rtsao/scc@1.1.0': {} + + '@rushstack/eslint-patch@1.10.3': {} + + '@storybook/csf@0.0.1': + dependencies: + lodash: 4.17.21 + + '@tootallnate/once@1.1.2': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/acorn@4.0.6': + dependencies: + '@types/estree': 1.0.5 + + '@types/concat-stream@2.0.3': + dependencies: + '@types/node': 22.10.6 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 0.7.34 + + '@types/denodeify@1.2.35': {} + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.5 + + '@types/estree@1.0.5': {} + + '@types/estree@1.0.6': {} + + '@types/glob@8.1.0': + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 22.10.6 + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.2 + + '@types/is-empty@1.2.3': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/mdast@3.0.15': + dependencies: + '@types/unist': 2.0.10 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.2 + + '@types/mime@2.0.3': {} + + '@types/minimatch@3.0.5': {} + + '@types/minimatch@5.1.2': {} + + '@types/minimist@1.2.5': {} + + '@types/mkdirp@0.5.2': + dependencies: + '@types/node': 22.10.6 + + '@types/mocha@5.2.7': {} + + '@types/ms@0.7.34': {} + + '@types/node@12.20.55': {} + + '@types/node@20.12.12': + dependencies: + undici-types: 5.26.5 + + '@types/node@22.10.6': + dependencies: + undici-types: 6.20.0 + + '@types/node@8.10.66': {} + + '@types/normalize-package-data@2.4.4': {} + + '@types/semver@7.5.8': {} + + '@types/supports-color@8.1.3': {} + + '@types/unist@2.0.10': {} + + '@types/unist@3.0.2': {} + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.4 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.6.2 + ts-api-utils: 1.3.0(typescript@5.7.3) + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 8.20.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/type-utils': 8.20.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/utils': 8.20.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/visitor-keys': 8.20.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 2.0.0(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 8.20.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/type-utils': 8.20.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/utils': 8.20.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.20.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.4 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/typescript-estree': 8.20.0(typescript@4.9.5) + '@typescript-eslint/visitor-keys': 8.20.0 + debug: 4.4.0(supports-color@8.1.1) + eslint: 8.57.1 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.20.0 + debug: 4.4.0(supports-color@8.1.1) + eslint: 8.57.1 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/scope-manager@8.20.0': + dependencies: + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/visitor-keys': 8.20.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.7.3) + debug: 4.4.0(supports-color@8.1.1) + eslint: 8.57.1 + ts-api-utils: 1.3.0(typescript@5.7.3) + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@8.20.0(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@typescript-eslint/typescript-estree': 8.20.0(typescript@4.9.5) + '@typescript-eslint/utils': 8.20.0(eslint@8.57.1)(typescript@4.9.5) + debug: 4.4.0(supports-color@8.1.1) + eslint: 8.57.1 + ts-api-utils: 2.0.0(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@8.20.0(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3) + '@typescript-eslint/utils': 8.20.0(eslint@8.57.1)(typescript@5.7.3) + debug: 4.4.0(supports-color@8.1.1) + eslint: 8.57.1 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@5.62.0': {} + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/types@8.20.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.0(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.6.2 + tsutils: 3.21.0(typescript@5.7.3) + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.0(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.6.2 + ts-api-utils: 1.3.0(typescript@5.7.3) + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.20.0(typescript@4.9.5)': + dependencies: + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/visitor-keys': 8.20.0 + debug: 4.4.0(supports-color@8.1.1) + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.2 + ts-api-utils: 2.0.0(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.20.0(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/visitor-keys': 8.20.0 + debug: 4.4.0(supports-color@8.1.1) + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.2 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.7.3) + eslint: 8.57.1 + eslint-scope: 5.1.1 + semver: 7.6.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.7.3) + eslint: 8.57.1 + semver: 7.6.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@8.20.0(eslint@8.57.1)(typescript@4.9.5)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/typescript-estree': 8.20.0(typescript@4.9.5) + eslint: 8.57.1 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.20.0(eslint@8.57.1)(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3) + eslint: 8.57.1 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@typescript-eslint/visitor-keys@8.20.0': + dependencies: + '@typescript-eslint/types': 8.20.0 + eslint-visitor-keys: 4.2.0 + + '@ungap/structured-clone@1.2.0': {} + + '@vercel/style-guide@5.2.0(eslint@8.57.1)(prettier@3.2.5)(typescript@5.7.3)': + dependencies: + '@babel/core': 7.24.5 + '@babel/eslint-parser': 7.24.5(@babel/core@7.24.5)(eslint@8.57.1) + '@rushstack/eslint-patch': 1.10.3 + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.3) + eslint-config-prettier: 9.1.0(eslint@8.57.1) + eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) + eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.1) + eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1) + eslint-plugin-react: 7.34.1(eslint@8.57.1) + eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) + eslint-plugin-testing-library: 6.2.2(eslint@8.57.1)(typescript@5.7.3) + eslint-plugin-tsdoc: 0.2.17 + eslint-plugin-unicorn: 48.0.1(eslint@8.57.1) + prettier-plugin-packagejson: 2.5.0(prettier@3.2.5) + optionalDependencies: + eslint: 8.57.1 + prettier: 3.2.5 + typescript: 5.7.3 + transitivePeerDependencies: + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - jest + - supports-color + + abbrev@1.0.9: {} + + abbrev@2.0.0: {} + + acorn-jsx@5.3.2(acorn@8.11.3): + dependencies: + acorn: 8.11.3 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.11.3 + + acorn@8.11.3: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.0(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + amdefine@1.0.1: + optional: true + + ansi-colors@4.1.3: {} + + ansi-regex@2.1.1: {} + + ansi-regex@3.0.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + argv@0.0.2: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + + array-union@2.1.0: {} + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.findlastindex@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.toreversed@1.1.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.tosorted@1.1.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + + arrify@1.0.1: {} + + assertion-error@1.1.0: {} + + ast-types-flow@0.0.8: {} + + async@1.5.2: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + axe-core@4.7.0: {} + + axobject-query@3.2.1: + dependencies: + dequal: 2.0.3 + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.2: + dependencies: + fill-range: 7.0.1 + + breakword@1.0.6: + dependencies: + wcwidth: 1.0.1 + + browser-stdout@1.3.1: {} + + browserslist@4.23.0: + dependencies: + caniuse-lite: 1.0.30001620 + electron-to-chromium: 1.4.774 + node-releases: 2.0.14 + update-browserslist-db: 1.0.16(browserslist@4.23.0) + + buffer-from@1.1.2: {} + + builtin-modules@1.1.1: {} + + builtin-modules@3.3.0: {} + + bundle-require@5.1.0(esbuild@0.24.2): + dependencies: + esbuild: 0.24.2 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + + camelcase-keys@6.2.2: + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + + camelcase@4.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001620: {} + + ccount@2.0.1: {} + + chai@3.5.0: + dependencies: + assertion-error: 1.1.0 + deep-eql: 0.1.3 + type-detect: 1.0.0 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities-html4@2.1.0: {} + + character-entities-legacy@1.1.4: {} + + character-entities-legacy@3.0.0: {} + + character-entities@1.2.4: {} + + character-entities@2.0.2: {} + + character-reference-invalid@1.1.4: {} + + character-reference-invalid@2.0.1: {} + + chardet@0.7.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.1 + + ci-info@3.9.0: {} + + ci-info@4.0.0: {} + + clean-regexp@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + cliui@4.1.0: + dependencies: + string-width: 2.1.1 + strip-ansi: 4.0.0 + wrap-ansi: 2.1.0 + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: {} + + code-point-at@1.1.0: {} + + codecov@3.8.3: + dependencies: + argv: 0.0.2 + ignore-walk: 3.0.4 + js-yaml: 3.14.1 + teeny-request: 7.1.1 + urlgrey: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + comment-parser@1.4.1: {} + + concat-map@0.0.1: {} + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + consola@3.4.0: {} + + convert-source-map@2.0.0: {} + + core-util-is@1.0.3: {} + + create-require@1.1.1: {} + + cross-spawn@5.1.0: + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csv-generate@3.4.3: {} + + csv-parse@4.16.3: {} + + csv-stringify@5.6.5: {} + + csv@5.5.3: + dependencies: + csv-generate: 3.4.3 + csv-parse: 4.16.3 + csv-stringify: 5.6.5 + stream-transform: 2.1.3 + + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.3.4: + dependencies: + ms: 2.1.2 + + debug@4.4.0(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + + decamelize@4.0.0: {} + + decode-named-character-reference@1.0.2: + dependencies: + character-entities: 2.0.2 + + deep-eql@0.1.3: + dependencies: + type-detect: 0.1.1 + + deep-is@0.1.4: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + denodeify@1.2.1: {} + + dequal@2.0.3: {} + + detect-indent@6.1.0: {} + + detect-indent@7.0.1: {} + + detect-newline@4.0.1: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@4.0.2: {} + + diff@5.2.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@0.7.2: + dependencies: + esutils: 1.1.6 + isarray: 0.0.1 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dotenv@16.0.3: {} + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.4.774: {} + + emoji-regex@10.3.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + enhanced-resolve@5.16.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + errno@0.1.8: + dependencies: + prr: 1.0.1 + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.1 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + + es-iterator-helpers@1.0.19: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + iterator.prototype: 1.1.2 + safe-array-concat: 1.1.2 + + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + escalade@3.1.2: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escodegen@1.8.1: + dependencies: + esprima: 2.7.3 + estraverse: 1.9.3 + esutils: 2.0.3 + optionator: 0.8.3 + optionalDependencies: + source-map: 0.2.0 + + eslint-config-prettier@9.1.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-config-turbo@2.0.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-plugin-turbo: 2.0.0(eslint@8.57.1) + + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): + dependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1): + dependencies: + debug: 4.3.4 + enhanced-resolve: 5.16.1 + eslint: 8.57.1 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + fast-glob: 3.3.2 + get-tsconfig: 4.7.5 + is-core-module: 2.13.1 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-mdx@3.1.5(eslint@8.57.1): + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint: 8.57.1 + espree: 9.6.1 + estree-util-visit: 2.0.0 + remark-mdx: 3.0.1 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + synckit: 0.9.0 + tslib: 2.6.2 + unified: 11.0.4 + unified-engine: 11.2.1 + unist-util-visit: 5.0.0 + uvu: 0.5.6 + vfile: 6.0.1 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.20.0(eslint@8.57.1)(typescript@4.9.5) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.3) + eslint: 8.57.1 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): + dependencies: + escape-string-regexp: 1.0.5 + eslint: 8.57.1 + ignore: 5.3.1 + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.7.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.20.0(eslint@8.57.1)(typescript@4.9.5) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-import@2.31.0(eslint@8.57.1): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3): + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.7.3) + eslint: 8.57.1 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.1): + dependencies: + '@babel/runtime': 7.24.5 + aria-query: 5.3.0 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.2 + ast-types-flow: 0.0.8 + axe-core: 4.7.0 + axobject-query: 3.2.1 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + es-iterator-helpers: 1.0.19 + eslint: 8.57.1 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + + eslint-plugin-markdown@3.0.1(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + mdast-util-from-markdown: 0.8.5 + transitivePeerDependencies: + - supports-color + + eslint-plugin-mdx@3.1.5(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-mdx: 3.1.5(eslint@8.57.1) + eslint-plugin-markdown: 3.0.1(eslint@8.57.1) + remark-mdx: 3.0.1 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + tslib: 2.6.2 + unified: 11.0.4 + vfile: 6.0.1 + transitivePeerDependencies: + - supports-color + + eslint-plugin-only-warn@1.1.0: {} + + eslint-plugin-playwright@0.16.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + optionalDependencies: + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) + + eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-react@7.34.1(eslint@8.57.1): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.2 + array.prototype.toreversed: 1.1.2 + array.prototype.tosorted: 1.1.3 + doctrine: 2.1.0 + es-iterator-helpers: 1.0.19 + eslint: 8.57.1 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + object.hasown: 1.1.4 + object.values: 1.2.0 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.11 + + eslint-plugin-regexp@2.7.0(eslint@8.57.1): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 + comment-parser: 1.4.1 + eslint: 8.57.1 + jsdoc-type-pratt-parser: 4.1.0 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + scslre: 0.3.0 + + eslint-plugin-storybook@0.8.0(eslint@8.57.1)(typescript@5.7.3): + dependencies: + '@storybook/csf': 0.0.1 + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.7.3) + eslint: 8.57.1 + requireindex: 1.2.0 + ts-dedent: 2.2.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-testing-library@6.2.2(eslint@8.57.1)(typescript@5.7.3): + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.7.3) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-tsdoc@0.2.17: + dependencies: + '@microsoft/tsdoc': 0.14.2 + '@microsoft/tsdoc-config': 0.16.2 + + eslint-plugin-turbo@2.0.0(eslint@8.57.1): + dependencies: + dotenv: 16.0.3 + eslint: 8.57.1 + + eslint-plugin-unicorn@48.0.1(eslint@8.57.1): + dependencies: + '@babel/helper-validator-identifier': 7.24.5 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + ci-info: 3.9.0 + clean-regexp: 1.0.0 + eslint: 8.57.1 + esquery: 1.5.0 + indent-string: 4.0.0 + is-builtin-module: 3.2.1 + jsesc: 3.0.2 + lodash: 4.17.21 + pluralize: 8.0.0 + read-pkg-up: 7.0.1 + regexp-tree: 0.1.27 + regjsparser: 0.10.0 + semver: 7.6.2 + strip-indent: 3.0.0 + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.4.0(supports-color@8.1.1) + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + 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.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 3.4.3 + + esprima@2.7.3: {} + + esprima@4.0.1: {} + + esquery@1.5.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@1.9.3: {} + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.2 + + esutils@1.1.6: {} + + esutils@2.0.3: {} + + execa@1.0.0: + dependencies: + cross-spawn: 6.0.6 + get-stream: 4.1.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + + extend@3.0.2: {} + + extendable-error@0.1.7: {} + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-url-parser@1.1.3: + dependencies: + punycode: 1.4.1 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fdir@6.4.2(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.0.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@2.1.0: + dependencies: + locate-path: 2.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-yarn-workspace-root2@1.2.16: + dependencies: + micromatch: 4.0.5 + pkg-dir: 4.2.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + + flat@5.0.2: {} + + flatted@3.3.1: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.1.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + fs-extra@4.0.3: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@1.0.3: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-stdin@9.0.0: {} + + get-stream@4.1.0: + dependencies: + pump: 3.0.2 + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + + get-tsconfig@4.7.5: + dependencies: + resolve-pkg-maps: 1.0.0 + + git-hooks-list@3.1.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.1.1 + jackspeak: 3.4.3 + minimatch: 9.0.4 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@11.0.1: + dependencies: + foreground-child: 3.1.1 + jackspeak: 4.0.2 + minimatch: 10.0.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + + glob@5.0.15: + dependencies: + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@11.12.0: {} + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globals@15.14.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.0.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + globby@13.2.2: + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 4.0.0 + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + graceful-fs@4.2.11: {} + + grapheme-splitter@1.0.4: {} + + graphemer@1.4.0: {} + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + hard-rejection@2.1.0: {} + + has-bigints@1.0.2: {} + + has-flag@1.0.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + hosted-git-info@2.8.9: {} + + http-proxy-agent@4.0.1: + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + + human-id@1.0.2: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ignore-walk@3.0.4: + dependencies: + minimatch: 3.1.2 + + ignore@5.3.1: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.1.0: {} + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@4.1.2: {} + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 + + invert-kv@2.0.0: {} + + is-alphabetical@1.0.4: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@1.0.4: + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-arrayish@0.2.1: {} + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-builtin-module@3.2.1: + dependencies: + builtin-modules: 3.3.0 + + is-callable@1.2.7: {} + + is-core-module@2.13.1: + dependencies: + hasown: 2.0.2 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-decimal@1.0.4: {} + + is-decimal@2.0.1: {} + + is-empty@1.2.0: {} + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-fullwidth-code-point@1.0.0: + dependencies: + number-is-nan: 1.0.1 + + is-fullwidth-code-point@2.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@1.0.4: {} + + is-hexadecimal@2.0.1: {} + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@1.1.0: {} + + is-plain-obj@2.1.0: {} + + is-plain-obj@4.1.0: {} + + is-regex@1.1.4: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-stream@1.1.0: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-unicode-supported@0.1.0: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-windows@1.0.2: {} + + isarray@0.0.1: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + istanbul@0.4.5: + dependencies: + abbrev: 1.0.9 + async: 1.5.2 + escodegen: 1.8.1 + esprima: 2.7.3 + glob: 5.0.15 + handlebars: 4.7.8 + js-yaml: 3.14.1 + mkdirp: 0.5.6 + nopt: 3.0.6 + once: 1.4.0 + resolve: 1.1.7 + supports-color: 3.2.3 + which: 1.3.1 + wordwrap: 1.0.0 + + iterator.prototype@1.1.2: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.6 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jackspeak@4.0.2: + dependencies: + '@isaacs/cliui': 8.0.2 + + jju@1.4.0: {} + + joycon@3.1.1: {} + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsdoc-type-pratt-parser@4.1.0: {} + + jsesc@0.5.0: {} + + jsesc@2.5.2: {} + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-parse-even-better-errors@3.0.2: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.2 + object.assign: 4.1.5 + object.values: 1.2.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@6.0.3: {} + + kleur@4.1.5: {} + + language-subtag-registry@0.3.22: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.22 + + lcid@2.0.0: + dependencies: + invert-kv: 2.0.0 + + levn@0.3.0: + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lines-and-columns@2.0.4: {} + + load-plugin@6.0.3: + dependencies: + '@npmcli/config': 8.3.2 + import-meta-resolve: 4.1.0 + + load-tsconfig@0.2.5: {} + + load-yaml-file@0.2.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + + locate-path@2.0.0: + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash.sortby@4.7.0: {} + + lodash.startcase@4.4.0: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.2.2: {} + + lru-cache@11.0.2: {} + + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + make-error@1.3.6: {} + + map-age-cleaner@0.1.3: + dependencies: + p-defer: 1.0.0 + + map-obj@1.0.1: {} + + map-obj@4.3.0: {} + + mdast-util-from-markdown@0.8.5: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-string: 2.0.0 + micromark: 2.11.4 + parse-entities: 2.0.0 + unist-util-stringify-position: 2.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-from-markdown@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.2 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-decode-string: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.0 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.1.2: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.2 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.0 + mdast-util-to-markdown: 2.1.0 + parse-entities: 4.0.1 + stringify-entities: 4.0.4 + unist-util-remove-position: 5.0.0 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.0 + mdast-util-mdx-expression: 2.0.0 + mdast-util-mdx-jsx: 3.1.2 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.0 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-markdown@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.2 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-decode-string: 2.0.0 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@2.0.0: {} + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mem@4.3.0: + dependencies: + map-age-cleaner: 0.1.3 + mimic-fn: 2.1.0 + p-is-promise: 2.1.0 + + meow@6.1.1: + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + 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.1 + type-fest: 0.13.1 + yargs-parser: 18.1.3 + + merge2@1.4.1: {} + + micromark-core-commonmark@2.0.1: + dependencies: + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.0 + micromark-factory-label: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-factory-title: 2.0.0 + micromark-factory-whitespace: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-html-tag-name: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-mdx-expression@3.0.0: + dependencies: + '@types/estree': 1.0.5 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-events-to-acorn: 2.0.2 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-extension-mdx-jsx@3.0.0: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + vfile-message: 4.0.2 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.0 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.5 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.1 + micromark-util-character: 2.1.0 + micromark-util-events-to-acorn: 2.0.2 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + micromark-extension-mdx-expression: 3.0.0 + micromark-extension-mdx-jsx: 3.0.0 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-destination@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-label@2.0.0: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-mdx-expression@2.0.1: + dependencies: + '@types/estree': 1.0.5 + devlop: 1.1.0 + micromark-util-character: 2.1.0 + micromark-util-events-to-acorn: 2.0.2 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 + + micromark-factory-space@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-types: 2.0.0 + + micromark-factory-title@2.0.0: + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-factory-whitespace@2.0.0: + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-character@2.1.0: + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-chunked@2.0.0: + dependencies: + micromark-util-symbol: 2.0.0 + + micromark-util-classify-character@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-combine-extensions@2.0.0: + dependencies: + micromark-util-chunked: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-decode-numeric-character-reference@2.0.1: + dependencies: + micromark-util-symbol: 2.0.0 + + micromark-util-decode-string@2.0.0: + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-symbol: 2.0.0 + + micromark-util-encode@2.0.0: {} + + micromark-util-events-to-acorn@2.0.2: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.5 + '@types/unist': 3.0.2 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + vfile-message: 4.0.2 + + micromark-util-html-tag-name@2.0.0: {} + + micromark-util-normalize-identifier@2.0.0: + dependencies: + micromark-util-symbol: 2.0.0 + + micromark-util-resolve-all@2.0.0: + dependencies: + micromark-util-types: 2.0.0 + + micromark-util-sanitize-uri@2.0.0: + dependencies: + micromark-util-character: 2.1.0 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 + + micromark-util-subtokenize@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + + micromark-util-symbol@2.0.0: {} + + micromark-util-types@2.0.0: {} + + micromark@2.11.4: + dependencies: + debug: 4.4.0(supports-color@8.1.1) + parse-entities: 2.0.0 + transitivePeerDependencies: + - supports-color + + micromark@4.0.0: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.0(supports-color@8.1.1) + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-encode: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.5: + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimatch@10.0.1: + dependencies: + brace-expansion: 2.0.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.0.1 + + minimatch@9.0.4: + dependencies: + brace-expansion: 2.0.1 + + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mixme@0.5.10: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mocha-typescript@1.1.17: + dependencies: + '@types/mocha': 5.2.7 + chalk: 2.4.2 + cross-spawn: 6.0.6 + yargs: 11.1.1 + + mocha@11.0.1: + dependencies: + ansi-colors: 4.1.3 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.4.0(supports-color@8.1.1) + diff: 5.2.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 10.4.5 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.1.6 + ms: 2.1.3 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.5.1 + yargs: 16.2.0 + yargs-parser: 20.2.9 + yargs-unparser: 2.0.0 + + mri@1.2.0: {} + + ms@2.1.2: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + natural-compare@1.4.0: {} + + neo-async@2.6.2: {} + + nice-try@1.0.5: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.14: {} + + nopt@3.0.6: + dependencies: + abbrev: 1.0.9 + + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.8 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + npm-normalize-package-bin@3.0.1: {} + + npm-run-path@2.0.2: + dependencies: + path-key: 2.0.1 + + number-is-nan@1.0.1: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.1: {} + + object-keys@0.4.0: {} + + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + object.entries@1.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + object.hasown@1.1.4: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + object.values@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.8.3: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + word-wrap: 1.2.5 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + os-locale@3.1.0: + dependencies: + execa: 1.0.0 + lcid: 2.0.0 + mem: 4.3.0 + + os-tmpdir@1.0.2: {} + + outdent@0.5.0: {} + + p-defer@1.0.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-finally@1.0.0: {} + + p-is-promise@2.1.0: {} + + p-limit@1.3.0: + dependencies: + p-try: 1.0.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@2.0.0: + dependencies: + p-limit: 1.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@2.1.0: {} + + p-try@1.0.0: {} + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-entities@2.0.0: + dependencies: + character-entities: 1.2.4 + character-entities-legacy: 1.1.4 + character-reference-invalid: 1.1.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + is-hexadecimal: 1.0.4 + + parse-entities@4.0.1: + dependencies: + '@types/unist': 2.0.10 + character-entities: 2.0.2 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.0.2 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-json@7.1.1: + dependencies: + '@babel/code-frame': 7.24.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 3.0.2 + lines-and-columns: 2.0.4 + type-fest: 3.13.1 + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@2.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.2.2 + minipass: 7.1.2 + + path-scurry@2.0.0: + dependencies: + lru-cache: 11.0.2 + minipass: 7.1.2 + + path-type@4.0.0: {} + + picocolors@1.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pify@4.0.1: {} + + pirates@4.0.6: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pluralize@8.0.0: {} + + possible-typed-array-names@1.0.0: {} + + postcss-load-config@6.0.1(yaml@2.4.2): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + yaml: 2.4.2 + + preferred-pm@3.1.3: + dependencies: + find-up: 5.0.0 + find-yarn-workspace-root2: 1.2.16 + path-exists: 4.0.0 + which-pm: 2.0.0 + + prelude-ls@1.1.2: {} + + prelude-ls@1.2.1: {} + + prettier-plugin-packagejson@2.5.0(prettier@3.2.5): + dependencies: + sort-package-json: 2.10.0 + synckit: 0.9.0 + optionalDependencies: + prettier: 3.2.5 + + prettier@2.8.8: {} + + prettier@3.2.5: {} + + proc-log@4.2.0: {} + + progress-stream@1.2.0: + dependencies: + speedometer: 0.1.4 + through2: 0.2.3 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + prr@1.0.1: {} + + pseudomap@1.0.2: {} + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode@1.4.1: {} + + punycode@2.3.1: {} + + q@1.5.1: {} + + queue-microtask@1.2.3: {} + + quick-lru@4.0.1: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + react-is@16.13.1: {} + + read-package-json-fast@3.0.2: + dependencies: + json-parse-even-better-errors: 3.0.2 + npm-normalize-package-bin: 3.0.1 + + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + read-pkg@5.2.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + + readable-stream@1.1.14: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.1: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.12.1 + + reflect.getprototypeof@1.0.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + which-builtin-type: 1.1.3 + + regenerator-runtime@0.14.1: {} + + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.12.1 + refa: 0.12.1 + + regexp-tree@0.1.27: {} + + regexp.prototype.flags@1.5.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + regjsparser@0.10.0: + dependencies: + jsesc: 0.5.0 + + remark-mdx@3.0.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.0 + micromark-util-types: 2.0.0 + unified: 11.0.4 + transitivePeerDependencies: + - supports-color + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.0 + unified: 11.0.4 + + require-directory@2.1.1: {} + + require-main-filename@1.0.1: {} + + require-main-filename@2.0.0: {} + + requireindex@1.2.0: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.1.7: {} + + resolve@1.19.0: + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + + resolve@1.22.8: + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rimraf@6.0.1: + dependencies: + glob: 11.0.1 + package-json-from-dist: 1.0.1 + + rollup@4.30.1: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.30.1 + '@rollup/rollup-android-arm64': 4.30.1 + '@rollup/rollup-darwin-arm64': 4.30.1 + '@rollup/rollup-darwin-x64': 4.30.1 + '@rollup/rollup-freebsd-arm64': 4.30.1 + '@rollup/rollup-freebsd-x64': 4.30.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.30.1 + '@rollup/rollup-linux-arm-musleabihf': 4.30.1 + '@rollup/rollup-linux-arm64-gnu': 4.30.1 + '@rollup/rollup-linux-arm64-musl': 4.30.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.30.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.30.1 + '@rollup/rollup-linux-riscv64-gnu': 4.30.1 + '@rollup/rollup-linux-s390x-gnu': 4.30.1 + '@rollup/rollup-linux-x64-gnu': 4.30.1 + '@rollup/rollup-linux-x64-musl': 4.30.1 + '@rollup/rollup-win32-arm64-msvc': 4.30.1 + '@rollup/rollup-win32-ia32-msvc': 4.30.1 + '@rollup/rollup-win32-x64-msvc': 4.30.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + safer-buffer@2.1.2: {} + + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.12.1 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.6.2: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + set-blocking@2.0.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@1.0.0: {} + + shebang-regex@3.0.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.1 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + slash@4.0.0: {} + + slide@1.1.6: {} + + smartwrap@2.0.2: + dependencies: + array.prototype.flat: 1.3.2 + breakword: 1.0.6 + grapheme-splitter: 1.0.4 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + yargs: 15.4.1 + + sort-object-keys@1.1.3: {} + + sort-package-json@2.10.0: + dependencies: + detect-indent: 7.0.1 + detect-newline: 4.0.1 + get-stdin: 9.0.0 + git-hooks-list: 3.1.0 + globby: 13.2.2 + is-plain-obj: 4.1.0 + semver: 7.6.2 + sort-object-keys: 1.1.3 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.2.0: + dependencies: + amdefine: 1.0.1 + optional: true + + source-map@0.6.1: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + + spawndamnit@2.0.0: + dependencies: + cross-spawn: 5.1.0 + signal-exit: 3.0.7 + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.17 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.17 + + spdx-license-ids@3.0.17: {} + + speedometer@0.1.4: {} + + sprintf-js@1.0.3: {} + + stream-events@1.0.5: + dependencies: + stubs: 3.0.0 + + stream-transform@2.1.3: + dependencies: + mixme: 0.5.10 + + string-width@1.0.2: + dependencies: + code-point-at: 1.1.0 + is-fullwidth-code-point: 1.0.0 + strip-ansi: 3.0.1 + + string-width@2.1.1: + dependencies: + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string-width@6.1.0: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 10.3.0 + strip-ansi: 7.1.0 + + string.prototype.matchall@4.0.11: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + regexp.prototype.flags: 1.5.2 + set-function-name: 2.0.2 + side-channel: 1.0.6 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string_decoder@0.10.31: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + + strip-ansi@4.0.0: + dependencies: + ansi-regex: 3.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-bom@3.0.0: {} + + strip-eof@1.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + stubs@3.0.0: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@3.2.3: + dependencies: + has-flag: 1.0.0 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-color@9.4.0: {} + + supports-preserve-symlinks-flag@1.0.0: {} + + synckit@0.9.0: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.6.2 + + tapable@2.2.1: {} + + teeny-request@7.1.1: + dependencies: + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0 + stream-events: 1.0.5 + uuid: 8.3.2 + transitivePeerDependencies: + - encoding + - supports-color + + term-size@2.2.1: {} + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through2@0.2.3: + dependencies: + readable-stream: 1.1.14 + xtend: 2.1.2 + + tinyexec@0.3.2: {} + + tinyglobby@0.2.10: + dependencies: + fdir: 6.4.2(picomatch@4.0.2) + picomatch: 4.0.2 + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + trim-newlines@3.0.1: {} + + trough@2.2.0: {} + + ts-api-utils@1.3.0(typescript@5.7.3): + dependencies: + typescript: 5.7.3 + + ts-api-utils@2.0.0(typescript@4.9.5): + dependencies: + typescript: 4.9.5 + + ts-api-utils@2.0.0(typescript@5.7.3): + dependencies: + typescript: 5.7.3 + + ts-dedent@2.2.0: {} + + ts-interface-checker@0.1.13: {} + + ts-node@10.9.2(@types/node@22.10.6)(typescript@5.7.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.10.6 + acorn: 8.11.3 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.7.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + ts-node@10.9.2(@types/node@8.10.66)(typescript@4.9.5): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 8.10.66 + acorn: 8.11.3 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@1.14.1: {} + + tslib@2.6.2: {} + + tslint-eslint-rules@4.1.1(tslint@5.20.1(typescript@5.7.3))(typescript@5.7.3): + dependencies: + doctrine: 0.7.2 + tslib: 1.14.1 + tslint: 5.20.1(typescript@5.7.3) + tsutils: 1.9.1(typescript@5.7.3) + transitivePeerDependencies: + - typescript + + tslint-presets@2.0.0(typescript@5.7.3): + dependencies: + tslint: 5.20.1(typescript@5.7.3) + tslint-eslint-rules: 4.1.1(tslint@5.20.1(typescript@5.7.3))(typescript@5.7.3) + transitivePeerDependencies: + - typescript + + tslint@5.20.1(typescript@5.7.3): + dependencies: + '@babel/code-frame': 7.24.2 + builtin-modules: 1.1.1 + chalk: 2.4.2 + commander: 2.20.3 + diff: 4.0.2 + glob: 7.2.3 + js-yaml: 3.14.1 + minimatch: 3.1.2 + mkdirp: 0.5.6 + resolve: 1.22.8 + semver: 5.7.2 + tslib: 1.14.1 + tsutils: 2.29.0(typescript@5.7.3) + typescript: 5.7.3 + + tslog@3.3.4: + dependencies: + source-map-support: 0.5.21 + + tsup@8.3.5(typescript@4.9.5)(yaml@2.4.2): + dependencies: + bundle-require: 5.1.0(esbuild@0.24.2) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.0 + debug: 4.4.0(supports-color@8.1.1) + esbuild: 0.24.2 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(yaml@2.4.2) + resolve-from: 5.0.0 + rollup: 4.30.1 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.10 + tree-kill: 1.2.2 + optionalDependencies: + typescript: 4.9.5 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + tsutils@1.9.1(typescript@5.7.3): + dependencies: + typescript: 5.7.3 + + tsutils@2.29.0(typescript@5.7.3): + dependencies: + tslib: 1.14.1 + typescript: 5.7.3 + + tsutils@3.21.0(typescript@5.7.3): + dependencies: + tslib: 1.14.1 + typescript: 5.7.3 + + tty-table@4.2.3: + dependencies: + chalk: 4.1.2 + csv: 5.5.3 + kleur: 4.1.5 + smartwrap: 2.0.2 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + yargs: 17.7.2 + + turbo-darwin-64@2.3.3: + optional: true + + turbo-darwin-arm64@2.3.3: + optional: true + + turbo-linux-64@2.3.3: + optional: true + + turbo-linux-arm64@2.3.3: + optional: true + + turbo-windows-64@2.3.3: + optional: true + + turbo-windows-arm64@2.3.3: + optional: true + + turbo@2.3.3: + optionalDependencies: + turbo-darwin-64: 2.3.3 + turbo-darwin-arm64: 2.3.3 + turbo-linux-64: 2.3.3 + turbo-linux-arm64: 2.3.3 + turbo-windows-64: 2.3.3 + turbo-windows-arm64: 2.3.3 + + type-check@0.3.2: + dependencies: + prelude-ls: 1.1.2 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@0.1.1: {} + + type-detect@1.0.0: {} + + type-fest@0.13.1: {} + + type-fest@0.20.2: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + type-fest@3.13.1: {} + + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typedarray@0.0.6: {} + + typescript-eslint@8.20.0(eslint@8.57.1)(typescript@4.9.5): + dependencies: + '@typescript-eslint/eslint-plugin': 8.20.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': 8.20.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/utils': 8.20.0(eslint@8.57.1)(typescript@4.9.5) + eslint: 8.57.1 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + typescript-eslint@8.20.0(eslint@8.57.1)(typescript@5.7.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.20.0(@typescript-eslint/parser@8.20.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/parser': 8.20.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/utils': 8.20.0(eslint@8.57.1)(typescript@5.7.3) + eslint: 8.57.1 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + typescript@4.9.5: {} + + typescript@5.7.3: {} + + uglify-js@3.19.3: + optional: true + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + undici-types@5.26.5: {} + + undici-types@6.20.0: {} + + unified-engine@11.2.1: + dependencies: + '@types/concat-stream': 2.0.3 + '@types/debug': 4.1.12 + '@types/is-empty': 1.2.3 + '@types/node': 20.12.12 + '@types/unist': 3.0.2 + concat-stream: 2.0.0 + debug: 4.3.4 + extend: 3.0.2 + glob: 10.4.5 + ignore: 5.3.1 + is-empty: 1.2.0 + is-plain-obj: 4.1.0 + load-plugin: 6.0.3 + parse-json: 7.1.1 + trough: 2.2.0 + unist-util-inspect: 8.0.0 + vfile: 6.0.1 + vfile-message: 4.0.2 + vfile-reporter: 8.1.1 + vfile-statistics: 3.0.0 + yaml: 2.4.2 + transitivePeerDependencies: + - supports-color + + unified@11.0.4: + dependencies: + '@types/unist': 3.0.2 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.1 + + unist-util-inspect@8.0.0: + dependencies: + '@types/unist': 3.0.2 + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.2 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.2 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.2 + unist-util-visit: 5.0.0 + + unist-util-stringify-position@2.0.3: + dependencies: + '@types/unist': 2.0.10 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.2 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.2 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.2 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + universalify@0.1.2: {} + + update-browserslist-db@1.0.16(browserslist@4.23.0): + dependencies: + browserslist: 4.23.0 + escalade: 3.1.2 + picocolors: 1.0.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + urlgrey@1.0.0: + dependencies: + fast-url-parser: 1.1.3 + + util-deprecate@1.0.2: {} + + uuid@8.3.2: {} + + uvu@0.5.6: + dependencies: + dequal: 2.0.3 + diff: 5.2.0 + kleur: 4.1.5 + sade: 1.8.1 + + v8-compile-cache-lib@3.0.1: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.2 + unist-util-stringify-position: 4.0.0 + + vfile-reporter@8.1.1: + dependencies: + '@types/supports-color': 8.1.3 + string-width: 6.1.0 + supports-color: 9.4.0 + unist-util-stringify-position: 4.0.0 + vfile: 6.0.1 + vfile-message: 4.0.2 + vfile-sort: 4.0.0 + vfile-statistics: 3.0.0 + + vfile-sort@4.0.0: + dependencies: + vfile: 6.0.1 + vfile-message: 4.0.2 + + vfile-statistics@3.0.0: + dependencies: + vfile: 6.0.1 + vfile-message: 4.0.2 + + vfile@6.0.1: + dependencies: + '@types/unist': 3.0.2 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + + walk-up-path@3.0.1: {} + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webidl-conversions@3.0.1: {} + + webidl-conversions@4.0.2: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-builtin-type@1.1.3: + dependencies: + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.2 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.15 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 + + which-module@2.0.1: {} + + which-pm@2.0.0: + dependencies: + load-yaml-file: 0.2.0 + path-exists: 4.0.0 + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wordwrap@1.0.0: {} + + workerpool@6.5.1: {} + + wrap-ansi@2.1.0: + dependencies: + string-width: 1.0.2 + strip-ansi: 3.0.1 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + write-file-atomic@1.3.4: + dependencies: + graceful-fs: 4.2.11 + imurmurhash: 0.1.4 + slide: 1.1.6 + + xtend@2.1.2: + dependencies: + object-keys: 0.4.0 + + y18n@3.2.2: {} + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yallist@2.1.2: {} + + yallist@3.1.1: {} + + yaml@2.4.2: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs-parser@9.0.2: + dependencies: + camelcase: 4.1.0 + + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@11.1.1: + dependencies: + cliui: 4.1.0 + decamelize: 1.2.0 + find-up: 2.1.0 + get-caller-file: 1.0.3 + os-locale: 3.1.0 + require-directory: 2.1.1 + require-main-filename: 1.0.1 + set-blocking: 2.0.0 + string-width: 2.1.1 + which-module: 2.0.1 + y18n: 3.2.2 + yargs-parser: 9.0.2 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod@3.24.1: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..dee51e92 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - "packages/*" diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 00000000..1a9801dc --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "strict": true, + "moduleResolution": "Node16", + "target": "ES2022", + "module": "Node16", + "esModuleInterop": true, + "skipLibCheck": true, + "stripInternal": true, + "outDir": "./dist" + } +} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 00000000..678a223c --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "allowJs": true + }, + "extends": "./tsconfig.base.json" +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 00000000..4ed88bfb --- /dev/null +++ b/turbo.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "inputs": ["$TURBO_DEFAULT$", ".env*"], + "outputs": ["dist/**", "storybook-static/**"], + "dependsOn": ["^build"] + }, + "lint": { + "dependsOn": ["^lint"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "clean": { + "cache": false + } + } +}